详解RTP打包AAC实战分析(1)

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 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”,欢迎关注

举报
评论 0