欢迎光临
我们一直在努力

音频格式之WAV

        WAV是基于RIFF结构的音频文件,是微软为windows系统开发的一种标准音频文件格式。如PCM中介绍,粗略的可以理解为wav数据就是加了wav头的pcm数据,实际WAV文件由RIFF chunk、Format chunk 和 Data chunk三个主要区块组成。辅以fact、cue与list chunk。

一、WAV格式概要

┌─────────────────────────────────────────────────────────────────┐
│ WAV 文件结构 │
├─────────────────────────────────────────────────────────────────┤
│ RIFF Header │
│ ┌─────────────┬─────────────┬─────────────┐ │
│ │ Chunk ID │ Chunk Size │ Format │ │
│ │ "RIFF" │ 4 bytes │ "WAVE" │ │
│ │ 4 bytes │ (小端序) │ 4 bytes │ │
│ └─────────────┴─────────────┴─────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Chunk 1 (fmt 、XMA2 等) │
│ ┌─────────────┬─────────────┬─────────────┐ │
│ │ Chunk ID │ Chunk Size │ Chunk Data │ │
│ │ 4 bytes │ 4 bytes │ N bytes │ │
│ └─────────────┴─────────────┴─────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Chunk 2 (data 等) │
│ ┌─────────────┬─────────────┬─────────────┐ │
│ │ Chunk ID │ Chunk Size │ Chunk Data │ │
│ │ 4 bytes │ 4 bytes │ N bytes │ │
│ └─────────────┴─────────────┴─────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ … 更多 Chunk … │
└─────────────────────────────────────────────────────────────────┘

常规主要trunk

RIFF RIFF chunk:主要说明本文件保持的音频格式; Formt chunk:主要说明音频格式的相关属性,如采样率、采用位数等信息; Data chunk:主要记录实际的音频数据和长度信息;

大部分的都以以上三个trunk块组成,

辅助chunk

 不过wav还有fact、cue与list 等其他辅助trunk:

Chunk ID 名称 必选 功能描述
fmt Format 是 音频格式参数(采样率、位深、通道数等)
data Data 是 音频采样数据
fact Fact 否 采样数信息(压缩格式必需)
bext Broadcast Extension 否 广播元数据(EBU R128 标准)
LIST List 否 容器 chunk,包含 INFO/adtl 子块
ID3 ID3v2 否 ID3 元数据
cue Cue Points 否 章节标记点
XMA2 XMA2 Config 否 XMA2 编解码器配置,与fmt互斥
SMV0 SMV Video 否 SMV 视频附加数据

 注意: 整个chunk除标识字符(RIFF、WAVE、fmt和data)外,都是小端计算。

二、RIFF chunk

2.1 结构介绍

RIFFchunk结构分为3个子块,chunk id、chunk size和chunk format。

2.2

子块字节数含义
Chunk id 4 “RIFF”,  所有的wav都固定为这个值。  
Chunk size 4 该子块之后文件的总字节数。文件长度 – 8  =Chunk size 。
chunk format 4 “WAVE”,  所有的wav都固定为这个值。  

2.2 示例:

        如上图为许嵩-有何不可的wav文件,根据以上结构,解析出

子块字节数16进制对应值解析值
Chunk id 4 52 49 46 46 RIFF
Chunk size 4 B8 E7 8C 02 Chunk size = 0x028ce7b8 = 42,788,792字节(小端计算,见上面注意说明)
chunk format 4 B8 E7 8C 02

WAVE

问

文件大小 =  42,788,792 +8 = 42,788,800 字节, 如文件的实际大小一致,见下图的“大小”

三、Formt(fmt) chunk

3.1 结构介绍

子块字节数含义
ID 4 "fmt ",固定值。
Size 4 fmt chunk 的大小,一般有 16/18/20/22/40 字节 (也有超过 40 字节的情况),超过 16 字节部分为扩展块
Info Format 2 编码格式代码,其值见常见编码格式表,如果上述取值为 16,则此值通常为 1,代表该音频的编码方式是 PCM 编码
Channels 2 声道数目,1 代表单声道,2 代表双声道
SampleRate 4 采样频率,8/11.025/12/16/22.05/24/32/44.1/48/64/88.2/96/176.4/192 kHZ4 bytesByteRate 传输速率,每秒的字节数,计算公式为:SampleRate * FmtChannels * BitsPerSample/8
ByteRate 4 比特率,每秒的数据字节数  b/s
BlockAilgn 2 块对齐,告知播放软件一次性需处理多少字节,公式为: BitsPerSample*FmtChannels/8
BitsPerSample 2 采样位数,一般有8/16/24/32/64,值越大,对声音的还原度越高
other-data              size-16 扩展数据,用户可以根据实际情况进行扩展

       

上表中的format和size 对应下表的format和size

Format格式名称Sizefact 块
0x01 PCM / 非压缩格式 16
0x02 Microsoft ADPCM 18 √
0x03 IEEE float 18 √
0x06 ITU G.711 μ-law 18√
0x07 ITU G.711 a-law 18√
0x031 GSM 6.10 20 √
0x040 ITU G.721 ADPCM √
0xFFFE 见子格式块中的编码格式 40

3.2 示例

子块字节数16进制值解析
ID 4 66 6D 74 20 “fmt ”
Size 4 10 00 00 00 0x10 = 16
Info Format 2 01 00 0x01 = 1
Channels 2 02 00 0x02 =2
SampleRate 4 44 AC 00 00 0xAC44 =44,100
ByteRate 4 10 B1 02 00 0x02B110 = 176,400 b/s
BlockAilgn 2 04 00 0x04 = 4
BitsPerSample 2 10 00 0x10 = 16
Extended-data              16-16 =0 / 无扩展

 由以上可以看到,当前wav格式的

编码格式为:PCM

声道数为:双声道

采样率为:44100 HZ

对齐位位:4字节 

采用位数为:16 bit

比特率= 176400 b/s  *8 = 176.4 KB/s‌ * 8 = 1411.2 kbps ≈ 1411 kbps

以上信息与播放器解析的一致,如下图

四、Data chunk

4.1 结构介绍

子块字节数含义
ID 4 “data”,  所有的wav都固定为这个值。  
size 4 原始音频数据的大小,就是这之后字节数。
Audio       不定       实际的音频数据

4.2 示例

子块字节数16进制对应值解析值
ID 4 64 61 74 61 data
size 4 B8 E7 8C 02  size = 0x028ce7b8 = 42,788,792字节(小端计算,见上面注意说明)

、

五、 list chunk

LIST chunk
┌─────────────┬─────────────┬─────────────┬─────────────┐
│ "LIST" │ Size │ Type │ Sub-chunks │
│ 4 bytes │ 4 bytes │ 4 bytes │ N bytes │
└─────────────┴─────────────┴─────────────┴─────────────┘

5.1 info 子chunk

LIST chunk
┌─────────────┬─────────────┬─────────────┬─────────────────────────────┐
│ "LIST" │ Size │ "INFO" │ 子块序列 │
│ 4 bytes │ 4 bytes │ 4 bytes │ │
└─────────────┴─────────────┴─────────────┴─────────────────────────────┘
│
▼
┌───────────────────────────────────────┐
│ 子块1 │ 子块2 │ … │ 子块N │
│ (IART) │ (INAM) │ │ (ICOP) │
└───────────────────────────────────────┘

每个子块结构:
┌─────────────┬─────────────┬─────────────┐
│ Chunk Code │ Chunk Size │ Chunk Data │
│ 4 bytes │ 4 bytes │ N bytes │
└─────────────┴─────────────┴─────────────┘

子块代码 含义 示例
IART Artist(艺术家) "John Doe"
INAM Name(标题) "My Song"
ICOP Copyright(版权) "(C) 2024"
IACD Album(专辑) "Greatest Hits"
ITRK Track(曲目) "03/12"
IGNR Genre(流派) "Rock"
IYER Year(年份) "2024"

5.2 adt1 子chunk

adtl(Audio Data List) 是 RIFF LIST chunk 的子类型,用于存储章节标签名称。它与 cue chunk 配合使用:

Chunk功能
cue 定义章节位置(时间戳)
adtl 定义章节名称(人类可读标签)

WAV 文件中的章节信息组织
┌─────────────────────────────────────────────────────────┐
│ cue chunk │
│ ┌───────────────────────────────────────────────────┐ │
│ │ Chapter 1: id=1, offset=10000 │ │
│ │ Chapter 2: id=2, offset=20000 │ │
│ └───────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ LIST chunk (adtl) │
│ ┌───────────────────────────────────────────────────┐ │
│ │ labl: id=1, label="Chapter 1: Introduction" │ │
│ │ labl: id=2, label="Chapter 2: Main Content" │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────┐
│ 最终章节元数据 │
│ Chapter 1: │
│ id=1 │
│ offset=10000 │
│ title="Chapter 1…"│
└───────────────────────┘

六、其它chunk

6.1  fact chunk

文件总采样率,可用用于计算播放整个音频文件所需的时间。

┌─────────────────────┬─────────────────┬─────────────────────┐
│ 字段名 │ 大小 (字节) │ 说明 │
├─────────────────────┼─────────────────┼─────────────────────┤
│ ID │ 4 │ 固定“fact”
│ size │ 4 │ trunk大小
│ SampleCount │ 4 │ 总采样数 │
└─────────────────────┴─────────────────┴─────────────────────┘

6.2 bext chunk

BEXT Chunk 的作用:

  • 广播专业元数据:存储符合 EBU R128 标准的专业信息
  • 时间同步:time_reference 用于多轨录音同步
  • 物料追踪:UMID 提供全球唯一标识
  • 处理历史:coding_history 记录音频处理链
  • 版本兼容:支持不同版本的 BWF 格式
  • bext chunk 结构(BWF Version 1)
    ┌─────────────────────────────────────────────────────────┐
    │ Chunk ID │ "bext" │ 4 bytes │
    │ Chunk Size │ 可变长度 │ 4 bytes │
    ├─────────────────────────────────────────────────────────┤
    │ description │ 文件描述 │ 256 bytes │
    │ originator │ 创建者 │ 32 bytes │
    │ originator_reference │ 创建者参考编号 │ 32 bytes │
    │ origination_date │ 创建日期 │ 10 bytes (YYYY-MM-DD) │
    │ origination_time │ 创建时间 │ 8 bytes (HH:MM:SS) │
    │ time_reference │ 时间参考 │ 8 bytes (采样数) │
    │ version │ BWF 版本 │ 2 bytes │
    │ UMID │ 唯一物料标识 │ 64 bytes (可选) │
    │ reserved │ 保留字段 │ 190 bytes │
    │ coding_history │ 编码历史 │ 可变长度 (可选) │
    └─────────────────────────────────────────────────────────┘

    6.3 ID3 chunk

    ID3 chunk 结构
    ┌─────────────┬─────────────┬─────────────────────────────┐
    │ Chunk ID │ Chunk Size │ ID3v2 Tag Data │
    │ "ID3 " │ 4 bytes │ 可变长度 │
    │ 4 bytes │ (小端序) │ │
    └─────────────┴─────────────┴─────────────────────────────┘

    ID3的格式可以具体参考相关ID3介绍,此处不做赘述。

    6.4 cue chunk

    Cue chunk 结构
    ┌─────────────┬─────────────┬─────────────────────────────┐
    │ Chunk ID │ Chunk Size │ Number of Cues │
    │ "cue " │ 4 bytes │ 4 bytes │
    │ 4 bytes │ (小端序) │ │
    ├───────────────────────────────────────────────────────────┤
    │ Cue Point 1 │
    │ ┌─────────────┬─────────────┬─────────────────────────┐ │
    │ │ Cue ID │ Position │ Chunk ID │ │
    │ │ 4 bytes │ 4 bytes │ 4 bytes │ │
    │ ├─────────────┼─────────────┼─────────────────────────┤ │
    │ │ Chunk Start │ Block Start │ Sample Offset │ │
    │ │ 4 bytes │ 4 bytes │ 4 bytes │ │
    │ └─────────────┴─────────────┴─────────────────────────┘ │
    ├───────────────────────────────────────────────────────────┤
    │ Cue Point 2 … │
    └───────────────────────────────────────────────────────────┘

    字段大小说明
    Cue ID 4 bytes 章节唯一标识
    Position 4 bytes 播放位置(采样数)
    Chunk ID 4 bytes 目标 chunk(通常为 data)
    Chunk Start 4 bytes chunk 起始偏移
    Block Start 4 bytes 块起始偏移
    Sample Offset 4 bytes 相对于块的采样偏移

    6.5 XMA2 chunk

    XMA2 chunk 结构
    ┌─────────────┬─────────────┬─────────────────────────────┐
    │ Chunk ID │ Chunk Size │ XMA2 Configuration Data │
    │ "XMA2" │ 4 bytes │ 可变长度 │
    │ 4 bytes │ (小端序) │ │
    └─────────────┴─────────────┴─────────────────────────────┘

    编解码器特性
    特性说明
    压缩类型 有损压缩
    采样率 最高 48kHz
    通道数 最多 8 通道
    比特率 可变比特率(VBR)
    平台 Xbox 360、Windows

    6.6 SMV0 chunk

    SMV0 chunk 结构
    ┌─────────────┬─────────────┬─────────────────────────────┐
    │ Chunk ID │ Chunk Size │ SMV Configuration │
    │ "SMV0" │ 4 bytes │ 版本号 + 视频参数 │
    │ 4 bytes │ (小端序) │ │
    └─────────────┴─────────────┴─────────────────────────────┘

    SMV 格式特点

    特性说明
    复合格式 WAV 音频 + JPEG 视频
    版本号 固定为 0200
    视频编码 SMVJPEG(特殊 JPEG 变体)
    帧存储 多个视频帧共用一个 JPEG

    七、ffmpeg解码源码

    截取ffmpeg解码源码

    for (;;) {
    AVStream *vst;
    size = next_tag(pb, &tag, wav->rifx);
    next_tag_ofs = avio_tell(pb) + size;

    if (avio_feof(pb))
    break;

    switch (tag) {
    case MKTAG('f', 'm', 't', ' '):
    /* only parse the first 'fmt ' tag found */
    if (!got_xma2 && !got_fmt && (ret = wav_parse_fmt_tag(s, size, st)) < 0) {
    return ret;
    } else if (got_fmt)
    av_log(s, AV_LOG_WARNING, "found more than one 'fmt ' tag\\n");

    got_fmt = 1;
    break;
    case MKTAG('X', 'M', 'A', '2'):
    /* only parse the first 'XMA2' tag found */
    if (!got_fmt && !got_xma2 && (ret = wav_parse_xma2_tag(s, size, st)) < 0) {
    return ret;
    } else if (got_xma2)
    av_log(s, AV_LOG_WARNING, "found more than one 'XMA2' tag\\n");

    got_xma2 = 1;
    break;
    case MKTAG('d', 'a', 't', 'a'):
    if (!(pb->seekable & AVIO_SEEKABLE_NORMAL) && !got_fmt && !got_xma2) {
    av_log(s, AV_LOG_ERROR,
    "found no 'fmt ' tag before the 'data' tag\\n");
    return AVERROR_INVALIDDATA;
    }

    if (rf64 || bw64) {
    next_tag_ofs = wav->data_end = av_sat_add64(avio_tell(pb), data_size);
    } else if (size != 0xFFFFFFFF) {
    data_size = size;
    next_tag_ofs = wav->data_end = size ? next_tag_ofs : INT64_MAX;
    } else {
    av_log(s, AV_LOG_WARNING, "Ignoring maximum wav data size, "
    "file may be invalid\\n");
    data_size = 0;
    next_tag_ofs = wav->data_end = INT64_MAX;
    }

    data_ofs = avio_tell(pb);

    /* don't look for footer metadata if we can't seek or if we don't
    * know where the data tag ends
    */
    if (!(pb->seekable & AVIO_SEEKABLE_NORMAL) || (!(rf64 && !bw64) && !size))
    goto break_loop;
    break;
    case MKTAG('f', 'a', 'c', 't'):
    if (!sample_count)
    sample_count = (!wav->rifx ? avio_rl32(pb) : avio_rb32(pb));
    break;
    case MKTAG('b', 'e', 'x', 't'):
    if ((ret = wav_parse_bext_tag(s, size)) < 0)
    return ret;
    break;
    case MKTAG('S','M','V','0'):
    if (!got_fmt) {
    av_log(s, AV_LOG_ERROR, "found no 'fmt ' tag before the 'SMV0' tag\\n");
    return AVERROR_INVALIDDATA;
    }
    // SMV file, a wav file with video appended.
    if (size != MKTAG('0','2','0','0')) {
    av_log(s, AV_LOG_ERROR, "Unknown SMV version found\\n");
    goto break_loop;
    }
    av_log(s, AV_LOG_DEBUG, "Found SMV data\\n");
    wav->smv_given_first = 0;
    vst = avformat_new_stream(s, NULL);
    if (!vst)
    return AVERROR(ENOMEM);
    wav->vst = vst;
    avio_r8(pb);
    vst->id = 1;
    vst->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
    vst->codecpar->codec_id = AV_CODEC_ID_SMVJPEG;
    vst->codecpar->width = avio_rl24(pb);
    vst->codecpar->height = avio_rl24(pb);
    if ((ret = ff_alloc_extradata(vst->codecpar, 4)) < 0) {
    av_log(s, AV_LOG_ERROR, "Could not allocate extradata.\\n");
    return ret;
    }
    size = avio_rl24(pb);
    wav->smv_data_ofs = avio_tell(pb) + (size – 5) * 3;
    avio_rl24(pb);
    wav->smv_block_size = avio_rl24(pb);
    if (!wav->smv_block_size)
    return AVERROR_INVALIDDATA;
    avpriv_set_pts_info(vst, 32, 1, avio_rl24(pb));
    vst->duration = avio_rl24(pb);
    avio_rl24(pb);
    avio_rl24(pb);
    wav->smv_frames_per_jpeg = avio_rl24(pb);
    if (wav->smv_frames_per_jpeg > 65536) {
    av_log(s, AV_LOG_ERROR, "too many frames per jpeg\\n");
    return AVERROR_INVALIDDATA;
    }
    AV_WL32(vst->codecpar->extradata, wav->smv_frames_per_jpeg);
    goto break_loop;
    case MKTAG('L', 'I', 'S', 'T'):
    case MKTAG('l', 'i', 's', 't'):
    if (size < 4) {
    av_log(s, AV_LOG_ERROR, "too short LIST tag\\n");
    return AVERROR_INVALIDDATA;
    }
    switch (avio_rl32(pb)) {
    case MKTAG('I', 'N', 'F', 'O'):
    ff_read_riff_info(s, size – 4);
    break;
    case MKTAG('a', 'd', 't', 'l'):
    if (s->nb_chapters > 0) {
    while (avio_tell(pb) < next_tag_ofs &&
    !avio_feof(pb)) {
    char cue_label[512];
    unsigned id, sub_size;

    if (avio_rl32(pb) != MKTAG('l', 'a', 'b', 'l'))
    break;

    sub_size = avio_rl32(pb);
    if (sub_size < 5)
    break;
    id = avio_rl32(pb);
    avio_get_str(pb, sub_size – 4, cue_label, sizeof(cue_label));
    avio_skip(pb, avio_tell(pb) & 1);

    for (int i = 0; i < s->nb_chapters; i++) {
    if (s->chapters[i]->id == id) {
    av_dict_set(&s->chapters[i]->metadata, "title", cue_label, 0);
    break;
    }
    }
    }
    }
    break;
    }
    break;
    case MKTAG('I', 'D', '3', ' '):
    case MKTAG('i', 'd', '3', ' '): {
    ID3v2ExtraMeta *id3v2_extra_meta;
    ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, 0);
    if (id3v2_extra_meta) {
    ff_id3v2_parse_apic(s, id3v2_extra_meta);
    ff_id3v2_parse_chapters(s, id3v2_extra_meta);
    ff_id3v2_parse_priv(s, id3v2_extra_meta);
    }
    ff_id3v2_free_extra_meta(&id3v2_extra_meta);
    }
    break;
    case MKTAG('c', 'u', 'e', ' '):
    if (size >= 4 && got_fmt && st->codecpar->sample_rate > 0) {
    AVRational tb = {1, st->codecpar->sample_rate};
    unsigned nb_cues = avio_rl32(pb);

    if (size >= nb_cues * 24LL + 4LL) {
    for (int i = 0; i < nb_cues; i++) {
    unsigned offset, id = avio_rl32(pb);

    if (avio_feof(pb))
    return AVERROR_INVALIDDATA;

    avio_skip(pb, 16);
    offset = avio_rl32(pb);

    if (!avpriv_new_chapter(s, id, tb, offset, AV_NOPTS_VALUE, NULL))
    return AVERROR(ENOMEM);
    }
    }
    }
    break;
    }

    /* seek to next tag unless we know that we'll run into EOF */
    if ((avio_size(pb) > 0 && next_tag_ofs >= avio_size(pb)) ||
    wav_seek_tag(wav, pb, next_tag_ofs, SEEK_SET) < 0) {
    break;
    }
    }

    赞(0)
    未经允许不得转载:171主机测评 » 音频格式之WAV
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址