从RTP包中解析AAC数据
源码地址:https://github.com/zhouyinfei/rtsp-netty-server
首先上代码:
//rtp拆包成ADTS列表
public static List<byte[]> rtpToAdtsPack(RawPacket rtpPacket){
//aac码流处理
if (rtpPacket.getPayloadType() == 96) { //以下处理仅针对H264码流
byte[] rtpPayload = rtpPacket.getPayload();
int aUHeadersLength = ((rtpPayload[0]&0xFF)<<16) + (rtpPayload[1]&0xFF);
int auCount = aUHeadersLength/16; //AAC帧的数量
int index = 2 + auCount*2; //上一次解析到的位置
List<byte[]> adtsList = new ArrayList<byte[]>(); //ADTS帧列表
for (int i = 2; i < 2+2*auCount; i+=2) { //遍历所有auHeader
int aacDataLen = ((rtpPayload[i]&0xFF)<<5) + ((rtpPayload[i+1]&0xF8)>>3); //AAC的数据长度(不包括header)
byte[] aacData = new byte[aacDataLen]; //AAC的数据
System.arraycopy(rtpPayload, index, aacData, 0, aacDataLen);
index += aacDataLen;
byte[] adts = addAdtsHeader(aacData);
adtsList.add(adts);
}
return adtsList;
}
return null;
}
//构造ADTS帧,添加ADTS头
public static byte[] addAdtsHeader(byte[] aacData){
ByteBuffer bb = ByteBuffer.allocate(aacData.length + 7);
bb.put((byte) 0xFF);
bb.put((byte) 0xF1);
bb.put((byte) 0x60);
short aacFrameLength = (short) ((aacData.length + 7)&0x1FFF); //取低13位
bb.put((byte) (((aacFrameLength>>11)&0x0F) + 0x40));
bb.put((byte) ((aacFrameLength>>3)&0xFF)); //取低3位往后数8位
bb.put((byte) (((aacFrameLength&0x07)<<5) + 0x02)); //取低3位作为结果的高3位,后5位固定:0 0010
bb.put((byte) 0x40);
bb.put(aacData);
return bb.array();
}
从RTP包中解析AAC数据,相对于从RTP包中解析H264数据来说要简单一些,因为AAC的数据帧比较小,不会出现分片封包情况,只有组合封包和单一封包这两种情况。
首先,我们需要了解AAC音频文件格式。
AAC音频文件有一帧一帧的ADTS帧组成,每个ADTS帧包含ADTS头部和AAC数据,如下所示
ADTS头部的大小通常为7个字节,包含着这一帧数据的信息,内容如下
各字段的意思如下
- syncword
总是0xFFF, 代表一个ADTS帧的开始, 用于同步.
- ID
MPEG Version: 0 for MPEG-4,1 for MPEG-2
- Layer
always: ‘00’
- protection_absent
Warning, set to 1 if there is no CRC and 0 if there is CRC
- profile
表示使用哪个级别的AAC,如01 Low Complexity(LC) – AAC LC
- sampling_frequency_index
采样率的下标
- aac_frame_length
一个ADTS帧的长度包括ADTS头和AAC原始流
- adts_buffer_fullness
0x7FF 说明是码率可变的码流
- number_of_raw_data_blocks_in_frame
表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧
这里主要记住ADTS头部通常为7个字节,并且头部包含aac_frame_length,表示ADTS帧的大小。
AAC的RTP打包方式如下:
首先是2个字节的AU-headers-length,因为可以有多个au-header所以AU-headers-length的值是 16的倍数。例如上述例子中AU-headers-length是0x0060,就表示AU-header有6个,每个AU-header占2个字节。
//计算AU-headers-length值
int aUHeadersLength = ((rtpPayload[0]&0xFF)<<16) + (rtpPayload[1]&0xFF);
而AAC data是多个adts里的data合在了一起,需要根据长度来拆解开。并且,有AU-header,就有多少个ADTS帧。
AU-header中高13个bits就是一个au 的字节长度,只要获取到该长度,就可以从RTP包中解析出AAC的data,然后再添加一个ADTS头,存储到文件中即可。
//计算每一个AAC data的长度
int aacDataLen = ((rtpPayload[i]&0xFF)<<5) + ((rtpPayload[i+1]&0xF8)>>3); //AAC的数据长度(不包括header)
例如,上面例子中,第一个AU-header是05 C8,换成2进制是0000 0101 1100 1000,取前13位是0000 0101 1100 1,换算成十进制就是185,也就是第一个AAC data的长度是185。
取出AAC data添加一个ADTS头,再存入文件中。
int aacDataLen = ((rtpPayload[i]&0xFF)<<5) + ((rtpPayload[i+1]&0xF8)>>3); //AAC的数据长度(不包括header)
byte[] aacData = new byte[aacDataLen]; //AAC的数据
System.arraycopy(rtpPayload, index, aacData, 0, aacDataLen);
index += aacDataLen;
byte[] adts = addAdtsHeader(aacData);
关于添加一个ADTS头,这里有两个地方需要注意的,一个是采样率,一个是ADTS帧长度。我这里采样率采用的是0x8(16000),
而ADTS帧长度存储在倒数13位往前数13位,跨越了3个字节,计算的时候稍微有一点点麻烦。
short aacFrameLength = (short) ((aacData.length + 7)&0x1FFF); //取低13位
bb.put((byte) (((aacFrameLength>>11)&0x0F) + 0x40));
bb.put((byte) ((aacFrameLength>>3)&0xFF)); //取低3位往后数8位
bb.put((byte) (((aacFrameLength&0x07)<<5) + 0x02)); //取低3位作为结果的高3位,后5位固定:0 0010
其他字段我采用的是固定格式,没有传参数进来
ByteBuffer bb = ByteBuffer.allocate(aacData.length + 7);
bb.put((byte) 0xFF);
bb.put((byte) 0xF1);
bb.put((byte) 0x60);
目前的代码都经过了测试,是可用的。