详解RTP打包AAC实战分析(1)
0.引言
为了更好理解本篇文章,可以先阅读前面几篇文章,文章列表如下:
建议:阅读本文前,一定要阅读前面的文章,只有理解了原理,才能够真正读懂代码。
1.RTP实战测试效果
(1)源码工程运行界面
(2)源码中生成的aac.sdp,是可以通过播放器,如ffplay或vlc进行实时播放。out.aac是源码工程中,生成的本地aac文件,在源码工程执行完毕后,是可以直接播放。
注意:out.aac需要先存储到本地,然后才能播放。
(3)ffplay播放命令:
ffplay aac.sdp -protocol_whitelist "file,http,https,rtp,udp,tcp,tls"
(4)根据ffmpeg的源码sdp.c,如果是音频编码出错了,可能有2种原因。需要根据源码的日志去判断,是使用的哪个编码器。
3.源码详解
整个数据的流程是如下:
首先从输入文件in.aac循环读取数据,当然头部和数据是分开读取,然后RTP打包,头部和数据也是分开的函数调用,为了演示解封装的效果,这里又调用了解封装,解封装出来的AAC data再加上adts header,就可以写入输出文件out.aac,这样就能正确播放和演示。
in.aac->读取一帧数据->RTP打包->RTP解包->封装成一帧数据->写入文件out.aac。
3.1 添加AAC解封装模块
(1)怎么在原有的结构添加支持?这里就使用mpeg-generic(AAC)代替。相对原有的结构,添加AAC的封装和解封装。如下图:
在接口层rtp-payload-internal.h,添加如下AAC解封装定义:
struct rtp_payload_encode_t *rtp_mpeg4_generic_encode(void);
struct rtp_payload_decode_t *rtp_mpeg4_generic_decode(void);
(2)AAC解封装的实现是rtp-mpeg4-generic-unpack.c和rtp-mpeg4-generic-pack.c。相较于之前的代码,在接口层rtp-payload.c,把AAC的解封装接口层添加进来。
3.2 AAC封装的数据结构
(1)AAC应用层封装的数据结构。源码如下:
struct rtp_encode_mpeg4_generic_t
{
struct rtp_packet_t pkt;
struct rtp_payload_t handler; // 保存回调函数用的
void* cbparam; // 回调函数要用到的参数
int size;
};
(2)AAC header的数据结构,它有7个字节或9个字节,源码如下:
typedef struct{
unsigned int syncword; //12 bit 同步字 '1111 1111 1111',说明一个ADTS帧的开始
unsigned int id; //1 bit MPEG 标示符, 0 for MPEG-4,1 for MPEG-2
unsigned int layer; //2 bit 总是'00'
unsigned int protection_absent; //1 bit 1表示没有crc,0表示有crc
unsigned int profile; //2 bit 表示使用哪个级别的AAC
unsigned int sampling_frequency_index; //4 bit 表示使用的采样频率
unsigned int private_bit; //1 bit
unsigned int channel_configuration; //3 bit 表示声道数
unsigned int original_copy; //1 bit
unsigned int home; //1 bit
/*下面的为改变的参数即每一帧都不同*/
unsigned int copyright_id; //1 bit
unsigned int copyright_id_start; //1 bit
unsigned int aac_frame_length; //13 bit 一个ADTS帧的长度包括ADTS头和AAC原始流
unsigned int adts_buffer_fullness; //11 bit 0x7FF adts buffer fullness
/* number_of_raw_data_blocks_in_frame
* 表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧
* 所以说number_of_raw_data_blocks_in_frame == 0
* 表示说ADTS帧中有一个AAC数据块并不是说没有。(一个AAC原始帧包含一段时间内1024个采样及相关数据)
*/
unsigned int num_raw_data_blocks; //2 bit
}aac_header_t;
(3)存储AAC 一帧(包括头header+data)的数据结构,源码如下:
typedef struct
{
aac_header_t header; // 存储到是adts头部解析后的参数
uint8_t adts_buf[9]; // adts header最多9字节,从文件读取出来的头部数据先放到adts_buf里面
int adts_len; // adts 头部长度
uint8_t frame_buf[8192]; // 包括adts header 13bit最多8192字节
int frame_len; // 一帧的长度包括adts header + data length
}aac_frame_t;
3.3 AAC封装的重要函数
(1)通过应用层传递参数,然后传递进来,做些初始化工作。源码如下:
注意:这里的payload type一定要和生成的aac.sdp的RTP/AVP一致,如下的RTP/AVP是97,所以这里的payload type也会是97。还有这里的packer->pkt.rtp.v = RTP_VERSION,一定是2,否则就不可能播出来。
static void* rtp_mpeg4_generic_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, void* cbparam)
{
struct rtp_encode_mpeg4_generic_t *packer;
packer = (struct rtp_encode_mpeg4_generic_t *)calloc(1, sizeof(*packer));
if (!packer) return NULL;
memcpy(&packer->handler, handler, sizeof(packer->handler));
packer->cbparam = cbparam;
packer->size = size;
packer->pkt.rtp.v = RTP_VERSION; // 版本号一定是2
packer->pkt.rtp.pt = pt; // payload type
packer->pkt.rtp.seq = seq; // 起始的sequence
packer->pkt.rtp.ssrc = ssrc;
return packer;
}
(2)aac.sdp文件如下内容:
m=audio 9832 RTP/AVP 97
a=rtpmap:97 mpeg4-generic/48000/2
a=fmtp:97 streamtype=5;profile-level-id=1;sizeLength=13;IndexLength=3;indexDeltaLength=3;mode=AAC-hbr;config=2190;
c=IN IP4 192.168.129.215
(3)应用层对payload type的设置类型。如下源码:
const char* encoding = "mpeg4-generic";
ctx.payload = 97;
ctx.encoding = encoding;
ctx.encoder_aac = rtp_payload_encode_create(ctx.payload, ctx.encoding, 1, 0x32411, &handler_rtp_encode_aac, &ctx);
3.4.AAC封装源码分析
(1)打开本地的in.aac文件用作输入,创建本地文件的out.aac用作输出。源码如下:
//1.打开本地输入文件,这个文件是要实时发送的
FILE *bits = aac_open_bitstream_file("in.aac");//打开aac文件,并将文件指针赋给bits,在此修改文件名实现打开别的aac文件。
if(!bits)
{
printf("open file failed\n");
return -1;
}
//2.解包后保存的aac文件,主要测试对比in.aac文件是否一致
FILE *out_file = fopen("out.aac", "wb");
if(!out_file)
{
printf("open out_file failed\n");
return -1;
}
(2)RTP封装AAC数据的回调函数说明。源码如下:
struct rtp_payload_t handler_rtp_encode_aac;
handler_rtp_encode_aac.alloc = rtp_alloc;
handler_rtp_encode_aac.free = rtp_free;
handler_rtp_encode_aac.packet = rtp_encode_packet;
//这个是要设置为mpeg4-generic,不能写错
const char* encoding = "mpeg4-generic";
//这个值设置错了,有可能读取AAC输出文件出错
ctx.payload = 97;
ctx.encoding = encoding;
(3)绑定了AAC的回调处理函数handler_rtp_encode_aac与用户设置的参数ctx。源码如下:
ctx.encoder_aac = rtp_payload_encode_create(ctx.payload, ctx.encoding, 1, 0x32411, &handler_rtp_encode_aac, &ctx);
(4)进入到接口层,接口层实际是主要查看是否有支持的编码器。注意,RTP包是支持编码器H264和H265。源码如下:
/**
* @brief rtp_payload_encode_create
* @param payload 媒体的类型
* @param name 对应的编码器 H264/H265
* @param seq
* @param ssrc
* @param handler
* @param cbparam
* @return
*/
void* rtp_payload_encode_create(int payload, const char* name, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, void* cbparam)
{
int size;
struct rtp_payload_delegate_t* ctx;
ctx = calloc(1, sizeof(*ctx));
if (ctx)
{
size = rtp_packet_getsize();
if (rtp_payload_find(payload, name, ctx) < 0 // 查找有没有注册该编码器(封装器)
|| NULL == (ctx->packer = ctx->encoder->create(size, (uint8_t)payload, seq, ssrc, handler, cbparam)))
{
free(ctx);
return NULL;
}
}
return ctx;
}
函数rtp_payload_encode_create(xxx)调用rtp_payload_find(xxx),查找是否支持对应的封装器,这里设置的是"mpeg4-generic",与上面的格式对应起来。同时,这里也提供了AAC封装实现的入口。源码如下:
static int rtp_payload_find(int payload, const char* encoding, struct rtp_payload_delegate_t* codec)
{
assert(payload >= 0 && payload <= 127);
if (payload >= 96 && encoding)
{
if (0 == strcasecmp(encoding, "H264"))
{
// H.264 video (MPEG-4 Part 10) (RFC 6184)
codec->encoder = rtp_h264_encode();
codec->decoder = rtp_h264_decode();
}
else if (0 == strcasecmp(encoding, "mpeg4-generic"))
{
/// RFC3640 RTP Payload Format for Transport of MPEG-4 Elementary Streams
/// 4.1. MIME Type Registration (p27)
codec->encoder = rtp_mpeg4_generic_encode();
codec->decoder = rtp_mpeg4_generic_decode();
}
else
{
return -1;
}
}
else
{
return -1;
}
return 0;
}
(5)函数rtp_mpeg4_generic_encode()是AAC封装的总入口,源码如下:
struct rtp_payload_encode_t *rtp_mpeg4_generic_encode()
{
static struct rtp_payload_encode_t encode = {
rtp_mpeg4_generic_pack_create,
rtp_mpeg4_generic_pack_destroy,
rtp_mpeg4_generic_pack_get_info,
rtp_mpeg4_generic_pack_input,
};
return &encode;
}
函数ctx->encoder->create(size, (uint8_t)payload, seq, ssrc, handler, cbparam)指向的实现函数是rtp_mpeg4_generic_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, void* cbparam)。主要是做些初始化工作,把用户设置的参数传递进来,如RTP_VERSION、sequence、payload type等。源码如下:
static void* rtp_mpeg4_generic_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, void* cbparam)
{
struct rtp_encode_mpeg4_generic_t *packer;
packer = (struct rtp_encode_mpeg4_generic_t *)calloc(1, sizeof(*packer));
if (!packer) return NULL;
memcpy(&packer->handler, handler, sizeof(packer->handler));
packer->cbparam = cbparam;
packer->size = size;
packer->pkt.rtp.v = RTP_VERSION; // 版本号一定是2
packer->pkt.rtp.pt = pt; // payload type
packer->pkt.rtp.seq = seq; // 起始的sequence
packer->pkt.rtp.ssrc = ssrc;
return packer;
}
(6)rtp_encode_packet是应用层由用户层设置的封装的回调函数,与前面H264的封装函数是大同小异,主要功能就是网络发送(这里发送到本地)和解封装后,存储到本地进行播放,源码如下:
/**
* @brief rtp_mpeg4_generic_pack_input
* @param pack
* @param data 包括adts header
* @param bytes 包括adts header的长度
* @param timestamp
* @return <0:失败
*/
static int rtp_mpeg4_generic_pack_input(void* pack, const void* data, int bytes, uint32_t timestamp)
{
int r;
int n, size;
uint8_t *rtp;
uint8_t header[4];
const uint8_t *ptr;
struct rtp_encode_mpeg4_generic_t *packer;
packer = (struct rtp_encode_mpeg4_generic_t *)pack;
packer->pkt.rtp.timestamp = timestamp; //(uint32_t)(time * KHz);
r = 0;
ptr = (const uint8_t *)data;
if (0xFF == ptr[0] && 0xF0 == (ptr[1] & 0xF0) && bytes > 7)
{
// skip ADTS header 跳过adts头部
assert(bytes == (((ptr[3] & 0x03) << 11) | (ptr[4] << 3) | ((ptr[5] >> 5) & 0x07)));
ptr += 7; // 这里直接判断为7个字节
bytes -= 7;
}
// 担心AAC帧可能要拆分多个RTP包
for (size = bytes; 0 == r && bytes > 0; ++packer->pkt.rtp.seq)
{
// 3.3.6. High Bit-rate AAC
// SDP fmtp: sizeLength=13; indexLength=3; indexDeltaLength = 3;
// au-header的高13个bits就是一个au 的字节长度:
header[0] = 0x00; // 高位
header[1] = 0x10; // 低位 16-bits AU headers-lenght
// AU headers 用2个字节,高13bit是 AU size
header[2] = 0; header[3] = 0;
header[2] = (uint8_t)(size >> 5); // 高位
header[3] = (uint8_t)(size & 0x1f) << 3; // 低位 这里一个包只发送一个AU
packer->pkt.payload = ptr;
// N_AU_HEADER 占据了4个字节,AU-headers-length占2个字节,AU-header占2个字节
packer->pkt.payloadlen = (bytes + N_AU_HEADER + RTP_FIXED_HEADER) <= packer->size ? // 判断是否要划分多个AU
bytes : (packer->size - N_AU_HEADER - RTP_FIXED_HEADER);
ptr += packer->pkt.payloadlen;
bytes -= packer->pkt.payloadlen; // 剩余的字节数
n = RTP_FIXED_HEADER + N_AU_HEADER + packer->pkt.payloadlen;
rtp = (uint8_t*)packer->handler.alloc(packer->cbparam, n); // 分配缓存,以备用来保存序列化数据
if (!rtp) return -ENOMEM;
// Marker (M) bit: The M bit is set to 1 to indicate that the RTP packet
// payload contains either the final fragment of a fragmented Access
// Unit or one or more complete Access Units
packer->pkt.rtp.m = (0 == bytes) ? 1 : 0; // 最后一个AU mark设置为1,否则为0
n = rtp_packet_serialize_header(&packer->pkt, rtp, n);
if (n != RTP_FIXED_HEADER)
{
assert(0);
return -1;
}
memcpy(rtp + n, header, N_AU_HEADER); // N_AU_HEADER为什么是4, AU_HEADER_LENGTH AU_HEADER
memcpy(rtp + n + N_AU_HEADER, packer->pkt.payload, packer->pkt.payloadlen); // 封装payload
r = packer->handler.packet(packer->cbparam, rtp, n + N_AU_HEADER + packer->pkt.payloadlen, packer->pkt.rtp.timestamp, 0);
packer->handler.free(packer->cbparam, rtp);
}
return r;
}
(7)读取in.aac数据(包括adts header)到aac_frame->frame_buf,封装RTP包应用层的入口是rtp_payload_encode_input(ctx.encoder_aac, aac_frame->frame_buf, aac_frame->frame_len, timestamp),这里是循环读取,还设置了时间戳。根据frame_duration累加的值读,控制了发送时间的速度。源码如下:
while(!feof(bits))
{
// 传入的aac帧是带adts header的
int ret = rtp_payload_encode_input(ctx.encoder_aac, aac_frame->frame_buf, aac_frame->frame_len, timestamp);//封包
//时间戳的单位是采样率
timestamp += (aac_frame->header.adts_buffer_fullness + 1)/2; // 叠加采样点,单位为1/采样率
// if(total_size > 200*1024) //输出文件限制200k测试代码
// break;
total_size += aac_frame->frame_len;
printf("ret:%d,frame_len:%d,total_size:%uk, frame_duration:%lf\n", ret, aac_frame->frame_len, total_size/1024, frame_duration);
sum_time += frame_duration; // 叠加可以播放的时长
cur_time = get_current_time(); // darren修正发送aac的时间间隔
while((cur_time - start_time) < (int64_t)(sum_time - 50)) //如果打开了输出文件限制200k测试代码,这里就不用设置限制条件了
{
// printf("cur_time - start_time:%ld\n", cur_time - start_time);
// printf("sum_time:%lf\n", sum_time);
#ifdef WIN32
Sleep(10);
#endif
#ifdef linux
usleep(10000);
#endif
cur_time = get_current_time();
if(feof(bits))
break;
}
if(aac_get_one_frame(aac_frame, bits) < 0)
{
printf("aac_get_one_frame failed\n");
break;
}
}
(8)进入到接口层,然后去适配,源码如下:
/**
* @brief rtp_payload_encode_input 这里是通用的接口
* @param encoder
* @param data data具体是什么媒体类型的数据,接口不关注,具体由ctx->encoder->input去处理
* @param bytes
* @param timestamp
* @return 返回0成功
*/
int rtp_payload_encode_input(void* encoder, const void* data, int bytes, uint32_t timestamp)
{
struct rtp_payload_delegate_t* ctx;
ctx = (struct rtp_payload_delegate_t*)encoder;
return ctx->encoder->input(ctx->packer, data, bytes, timestamp);
}
(9)调用rtp_mpeg4_generic_pack_input(xxx)十分重要,这里就是开始把读取的AAC数据,按照RTP协议的要求,进行打包。
/**
* @brief rtp_mpeg4_generic_pack_input
* @param pack
* @param data 包括adts header
* @param bytes 包括adts header的长度
* @param timestamp
* @return <0:失败
*/
static int rtp_mpeg4_generic_pack_input(void* pack, const void* data, int bytes, uint32_t timestamp)
{
int r;
int n, size;
uint8_t *rtp;
uint8_t header[4];
const uint8_t *ptr;
struct rtp_encode_mpeg4_generic_t *packer;
packer = (struct rtp_encode_mpeg4_generic_t *)pack;
packer->pkt.rtp.timestamp = timestamp; //(uint32_t)(time * KHz);
r = 0;
ptr = (const uint8_t *)data;
if (0xFF == ptr[0] && 0xF0 == (ptr[1] & 0xF0) && bytes > 7)
{
// skip ADTS header 跳过adts头部
assert(bytes == (((ptr[3] & 0x03) << 11) | (ptr[4] << 3) | ((ptr[5] >> 5) & 0x07)));
ptr += 7; // 这里直接判断为7个字节
bytes -= 7;
}
// 担心AAC帧可能要拆分多个RTP包
for (size = bytes; 0 == r && bytes > 0; ++packer->pkt.rtp.seq)
{
// 3.3.6. High Bit-rate AAC
// SDP fmtp: sizeLength=13; indexLength=3; indexDeltaLength = 3;
// au-header的高13个bits就是一个au 的字节长度:
header[0] = 0x00; // 高位
header[1] = 0x10; // 低位 16-bits AU headers-lenght
// AU headers 用2个字节,高13bit是 AU size
header[2] = 0; header[3] = 0;
header[2] = (uint8_t)(size >> 5); // 高位
header[3] = (uint8_t)(size & 0x1f) << 3; // 低位 这里一个包只发送一个AU
packer->pkt.payload = ptr;
// N_AU_HEADER 占据了4个字节,AU-headers-length占2个字节,AU-header占2个字节
packer->pkt.payloadlen = (bytes + N_AU_HEADER + RTP_FIXED_HEADER) <= packer->size ? // 判断是否要划分多个AU
bytes : (packer->size - N_AU_HEADER - RTP_FIXED_HEADER);
ptr += packer->pkt.payloadlen;
bytes -= packer->pkt.payloadlen; // 剩余的字节数
n = RTP_FIXED_HEADER + N_AU_HEADER + packer->pkt.payloadlen;
rtp = (uint8_t*)packer->handler.alloc(packer->cbparam, n); // 分配缓存,以备用来保存序列化数据
if (!rtp) return -ENOMEM;
// Marker (M) bit: The M bit is set to 1 to indicate that the RTP packet
// payload contains either the final fragment of a fragmented Access
// Unit or one or more complete Access Units
packer->pkt.rtp.m = (0 == bytes) ? 1 : 0; // 最后一个AU mark设置为1,否则为0
n = rtp_packet_serialize_header(&packer->pkt, rtp, n);
if (n != RTP_FIXED_HEADER)
{
assert(0);
return -1;
}
memcpy(rtp + n, header, N_AU_HEADER); // N_AU_HEADER为什么是4, AU_HEADER_LENGTH AU_HEADER
memcpy(rtp + n + N_AU_HEADER, packer->pkt.payload, packer->pkt.payloadlen); // 封装payload
//回调用户函数
r = packer->handler.packet(packer->cbparam, rtp, n + N_AU_HEADER + packer->pkt.payloadlen, packer->pkt.rtp.timestamp, 0);
packer->handler.free(packer->cbparam, rtp);
}
return r;
}
(10)ctx->encoder->input指向的函数是rtp_mpeg4_generic_pack_input(xxx),这个函数是正真的实现,其中传递的data是包括了adts header。同样这里也是要分单包还是多包来处理。这里有个重要的函数调用,就是回调用户设置的函数,去进行数据的发送或保存,这个函数就是packer->handler.packet(xxx),它指向的就是rtp_encode_packet(void* param, const void *packet, int bytes, uint32_t timestamp, int flags),它的源码如下:
// 拿到一帧RTP序列化后的数据
static int rtp_encode_packet(void* param, const void *packet, int bytes, uint32_t timestamp, int flags)
{
struct rtp_aac_test_t* ctx = (struct rtp_aac_test_t*)param;
int ret = 0;
#ifdef WIN32
ret = sendto(
ctx->fd,
(void*)packet,
bytes,
0,
(struct sockaddr*)&ctx->addr,
ctx->addr_size);
#else
//通过网络发送数据
ret = sendto(ctx->fd,
(void*)packet,
bytes,
MSG_DONTWAIT,
(struct sockaddr*)&ctx->addr,
ctx->addr_size);
#endif
// uint8_t *ptr = (uint8_t *)packet;
// for(int i = 0; i < 20; i++)
// {
// printf("%02x ", ptr[i]);
// }
printf("\nret:%d, rtp send packet -> bytes:%d, timestamp:%u\n", ret, bytes, timestamp);
//为了测试,又把packet进行解封装
ret = rtp_payload_decode_input(ctx->decoder_aac, packet, bytes); // 解封装
// printf("decode ret:%d\n", ret);
return 0;
}
3.4.AAC解封装流程
(1)RTP解封装AAC数据的回调函数说明。源码如下:
// AAC RTP decode回调
struct rtp_payload_t handler_rtp_decode_aac;
handler_rtp_decode_aac.alloc = rtp_alloc;
handler_rtp_decode_aac.free = rtp_free;
handler_rtp_decode_aac.packet = rtp_decode_packet;
ctx.decoder_aac = rtp_payload_decode_create(ctx.payload, ctx.encoding, &handler_rtp_decode_aac, &ctx);
其中rtp_alloc、rtp_free和rtp_decode_packet都是用户函数,都是回调函数。
(2)绑定了AAC的回调处理函数handler_rtp_decode_aac与用户设置的参数ctx。源码如下:
ctx.decoder_aac = rtp_payload_decode_create(ctx.payload, ctx.encoding, &handler_rtp_decode_aac, &ctx);
(3)函数rtp_payload_decode_create(xxx)调用rtp_payload_find(xxx),查找是否支持对应的封装器,这里设置的是"mpeg4-generic",与上面的格式对应起来。同时,这里也提供了AAC解封装实现的入口。源码如下:
void* rtp_payload_decode_create(int payload, const char* name, struct rtp_payload_t *handler, void* cbparam)
{
struct rtp_payload_delegate_t* ctx;
ctx = calloc(1, sizeof(*ctx));
if (ctx)
{
if (rtp_payload_find(payload, name, ctx) < 0
|| NULL == (ctx->packer = ctx->decoder->create(handler, cbparam)))
{
free(ctx);
return NULL;
}
}
return ctx;
}
(4)调用rtp_payload_find(payload, name, ctx)就是为了找到实现层入口,源码如下:
static int rtp_payload_find(int payload, const char* encoding, struct rtp_payload_delegate_t* codec)
{
assert(payload >= 0 && payload <= 127);
if (payload >= 96 && encoding)
{
if (0 == strcasecmp(encoding, "H264"))
{
// H.264 video (MPEG-4 Part 10) (RFC 6184)
codec->encoder = rtp_h264_encode();
codec->decoder = rtp_h264_decode();
}
else if (0 == strcasecmp(encoding, "mpeg4-generic"))
{
/// RFC3640 RTP Payload Format for Transport of MPEG-4 Elementary Streams
/// 4.1. MIME Type Registration (p27)
//封装层的实现入口
codec->encoder = rtp_mpeg4_generic_encode();
//解封装层的实现入口
codec->decoder = rtp_mpeg4_generic_decode();
}
else
{
return -1;
}
}
else
{
return -1;
}
return 0;
}
(5)函数ctx->decoder->create(handler, cbparam)指向的实现函数,rtp_payload_helper_create(struct rtp_payload_t *handler, void* cbparam),实际就是获取参数,源码如下:
void* rtp_payload_helper_create(struct rtp_payload_t *handler, void* cbparam)
{
struct rtp_payload_helper_t *helper;
helper = (struct rtp_payload_helper_t *)calloc(1, sizeof(*helper));
if (!helper)
return NULL;
memcpy(&helper->handler, handler, sizeof(helper->handler));
helper->maxsize = RTP_PAYLOAD_MAX_SIZE;
helper->cbparam = cbparam;
helper->__flags = -1;
return helper;
}
(6)前面的小结已经讲过,应用层在封装的函数rtp_encode_packet(xxx)中调用了rtp_payload_decode_input(xxx),又进行了解封装,来看看这个函数,源码如下:
int rtp_payload_decode_input(void* decoder, const void* packet, int bytes)
{
struct rtp_payload_delegate_t* ctx;
ctx = (struct rtp_payload_delegate_t*)decoder;
return ctx->decoder->input(ctx->packer, packet, bytes);
}
(7)其中ctx->decoder->input(xxx)指向真正函数实现是rtp_decode_mpeg4_generic(xxx),就是RTP封装好的数据按照协议解析出来,然后回调应用层的用户函数。源码如下:
static int rtp_decode_mpeg4_generic(void* p, const void* packet, int bytes)
{
int i, size;
int au_size;
int au_numbers;
int au_header_length;
const uint8_t *ptr, *pau, *pend;
struct rtp_packet_t pkt;
struct rtp_payload_helper_t *helper;
helper = (struct rtp_payload_helper_t *)p;
//1. 反序列化,把RTP 头解析出来
if (!helper || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || pkt.payloadlen < 4)
return -EINVAL;
rtp_payload_check(helper, &pkt);
// save payload
ptr = (const uint8_t *)pkt.payload; // 这里还是包括了AU_header_length au_header占用的字节的
pend = ptr + pkt.payloadlen;
// AU-headers-length AU-headers-length是固定占用2个字节
au_header_length = (ptr[0] << 8) + ptr[1]; // 读取长度
au_header_length = (au_header_length + 7) / 8; // bit -> byte 计算出来au_heade占用的字节情况
if (ptr + au_header_length /*AU-size*/ > pend || au_header_length < 2)
{
assert(0);
//helper->size = 0;
helper->lost = 1;
//helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST;
return -1; // invalid packet
}
// 3.3.6. High Bit-rate AAC
// SDP fmtp: sizeLength=13; indexLength=3; indexDeltaLength=3; 一般au header占用2字节,其他格式不支持
au_size = 2; // only AU-size
au_numbers = au_header_length / au_size; // 计算au个数
assert(0 == au_header_length % au_size);
ptr += 2; // skip AU headers length section 2-bytes ; 此时ptr指向au header的起始位置
pau = ptr + au_header_length; // point to Access Unit 跳过AU header才是真正的数据; 此时pau指向的是au的起始位置
for (i = 0; i < au_numbers; i++)
{
size = (ptr[0] << 8) | (ptr[1] & 0xF8); // 获取au的size
size = size >> 3; // 右移3bit 转成字节
if (pau + size > pend)
{
assert(0);
//helper->size = 0;
helper->lost = 1;
//helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST;
return -1; // invalid packet
}
// TODO: add ADTS/ASC ???
pkt.payload = pau;
pkt.payloadlen = size;
rtp_payload_write(helper, &pkt);
ptr += au_size; // 跳过au header size
pau += size; // 跳过au size
if (au_numbers > 1 || pkt.rtp.m) // 收到完整数据再发送给应用层
{
rtp_payload_onframe(helper);
}
}
return helper->lost ? 0 : 1; // packet handled
}
(8)函数rtp_payload_onframe回调应用层的函数helper->handler.packet(xxx),函数helper->handler.packet(xxx)指向的是rtp_decode_packet(void* param, const void *packet, int bytes, uint32_t timestamp, int flags),函数rtp_payload_onframe源码如下:
int rtp_payload_onframe(struct rtp_payload_helper_t *helper)
{
int r;
r = 0;
if (helper->size > 0
#if !defined(RTP_ENABLE_COURRUPT_PACKET)
&& 0 == helper->lost
#endif
)
{
// previous packet done
//回调应用层函数
r = helper->handler.packet(helper->cbparam, helper->ptr, helper->size, helper->timestamp, helper->__flags | (helper->lost ? RTP_PAYLOAD_FLAG_PACKET_CORRUPT : 0));
// RTP_PAYLOAD_FLAG_PACKET_LOST: miss
helper->__flags &= ~RTP_PAYLOAD_FLAG_PACKET_LOST; // clear packet lost flag
}
// set packet lost flag on next frame
if(helper->lost)
helper->__flags |= RTP_PAYLOAD_FLAG_PACKET_LOST;
// new frame start
helper->lost = 0;
helper->size = 0;
return r;
}
(9)应用层由用户层设置的解封装的回调函数,与前面H264的解封装函数是大同小异,源码如下:profile、sampling_frequency_index、channel_configuration这三个参数是写在sdp里面。在rtp_encode_packet(xxx)里调用rtp_payload_decode_input(xxx),又重新解封装,并保存到本地文件为out.aac。用户定义的回调函数源码如下:
static int rtp_decode_packet(void* param, const void *packet, int bytes, uint32_t timestamp, int flags)
{
struct rtp_aac_test_t* ctx = (struct rtp_aac_test_t*)param;
static uint8_t buffer[2 * 1024 * 1024];
assert(bytes + 4 < sizeof(buffer));
assert(0 == flags);
size_t size = 0;
if (0 == strcasecmp("mpeg4-generic", ctx->encoding))
{
int len = bytes + 7;
//
uint8_t profile = ctx->profile;
uint8_t sampling_frequency_index = ctx->sampling_frequency_index; // 本质上这些是从sdp读取的
uint8_t channel_configuration = ctx->channel_configuration;
buffer[0] = 0xFF; /* 12-syncword */
buffer[1] = 0xF0 /* 12-syncword */ | (0 << 3)/*1-ID*/ | (0x00 << 2) /*2-layer*/ | 0x01 /*1-protection_absent*/;
buffer[2] = ((profile) << 6) | ((sampling_frequency_index & 0x0F) << 2) | ((channel_configuration >> 2) & 0x01);
buffer[3] = ((channel_configuration & 0x03) << 6) | ((len >> 11) & 0x03); /*0-original_copy*/ /*0-home*/ /*0-copyright_identification_bit*/ /*0-copyright_identification_start*/
buffer[4] = (uint8_t)(len >> 3);
buffer[5] = ((len & 0x07) << 5) | 0x1F;
buffer[6] = 0xFC | ((len / 1024) & 0x03);
size = 7;
}
memcpy(buffer + size, packet, bytes);
size += bytes;
// printf("aac get -> bytes:%d, timestamp:%u\n", size, timestamp);
// TODO:
// check media file
//一定要带上adts header,否则有可能无法正确打开输出文件
fwrite(buffer, 1, size, ctx->out_file);
}
3.5 设置网络参数和音频参数
(1)设置网络相关的参数,如发送地址,端口号等。设置音频参数,如duration,采样率等,源码如下:
static int rtp_decode_mpeg4_generic(void* p, const void* packet, int bytes)
{
int i, size;
int au_size;
int au_numbers;
int au_header_length;
const uint8_t *ptr, *pau, *pend;
struct rtp_packet_t pkt;
struct rtp_payload_helper_t *helper;
helper = (struct rtp_payload_helper_t *)p;
//1. 反序列化,把RTP 头解析出来
if (!helper || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || pkt.payloadlen < 4)
return -EINVAL;
rtp_payload_check(helper, &pkt);
// save payload
ptr = (const uint8_t *)pkt.payload; // 这里还是包括了AU_header_length au_header占用的字节的
pend = ptr + pkt.payloadlen;
// AU-headers-length AU-headers-length是固定占用2个字节
au_header_length = (ptr[0] << 8) + ptr[1]; // 读取长度
au_header_length = (au_header_length + 7) / 8; // bit -> byte 计算出来au_heade占用的字节情况
if (ptr + au_header_length /*AU-size*/ > pend || au_header_length < 2)
{
assert(0);
//helper->size = 0;
helper->lost = 1;
//helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST;
return -1; // invalid packet
}
// 3.3.6. High Bit-rate AAC
// SDP fmtp: sizeLength=13; indexLength=3; indexDeltaLength=3; 一般au header占用2字节,其他格式不支持
au_size = 2; // only AU-size
au_numbers = au_header_length / au_size; // 计算au个数
assert(0 == au_header_length % au_size);
ptr += 2; // skip AU headers length section 2-bytes ; 此时ptr指向au header的起始位置
pau = ptr + au_header_length; // point to Access Unit 跳过AU header才是真正的数据; 此时pau指向的是au的起始位置
for (i = 0; i < au_numbers; i++)
{
size = (ptr[0] << 8) | (ptr[1] & 0xF8); // 获取au的size
size = size >> 3; // 右移3bit 转成字节
if (pau + size > pend)
{
assert(0);
//helper->size = 0;
helper->lost = 1;
//helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST;
return -1; // invalid packet
}
// TODO: add ADTS/ASC ???
pkt.payload = pau;
pkt.payloadlen = size;
rtp_payload_write(helper, &pkt);
ptr += au_size; // 跳过au header size
pau += size; // 跳过au size
if (au_numbers > 1 || pkt.rtp.m) // 收到完整数据再发送给应用层
{
rtp_payload_onframe(helper);
}
}
return helper->lost ? 0 : 1; // packet handled
}
4.读取和写入AAC获取数据重要说明
4.1怎么去读取AAC文件,怎么一帧帧读取出来?
(1)aac-util.h提供的接口,如有打开文件,写入头部等,源码如下:
不去查找sync word,表达的意思就是假设一帧音频数据是完整的,中间没有数据丢失。
// 打开AAC文件
FILE *aac_open_bitstream_file (char *filename);
// 根据传入的adts header buffer 解析出来对应的参数, show=1的时候打印解析结果,=0就不打印了
int aac_parse_header(uint8_t* in, aac_header_t* res, uint8_t show);
// 读取一帧完整的AAC帧(adts header + data),不会去查找sync word
//这个FILE *bits是传递文件进来
int aac_get_one_frame (aac_frame_t *aac_frame, FILE *bits);
//生成能够播放的sdp
void aac_rtp_create_sdp(uint8_t *file, uint8_t *ip, uint16_t port,
uint16_t profile, uint16_t chn,
uint16_t freq, uint16_t type);
(2)根据传入的adts header buffer 解析出来对应的参数, show=1的时候打印解析结果,=0就不打印了。
int aac_get_one_frame (aac_frame_t *aac_frame, FILE *bits)
{
// 1. 先读取7个字节(大部分情况),放到adts_buf
if (7 != fread (aac_frame->adts_buf, 1, 7, bits))//从码流中读3个字节
{
printf("read adts_buf failed\n");
return -1;
}
// 2.解析adts header
if(aac_parse_header(aac_frame->adts_buf, &aac_frame->header, 0) < 0)
{
return -1;
}
// 3.根据解析结果查看有没有校验,如果有校验就表示头部是9个字节
if(0 == aac_frame->header.protection_absent)
{
aac_frame->adts_len = 9; // 变成adts header长度就是9个字节
// 0表示有CRC校验
if (2 != fread (&aac_frame->adts_buf[7], 1, 2, bits))//从码流中读2个字节
{
return -1;
}
} else {
aac_frame->adts_len = 7;
}
aac_frame->frame_len = aac_frame->header.aac_frame_length; // 整帧长度
// 拷贝adts header到aac_frame->frame_buf里面去。
memcpy(aac_frame->frame_buf, aac_frame->adts_buf, aac_frame->adts_len);
//由于前面的AAC的头部已经从文件中读取出来,这里就需要跳过头部,读取data部分。
if ((aac_frame->frame_len - aac_frame->adts_len)
!= fread (&aac_frame->frame_buf[aac_frame->adts_len], 1,
aac_frame->frame_len - aac_frame->adts_len, bits))
{
printf("read aac frame data failed\n");
return -1;
}
return 0;
}
(3)从AAC文件里面去读取一帧完整的数据,这里没有去检测sync word,如果中间有数据丢失该函数将不起作用。源码如下:
int aac_get_one_frame (aac_frame_t *aac_frame, FILE *bits)
{
// 1. 先读取7个字节(大部分情况),放到adts_buf
if (7 != fread (aac_frame->adts_buf, 1, 7, bits))//从码流中读3个字节
{
printf("read adts_buf failed\n");
return -1;
}
// 2.解析adts header,放到aac_frame->header
if(aac_parse_header(aac_frame->adts_buf, &aac_frame->header, 0) < 0)
{
return -1;
}
// 3.根据解析结果查看有没有校验,如果有校验就表示头部是9个字节
if(0 == aac_frame->header.protection_absent)
{
aac_frame->adts_len = 9; // 变成adts header长度就是9个字节
// 0表示有CRC校验
if (2 != fread (&aac_frame->adts_buf[7], 1, 2, bits))//从码流中读2个字节
{
return -1;
}
} else {
aac_frame->adts_len = 7;
}
aac_frame->frame_len = aac_frame->header.aac_frame_length; // 整帧长度
// 拷贝adts header到aac_frame->frame_buf里面去。
memcpy(aac_frame->frame_buf, aac_frame->adts_buf, aac_frame->adts_len);
//由于前面的AAC的头部已经从文件中读取出来,这里就需要跳过头部,读取data部分。
if ((aac_frame->frame_len - aac_frame->adts_len)
!= fread (&aac_frame->frame_buf[aac_frame->adts_len], 1,
aac_frame->frame_len - aac_frame->adts_len, bits))
{
printf("read aac frame data failed\n");
return -1;
}
return 0;
}
4.2怎么实时发送数据?
(1)计算出每帧播放时长,实际上每帧是固定的frame_duration(不能用整数值,否则会有累计误差)。发送了多少帧,就可以叠加,然后计算总时长sum_time。
(2)设置起始发送时间start_time,获取当前的时间cur_time,就可以计算出经过时间cur_time-start_time。
如sum_time是60000ms,那发送的时间cur_time-start_time应该也是60000ms。
(3)每次就通过frame_duration的累加值与发送的时间cur_time-start_time去比较,如果没有到发送时间,就不能发送,做一个流控目的。源码如下:
sum_time - 50的50(可以根据实际情况来设计)表示一个经验值,就是可以提前50ms发送,这样接收端可以提前缓存。如果没有休眠,数据发送出去,可能就会在接收端出错,如马赛克现象。
//sum_time - 50的50表示一个经验值,就是可以提前50ms发送,这样接收端可以提前缓存。
while((cur_time - start_time) < (int64_t)(sum_time - 50))
{
// printf("cur_time - start_time:%ld\n", cur_time - start_time);
// printf("sum_time:%lf\n", sum_time);
#ifdef WIN32
//如果没有休眠,数据发送出去,可能就会在接收端出错,如马赛克
Sleep(10);
#endif
#ifdef linux
usleep(10000);
#endif
cur_time = get_current_time();
if(feof(bits))
break;
}
(4)源码中特别注意音频AAC封RTP包,是跳过了ADTS Header,而H264封RTP包,是带来Start code。
5.总结
本篇文章主要讲解了AAC是如何进行RTP打包和封包的源码分析,再结合前面理论的文章,能够帮助我们更好地理解整个过程。希望能够帮助到大家。欢迎关注,收藏,转发,分享。
后期关于项目知识,也会更新在微信公众号“记录世界 from antonio”,欢迎关注
请先 后发表评论~