Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can someone explain .wav(WAVE) file headers?

OK, so I'm trying to make a program that will manipulate .wav files, and I've seen this question/answers, but I'm not entirely sure as to what each piece of data in the header refers to. For example, what does a "chunk" refer to? Is that a specific number of bits/bytes?

If somebody could just tell me, at least in the format used in this question, what each datum being written to the .wav, aside from the constant String Literals and the 'data' array, refer to? In particular I'd especially like to know what a "chunk" is, and how Sample Rate, Byte Rate, Bytes per Sample, and Bytes per Sample for all Channels relate?(I suspect Byte Rate is Sample Rate * Bytes per Sample, but what about the 'for all channels' one?)

Any help is appreciated.

like image 416
user2649681 Avatar asked Jan 25 '15 14:01

user2649681


People also ask

What information does a WAV file contain?

A WAV file is a raw audio format created by Microsoft and IBM. The format uses containers to store audio data, track numbers, sample rate, and bit rate. WAV files are uncompressed lossless audio and as such can take up quite a bit of space, coming in around 10 MB per minute with a maximum file size of 4 GB.

What is the difference between WAVE and WAV?

As pointed out by the O.P. "wave" and "wav" are the same thing. But the file manager application does not recognize wave extension. The solution is just to rename the "file. wave" to "file.

What does the data in WAV file represent?

Waveform Audio File Format (WAVE, or WAV due to its filename extension; pronounced "wave") is an audio file format standard, developed by IBM and Microsoft, for storing an audio bitstream on PCs. It is the main format used on Microsoft Windows systems for uncompressed audio.

How do I read a WAV file?

Windows and Mac are both capable of opening WAV files. For Windows, if you double-click a WAV file, it will open using Windows Media Player. For Mac, if you double-click a WAV, it will open using iTunes or Quicktime. If you're on a system without these programs installed, then consider third-party software.


2 Answers

It is against the board rules to just post a link, so here is the table I took from http://www.topherlee.com/software/pcm-tut-wavformat.html

Positions   Sample Value         Description
1 - 4       "RIFF"               Marks the file as a riff file. Characters are each 1. byte long.
5 - 8       File size (integer)  Size of the overall file - 8 bytes, in bytes (32-bit integer). Typically, you'd fill this in after creation.
9 -12       "WAVE"               File Type Header. For our purposes, it always equals "WAVE".
13-16       "fmt "               Format chunk marker. Includes trailing null
17-20       16                   Length of format data as listed above
21-22       1                    Type of format (1 is PCM) - 2 byte integer
23-24       2                    Number of Channels - 2 byte integer
25-28       44100                Sample Rate - 32 bit integer. Common values are 44100 (CD), 48000 (DAT). Sample Rate = Number of Samples per second, or Hertz.
29-32       176400               (Sample Rate * BitsPerSample * Channels) / 8.
33-34       4                    (BitsPerSample * Channels) / 8.1 - 8 bit mono2 - 8 bit stereo/16 bit mono4 - 16 bit stereo
35-36       16                   Bits per sample
37-40       "data"               "data" chunk header. Marks the beginning of the data section.
41-44       File size (data)     Size of the data section, i.e. file size - 44 bytes header.

Sample values are given above for a 16-bit stereo source.

Update/Reminder

The header integers are all in Least significant byte order, so the two byte channel information 0x01 0x00 are actually 0x00001 e.g. mono.

like image 177
Hannes Avatar answered Oct 19 '22 05:10

Hannes


enter image description here

I know OP tagged the question as Java, but here's complete Kotlin code for reading the header that could pass for Java. Reading Little Endian could be tricky, but thankfully we don't have to do that.

class WaveHeader(bytes: ByteArray) {
    init {
        require(bytes.size >= SIZE) { "Input size is must be at least $SIZE bytes" }
    }

    private var start = 0
    private val riff = RiffChunk(
        String(bytes.copyOfRange(start, start + 4))
            .also {
                require(it == "RIFF") { "$it must be 'RIFF'" }
                start += it.length
            },
        ByteBuffer.wrap(bytes.copyOfRange(start, start + 4)).order(ByteOrder.LITTLE_ENDIAN)
            .also { start += it.capacity() }.int,
        String(bytes.copyOfRange(start, start + 4))
            .also {
                require(it == "WAVE") { "$it must be 'WAVE'" }
                start += it.length
            }
    )
    private val format = FormatChunk(
        // null terminated
        String(bytes.copyOfRange(start, start + 3))
            .also {
                require(it == "fmt") { "$it must be 'fmt'" }
                start += 4
            },
        ByteBuffer.wrap(bytes.copyOfRange(start, start + 4)).order(ByteOrder.LITTLE_ENDIAN)
            .also { start += it.capacity() }.int,
        ByteBuffer.wrap(bytes.copyOfRange(start, start + 2)).order(ByteOrder.LITTLE_ENDIAN)
            .also { start += it.capacity() }
            .let { if (it.short == 1.toShort()) "PCM" else "OTHER (${it.short})" },
        ByteBuffer.wrap(bytes.copyOfRange(start, start + 2)).order(ByteOrder.LITTLE_ENDIAN)
            .also { start += it.capacity() }.short,
        ByteBuffer.wrap(bytes.copyOfRange(start, start + 4)).order(ByteOrder.LITTLE_ENDIAN)
            .also { start += it.capacity() }.int,
        ByteBuffer.wrap(bytes.copyOfRange(start, start + 4)).order(ByteOrder.LITTLE_ENDIAN)
            .also { start += it.capacity() }.int,
        ByteBuffer.wrap(bytes.copyOfRange(start, start + 2)).order(ByteOrder.LITTLE_ENDIAN)
            .also { start += it.capacity() }.short,
        ByteBuffer.wrap(bytes.copyOfRange(start, start + 2)).order(ByteOrder.LITTLE_ENDIAN)
            .also { start += it.capacity() }.short
    )
    private val `data` = DataChunk(
        String(bytes.copyOfRange(start, start + 4))
             // remove all null chars
            .replace("\u0000", "")
            .also { start += it.length },
        ByteBuffer.wrap(bytes.copyOfRange(start, start + 4)).order(ByteOrder.LITTLE_ENDIAN)
            .also { start += it.capacity() }.int
    )

    init {
        assert(start == 44) { "Illegal state" }
    }

    data class RiffChunk(val id: String, val size: Int, val format: String)
    data class FormatChunk(
        val id: String, val size: Int, val format: String, val numChannels: Short,
        val sampleRate: Int, val byteRate: Int, val blockAlign: Short, val bitsPerSample: Short
    )

    data class DataChunk(val id: String, val size: Int)

    override fun toString(): String {
        val ls = System.lineSeparator()
        return "WaveHeader($ls\t$riff}$ls\t$format$ls\t$`data`$ls)"
    }

    companion object {
        const val SIZE = 44

        fun fromPath(path: String): WaveHeader  = fromInputStream(WaveHeader::class.java.getResourceAsStream(path))

        fun fromUrl(url: String): WaveHeader  = fromInputStream(URL(url).openStream())

        private fun fromInputStream(input: InputStream): WaveHeader {
            val bytes = input.use {
                it.readNBytes(SIZE)
            }
            return WaveHeader(bytes)
        }
    }
}

fun main(args: Array<String>) {
    if (args.isEmpty()) {
        System.err.println("Argument is missing")
    }
    println(WaveHeader.fromUrl(args[0]))
}

Running with this URL produces the output:

WaveHeader(
    RiffChunk(id=RIFF, size=168050, format=WAVE)}
    FormatChunk(id=fmt, size=18, format=PCM, numChannels=1, sampleRate=16000, byteRate=32000, blockAlign=2, bitsPerSample=16)
    DataChunk(id=fa, size=1952670054)
)
like image 45
Abhijit Sarkar Avatar answered Oct 19 '22 04:10

Abhijit Sarkar