音频格式解码之opus
opus是一种有损编码格式,与Vorbis同率属于Xiph.Org基金会,并且也进行了标准化。当前opus不仅取代了Vorbis,还成为了webrtc的标准音频格式。opus也基于ogg,ogg的介绍请参考音频格式之OGG-CSDN博客 , 本文主要介绍opus格式。
一、opus 在ogg中的格式
1.1 opus ogg 流
Page 0 Pages 1 ... n Pages (n+1) ... +------------+ +---+ +---+ ... +---+ +-----------+ +---------+ +-- | | | | | | | | | | | | | |+----------+| |+-----------------+| |+-------------------+ +----- |||ID Header|| || Comment Header || ||Audio Data Packet 1| | ... |+----------+| |+-----------------+| |+-------------------+ +----- | | | | | | | | | | | | | +------------+ +---+ +---+ ... +---+ +-----------+ +---------+ +-- ^ ^ ^ | | | | | Mandatory Page Break | | | ID header is contained on a single page | 'Beginning Of Stream'opus的ogg流,主要由ID Header、 Comment Header和AudioData Packet组成,
ID Header:是 Ogg Opus 文件格式的第一个数据包,用于唯一标识一个流为 Opus 音频。它包含了解码 Opus 音频所需的基本元数据。
Comment Header:是 Ogg Opus 文件格式的第二个必需数据包,包含用户提供的元数据信息。它采用与 Ogg Vorbis 评论头相同的格式(但没有 Vorbis 规范中规定的最终"帧位")。
Audio Data Packet:是 Ogg Opus 流中包含实际音频数据的数据包。在 ID Header 和 Comment Header 之后,所有后续的页面都包含音频数据。
1.2ID Header
根据 RFC 7845(Ogg Opus Encapsulation)规范,ID Header 的二进制结构如下:
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 'O' | 'p' | 'u' | 's' | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 'H' | 'e' | 'a' | 'd' | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Version = 1 | Channel Count | Pre-skip | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Input Sample Rate (Hz) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Output Gain (Q7.8 in dB) | Mapping Family| | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ : | | : Optional Channel Mapping Table... : | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+1.2.1 属性介绍
| 字段 | 位 | 含义 |
|---|---|---|
| Magic | 64 | Opus 文件标识"OpusHead" |
| Version | 8 | 版本 ,固定为1 |
| Channel Count | 8 | 声道数 |
| Pre-skip | 16 | 无预跳过 |
| Input Sample Rate | 32 | 采样率,这是原始输入(编码前)的采样率,单位为赫兹。这个字段并不是用于播放编码数据的采样率。 |
| Output Gain | 16 | 增益调整, sample *= pow(10, output_gain/(20.0*256)) |
| Mapping Family | 8 | 标准立体声(无映射表) |
Channel Mapping Table | N | Mapping Family > 0时才有,从编码流到输出通道的映射 |
1,channel count
这是输出通道的数量。这可能与编码通道的数量不同,编码通道数量可能会在每个数据包中变化。这个值不能为零。最大允许值取决于通道映射类型,并可能大到 255
2,Pre-skip
这是在开始播放时需要从解码器输出丢弃的样本数(48 kHz),同时也是计算页面 PCM 样本位置时需要从页面颗粒位置中减去的数值。在裁剪已有的 Ogg Opus 流的开头时,建议预跳至少 3,840 个样本(80 毫秒),以确保解码器完全收敛。
3,Input Sample Rate
这是原始输入的采样率(编码前),单位是赫兹。这个字段_不是_用于播放编码后数据的采样率。
Opus 可以在 4、6、8、12 和 20 kHz 的内部音频带宽之间切换。流中的每个数据包可以有不同的音频带宽。无论音频带宽如何,参考解码器都支持以 8、12、16、24 或 48 kHz 的采样率解码任何流。传递给编码器的音频的原始采样率不会通过有损压缩被保留。
4,Output Gain
这是在解码时要应用的增益。它是将解码器输出缩放到期望播放音量的因子的 20*log10 值,以 16 位有符号二进制补码定点值存储,带有 8 位小数(Q-表示法)
5,Mapping Family
家族值定义
| 家族值 | 名称 | 说明 |
|---|---|---|
| 0 | 默认映射 | 单声道或立体声,无映射表 |
| 1 | 环绕声映射 | 支持多通道环绕声格式 |
| 2 | Ambisonics | 支持 Ambisonics 格式 |
家族 0(默认映射)
最简单的映射方式,无需显式映射表:
| 输出通道数 C | Stream Count N | Coupled Count M | 通道含义 |
|---|---|---|---|
| 1 | 1(默认) | 0(默认) | 单声道 |
| 2 | 1(默认) | 1(默认) | 立体声(左、右) |
家族 1(环绕声映射)
支持标准环绕声配置:
| 通道数 | 配置名称 | 通道顺序 |
|---|---|---|
| 3 | 立体声 + 低频效果 (LFE) | FL, FR, LFE |
| 4 | 四声道 | FL, FR, BL, BR |
| 5 | 5.0 | FL, FR, FC, BL, BR |
| 6 | 5.1 | FL, FR, FC, LFE, BL, BR |
| 7 | 6.1 | FL, FR, FC, LFE, BL, BR, BC |
| 8 | 7.1 | FL, FR, FC, LFE, BL, BR, SL, SR |
6,Channel Mapping Table
Channel Mapping Table 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+ | Stream Count | N +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Coupled Count | Channel Mapping... : M + C bytes +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| 字段 | 大小 | 说明 |
|---|---|---|
| Stream Count (N) | 1 字节 | 编码流总数 |
| Coupled Count (M) | 1 字节 | 立体声流数量(前 M 个流为立体声) |
| Channel Mapping | C 字节 | 输出通道到解码通道的映射表 |
| Channel Mapping值范围 | 含义 |
|---|---|
0到2M-1 | 立体声流的通道(偶数=左,奇数=右) |
2M到M+N-1 | 单声道流的通道 |
255 | 静音通道 |
- Mapping Family决定通道语义(单声道/立体声/环绕声/Ambisonics)
- Stream Count (N)和Coupled Count (M)定义编码流结构
- Channel Mapping Table定义从解码通道到输出通道的映射关系
- 支持灵活的通道配置和静音通道处理
1.3 Comment Header
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 'O' | 'p' | 'u' | 's' | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 'T' | 'a' | 'g' | 's' | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Vendor String Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | : Vendor String... : | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | User Comment List Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | User Comment #0 String Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | : User Comment #0 String... : | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | User Comment #1 String Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ : :| 字段 | 位 | 值举例 | 说明 |
|---|---|---|---|
| Magic | 64 | "OpusTags" | 标识 |
| Vendor Length | 32 | 0x0000000C = 12 | Vendor 字符串长度 |
| Vendor String | N | "LibOpus 1.4" | Vendor Length决定长度,编码器信息 |
| Comment Count | 32 | 0x00000002 = 2 | Comment 的数量,决定后面Comment Block的数量 |
| Comment 0 Length | 32 | 0x0000000C = 12 | 第一个评论长度 |
| Comment 0 | N | "TITLE=听妈妈的话" | 标题 |
| Comment 1 Length | 32 | 0x0000000E = 14 | 第二个评论长度 |
| Comment 1 | N | "ARTIST=周杰伦" | 艺术家: |
这个主要储存相关记录信息, 具体可以参考“标准元数据标签” 介绍TITLE/ARTIST/DATE等 元数据。 本章节不赘述,这些元数据都是通用格式。
二,opus格式
opus音频数据的格式,对应OGG中 audio data packet,ogg打包opus的音频数据。opus包的大小在内部没有存储,因此需要外部封装的容器存储,或者自己扩展一个opus packet的长度字节。
Opus Packet Structure ┌──────────────────────────────────────────────────────────────────────┐ │ TOC Byte │ Frame Size(s) │ Audio Data │ Extensions │ │ (1 byte) │ (0-2 bytes) │ (variable) │ (可选) │ └──────────────────────────────────────────────────────────────────────┘ │ │ │ │ ▼ ▼ ▼ ▼ 编码模式/带宽 帧大小信息 压缩音频数据 LBRR/DRED等 /通道数/帧数 (VBR模式) 扩展数据2.1,TOC字节 1字节(根据opus源码解析)
TOC 字节是 Opus 包的第一个字节,包含了关键的编码参数
TOC 8位格式 0 1 2 3 4 5 6 7 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | frame Count | channels | bandwidth | mode +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| 位 | 字段 | 含义 |
|---|---|---|
| 7-5 | mode | 编码模式(0=SILK, 1=HYBRID, 2=CELT, 3=保留) |
| 4-3 | bandwidth | 带宽 与模式共同计算如 |
| 2 | channels | 通道数(0=单声道, 1=立体声) |
| 1-0 | frame_count | 帧数量指示符 |
1,frame_count(帧数量指示)
switch (toc & 0x3) { case 0: // 00: 1个帧 count = 1; break; case 1: // 01: 2个CBR帧 count = 2; cbr = 1; break; case 2: // 10: 2个VBR帧 count = 2; // 需要解析帧大小 break; case 3: // 11: 多个帧(0-120ms) // 第二个字节包含帧数和标志 break; }2,bandwidt 音频带宽
带宽的解析取决于编码模式:
如: SILK 模式(Mode = 0):
| 值 | 带宽名称 | 频率范围 |
|---|---|---|
| 0 | Narrowband (NB) | 4 kHz |
| 1 | Mediumband (MB) | 6 kHz |
| 2 | Wideband (WB) | 8 kHz |
| 3 | Superwideband (SWB) | 12 kHz |
3,mode 编码模式
| 值 | 模式名称 | 说明 |
|---|---|---|
| 0 | SILK_ONLY | 纯 SILK 模式,适用于语音 |
| 1 | HYBRID | 混合模式,同时使用 SILK 和 CELT |
| 2 | CELT_ONLY | 纯 CELT 模式,适用于音乐 |
| 3 | 保留 | 未使用 |
2.2, 解码流程
┌─────────────────────┐ │ 输入 Opus 数据包 │ └──────────┬──────────┘ │ ┌──────────▼──────────┐ │ 读取 TOC 字节 │ │ (解析 Mode/BW/C/F) │ └──────────┬──────────┘ │ ┌──────────▼──────────┐ │ 判断帧结构类型 │ │ (F=0/1/2/3) │ └──────────┬──────────┘ │ ┌──────────────────────┼──────────────────────┐ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ F=0 │ │ F=1/2 │ │ F=3 │ │ 单帧 │ │ 双帧 │ │ 多帧 │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ 直接解码 │ │ 解析大小 │ │ 解析帧头 │ │ 单帧 │ │ 后解码 │ │ +大小 │ └──────────┘ └──────────┘ └────┬─────┘ │ ▼ ┌──────────┐ │ 解码所有帧│ └──────────┘帧的长度通过,ogg等容器格式获取,就是这整个opus packet包的大小;
opus-packet size -1 = len;
2.2.1 单帧模式(F=0)
这种结构最简单,整个剩余数据就是一帧数据。只需要解码这一帧就好。
Single Frame Packet ┌─────────────────┐ │ TOC (1 byte) │ │ Audio Data len-1 │ └─────────────────┘2.2.2 双帧 CBR 模式(F=1)
两帧大小相等,无需显式编码, 两个帧的长度分别占总长度的一半;
Two CBR Frames Packet ┌─────────────────────────┐ │ TOC (1 byte) │ │ Frame 1 Data │ │ Frame 2 Data │ (大小 = (len-1) / 2)两帧等长,无显式大小信息: └─────────────────────────┘2.2.3 双帧 VBR 模式(F=2)
Two VBR Frames Packet ┌─────────────────────────┐ │ TOC (1 byte) │ │ Size 1 (1-2 bytes) │ │ Frame 1 Data 第一帧大小通过计算获取 │ │ Frame 2 Data │ (大小 = 剩余长度) └─────────────────────────┘第一帧大小显式编码,第二帧占剩余空间,Opus 使用可变长度编码(VLE)来表示帧大小,第一帧的计算公式为:
static int parse_size(const unsigned char *data, opus_int32 len, opus_int16 *size) { if (len < 1) { *size = -1; return -1; } else if (data[0] < 252) { // 单字节编码:0-251 *size = data[0]; return 1; } else if (len < 2) { *size = -1; return -1; } else { // 双字节编码:252-1275 *size = 4 * data[1] + data[0]; return 2; } }| 范围 | 编码方式 | 字节数 |
|---|---|---|
| 0-251 | 直接编码 | 1 字节 |
| 252-1275 | 4 * data[1] + data[0] | 2 字节 |
2.2.4 多帧模式(F=3)
Multi-Frame Packet ┌────────────────────────────────────────┐ │ TOC (1 byte) │ │ Frame Info (1 byte) │ │ - bits 0-5: 帧数量 N │ │ - bit 6: 填充标志 │ │ - bit 7: CBR/VBR标志 (0=VBR) │ │ Padding Bytes (if padded) │ │ Size 1 (VBR only, 1-2 bytes) │ │ ... │ │ Size N-1 (VBR only, 1-2 bytes) │ │ Frame 1 Data │ │ ... │ │ Frame N Data │ │ Extensions (可选) len -以上所有字节 │ └────────────────────────────────────────┘1 Frame Info
这种模式新增了一个Frame Info, 用于获取帧数量,和帧格式标志。
Frame Info (1 byte) │ │ - bits 0-5: 帧数量 N │ │ - bit 6: 填充标志 │ │ - bit 7: CBR/VBR标志 (0=VBR)2.2.5 扩展数据(Extensions)
Opus 包末尾可以包含扩展数据,如 LBRR(低比特率冗余)和 DRED(解码器冗余):
扩展类型
| ID | 类型 | 说明 |
|---|---|---|
| 0 | 填充 | 无意义填充字节 |
| 1 | 帧分隔符 | 标记帧边界 |
| 2 | 重复指示符 | 后续帧重复相同扩展 |
| 3-31 | 短扩展 | 载荷长度 0-1 字节 |
| 32-127 | 长扩展 | 载荷长度可变 |
