librtmp可以用于推rtmp流,有时候我们需要将采集的摄像头或桌面的视频数据以及麦克风的音频数据推流出去,这时候就需要使用librtmp的推流功能了,其推流流程比较简单,只是一些细节需要注意即可。
在推送idr前需要发送一个sps、pps数据包,代码如下:
///
/// 推送sps、pps,在每个idr前需要推送这些数据
///
/// rtmp对象
/// sps数据
/// sps数据长度
/// pps数据
/// pps数据长度
void PushSPSPPS(RTMP* rtmp, const unsigned char* sps, int spsLen, const unsigned char* pps, int ppsLen) {
int bodySize = spsLen + ppsLen + 16;
RTMPPacket rtmpPacket;
RTMPPacket_Alloc(&rtmpPacket, bodySize);
RTMPPacket_Reset(&rtmpPacket);
char* body = rtmpPacket.m_body;
int i = 0;
//frame type(4bit)和CodecId(4bit)合成一个字节(byte)
//frame type 关键帧1 非关键帧2
//CodecId 7表示avc
body[i++] = 0x17;
//fixed 4byte
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
//configurationVersion: 版本 1byte
body[i++] = 0x01;
//AVCProfileIndication:Profile 1byte sps[1]
body[i++] = sps[1];
//compatibility: 兼容性 1byte sps[2]
body[i++] = sps[2];
//AVCLevelIndication: ProfileLevel 1byte sps[3]
body[i++] = sps[3];
//lengthSizeMinusOne: 包长数据所使用的字节数 1byte
body[i++] = 0xff;
//sps个数 1byte
body[i++] = 0xe1;
//sps长度 2byte
body[i++] = (spsLen >> 8) & 0xff;
body[i++] = spsLen & 0xff;
//sps data 内容
memcpy(&body[i], sps, spsLen);
i += spsLen;
//pps个数 1byte
body[i++] = 0x01;
//pps长度 2byte
body[i++] = (ppsLen >> 8) & 0xff;
body[i++] = ppsLen & 0xff;
//pps data 内容
memcpy(&body[i], pps, ppsLen);
rtmpPacket.m_packetType = RTMP_PACKET_TYPE_VIDEO;
rtmpPacket.m_nBodySize = bodySize;
rtmpPacket.m_nTimeStamp = 0;
rtmpPacket.m_hasAbsTimestamp = 1;
rtmpPacket.m_nChannel = 0x04;//音频或者视频
rtmpPacket.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
rtmpPacket.m_nInfoField2 = rtmp->m_stream_id;
int nRet = RTMP_SendPacket(rtmp, &rtmpPacket, TRUE);
RTMPPacket_Free(&rtmpPacket);
}
推送视频需要区分是否为idr,然后需要设置时间戳,一般使用绝对时间戳较容易实现同步。
///
/// 推h264数据
///
/// rtmp对象
/// h264数据
/// h264数据长度
/// 是否为gop首帧
/// 时间戳,单位毫秒,绝对时间戳
void PushH264Data(RTMP* rtmp, const unsigned char* data, int dataLen, bool isIdr, int timestamp) {
int bodySize = dataLen + 9;
RTMPPacket rtmpPacket;
RTMPPacket_Alloc(&rtmpPacket, bodySize);
RTMPPacket_Reset(&rtmpPacket);
char* body = rtmpPacket.m_body;
int i = 0;
//frame type(4bit)和CodecId(4bit)合成一个字节(byte)
//frame type 关键帧1 非关键帧2
//CodecId 7表示avc
if (isIdr) {
body[i++] = 0x17;
}
else {
body[i++] = 0x27;
}
//fixed 4byte 0x01表示NALU单元
body[i++] = 0x01;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
//dataLen 4byte
body[i++] = (dataLen >> 24) & 0xff;
body[i++] = (dataLen >> 16) & 0xff;
body[i++] = (dataLen >> 8) & 0xff;
body[i++] = dataLen & 0xff;
//data
memcpy(&body[i], data, dataLen);
rtmpPacket.m_packetType = RTMP_PACKET_TYPE_VIDEO;
rtmpPacket.m_nBodySize = bodySize;
//持续播放时间
rtmpPacket.m_nTimeStamp = timestamp;
//使用绝对时间戳
rtmpPacket.m_hasAbsTimestamp = 1;
rtmpPacket.m_nChannel = 0x04;//音频或者视频
rtmpPacket.m_headerType = RTMP_PACKET_SIZE_LARGE;
rtmpPacket.m_nInfoField2 = rtmp->m_stream_id;
int nRet = RTMP_SendPacket(rtmp, &rtmpPacket, TRUE);
RTMPPacket_Free(&rtmpPacket);
}
推音频帧,需要设置采样率以及声道,时间戳采用绝对时间戳。
///
/// 推流aac数据
///
/// rtmp对象
/// 采样率下标,0 = 5.5 kHz,1 = 11 kHz,2 = 22 kHz,3 = 44 kHz
/// 是否立体声
/// aac数据
/// aac数据长度
/// 时间戳,单位毫秒,绝对时间戳
void PushAacData(RTMP* rtmp, int samplerateIndex, bool isStereo, const unsigned char* data, int dataLen, int timestamp) {
int bodySize = dataLen + 2;
RTMPPacket rtmpPacket;
RTMPPacket_Alloc(&rtmpPacket, bodySize);
RTMPPacket_Reset(&rtmpPacket);
char* body = rtmpPacket.m_body;
//前四位表示音频数据格式 10(十进制)表示AAC,16进制就是A
//第5-6位的数值表示采样率,0 = 5.5 kHz,1 = 11 kHz,2 = 22 kHz,3(11) = 44 kHz。
//第7位表示采样精度,0 = 8bits,1 = 16bits。
//第8位表示音频类型,0 = mono,1 = stereo
body[0] = 0xA2 | ((samplerateIndex << 2) & 0x0c) | (isStereo & 0x1);
//0x00 aac头信息, 0x01 aac 原始数据
//这里都用0x01都可以
body[1] = 0x01;
//data
memcpy(&body[2], data, dataLen);
rtmpPacket.m_packetType = RTMP_PACKET_TYPE_AUDIO;
rtmpPacket.m_nBodySize = bodySize;
//持续播放时间
rtmpPacket.m_nTimeStamp = timestamp;
//使用绝对时间戳
rtmpPacket.m_hasAbsTimestamp = 1;
rtmpPacket.m_nChannel = 0x04;//音频或者视频
rtmpPacket.m_headerType = RTMP_PACKET_SIZE_LARGE;
rtmpPacket.m_nInfoField2 = rtmp->m_stream_id;
int nRet = RTMP_SendPacket(rtmp, &rtmpPacket, TRUE);
RTMPPacket_Free(&rtmpPacket);
}
同时推流时需要注意:视频音频都需要在同一个线程推流。推送视频dr帧之前先推送sps、pps。音频一般采用固定时间戳,通过下标计数计算会比较准确。其中NaluParse
对象参考《C++ 读取h264中的nalu》。
#ifdef _WIN32
#include
#pragma comment(lib,"ws2_32.lib")
#endif // _WIN32
#include
#include "rtmp.h"
#include"NaluParse.h"
int main(int argc, char* argv[])
{
#ifdef _WIN32
WSADATA wsaData;
int nRet;
if ((nRet = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0) {
return nRet;
}
#endif // _Win32
RTMP* rtmp = NULL;
int64_t audioCount = 0;
try {
rtmp = RTMP_Alloc();
RTMP_Init(rtmp);
if (!RTMP_SetupURL(rtmp, (char*)"rtmp://127.0.0.1/live/123") )
{
throw std::exception("Setuping url failed!");
}
RTMP_EnableWrite(rtmp);
if (!RTMP_Connect(rtmp, NULL))
{
throw std::exception("Connecting failed!");
}
if (!RTMP_ConnectStream(rtmp, 0))
{
throw std::exception("Connecting stream failed!");
}
while (1)
{
//TODO:在实时流队列中取得h264和aac帧,videoFrame、audioFrame。
略
//解析h264获取nalu,这里示例的数据打包格式是Annex-B。
auto nalus = AC::NaluParse::GetNalusFromFrame(videoFrame.Data, videoFrame.DataLength);
AC::Nalu* sps = nullptr, * pps = nullptr;
bool isIdr = true;
//遍历h264帧内的nalu
for (auto& i : nalus)
{
switch (i.GetNaluType())
{
case 01:
case 02:
case 03:
case 04:
isIdr = false;
case 05:
{
//推视频帧,视频使用采集时的时间戳,单位毫秒
PushH264Data(rtmp, i.GetData(), i.GetDataLength(), isIdr, videoFrame.Timestamp);
}
break;
case 7:
{
sps = &i;
}
break;
case 8:
{
pps = &i;
if (sps)
{
//推sps和pps
PushSPSPPS(rtmp, sps->GetData(), sps->GetDataLength(), pps->GetData(), pps->GetDataLength());
}
}
break;
}
}
//推音频帧。这里音频是44100,双声道。采用固定间隔时间戳: audioCount++ * 1024 * 1000 / sampleRate。
PushAacData(rtmp, 3, true, audioFrame.Data, audioFrame.DataLength, audioCount++ * 1024 * 1000 / 44100);
}
}
catch (const std::exception& e) {
printf("%s\n", e.what());
}
if (rtmp)
{
RTMP_Close(rtmp);
RTMP_Free(rtmp);
}
return 0;
}
以上就是今天要讲的内容,使用librtmp推流其实是比较简单的,而且只需要初始化之后,直接推音视频数据即可,设置metada都不是必须的,唯一需要注意的就是在单线程中使用其方法。