AAC与g711音频RTMP推流实践

2022-06-14 08:46:33 浏览数 (1)

      前面完成了视频RTMP推流实践,本文介绍RTMP的音频推流,包括AACg711a,g711u三种场景音频推流。基于前面的视频推流实践,我们新增了推流AAC,g711a,g711u的三个接口。分别为SendAAcData(),sendg711a_audio(),sendg711u_audio(),对外提供API调用。接口类对外定义如下:   

代码语言:javascript复制
class Wrapper_RtmpLib
{
public:
    Wrapper_RtmpLib(char * url);
    ~Wrapper_RtmpLib();
    int Open();
    int SendVideoData(char * data,int dataLength, unsigned int timeStamp);   
    int SendAAcData(char * data,int dataLength, unsigned int timeStamp);  
    int sendg711a_audio(unsigned char *buf,int len,unsigned int timeStamp);  
    int sendg711u_audio(unsigned char *buf,int len,unsigned int timeStamp);
    int IsConnect();
    int Close();
private:
         ………由于篇幅所限,这里省略
}; 
  1. AAC推流实践:

SendAAcData 接收上层传来的AAC帧数据,长度和时间戳。其中AAC帧数据包括ADTS头和RAW的AAC数据。该函数从上层接收一帧数据,将ADTS头组装一个packet 通过sendaac_headerconfig发送出去,然后再将raw的AAC数据打包通过sendaac_rawaudio发送出去。

代码语言:javascript复制
int Wrapper_RtmpLib::SendAAcData(char * data,int dataLength, unsigned int timeStamp)
{
    int ret = -1;
    uint8_t audioSpecificConfig[2] = { 0 };
    AdtsKeyHeader tAdtsKeyHeader;
    GetAdtsKeyConfig(data, &tAdtsKeyHeader);
    GetAAcSpecificConfig(tAdtsKeyHeader.nProfile 1, tAdtsKeyHeader.nSfIndex, tAdtsKeyHeader.nChannelConfiguration, audioSpecificConfig);
    printf("nProfile %d nSfIndex %d nChannelConfiguration %d n", tAdtsKeyHeader.nProfile, tAdtsKeyHeader.nSfIndex, tAdtsKeyHeader.nChannelConfiguration);
    ret = sendaac_headerconfig(audioSpecificConfig, 2, 0);
    if(ret==-1)
     {
        printf("send key frame is failedn");
     }
      else
     {
         printf("send key frame now len 7n");
     }
     ret = sendaac_rawaudio((unsigned char *)data 7,dataLength-7,timeStamp);
     if(ret==-1)
     {
          printf("send raw frame is failedn");
     }
     else
     {
          printf("send raw frame now len %d n", tAdtsKeyHeader.nAacFrameLength-7);
     }
}

AAC 推流的Demo使用如下:

代码语言:javascript复制
#include <cstdio>
#include"Wrapper_RtmpLib.hpp"
#include <unistd.h>
#include<string.h>
#include <signal.h>
#include<time.h>
#include <errno.h>
uint8_t tag0 = 0xff;
uint8_t tag1 = 0xf0;
void help(char *p)
{
    printf("Use:");
    printf("%s AudioFile RTMP_URL n",p);
}
int main(int argc,char *argv[])
{
    if(argc<3)
   {
       help(argv[0]);
       return 1;
    }
    uint8_t FrameBuffer[4096];
    signal(SIGPIPE, SIG_IGN);
    Wrapper_RtmpLib test(argv[2]);
    if (test.Open() < 0)
    {
        printf("open is failedn");
        return 0;
    }
    if (test.IsConnect() < 0)
     {
        printf("connect is failedn");
        return 0;
    }
    else
        printf("connect is okn");
    //uint8_t adtsheader[7];
    FILE *fd = fopen(argv[1], "rb ");
    if (fd == NULL)
    {
        printf("fopen is failed,err %dn", errno);
        return 0;
    }
    void *pStart = NULL;
    void *pEnd = NULL;
    int count = 0;
    unsigned int timestamp = 0;
    while (1)
    {
        int ret = fread(FrameBuffer, 7, 1, fd);
        if (ret != 1)
        {
           printf("fread header is failed,err %dn", errno);
           return 0;
        }

        if ((FrameBuffer[0] == tag0) && (FrameBuffer[1] & 0xf0 == tag1))
            //  if ((FrameBuffer[0] == 'xFF') && (FrameBuffer[1] & 0xf0 == 'xF0'))
        {
            //分析头数据,读裸露数据
            bs_s bitbuffer_Robj;
            bs_init(&bitbuffer_Robj, FrameBuffer, 7);

            bs_skip(&bitbuffer_Robj, 30);//调过30bit,找到len字段
            int len = bs_read(&bitbuffer_Robj, 13);
            printf("raw_len = %dn", len);

            int ret = fread(FrameBuffer 7, len-7, 1, fd);
            if (ret != 1)
            {
                printf("fread raw block is failed,err %dn", errno);
                return 0;
            }
                  test.SendAAcData((char *)FrameBuffer, len, timestamp);
                  timestamp  = 22;
                    usleep(1000*22);
        }
    }
    printf("data is endof now");
    getchar();
}

这里需要说明的是:因为AAC每一帧是1024个采样,所以一帧的时间间隔是:1026/48Khz=21.33ms,故设置时间戳间隔为22ms(注意我的测试程序AAC采用频率为48K hz)。

2)g711推流实践

 g711a和g711u推流接口为sendg711a_audio()和sendg711u_audio()。这个2个接口实现原理是一样。就是直接将raw数据推送出去即可。但毕竟是两个不同格式,rtmp的数据包的tag头不一样。后面我们会讲到rtmp的音频数据tag头定义。

代码语言:javascript复制
#include <cstdio>
#include"Wrapper_RtmpLib.hpp"
#include <unistd.h>
#include<string.h>
#include <signal.h>
#include<time.h>
#include <errno.h>
uint8_t tag0 = 0xff;
uint8_t tag1 = 0xf0;
void help(char *p)
{
    printf("Use:");
    printf("%s AudioFile RTMP_URL n",p);
}
int main(int argc,char *argv[])
{
    if(argc<3)
    {
        help(argv[0]);
        return 1;
    }
    uint8_t FrameBuffer[4096];
    signal(SIGPIPE, SIG_IGN);
    Wrapper_RtmpLib test(argv[2]);
    if (test.Open() < 0)
    {
        printf("open is failedn");
        return 0;
    }
    if (test.IsConnect() < 0)
    {
        printf("connect is failedn");
        return 0;
    }
    else
        printf("connect is okn");
    //uint8_t adtsheader[7];
    FILE *fd = fopen(argv[1], "rb ");
    if (fd == NULL)
    {
        printf("fopen is failed,err %dn", errno);
        return 0;
    }
    int count = 0;
    unsigned int timestamp = 0;
    while (1)
    {
        int ret = fread(FrameBuffer, 640, 1, fd);
        if (ret != 1)
        {
           printf("fread header is failed,err %dn", errno);
           return 0;
        }
        test.sendg711a_audio((unsigned char *)FrameBuffer, 640, timestamp);
        printf("send g711a now 80n");
        timestamp  = 80;
        usleep(1000*40);    
    }
    printf("data is endof now");
    getchar();
}

这里注意:因为测试g711a是大华IPC抓下来的包。大华IPC的g711是80ms打一个包(RTP时间戳增量为640,真实时间间隔为640/8000=80ms),所以时间戳增量为80ms.但考虑网络延时和处理开销,这里每个包只延时了40ms就发送下一个包(这个根据实际情况决定)。因为80ms打一个包,包的大小恰好为640个字节(80ms*8000hz*2/2=640,g711压缩率为2),所以每次拷贝640个字节(即一个RTP包的音频负载)。由于g711a和g711u算法类似,推流方式也类似,g711u这里不再赘述。

RTMP的音频tag头

 在RTMP发送音频数据包,包必须包括tag头 音频数据。类似flv的tag data的数据格式。但由于flv和RTMP格式是兼容的,所以tag头=RTMP的body头。

具体定义如下:

总结起来如下:

body第一个字节:

Bit7 bit6 bit5 bit4     |bit3 bit2    |bit1          | bit0

format               rate        bit depth       soudtype

body第二个字节

Bit7~bit0

if format=10

0:AAC config 1:aac raw

根据以上规范

AAC :

body第一个字节a|11|1|1=0xAF

body第二个字节

AAC config =0

AAC rawdata = 1

实测AAC的 body第一个字节低4bit影响不大。解码器主要还是根据发送的config数据来解码的。

g711a

body第一个字节7|01|1|1 =0x77 

//双通道,16bit采样精度,8K采样频率

g711u:

body 第一个字节 8|01|1|1 =0x87

//双通道,16bit采样精度,8K采样频率

0 人点赞