Asterisk播放mp4(2)——音频封装
Asterisk现有版本不支持播放视频文件(支持视频通话),无法满足发送视频通知、视频IVR等场景。本系列文章,通过学习音视频的相关知识和工具,尝试实现一个通过Asterisk播放mp4视频文件的应用。
在音视频媒体领域中,要区分容器格式和编码格式。我理解,容器(container)是包含了描述媒体流的信息和媒体流数据的介质(文件或网络通道),其中可以包含多路媒体流。编码格式描述的是媒体流中如何记录数据,例如:声音采样,一帧图片等。本文学习音频流处理时常用的容器,包括4个部分:WAV格式,MP3格式,RTP音频传输和制作样本数据。
WAV格式
WAV是微软开发的一种音频文件格式,符合RIFF(资源互换文件格式)
规范。RIFF,全称Resource Interchange File Format,是一种按照标记区块存储数据的通用文件存储格式。符合RIFF规范的文件中,,最小存储单位为“块”(Chunk),每个块(Chunk)包含三个部分:块标识(4字节,如果不够4字节,在右边以空格填充),块长度(4字节,数据块的长度,不包括块标识和块长度的8个字节,采用小子节序)和数据。RIFF格式规定,只有 RIFF及LIST块可以含有子块,其它的块不允许包含子块。WAV文件至少由3个块构成,分别是RIFF、fmt 和Data。所有基于压缩编码的WAV文件必须含有fact块。此外所有其它块都是可选的。块fmt,Data及fact均为RIFF块的子块。
![](https://img.haomeiwen.com/i258497/76850fff3a635c13.png)
MP3格式
MP3截掉了大量的冗余信号和无关的信号,编码器通过混合滤波器组将原始声音变换到频率域,利用心理声学模型,估算刚好能被察觉到的噪声水平,再经过量化,转换成Huffman编码,形成MP3位流。MP3是一种有损压缩,压缩比在1:10到1:12之间。
理解MP3,首先需要搞清版本(version)和层(layer)的概念,版本包括:MPEG-1(ISO/IEC 11172-3),MPEG-2(ISO/IEC 13818-3)和MPEG-2.5(不是官方标准);层包括:Layer-1,Layer-2和Layer-3。MP3的版本和层不是迭代升级的关系,而是为了适应不同使用场景,最主要的指标就是针对不同的采样率。
MPEG-1: Specifies the coded representation of high quality audio for storage media and the method for decoding of high quality audio signals. Is intended for application to digital storage media providing a total continuous transfer rate of about 1,5 Mbit/s for both audio and video bitstreams, such as CD, DAT and magnetic hard disc, and for sampling rates of 32 kHz, 44,1 kHz, and 48 kHz.
MPEG-2: In order to achieve better audio quality at very low bit rates (<64 kbit/s per audio channel), in particular if compared with ITU-T (formerly CCITT) Recommendation G.722 performance, three additional sampling frequencies are provided for ISO/IEC 11172-3 layers I, II and III. The additional sampling frequencies (Fs) are 16 kHz, 22,05 kHz and 24 kHz.
MPEG-2.5: 由于MPEG-2定义的最低的采样率为16kHz,于是Fraunhofer(是个德国的研究所)便对此进行扩展,将原来MPEG-2所支持的低采样率再除以2,得到: 8, 11.025, 和 12 kHz ,称为 "MPEG 2.5"。
Layer-1的编码器最为简单,编码器的输出数据率为384 kb/s,主要用于小型数字盒式磁带(digital compact cassette,DCC)。
Layer-2的编码器的复杂程度属中等,编码器的输出数据率为256 kb/s~192 kb/s,其应用包括数字广播声音(digital broadcast audio,DBA)、数字音乐、CD-I(compact disc-interactive)和VCD(video compact disc)等。
Layer-3的编码器最为复杂,编码器的输出数据率为64 kb/s,主要应用于ISDN上的声音传输。
![](https://img.haomeiwen.com/i258497/a55e3c2dfb604613.png)
MP3文件大体上分为三个部分:ID3v2(不影响对音频的解析,放音时可以跳过) + 音频数据(MP3编码的帧序列,每个帧包含帧头和数据两部分,帧和帧相互独立) + ID3v1(文件尾部128字节,可选)。
ID3v2
ID3v2标签用于记录mp3文件的元数据,标签头中标记是否包含扩展头(Extended Header),填充(Padding)等部分。ID3v2标签至少包含1个标签帧(frame),每帧描述了文件的某个信息,例如:作者,专辑等等。ID3v2标签内部采用大字节序。
![](https://img.haomeiwen.com/i258497/8f3f742641c82e46.png)
音频数据
每个音频数据帧由4个部分组成:帧头(4字节,必须),CRC(2字节,可选),边信息(9,17或32字节,必须),VBR头(8-120字节,可选),音频编码数据(必须)。
Side Infomation(边信息): If joint stereo is used in M/S (middle/side) mode, the left and right channels aren't encoded separately. Instead, a "middle" channel is encoded as the sum of the left and right channels, while a " side" channel is stored as the difference between the left and the right. During the decoding process, side information is read back out of the frame and applied to the bitstream so that the original signal can be reconstructed as accurately as possible. The side information is essentially a set of instructions on how the whole puzzle should be re-assembled on the other end.
MP3有两种编码方式:1,CBR(固定位率),帧的大小在整个文件中都是是固定的;2,VBR(可变位率),VBR是XING公司推出的算法,所以在MP3的FRAME里会有“Xing"这个关键字(也有用"Info"来标识的),它存放在MP3文件中的第一个有效帧的数据区里,它标识了这个MP3文件是VBR的。同时第一个帧里存放了MP3文件的帧的总个数,这就很容易获得播放总时间,同时还有100个字节存放了播放总时间的100个时间分段的帧索引,假设4分钟的MP3歌曲,240S,分成100段,每两个相邻INDEX的时间差就是2.4S,所以通过这个INDEX,只要前后处理少数的FRAME,就能快速找出我们需要快进的帧头。其实这第一帧就相当于文件头了。不过现在有些编码器在编码CBR文件时也像VBR那样将信息记入第一帧,比如著名的lame,它使用"Info"来做CBR的标记。
RTP音频传输
RTP全名是Real-time Transport Protocol(实时传输协议)。RTP用来为IP网上的语音、图像、传真等多种需要实时传输的多媒体数据提供端到端的实时传输服务。RTP为端到端的实时传输提供时间信息和流同步,但并不保证服务质量,服务质量由RTCP来提供。我理解RTP
就是一种容器,核心是解决通过网络实时传递数据的问题。
![](https://img.haomeiwen.com/i258497/f69c8a094313d424.png)
前12
个字节是固定的RTP帧头,每个帧都会携带;贡献源列表(CSRC List):0~15项(实际的数量由CC
字段指定),每项32比特,用来标志对一个RTP混合器产生的新包有贡献的所有RTP包的源;再后面就是携带的数据(payload)。
RTP头中各个字段的信息将在后续的文章中详细解析,这里先关注一下PT(payload type)
字段。PT指明了RTP传递的数据类型,值0-95是有预定义的(RFC3551),例如:8是pcma,0是pcmu等,96-127是动态指定的。通过asterisk建立通话的过程中,要通过sip
传递sdp
,sdp
中指定了要建立的rtp
通道的相关参数,例如:端口,媒体类型和媒体参数等,sdp
中指定的媒体类和rtp
头中的PT
字段相对应。
![](https://img.haomeiwen.com/i258497/e46893a72e10c5f1.png)
除了关注Payload type外,还要注意MTU(最大传输单元,Maximum Transmission Unit),它是数据链路层的概念,用于限制数据包的大小(例如:IP包),如果数据包大于这个值就会拆包或丢弃,取值一般是1500(或1492)字节。一个RTP包中,IP+UDP+RTP头=20+8+12=40字节。这样一个RTP最大的载荷数据为1460字节。
制作样本数据
WAV格式
生成一段10秒钟音频文件,编码格式为pcm_s16le
,容器格式为wav
:
ffmpeg -lavfi sine -t 10 -f wav -c:a pcm_s16le sine-10s-s16le.wav
生成的文件大小为882078
字节,我们知道10秒钟44.1k采样率单声道pcm_s16le
编码的裸流大小为882000
,那么wav
容器编码就是增加78字节的文件头。下面我们看看这78字节的内容:
![](https://img.haomeiwen.com/i258497/efc326d0b2e9250d.png)
- RIFF块
子节位置 | 字节数 | 字段 | 数值 | 解析 |
---|---|---|---|---|
0-3 | 4 | 块标识 | 5249 4646 | ASCII码,大写RIFF,指明块是RIFF块。 |
4-7 | 4 | 块大小 | 9675 0d00 | 小字节序,882070,数据块大小。 |
8-11 | 4 | 格式 | 5741 5645 | ASCII码,大写WAVE,说明文件格式 |
文件的大小是882078,要减去12个字节,还剩882066字节需要解析。
- fmt子块
子节位置 | 字节数 | 字段 | 数值 | 解析 |
---|---|---|---|---|
12-15 | 4 | 块标识 | 666d 7420 | ASCII码,小写fmt,注意最后1个字节填充的是空格(20),指明块是fmt块。 |
16-19 | 4 | 块大小 | 1000 0000 | 小字节序,16,数据块大小。不同音频编码格式数据块的长度并不相同,详细内容看参考链接。 |
20-21 | 2 | 音频格式(AudioFormat) | 0100 | 小字节序,1,PCM/非压缩格式。(其他格式的定义查看参考资料) |
22-23 | 2 | 声道数(NumChannels) | 0100 | 小字节序,1,单声道。有几个声道就是几。 |
24-27 | 4 | 采样率(SampleRate) | 44ac 0000 | 小字节序,44100。 |
28-31 | 4 | 传输速率(ByteRate) | 8858 0100 | 小字节序,88200,每个采样2个字节,所以每秒要传输88200字节。 |
32-33 | 2 | 数据块对齐单位(BlockAlign) | 0200 | 小字节序,2,该数值为:声道数×位数/8。 |
34-35 | 2 | 采样位数(BitsPerSample) | 0010 | 小字节序,8,每个采样8位二进制。 |
fmt子块一共24个字节,882066减24,还有882042个字节需要解析。
- LIST子块
子节位置 | 字节数 | 字段 | 数值 | 解析 |
---|---|---|---|---|
36-39 | 4 | 块标识 | 4c49 5354 | ASCII码,大写LIST,指明块是LIST块。 |
40-43 | 4 | 块大小 | 1a00 0000 | 小字节序,26,数据块大小。 |
44-47 | 4 | 块类型 | 494e 464f | ASCII码,大写INFO。这个块是用来描述文件信息的,并不影响解析,大体知道就行。 |
48-51 | 4 | 块标识 | 4953 4654 | ASCII码,大写ISFT,创建文件的软件。 |
52-55 | 4 | 块大小 | 0e00 0000 | 小字节序,14,数据块大小。 |
56-69 | 14 | 块数据 | 4c61-3000 | Lavf58.29.100 |
LIST子块一共34个字节,882042减34,还有882008个字节需要解析。
- data子块
子节位置 | 字节数 | 字段 | 数值 | 解析 |
---|---|---|---|---|
70-73 | 4 | 块标识 | 6461 7461 | ASCII码,小写data,指明块是data块。 |
74-77 | 4 | 块大小 | 5075 0d00 | 小字节序,882000,数据块大小,和裸流字节数一致。 |
至此,78个字节的文件头解析完毕。由此可以进一步理解:容器是媒体流的描述信息和数据流,只有提供了描述信息,解析程序才知道如何读取数据流。
MP3格式
ffmpeg -lavfi sine -t 10 -f mp3 -c:a mp3 sine-10s.mp3
![](https://img.haomeiwen.com/i258497/fcca14bf11b9f7c3.png)
生成的mp3文件是单声道,采样率44.1k。文件大小80475
字节,是WAV格式882078
字节的1/11,极大减少了存储空间。
ID3v2头
![](https://img.haomeiwen.com/i258497/6efcf56bb9870be4.png)
- 标签头
子节位置 | 字节数 | 字段 | 数值 | 解析 |
---|---|---|---|---|
0-2 | 3 | 标签头标识 | 4944 33 | ASCII码,ID3,必须以此开头,说明是ID3v2标签。 |
3-4 | 2 | 版本号 | 04 00 | 4,0,表示为ID3v2.4.0版本。 |
5 | 1 | 标识字节 | 00 | 具体解释查看参考资料。其中第6位(左边第2位)指明是否包含扩展头,第4位(左边第4位)指明是否包含尾(footer)。 |
6-9 | 4 | 标签大小 | 0000 0023 | 每个字节去掉首位后的28位二进制数。 |
The ID3v2 tag size is the sum of the byte length of the extended
header, the padding and the frames after unsynchronisation. If a
footer is present this equals to ('total size' - 20) bytes, otherwise
('total size' - 10) bytes.
ID3标签的大小是35字节,加上10字节的头,所以总长度是45个字节。
- 标签帧
子节位置 | 字节数 | 字段 | 数值 | 解析 |
---|---|---|---|---|
10-13 | 4 | 帧ID | 5453 5345 | ASCII码,TSSE(Software/Hardware and settings used for encoding) |
14-17 | 4 | 帧大小 | 0000 000f | 15字节,不包含帧头的10字节。 |
18-19 | 2 | 标志 | 0000 | 描述帧的特征,不影响解析,可以不深究。 |
20-34 | 15 | 帧数据 | 034c ... 3030 00 | Lavf58.29.100 |
10个字节的头和15字节的数据,共25个字节。
- 填充(Padding)
在ID3v2标签在尾部填充了10个空字节(0x00),把差的10个字节凑齐。
数据第1帧
![](https://img.haomeiwen.com/i258497/522641cd05d06594.png)
- 帧头
帧头32个字节,ff fb 40 c0
,由11个部分组成,用如下方式AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM(00_0000)
标记。
A_11 | B_2 | C_2 | D_1 | E_4 | F_2 | G_1 | H_1 | I_2 | J_2 | K_1 | L_1 | M_2 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
1111 1111 111 | 11 | 01 | 1 | 0100 | 00 | 0 | 0 | 11 | 00 | 0 | 0 | 00 |
标记 | 位数 | 位置 | 值 | 描述 |
---|---|---|---|---|
A | 11 | 31-21 | 1111 1111 1 | 同步信息,所有位均为1,表示帧开始。 |
B | 2 | 20-19 | 11 | 版本,MPEG1。 |
C | 2 | 18-17 | 01 | 层,Layer3。 |
D | 1 | 16 | 1 | CRC校验,等于1,不校验。 |
E | 4 | 15-12 | 0100 | 传输率,等于0100,56kbps。需要查对照表(看参考资料)。 |
F | 2 | 11-10 | 00 | 采样率,等于00,44.1kHz。 |
G | 1 | 9 | 0 | 填充位,等于0,不需要。 |
H | 1 | 8 | 0 | 保留字 |
I | 2 | 7-6 | 11 | 声道模式,11-单声道 |
J | 2 | 5-4 | 00 | 扩充模式,无 |
K | 1 | 3 | 0 | 版权,0-不合法 |
L | 1 | 2 | 0 | 原版标识,0-非原版 |
M | 2 | 1-0 | 00 | 强调方式,等于00,未定义。 |
需要注意采样率(11-10位)是和版本和层有对应关系的,其取值对照含义如下表:
bits | MPEG1 | MPEG2 | MPEG2.5 |
---|---|---|---|
00 | 44100 | 22050 | 11025 |
01 | 48000 | 24000 | 12000 |
10 | 32000 | 16000 | 8000 |
11 | reserv. | reserv. | reserv. |
- 边(Side Info)
17字节,首帧中数据全是0,还没有找到详细的解释。真正的数据帧这部分信息都是有对应值的,解码器要根据这些信息才能把数据解出来。
- VBR头(Info(Xing))
位置 | 长度 | 值 | 说明 |
---|---|---|---|
0 | 4 | 496e 666f | ASCII码,Info。VBR头是Xing公司提出的,所以也有用Xing作为标识的情况。 |
4 | 4 | 0000 000f | 标志显示当前字段的标志,与逻辑或逻辑相结合。字段是强制性的。0x00000001 - 包含帧数字段;0x00000002 - 包含字节数字段;0x00000004 - 包含TOC部分 ;0x00000008 - 包含质量指示(0-100)。说明后面的4个部分内容都有。 |
8 | 4 | 0000 0180 | 384 帧数量 Number of Frames as Big-Endian DWORD (optional) |
8 or 12 | 4 | 0001 3a2e | 80430 帧字节数(去掉ID3标签) Number of Bytes in file as Big-Endian DWORD (optional) |
8, 12 or 16 | 100 | - | 100 TOC entries for seeking as integral BYTE |
8, 12, 16, 108, 112 or 116 | 4 | 0000 0000 | Quality indicator as Big-Endian DWORDfrom 0 - best quality to 100 - worst quality |
帧头 | 边信息 | VBR标记 | 填充 |
---|---|---|---|
4字节 | 17字节 | 120 字节 | 41字节 |
在VBR
头后面存储编码器的信息,比如 lame 在其后就是存储其版本,把第1帧填满。
后续数据帧
MP3中每帧的采样数和版本和层的对照是固定的,如下表:
MPEG1 | MPEG2 | MPEG2.5 | |
---|---|---|---|
Layer1 | 384 | 384 | 384 |
Layer2 | 1152 | 1152 | 1152 |
Layer3 | 1152 | 576 | 576 |
根据该表可知每帧的采样数是1152个,采样率是44100,每帧字节数:56(比特率)/44.1(采样率)x1152(帧采样数)/8 = 182字节(截断小数部分)。首帧的地址为2d
,向后182个字节e3
,是第2帧的开始位置。通过这种方式就可以找到后面每一帧的起始地址。
第2帧的起始地址0x00e3
,帧头为ff fb 50 c4
。
A_11 | B_2 | C_2 | D_1 | E_4 | F_2 | G_1 | H_1 | I_2 | J_2 | K_1 | L_1 | M_2 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
1111 1111 111 | 11 | 01 | 1 | 0101 |
00 | 0 | 0 | 11 | 00 | 0 | 1 | 00 |
传输率和首帧的值不同,为0101
,对应64kbps
,计算帧大小为208字节(截断小数部分)。
第3帧的起始地址0x01b3
,帧头为ff fb 52 c4
。
A_11 | B_2 | C_2 | D_1 | E_4 | F_2 | G_1 | H_1 | I_2 | J_2 | K_1 | L_1 | M_2 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
1111 1111 111 | 11 | 01 | 1 | 0101 | 00 | 1 |
0 | 11 | 00 | 0 | 1 | 00 |
和第1帧填充位值不同,等于1,需要填充。帧大小为截断后加1,等于209。
第4帧的起始地址0x0284
,和计算结果一致。
转换为PCM
我们研究的目标是通过asterisk
进行媒体的播放,mp3是一种有损压缩,那么将mp3转码为pcm后失真是否严重?下面将ffmpeg生成的mp3格式的sine波形转换为pcm_s16le,看看波形的变化。
ffmpeg -i sine-10s.mp3 -f s16le -c:a pcm_s16le sine-mp32s16le-10s.s16le
![](https://img.haomeiwen.com/i258497/a519c3e1d8228bc1.png)
红色为原始pcm_s16le的数据,蓝色为mp3转换的结果。
![](https://img.haomeiwen.com/i258497/320e3cf40cc0093e.png)
![](https://img.haomeiwen.com/i258497/bdb5f4a292b7ee02.png)
虽然MP3确实导致了数据的损失,但是总体上还是很好的保持了波形。(MP3确实很厉害,只是原始数据的1/10不到,但是损失很小!)
8k采样率的MP3
前面用ffmpeg
默认生成的mp3文件的采样率是44.1k,而且按照mp3的规范我们知道不同采样率会对应不同的版本和层,那么ffmpeg
是否能生成一个采样率是电话系统常用的8k采样率的mp3?
ffmpeg -lavfi sine -t 10 -ar 8000 -f mp3 -c:a mp3 sine-8k-10s.mp3
![](https://img.haomeiwen.com/i258497/6a45887640a83a98.png)
文件的大小为10413
字节,小了很多。
![](https://img.haomeiwen.com/i258497/ff2a9167a4a6dc15.png)
首帧的头为ff e3 38 c0
,解析如下:
A_11 | B_2 | C_2 | D_1 | E_4 | F_2 | G_1 | H_1 | I_2 | J_2 | K_1 | L_1 | M_2 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
1111 1111 111 | 00 | 01 | 1 | 0011 | 10 | 0 | 0 | 11 | 00 | 0 | 0 | 00 |
版本域(B)的值是00
,对应的是MPEG version2.5
;层域(C)的值是01
,对应的是Layer3
;比特率域(E)的值是0011
,对应的是24kbps
;采样率域(F)的值是10
,对应的是8000
。
从这个结果看,我们可以将从电话系统收到的8k音频数据保存为8k的mp3文件。
RTP音频流
ffmpeg -lavfi sine -f rtp rtp://127.0.0.1:5005
![](https://img.haomeiwen.com/i258497/2f8fe0ca833324c1.png)
注意这时输出的sdp
信息,payload type
的值是97
,对应的参数是PCMU/44100/1
,含义是编码格式PCMU
,采样率44100
,单声道。
下面改变一下输出的参数:
ffmpeg -lavfi sine -ar 8000 -c:a pcm_alaw -f rtp rtp://127.0.0.1:5005
![](https://img.haomeiwen.com/i258497/0d0e4b859c602051.png)
前面提到过8
是预定义的媒体类型,对应的是pcma/8000/1
。
下面我们通过wireshark
抓包看一下通过RTP传送的数据什么样。执行如下命令 ,启动nc
监听端口5005
,ffmpeg
向端口发送RTP包(直接用过滤器生成波形无法控制播放速率,导致收到的结果不争取,所以改成播放生成好的文件,添加-re
参数播放速率),启动wireshark
抓包:
ffmpeg -lavfi sine -t 10 -ar 8000 -f alaw -c:a pcm_alaw sine-8k-10s.alaw
nc -lu 5005
ffmpeg -re -ar 8000 -f alaw -i sine-8k-10s.alaw -c:a pcm_alaw -f rtp rtp://127.0.0.1:5005
将抓包结果解码为RTP包,在协议列(Protocol)选中一个UDP的格子:
![](https://img.haomeiwen.com/i258497/fcb2c412ab6c74eb.png)
![](https://img.haomeiwen.com/i258497/a9182b66bb4c9c2a.png)
![](https://img.haomeiwen.com/i258497/2bd0046a0241949c.png)
![](https://img.haomeiwen.com/i258497/e6fd40f5b28b902b.png)
下面我们查看一帧RTP数据的内部:
![](https://img.haomeiwen.com/i258497/61dcd8784a78a575.png)
![](https://img.haomeiwen.com/i258497/060f494cc2f24f71.png)
由上两图可以看到,RTP Payload
中的数据和原始alaw
文件中数据完全一致,说明Payload中的数据是Payload type
指定的格式。Payload的大小是320字节,也就是携带了320个采样,也就是0.0.4秒的时长。 前面提到过,Payload的大小受MTU的限制,所以ffmpeg
发送数据时会控制每个RTP包中包含的采样数。
参考资料
RTP: A Transport Protocol for Real-Time Applications
RTP Profile for Audio and Video Conferences with Minimal Control