使用librtmp推h264、aac实时流

文章目录

  • 前言
  • 一、推视频流
    • 1.sps、pps
    • 2.视频帧
  • 二、推音频流
    • 1.音频帧
  • 三、完整推流
    • 1.实时流
  • 总结


前言

librtmp可以用于推rtmp流,有时候我们需要将采集的摄像头或桌面的视频数据以及麦克风的音频数据推流出去,这时候就需要使用librtmp的推流功能了,其推流流程比较简单,只是一些细节需要注意即可。


一、推视频流

1.sps、pps

在推送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);
}

2.视频帧

推送视频需要区分是否为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);
}

二、推音频流

1.音频帧

推音频帧,需要设置采样率以及声道,时间戳采用绝对时间戳。

/// 
/// 推流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);
}

三、完整推流

1.实时流

同时推流时需要注意:视频音频都需要在同一个线程推流。推送视频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都不是必须的,唯一需要注意的就是在单线程中使用其方法。

你可能感兴趣的:(音视频,windows,c++,开发语言)