【GB28181】RTSP服务器传输AAC音频

发布于:2025-03-15 ⋅ 阅读:(20) ⋅ 点赞:(0)

概述

实现一个简单的RTSP服务器,主要用于从本地AAC文件读取音频数据,然后通过RTP协议实时传输AAC音频流。整体结构和H264视频流服务器结构相似

ADTS头部

结构体分析

该结构体主要用于描述ADTS头部,该头部信息位于每个AAC音频帧之前,其中包含了音频帧的同步信息、长度、采样率等重要参数

  • syncword: 同步字 (0xFFF),标志 ADTS 帧的开始。
  • aacFrameLength: ADTS 帧长度 (包括头部和 AAC 原始数据),这是确定每个 AAC 帧大小的关键信息。
  • samplingFreqIndex: 采样率索引,对应实际的采样率 (如 44100Hz, 48000Hz)。
  • channelCfg: 声道配置,表示音频声道数 (如单声道、立体声)
struct AdtsHeader {
    unsigned int syncword;  //12 bit 同步字 '1111 1111 1111',一个ADTS帧的开始
    uint8_t id;        //1 bit 0代表MPEG-4, 1代表MPEG-2。
    uint8_t layer;     //2 bit 必须为0
    uint8_t protectionAbsent;  //1 bit 1代表没有CRC,0代表有CRC
    uint8_t profile;           //1 bit AAC级别(MPEG-2 AAC中定义了3种profile,MPEG-4 AAC中定义了6种profile)
    uint8_t samplingFreqIndex; //4 bit 采样率
    uint8_t privateBit;        //1bit 编码时设置为0,解码时忽略
    uint8_t channelCfg;        //3 bit 声道数量
    uint8_t originalCopy;      //1bit 编码时设置为0,解码时忽略
    uint8_t home;               //1 bit 编码时设置为0,解码时忽略

    uint8_t copyrightIdentificationBit;   //1 bit 编码时设置为0,解码时忽略
    uint8_t copyrightIdentificationStart; //1 bit 编码时设置为0,解码时忽略
    unsigned int aacFrameLength;               //13 bit 一个ADTS帧的长度包括ADTS头和AAC原始流
    unsigned int adtsBufferFullness;           //11 bit 缓冲区充满度,0x7FF说明是码率可变的码流,不需要此字段。CBR可能需要此字段,不同编码器使用情况不同。这个在使用音频编码的时候需要注意。

    /* 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个采样及相关数据)
     */
    uint8_t numberOfRawDataBlockInFrame; //2 bit
};

解析函数分析

主要用于解析ADTS头部,从字节流中提取ADTS头部信息,然后填充到AdtsHeader结构体中

  • 同步字校验: 首先检查前两个字节是否为 ADTS 同步字 (0xFFF),这是判断是否为有效 ADTS 头部的首要条件
  • 位域提取: 使用位运算从 ADTS 头部字节中提取各个字段的值,例如 AAC 级别、采样率索引、声道配置、ADTS 帧长度等,并将解析出的值存储到 AdtsHeader 结构体中
  • 错误处理: 如果同步字校验失败,则认为 ADTS 头部解析失败,返回错误代码
static int parseAdtsHeader(uint8_t* in, struct AdtsHeader* res) {
    static int frame_number = 0;
    memset(res, 0, sizeof(*res));

    if ((in[0] == 0xFF) && ((in[1] & 0xF0) == 0xF0))
    {
        res->id = ((uint8_t)in[1] & 0x08) >> 3;//第二个字节与0x08与运算之后,获得第13位bit对应的值
        res->layer = ((uint8_t)in[1] & 0x06) >> 1;//第二个字节与0x06与运算之后,右移1位,获得第14,15位两个bit对应的值
        res->protectionAbsent = (uint8_t)in[1] & 0x01;
        res->profile = ((uint8_t)in[2] & 0xc0) >> 6;
        res->samplingFreqIndex = ((uint8_t)in[2] & 0x3c) >> 2;
        res->privateBit = ((uint8_t)in[2] & 0x02) >> 1;
        res->channelCfg = ((((uint8_t)in[2] & 0x01) << 2) | (((unsigned int)in[3] & 0xc0) >> 6));
        res->originalCopy = ((uint8_t)in[3] & 0x20) >> 5;
        res->home = ((uint8_t)in[3] & 0x10) >> 4;
        res->copyrightIdentificationBit = ((uint8_t)in[3] & 0x08) >> 3;
        res->copyrightIdentificationStart = (uint8_t)in[3] & 0x04 >> 2;
        
        res->aacFrameLength = (((((unsigned int)in[3]) & 0x03) << 11) |
            (((unsigned int)in[4] & 0xFF) << 3) |
            ((unsigned int)in[5] & 0xE0) >> 5);

        res->adtsBufferFullness = (((unsigned int)in[5] & 0x1f) << 6 |
            ((unsigned int)in[6] & 0xfc) >> 2);
        res->numberOfRawDataBlockInFrame = ((uint8_t)in[6] & 0x03);

        return 0;
    }
    else
    {
        printf("failed to parse adts header\n");
        return -1;
    }
}

RTP AAC帧发送函数

负责将 AAC 音频帧封装成 RTP 包并通过 UDP 发送,其中AAC的RTP负载格式使用的是AU头部;整体逻辑是先设置通用的AU头部,然后将负载加入到RTP负载空间中,通过RTP发送,然后更新序列号和时间戳

代码分析

主要流程总结

  • 从AAC音频文件中读取数据,然后解析ADTS的头部
  • RTP数据包封装:将音频数据封装进一个RTP包中,然后通过网络进行发送
  • 控制帧率,确保音频数据按照正确的速度进行播放 
 //开始播放,发送RTP包
        if (!strcmp(method, "PLAY")) {

            struct AdtsHeader adtsHeader; // 存储ADTS头部信息
            struct RtpPacket* rtpPacket;  // RTP包
            uint8_t* frame; //存储文件中读取AAC音频数据
            int ret;

            FILE* fp = fopen(AAC_FILE_NAME, "rb");
            if (!fp) {
                printf("读取 %s 失败\n", AAC_FILE_NAME);
                break;
            }

            frame = (uint8_t*)malloc(5000);
            rtpPacket = (struct RtpPacket*)malloc(5000);

            // 初始化RTP包头信息(一般是版本负载类型等)
            rtpHeaderInit(rtpPacket, 0, 0, 0, RTP_VESION, RTP_PAYLOAD_TYPE_AAC, 1, 0, 0, 0x32411);

            while (true)
            {
                // 读取前7个字节存储到frame中,也就是ADTS头部数据
                ret = fread(frame, 1, 7, fp);
                if (ret <= 0)
                {
                    printf("fread err\n");
                    break;
                }
                printf("fread ret=%d \n",ret);
                
                // 解析ADTS头部信息
                if (parseAdtsHeader(frame, &adtsHeader) < 0)
                {
                    printf("parseAdtsHeader err\n");
                    break;
                }

                // 读取完剩下的AAC音频数据
                ret = fread(frame, 1, adtsHeader.aacFrameLength - 7, fp);
                if (ret <= 0)
                {
                    printf("fread err\n");
                    break;
                }

                rtpSendAACFrame(serverRtpSockfd, clientIP, clientRtpPort,
                    rtpPacket, frame, adtsHeader.aacFrameLength - 7);

                Sleep(1);
                //usleep(23223);//1000/43.06 * 1000
            }

            free(frame);
            free(rtpPacket);

            break;

        }

        memset(method, 0, sizeof(method) / sizeof(char));
        memset(url, 0, sizeof(url) / sizeof(char));
        CSeq = 0;
    }

RTSP处理AAC音频SDP

将handleCmd_DESCRIBE修改为生成音频流的SDP描述

  • m=audio 0 RTP/AVP 97: 媒体类型改为 audio,payload type 设置为 97 (动态 payload type)。
  • a=rtpmap:97 mpeg4-generic/44100/2: RTP map 属性设置为 mpeg4-generic 音频,采样率 44100Hz,2 声道 (立体声)。
  • a=fmtp:97 ... config=1210;: 关键的 FMTP 属性,包含 AAC 音频流的格式特定参数,特别是 config=1210,这是 AudioSpecificConfig (音频特定配置),以十六进制表示,解码器需要这个配置信息才能正确解码 AAC 音频。 0x1210 代表 44100Hz 采样率和 2 声道

代码分析

核心流程分为三步

  • 解析RTSP的URL,从URL中提取本地的IP地址
  • 生成SDP数据,构建描述流媒体信息的SDP字符串
  • 构建RTSP响应,将生成的SDP数据与RTSP响应拼接在一起返回给客户端
static int handleCmd_DESCRIBE(char* result, int cseq, char* url) {
    char sdp[500];
    char localIp[100];

    // 从RTSP流地址中获取IP地址
    sscanf(url, "rtsp://%[^:]:", localIp);

    // 构建SDP数据
    sprintf(sdp, "v=0\r\n"
        "o=- 9%ld 1 IN IP4 %s\r\n"
        "t=0 0\r\n"
        "a=control:*\r\n"
        "m=audio 0 RTP/AVP 97\r\n"
        "a=rtpmap:97 mpeg4-generic/44100/2\r\n"
        "a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1210;\r\n"
   
        //"a=fmtp:97 SizeLength=13;\r\n"
        "a=control:track0\r\n",
        time(NULL), localIp);

    sprintf(result, "RTSP/1.0 200 OK\r\nCSeq: %d\r\n"
        "Content-Base: %s\r\n"
        "Content-type: application/sdp\r\n"
        "Content-length: %d\r\n\r\n"
        "%s",
        cseq,
        url,
        strlen(sdp),
        sdp);

    return 0;
}

构建SDP的内容分析

  • v=0: 版本号。
  • o=- 9%ld 1 IN IP4 %s: 发送者和会话标识。time(NULL) 提供当前时间戳,localIp 则是从 URL 中提取的本地 IP 地址。
  • t=0 0: 会话的开始和结束时间,这里设置为 0 0,表示该会话没有特定的开始或结束时间。
  • a=control:: 控制指令,指定 *,表示所有的流都可以被控制。
  • m=audio 0 RTP/AVP 97: 描述音频媒体流,使用 RTP 协议,流的媒体类型是音频,负载类型(Payload Type)为 97。
  • a=rtpmap:97 mpeg4-generic/44100/2: 负载类型 97 的详细信息,表示使用 MPEG4 编码,采样率为 44100 Hz,声道数为 2。
  • a=fmtp:97 ...: 为负载类型 97 提供的格式特定参数,指定编码方式、数据结构等详细信息。
  • a=control:track0: 控制指令,指定音频轨道为 track0