如何实现RTSP H.265(hevc)流转RTMP H.265(hevc)推流到RTMP流媒体服务器实现互联网CDN直播_h265 rtsp转rtmp-程序员宅基地

技术标签: RTSP转RTMP  RTMP推流  EasyRTMP说明  EasyRTSPLive  EasyRTSPClient  RTSP拉流  

说到RTSP H.265(HEVC)转RTMP,首先要对RTMP协议有所了解,RTMP协议里面原本是没有H.265的定义的,国内的CDN厂家联合起来制定了一个关于RTMP/FLV扩展H.265(HEVC)的方案,这是为了适应国内视频互联网高速发展而来的,目前国内开发者基本上都按这个扩展协议做的。

RTMP头部信息封装并没有定义HEVC,我们采用CDN联盟的HEVC扩展标准,将HEVC的VideoTagHeader定义为12,详见下图:

RTMP扩展H.265

RTMP推流

协议层已经确定,下一步的关键问题是要实现RTMP H.265(HEVC)推送模块,在视频推送上面,我们可以选择ffmpeg或者EasyRTMP,两者都可以支持H.265(HEVC)的RTMP推流,但是ffmpeg是需要做一些小小的修改和重新编译的:

  1. 第一步:我们在H264封装的基础上进行改进,以支持HEVC头部的封装,而HEVC头有
    SPS PPS VPS,我们参考ffmpeg的HEVCDecoderConfigurationRecord结构对metadata进行封装,该结构体代码
typedef struct HVCCNALUnitArray {
	uint8_t  array_completeness;
	uint8_t  NAL_unit_type;
	uint16_t numNalus;
	uint16_t *nalUnitLength;
	uint8_t  **nalUnit;
} HVCCNALUnitArray;

typedef struct HEVCDecoderConfigurationRecord {
	uint8_t  configurationVersion;
	uint8_t  general_profile_space;
	uint8_t  general_tier_flag;
	uint8_t  general_profile_idc;
	uint32_t general_profile_compatibility_flags;
	uint64_t general_constraint_indicator_flags;
	uint8_t  general_level_idc;
	uint16_t min_spatial_segmentation_idc;
	uint8_t  parallelismType;
	uint8_t  chromaFormat;
	uint8_t  bitDepthLumaMinus8;
	uint8_t  bitDepthChromaMinus8;
	uint16_t avgFrameRate;
	uint8_t  constantFrameRate;
	uint8_t  numTemporalLayers;
	uint8_t  temporalIdNested;
	uint8_t  lengthSizeMinusOne;
	uint8_t  numOfArrays;
	HVCCNALUnitArray *array;
} HEVCDecoderConfigurationRecord;

  1. 参考ffmeg对该结构进行初始化如下:
//需要注意的是,该结构其他参数我们其实可以不特别关心
//我们只需要在HVCCNALUnitArray数组中把HEVC的VPS,SPS和PPS信息填入即可。
static void hvcc_init(HEVCDecoderConfigurationRecord *hvcc)
{
    memset(hvcc, 0, sizeof(HEVCDecoderConfigurationRecord));
    hvcc->configurationVersion = 1;
    hvcc->lengthSizeMinusOne   = 3; // 4 bytes

    /*
     * The following fields have all their valid bits set by default,
     * the ProfileTierLevel parsing code will unset them when needed.
     */
    hvcc->general_profile_compatibility_flags = 0xffffffff;
    hvcc->general_constraint_indicator_flags  = 0xffffffffffff;

    /*
     * Initialize this field with an invalid value which can be used to detect
     * whether we didn't see any VUI (in which case it should be reset to zero).
     */
    hvcc->min_spatial_segmentation_idc = MAX_SPATIAL_SEGMENTATION + 1;
}

  1. 最后,填写好Metadata信息后,我们还需要在发送帧数据的时候做一下修改,将I帧的tag头改成12,P帧tag不变,设置成1即可,如下代码所示
	int i = 0;
	if(bIsKeyFrame)
	{
		//body[i++] = 0x17;// 2:Pframe  7:AVC
		body[i++] = 	 (m_metadata.nVideoCodec  == FLV_CODECID_HEVC) ? 0x1C:0x17;// 1:Iframe  7:AVC 12:HEVC

		if (m_bWaitingKeyFrame)
		{
			m_bWaitingKeyFrame = false;
		}
	}
	else
	{
		//body[i++] = 0x27;// 2:Pframe  7:AVC
		body[i++] = 	 (m_metadata.nVideoCodec  == FLV_CODECID_HEVC) ? 0x2C:0x27;// 1:Iframe  7:AVC 12:HEVC
	}
	body[i++] = 0x01;// AVC NALU
	body[i++] = 0x00;
	body[i++] = 0x00;
	body[i++] = 0x00;

	// NALU size
	body[i++] = size>>24;
	body[i++] = size>>16;
	body[i++] = size>>8;
	body[i++] = size&0xff;


EasyRTMP则可以直接支持H.265(HEVC)的推流,EasyRTMP是一套调用简单、功能完善、运行高效稳定的RTMP功能组件,支持市面上绝大部分的RTMP流媒体服务器,包括Wowza、Red5、ngnix_rtmp、crtmpserver等主流RTMP服务器,能够完美应用于各种行业的直播需求,手机直播、桌面直播、摄像机直播、课堂直播等等方面! Github地址:https://github.com/EasyDSS/EasyRTMP

RTSP拉流

目前市面上两套相对较好的RTSP协议拉流技术框架,一个是live555,一个是ffmpeg,两套框架各有千秋,各有一系列的应用案例:

  • live555:非常老牌的RTSP框架,十几年了,还在迭代与维护,作者Ross也以此为业,进行着开源+商业的运营,大家所熟知的VLC播放器,RTSP拉流采用的就是live555;

  • ffmpeg:ffmpeg就更不用说了,目前国内大部分的播放器,都用这个,RTSP模块自写自带的,兼容性也非常不错;

如果说对于RTSP流程协议不是很熟悉,而且对于ffmpeg的改造或者说编译都是一件非常费时费事,而且成功率不高的事情,那么可以直接采用EasyRTSPClient:https://github.com/tsingsee/EasyRTSPClient ,基于live555进行的优化和开发,大大简化了RTSP协议的调用流程,尤其是再也不用过多关心RTSP的DESCRIBE、SETUP、PLAY的流程,直接回调就能获取到对应的错误信息和数据信息,包括能将H.264/H.265的sps、pps、vps等数据信息;

EasyRTSPLive

实际以上描述的基本都是组件,而如何能将整套过程串联起来,进行一整套的、多路并行的RTSP拉流转RTMP推流,还是需要费很多事情的,于是,我们整合了一个EasyRTSPLive项目,能非常方便地将整个过程串联,支持H.264、H.265流的RTSP拉转RTMP推:https://github.com/EasyDarwin/EasyRTSPLive

#define _CRTDBG_MAP_ALLOC
#include <stdio.h>
#ifdef _WIN32
#include "windows.h"
#else
#include <string.h>
#include <unistd.h>
#endif
#include "getopt.h"
#include <stdio.h> 
#include <iostream> 
#include <time.h> 
#include <stdlib.h>
//#include <vector>
#include <list>

#include "EasyRTSPClientAPI.h"
#include "EasyRTMPAPI.h"
#include "ini.h"
#include "trace.h"

#ifdef _WIN32
#pragma comment(lib,"libEasyRTSPClient.lib")
#pragma comment(lib,"libeasyrtmp.lib")
#endif

#define MAX_RTMP_URL_LEN 256

#define BUFFER_SIZE  1024*1024
#define MAX_CHANNEL_INDEX 1024
#define CONF_FILE_PATH  "Config.ini"  

typedef struct _channel_cfg_struct_t
{
	int channelId;
	int option;
	char channelName[64];
	char srcRtspAddr[512];
	char destRtmpAddr[512];
}_channel_cfg;

typedef struct _rtmp_pusher_struct_t
{
	Easy_Handle rtmpHandle;
	unsigned int u32AudioCodec;	
	unsigned int u32AudioSamplerate;
	unsigned int u32AudioChannel;
}_rtmp_pusher;

typedef struct _channel_info_struct_t
{
	_channel_cfg		fCfgInfo;
	_rtmp_pusher		fPusherInfo;
	Easy_Handle	fNVSHandle;
	FILE*				fLogHandle;
	bool				fHavePrintKeyInfo;
	EASY_MEDIA_INFO_T	fMediainfo;
}_channel_info;

static std::list <_channel_info*> gChannelInfoList;

int __EasyRTMP_Callback(int _frameType, char *pBuf, EASY_RTMP_STATE_T _state, void *_userPtr)
{
	_channel_info* pChannel = (_channel_info*)_userPtr;

	switch(_state)
	{
	case EASY_RTMP_STATE_CONNECTING:
		TRACE_LOG(pChannel->fLogHandle, "Connecting...\n");
		break;
	case EASY_RTMP_STATE_CONNECTED:
		TRACE_LOG(pChannel->fLogHandle, "Connected\n");
		break;
	case EASY_RTMP_STATE_CONNECT_FAILED:
		TRACE_LOG(pChannel->fLogHandle, "Connect failed\n");
		break;
	case EASY_RTMP_STATE_CONNECT_ABORT:
		TRACE_LOG(pChannel->fLogHandle, "Connect abort\n");
		break;
	case EASY_RTMP_STATE_DISCONNECTED:
		TRACE_LOG(pChannel->fLogHandle, "Disconnect.\n");
		break;
	default:
		break;
	}
	return 0;
}

/* EasyRTSPClient callback */
int Easy_APICALL __RTSPSourceCallBack( int _chid, void *_chPtr, int _mediatype, char *pbuf, EASY_FRAME_INFO *frameinfo)
{
	if (NULL != frameinfo)
	{
		if (frameinfo->height==1088)		frameinfo->height=1080;
		else if (frameinfo->height==544)	frameinfo->height=540;
	}
	Easy_Bool bRet = 0;
	int iRet = 0;
	
	_channel_info* pChannel = (_channel_info*)_chPtr;

	if (_mediatype == EASY_SDK_VIDEO_FRAME_FLAG)
	{
		if(frameinfo && frameinfo->length)
		{
			if( frameinfo->type == EASY_SDK_VIDEO_FRAME_I)
			{
				if(pChannel->fPusherInfo.rtmpHandle == 0)
				{
					pChannel->fPusherInfo.rtmpHandle = EasyRTMP_Create();
					if (pChannel->fPusherInfo.rtmpHandle == NULL)
					{
						TRACE_LOG(pChannel->fLogHandle, "Fail to rtmp create failed ...\n");
						return -1;
					}
					EasyRTMP_SetCallback(pChannel->fPusherInfo.rtmpHandle, __EasyRTMP_Callback, pChannel);
					bRet = EasyRTMP_Connect(pChannel->fPusherInfo.rtmpHandle, pChannel->fCfgInfo.destRtmpAddr);
					if (!bRet)
					{
						TRACE_LOG(pChannel->fLogHandle, "Fail to rtmp connect failed ...\n");
					}

					EASY_MEDIA_INFO_T mediaInfo;
					memset(&mediaInfo, 0, sizeof(EASY_MEDIA_INFO_T));
					mediaInfo.u32VideoFps = pChannel->fMediainfo.u32VideoFps;
					mediaInfo.u32AudioSamplerate = 8000;

					iRet = EasyRTMP_InitMetadata(pChannel->fPusherInfo.rtmpHandle, &mediaInfo, 1024);
					if (iRet < 0)
					{
						TRACE_LOG(pChannel->fLogHandle, "Fail to Init Metadata ...\n");
					}
				}

				EASY_AV_Frame avFrame;
				memset(&avFrame, 0, sizeof(EASY_AV_Frame));
				avFrame.u32AVFrameFlag = EASY_SDK_VIDEO_FRAME_FLAG;
				avFrame.u32AVFrameLen = frameinfo->length;
				avFrame.pBuffer = (unsigned char*)pbuf;
				avFrame.u32VFrameType = EASY_SDK_VIDEO_FRAME_I;
				//avFrame.u32TimestampSec = frameinfo->timestamp_sec;
				//avFrame.u32TimestampUsec = frameinfo->timestamp_usec;
				//
				iRet = EasyRTMP_SendPacket(pChannel->fPusherInfo.rtmpHandle, &avFrame);
				if (iRet < 0)
				{
					TRACE_LOG(pChannel->fLogHandle, "Fail to Send H264 Packet(I-frame) ...\n");
				}
				else
				{
					if(!pChannel->fHavePrintKeyInfo)
					{
						TRACE_LOG(pChannel->fLogHandle, "I\n");
						pChannel->fHavePrintKeyInfo = true;
					}
				}
			}
			else
			{
				if(pChannel->fPusherInfo.rtmpHandle)
				{
					EASY_AV_Frame avFrame;
					memset(&avFrame, 0, sizeof(EASY_AV_Frame));
					avFrame.u32AVFrameFlag = EASY_SDK_VIDEO_FRAME_FLAG;
					avFrame.u32AVFrameLen = frameinfo->length-4;
					avFrame.pBuffer = (unsigned char*)pbuf+4;
					avFrame.u32VFrameType = EASY_SDK_VIDEO_FRAME_P;
					//avFrame.u32TimestampSec = frameinfo->timestamp_sec;
					//avFrame.u32TimestampUsec = frameinfo->timestamp_usec;
					iRet = EasyRTMP_SendPacket(pChannel->fPusherInfo.rtmpHandle, &avFrame);
					if (iRet < 0)
					{
						TRACE_LOG(pChannel->fLogHandle, "Fail to Send H264 Packet(P-frame) ...\n");
					}
					else
					{
						if(!pChannel->fHavePrintKeyInfo)
						{
							TRACE_LOG(pChannel->fLogHandle, "P\n");
						}
					}
				}
			}				
		}	
	}
	else if (_mediatype == EASY_SDK_MEDIA_INFO_FLAG)
	{
		if(pbuf != NULL)
		{
			EASY_MEDIA_INFO_T mediainfo;
			memset(&(pChannel->fMediainfo), 0x00, sizeof(EASY_MEDIA_INFO_T));
			memcpy(&(pChannel->fMediainfo), pbuf, sizeof(EASY_MEDIA_INFO_T));
			TRACE_LOG(pChannel->fLogHandle,"RTSP DESCRIBE Get Media Info: video:%u fps:%u audio:%u channel:%u sampleRate:%u \n", 
				pChannel->fMediainfo.u32VideoCodec, pChannel->fMediainfo.u32VideoFps, pChannel->fMediainfo.u32AudioCodec, pChannel->fMediainfo.u32AudioChannel, pChannel->fMediainfo.u32AudioSamplerate);
		}
	}

	return 0;
}

bool InitCfgInfo(void)
{
	int i = 0;
	gChannelInfoList.clear();
	for(i = 0; i < MAX_CHANNEL_INDEX; i++)
	{
		_channel_info* pChannelInfo = new _channel_info();
		if(pChannelInfo)
		{
			memset(pChannelInfo, 0, sizeof(_channel_info));
			pChannelInfo->fCfgInfo.channelId = i;
			pChannelInfo->fHavePrintKeyInfo = false;
			sprintf(pChannelInfo->fCfgInfo.channelName, "channel%d",i);
			strcpy(pChannelInfo->fCfgInfo.srcRtspAddr, GetIniKeyString(pChannelInfo->fCfgInfo.channelName, "rtsp", CONF_FILE_PATH));
			strcpy(pChannelInfo->fCfgInfo.destRtmpAddr, GetIniKeyString(pChannelInfo->fCfgInfo.channelName, "rtmp", CONF_FILE_PATH));
			pChannelInfo->fCfgInfo.option = GetIniKeyInt(pChannelInfo->fCfgInfo.channelName, "option", CONF_FILE_PATH);
			if(strlen(pChannelInfo->fCfgInfo.srcRtspAddr) > 0 && strlen(pChannelInfo->fCfgInfo.destRtmpAddr) > 0)
			{
				gChannelInfoList.push_back(pChannelInfo);
			}
		}
	}
	return true;
}

void ReleaseSpace(void)
{
	std::list<_channel_info*>::iterator it;
	for(it = gChannelInfoList.begin(); it != gChannelInfoList.end(); it++)
	{
		_channel_info* pChannel = *it;

		if (NULL != pChannel->fNVSHandle) 
		{
			EasyRTSP_CloseStream(pChannel->fNVSHandle);
			EasyRTSP_Deinit(&(pChannel->fNVSHandle));
			pChannel->fNVSHandle = NULL;
		}

		if (NULL != pChannel->fPusherInfo.rtmpHandle)
		{
			EasyRTMP_Release(pChannel->fPusherInfo.rtmpHandle);
			pChannel->fPusherInfo.rtmpHandle = NULL;
		}

		if(pChannel->fLogHandle)
		{
			TRACE_CloseLogFile(pChannel->fLogHandle);
			pChannel->fLogHandle = NULL;
		}

		delete pChannel;
	}

	gChannelInfoList.clear();
}

int main(int argc, char * argv[])
{
	InitCfgInfo();

	int iret = 0;
	iret = EasyRTMP_Activate(KEY);
	if (iret <= 0)
	{
		printf("RTMP Activate error. ret=%d!!!\n", iret);
		getchar();
		return -1;
	}

#ifdef _WIN32
	extern char* optarg;
#endif
	int ch;

	atexit(ReleaseSpace);

	iret = 0;
	iret = EasyRTSP_Activate(RTSP_KEY);
	if(iret <= 0)
	{
		printf("rtsp Activate error. ret=%d!!!\n", iret);
		return -2;
	}

	std::list<_channel_info*>::iterator it;
	for(it = gChannelInfoList.begin(); it != gChannelInfoList.end(); it++)
	{
		_channel_info* pChannel = *it;
		pChannel->fLogHandle = TRACE_OpenLogFile(pChannel->fCfgInfo.channelName);

		TRACE_LOG(pChannel->fLogHandle, "channel[%d] rtsp addr : %s\n", pChannel->fCfgInfo.channelId, pChannel->fCfgInfo.srcRtspAddr);
		TRACE_LOG(pChannel->fLogHandle, "channel[%d] rtmp addr : %s\n", pChannel->fCfgInfo.channelId, pChannel->fCfgInfo.destRtmpAddr);

		EasyRTSP_Init(&(pChannel->fNVSHandle));

		if (NULL == pChannel->fNVSHandle)
		{
			TRACE_LOG(pChannel->fLogHandle, "%s rtsp init error. ret=%d!!!\n", pChannel->fCfgInfo.channelName , iret);
			continue;
		}
		unsigned int mediaType = EASY_SDK_VIDEO_FRAME_FLAG | EASY_SDK_AUDIO_FRAME_FLAG;
	
		EasyRTSP_SetCallback(pChannel->fNVSHandle, __RTSPSourceCallBack);

		EasyRTSP_OpenStream(pChannel->fNVSHandle, pChannel->fCfgInfo.channelId, pChannel->fCfgInfo.srcRtspAddr, EASY_RTP_OVER_TCP, mediaType, 0, 0, pChannel, 1000, 0, pChannel->fCfgInfo.option, 0);
	}

	while(true)
	{
#ifdef _WIN32
		Sleep(1000);
#else
		sleep(1);
#endif
	}

    return 0;
}

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/EasyNVR/article/details/95753522

智能推荐

2020紫光展锐秋季线上发布会:造就人民的数字世界-程序员宅基地

文章浏览阅读448次。11月9日,紫光展锐秋季线上发布会在云上举办。紫光展锐CEO楚庆阐释了新展锐的企业战略——“人民的数字世界”。会上发布了搭载展锐5G芯片的联通第二代5G CPE VN007+、展锐5G射..._紫光展锐a7862 csdn

Win11右键菜单栏变win10样式_win11 刷新不能按e了-程序员宅基地

文章浏览阅读922次。Win11右键菜单变win10 _win11 刷新不能按e了

单指令周期CPU——移动操作指令的实现_移位操作和减法操作指令周期-程序员宅基地

文章浏览阅读3k次。单指令周期CPUMIPS移动操作指令_移位操作和减法操作指令周期

《Python编程-从入门到实践》第9、10章习题选练_python从入门到实践练习9-10-程序员宅基地

文章浏览阅读603次。9-1. Restaurant: Make a class called Restaurant. The __init__() method for Restaurant should store two attributes: a restaurant_name and a cuisine_type. Make a method called describe_restaurant() that..._python从入门到实践练习9-10

java class cast_Java异常ClassCastException-程序员宅基地

文章浏览阅读244次。在说ClassCastException之前,先介绍下引用类型转换;引用类型转换分为向上转型和向下转型两种;向上转型:多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的;当父类引用指向一个子类对象时,便是向上转换;使用格式:父类类型 变量名 = new 子类类型();向下转型:父类类型向子类类型向下转换的过程,这个过程时强制;一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强..._enhancerbyspringcglib 报错 classcastexception类型转换异常

graphQL(一)——入门_graphql query-程序员宅基地

文章浏览阅读418次。What graphQL关于graphQL的介绍在网上有很多,但从核心上讲,是一门语言;进一步讲,是一门查询语言;再进一步讲,是一种API查询语言。有疑问?API还能查?API不是用来调用的吗?没错,这正是GraphQL的强大之处。Why graphQL我们在使用Rest接口时,接口返回的数据格式、数据类型都是后端预先定义好的,如果返回的数据格式并不是调用者所期望的,作为前端的人员可以通过以..._graphql query

随便推点

iOS开发-技术知识盘点&总结(一)_accessorytype: .switch(ison-程序员宅基地

文章浏览阅读1.9k次。 1&gt; 主界面上下水波动态显示http://code4app.qiniudn.com/ 2&gt; 导入音乐铃声根据MJAutoTool 实现 3&gt; 语言本地化通过2个string文件实现(在plist文件中完成NSLocation加载语言本地化) NSLocalizedString(@"本地化",nil) 4&gt; 铃声的处理用 [NSUserD..._accessorytype: .switch(ison

Python模块之NumPy_print(b【0,:】)在python的numpy中表示什么-程序员宅基地

文章浏览阅读501次。Python模块之NumPy# 1 adarray程序示例:```b = np.zeros((2, 3, 5))print(b)print(b.ndim)print(b.size)print(b.shape)```执行结果:```[[[0. 0. 0. 0. 0.] [0. 0. 0. 0. 0.] [0. 0. 0. 0. 0.]] [[0. 0. 0. 0. 0.] [0. 0. 0. 0. 0.] [0. 0. 0. 0._print(b【0,:】)在python的numpy中表示什么

风雨十年:一个老程序员的心里话!_学校居然还在教foxpro-程序员宅基地

文章浏览阅读1.7k次。曲折的实践之路 1.希特勒地堡与cih病毒   99年4月,我来到北京参加研究生复试。复试完了之后就不回去了,我拿着一张光盘,里面刻着我用vb和authorware3.5编的一个cool 3d的教学软件,到处参加招聘会,开始了在北京的打工生涯。   说句实话,我心中真是一点底也没有。北京人才济济,我一个三流大学的毕业生,又不是计算机专业的,有人要我吗?   我在北京无_学校居然还在教foxpro

漏洞扫描工具---awvs配置使用_awvs网络扫描程序未配置-程序员宅基地

文章浏览阅读2k次。1.登录到awvs2.设置扫描目标并执行扫描任务3.所有选项保持默认即可,点击扫描4.查看扫描结果_awvs网络扫描程序未配置

使用nginx部署前端项目_前台包如何通过nginx访问-程序员宅基地

文章浏览阅读3.8k次。当前环境 centos7安装相关依赖gcc 安装安装 nginx 需要先将官网下载的源码进行编译,编译依赖 gcc 环境,如果没有 gcc 环境,则需要安装:yum install gcc-c++PCRE pcre-devel 安装PCRE(Perl Compatible Regular Expressions) 是一个Perl库,包括 perl 兼容的正则表达式库。ngi..._前台包如何通过nginx访问

领先氢燃料电池技术助力未来出行 现代汽车惊艳进博会-程序员宅基地

文章浏览阅读469次。2019年11月5日,第二届中国国际进口博览会在上海国家会展中心隆重开幕。作为中国坚定支持贸易自由化和经济全球化、主动向世界开放市场的重大举措,中国国际进口博览会的举办初..._氢燃料电池房车

推荐文章

热门文章

相关标签