前面完成了视频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:
………由于篇幅所限,这里省略
};
- 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采样频率