有人可以解释一下 .wav(WAVE) 文件头吗?

问题描述 投票:0回答:3

好的,所以我正在尝试制作一个可以操作 .wav 文件的程序,并且我已经看到了这个问题/答案,但我不完全确定标头中的每条数据指的是什么。例如,“块”指的是什么?这是具体的位数/字节数吗?

如果有人可以告诉我,至少以这个问题中使用的格式,除了常量字符串文字和“数据”数组之外,写入 .wav 的每个数据是指什么?特别是我特别想知道什么是“块”,以及所有通道的采样率、字节率、每个样本的字节数和每个样本的字节数有何关系?(我怀疑字节率是采样率 * 每个样本的字节数,但是“适用于所有频道”呢?)

如有任何帮助,我们将不胜感激。

java audio header byte wav
3个回答
25
投票

仅仅发布链接是违反董事会规则的,所以这是我从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.

上面给出了 16 位立体声源的示例值。

更新/提醒

标头整数均按最低有效字节顺序排列, 所以两个字节的通道信息 0x01 0x00 实际上是 0x00001 例如单声道。


6
投票

我知道 OP 将问题标记为 Java,但这里有完整的 Kotlin 代码,用于读取可以传递给 Java 的标头。读取 Little Endian 可能很棘手,但幸运的是我们不必这样做。

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]))
}

使用 this URL 运行会产生输出:

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
投票

尺寸始终表示剩余尺寸(即不包括 ID 和尺寸字段)。

© www.soinside.com 2019 - 2024. All rights reserved.