最近有客户要求在进行rtsp拉流时希望音频是mp3,而我们常规支持的只有常用的g711a,u这些。所以开始查找资料进行探索,目前rtp负载mp3有两种方式,一种是 rfc3119文档描述的方法把音频数据累积,然后提取出ADU数据作为一帧音频数据通过rtp发送出去,第二种就是 rfc2250文档描述的在每帧音频头前再加4个字节的头,两种方法的差异性在我看来是rfc2250文档描述的方法较简单,实现比较容易,rfc3119文档描述的方法较麻烦,优点是丢包后带来的音频解码影响相对较小. 经过两周的折腾,终于把两种方法都实现了,使用vlc都可以听到清晰的音频,现在来说说这两周中遇到的坑。
由于我们是嵌入式设备,内存和资源以及处理能力都比较有限,所以设备没有采用MPEG1 Layer3 和MPEG2 Layer3这两种官方标准音频格式编码,而是用的MPEG2.5 Layer3, 好处是采样率支持8k采样率,耗费设备资源性能较小. 坑就是这个MPEG2.5 ,在官方RFC文档中没有这个MPEG2.5的相关描述,它是一个非标准的编码格式,在解音频帧 side_info头信息时,官方文档中描述 side_info的长度有32,17,9 三种,根据编码方式和单双声道的不同而不同,而这个MPEG2.5 我无法确定其side_info的长度,就把这三个都试了一遍,结果解析出来的音频都不对,后面陷入瓶颈,只好老老实实去看live555的源码,live555支持rtp上传mp3,最后发现MPEG2.5单声道的side_info的长度是9字节,并且我之前写的解析ADU数据的解析方法也写错了,移位的时候写错了长度,导致解析出来的数据全是错的,所以无法正确解析音频。通过参考live555的源码,我修改了自己的代码,完成了对两种负载方法实现.
第一种rfc2250实现方法:
在mp3音频数据的帧头前加4个字节,如下图rfc2250文档描述,实际可以是4个0字节.
3.5 MPEG Audio-specific header
This header shall be attached to each RTP packet at the start of the
payload and after any RTP headers for an MPEG1/2 Audio payload type.
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| MBZ | Frag_offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Frag_offset: Byte offset into the audio frame for the data
in this packet.
这里需要保证rtp负载的是一帧完整的mp3音频帧,mp3音频帧可以通过前四个字节的头信息计算出这帧音频的长度,可以用此方法判断自己拿到的音频帧是否是完整的音频帧,mp3实时采样时位率是变化的,所以音频帧的长度并不固定,这里贴一个简单的头部解析图,是我之前分析时随手写的
FF E3 38 C4: 4字节头
1111 1111 1110 0011 0011 1000 1100 0100
FF E3 28 C4: 4字节头 位率是变化的
1111 1111 1110 0011 0010 1000 1100 0100
FF E3 48 C4: 4字节头 位率是变化的
1111 1111 1110 0011 0100 1000 1100 0100
同步信息 11 bit: 1111 1111 111
版本 2 bit: 00 00-MPEG2.5 01-未定义 10-MPEG2 11-MPEG1
层 2 bit: 01 00-未定义 01-Layer3 10-Layer2 11-Layer1
CRC校验 1 bit: 1 0-校验 1-不校验
位率 4 bit: 00 11 24kbps (16kbps)
采样频率 2 bit: 10 8kHZ
帧长调节 1 bit: 0 0-无需调整 1-需要调整
保留字 1 bit: 0 没有使用
声道模式 2 bit: 11 00-立体声Stereo 01-Joint Stereo 10-双声道 11-单声道
扩充模式 2 bit: 00 当声道模式为 01 时才使用
版权 1 bit: 0 0-无版权 1-有版权
原版标志 1 bit: 1 0-非原版 1-原版
强调方式 2 bit: 00 00-未定义
下文摘自互联网,文章末尾会标注出处。
名称 | 位长 |
|
---|---|---|
同步信息 | 11 | 所有位均为1,第1字节恒为FF> |
版本 | 2 | 00-MPEG 2.5 01-未定义 10-MPEG 2 11-MPEG 1 |
层 | 2 | 00-未定义 01-Layer 3 10-Layer 2 11-Layer 1 |
CRC校验 | 1 | 0-校验 1-不校验 |
比特率 | 4 | 单位是kbps,例如采用MPEG-1 Layer 3,128kbps是,值为1001 具体参数对应比特率表 |
采样率 | 2 | 采样频率,对于MPEG-1: 00-44.1kHz 01-48kHz 10-32kHz 11-未定义 对于MPEG-2: 00-22.05kHz 01-24kHz 10-16kHz 11-未定义 对于MPEG-2.5: 00-11.025kHz 01-12kHz 10-8kHz 11-未定义 |
帧长调节 | 1 | 用来调整文件头长度,0-无需调整,1-调整,具体调整计算方法见下文 |
保留字 | 1 | 没有使用 |
声道模式 | 2 | 表示声道, 00-立体声Stereo 01-Joint Stereo 10-双声道 11-单声道 |
扩充模式 | 2 | 当声道模式为01是才使用 Value 强度立体声 MS立体声 00 off off 01 on off 10 off on 11 on on |
版权 | 1 | 文件是否合法,0-不合法 1-合法 |
原版标志 | 1 | 是否原版, 0-非原版 1-原版 |
强调方式 | 2 | 用于声音经降噪压缩后再补偿的分类,很少用到,今后也可能不会用。 00-未定义 01-50/15ms 10-保留 11-CCITT J.17 |
|
|
|
|
|
|
|
---|---|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 2.2.2、CRC校验
如果帧头的校验位为0,则帧头后就有一个16位的CRC值,这个值是big-endian的值,把这个值和该帧通过计算得出的CRC值进行比较就可以得知该帧是否有效。
例子中是校验值为0,需要进行校验。CRC校验值即是 帧头的后2个字节:06 9B
CRC校验本文不做详细介绍。
###2.2.3、 MP3帧长的计算
MP3帧长取决于位率和频率,计算公式为:
Size=((采样个数 * (1 / 采样率))* 帧的比特率)/8 + 帧的填充大小
a),帧的填充大小就是第23位的帧长调节,不是0就是1。
**b),**采样个数:MPEG的不同规范(MPEG-1/2/3),以及同一规范中不同的 Layer(Layer I/II/III),每一帧所对应的采样数,都是固定的,其具体的值参见下表:
|
|
|
|
---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
对于Mp3格式,MPEG 2.5 Layer3 采样个数是固定的576,
Size = ((每帧采样数/8*比特率)/采样频率)+填充
例:
如2.2.1中的数据,比特率为16K,采样率为8K,填充0,则其帧长度为:
(576* 16K/8)/8K +0= 144 (字节)
###2.2.4、Side Info(通道信息)
在帧头后边是Side Info(姑且称之为通道信息)。对标准的立体声MP3文件来说其长度为32字节。通道信息后面是Scale factor(增益因子)信息。当解码器在读到上述信息后,就可以进行解码了。 图2.2.1中是紧接着校验位的后32字节的数据。
帧边信息解码的主要目的在于找出解这帧的各个参数,包括主数据开始位置,尺度因子长度等。帧边信息如图2.2.4-1所示。
其中,main_data_begin(主数据开始)是一个偏移值,指出主数据是在同步字之前多少个字节开始。需要注意的是,1.帧头不一定是一帧的开始,帧头CRC校验字和帧边信息在帧数据中是滑动的。2.这个数值忽略帧头和帧边信息的存在,如果main_data_begin = 0, 则主数据从帧边信息的下一个字节开始.
这里要细说main_data_begin,之前做的的时候好久都没搞清楚它的意思,简单的说,例如,从当前帧side_info数据中算出AUD数据的size是500, main_data_begin是380,则表示要从前面几帧音频数据中读到380字节数据,再从当前帧中读到120字节的数据,新的AUD帧的头信息要用当前mp3帧的头,头信息包含4字节头和边信息。
第二种rfc3119实现方法,收取到音频帧后把数据放入链表中,通过每一帧中的main_data_begin和ADU size来判断前面累积的音频数据是否够一帧ADU数据(累积的数据只算frame_date,不算4字节头和side_info的数据),如果前面累积的frame_date_size大于或者等于main_data_begin,则开始取ADU数据,方法是往前偏移main_data_begin字节开始取数据,不算头信息和side_info,取到足够main_data_begin的数据后再从当前mp3帧取剩下的数据,最终组成新的AUD帧,长度是前面计算出的ADU size.