龙空技术网

RTP之AAC框架分析

antonio 195

前言:

目前姐妹们对“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