天天看點

EasyRTSPLive錄影機NVR錄像機RTSP協定實時流轉RTMP協定直播流推送源碼流程解析

在《海康大華等安防錄影機采用通用RTSP協定流轉RTMP推送至Web無插件播放展示的流程方法》中,我們分析了整個将安防裝置網際網路化的主要思路,同時介紹了安防RTSP協定進行話聯網化的RTMP輸出的方法,這裡我們主要介紹的就是其中描述的專門将RTSP流轉換成RTMP推流的EasyRTSPLive的實作方法:

EasyRTSPLive就是RTSP to RTMP,拉流IPC錄影機或者NVR硬碟錄像機RTSP流轉成RTMP推送到阿裡雲CDN/騰訊雲CDN/RTMP流媒體伺服器,支援多路RTSP流同時拉取并以RTMP協定推送釋出,EasyRTMPLive我們支援任何平台,包括但不限于Windows/Linux/Android/ARM;

技術點分析:

  1. RTSP拉流:

EasyRTSPLive拉流采用的是EasyRTSPClient進行拉流,EasyRTSPClient在流媒體領域已經曆練多年,主要做的事情就是将RTSPServer的流擷取到本地,并進行标準化的組包和解析,EasyRTSPClient拉流比較的可控,尤其是在相容和保活、重連、自定義資料選取方面;

  1. H.264/H.265和PCM資料的分析:

EasyRTSPClient中内置了對整個RTSP/RTP過程的分析和解析,包括對RTSP流程的SDP資訊、RTP過程中的Timestamp、I幀、P幀的分析;

  1. RTMP推流:

RTMP推流這裡采用的是EasyRTMP,EasyRTMP支援RTMP/FLV協定的重連,而且EasyRTMP是支援H.265的,這個在視訊領域,尤其是在安防領域還是比較重要的,現在CDN都支援H.265;

  1. AAC音頻編碼:

EasyRTSPClient本身沒有轉碼的功能,如果需要轉碼,需要借助于外圍的配合,在音頻标準化方面,可選擇用EasyAACEncoder,資源消耗低、穩定;

  1. EasyRTSPClient和EasyRTMP都采用的是fork子線程,主線程控制子線程重連的方案,達到穩定、高可用的目标;

源碼解析

#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;
}
           

源碼下載下傳:https://github.com/EasyDarwin/EasyRTSPLive

這裡隻貼出了在x86平台上的實作,EasyRTSPLive是全平台都能支援的,尤其是在Windows/Linux/Android/ARM上,都能非常靈活的定制;

更多流媒體音視訊資源

EasyDarwin開源流媒體伺服器:www.EasyDarwin.org

EasyDSS高性能網際網路直播服務:www.EasyDSS.com

EasyNVR安防視訊可視化服務:www.EasyNVR.com

EasyNVS視訊綜合管理平台:www.EasyNVS.com

EasyNTS雲組網:www.EasyNTS.com

EasyGBS國标GB/T28181伺服器:www.EasyGBS.com

EasyRTC視訊會議解決方案:www.EasyRTC.cn

Copyright © TSINGSEE.com Team 2012-2019

EasyRTSPLive錄影機NVR錄像機RTSP協定實時流轉RTMP協定直播流推送源碼流程解析

繼續閱讀