前面完成了視頻RTMP推流實踐,本文介紹RTMP的音頻推流,包括AACg711a,g711u三種場景音頻推流。基於前面的視頻推流實踐,我們新增了推流AAC,g711a,g711u的三個接口。分別爲SendAAcData(),sendg711a_audio(),sendg711u_audio(),對外提供API調用。接口類對外定義如下:
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發送出去。
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 failed\n");
}
else
{
printf("send key frame now len 7\n");
}
ret = sendaac_rawaudio((unsigned char *)data+7,dataLength-7,timeStamp);
if(ret==-1)
{
printf("send raw frame is failed\n");
}
else
{
printf("send raw frame now len %d \n", tAdtsKeyHeader.nAacFrameLength-7);
}
}
AAC 推流的Demo使用如下:
#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 failed\n");
return 0;
}
if (test.IsConnect() < 0)
{
printf("connect is failed\n");
return 0;
}
else
printf("connect is ok\n");
//uint8_t adtsheader[7];
FILE *fd = fopen(argv[1], "rb+");
if (fd == NULL)
{
printf("fopen is failed,err %d\n", 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 %d\n", 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 = %d\n", len);
int ret = fread(FrameBuffer+7, len-7, 1, fd);
if (ret != 1)
{
printf("fread raw block is failed,err %d\n", 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頭定義。
#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 failed\n");
return 0;
}
if (test.IsConnect() < 0)
{
printf("connect is failed\n");
return 0;
}
else
printf("connect is ok\n");
//uint8_t adtsheader[7];
FILE *fd = fopen(argv[1], "rb+");
if (fd == NULL)
{
printf("fopen is failed,err %d\n", 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 %d\n", errno);
return 0;
}
test.sendg711a_audio((unsigned char *)FrameBuffer, 640, timestamp);
printf("send g711a now 80\n");
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採樣頻率
更多更詳細資源請關注公衆號:AV_Chat