前言:
目前姐妹们对“c语言rtp解析aac”都比较注意,同学们都想要分析一些“c语言rtp解析aac”的相关知识。那么小编同时在网上网罗了一些对于“c语言rtp解析aac””的相关知识,希望大家能喜欢,姐妹们快快来了解一下吧!0.引言
为了更好理解本篇文章,可以先阅读前面几篇文章,文章列表如下:
详解RTP协议之H264封包和解包实战
详解RTP协议之H264封包细节(1)
详细解析RTSP框架和数据包分析(1)
手把手搭建RTSP流媒体服务器
RTP协议
HLS实战之Wireshark抓包分析
HTTP实战之Wireshark抓包分析
建议:阅读本文前,一定要阅读前面的文章,只有理解了原理,才能够真正读懂代码。
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 97a=rtpmap:97 mpeg4-generic/48000/2a=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);//生成能够播放的sdpvoid 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”,欢迎关注
标签: #c语言rtp解析aac