Can someone explain .wav(WAVE) file headers?

前端 未结 2 715
眼角桃花
眼角桃花 2021-01-13 12:14

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 h

相关标签:
2条回答
  • 2021-01-13 12:36

    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.

    0 讨论(0)
  • 2021-01-13 12:53

    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)
    )
    
    0 讨论(0)
提交回复
热议问题