用live555將內網攝像機視訊推送到外網伺服器,附原始碼

xiejiashu發表於2014-06-25
最近很多人問,如何將內網的攝像機流媒體資料釋出到公網,如果用公網與區域網間的埠對映方式太過麻煩,一個攝像機要做一組對映,而且不是每一個區域網都是有固定ip地址,即使外網主機配置好了每一個攝像機的對映地址,也有可能會因為寬頻公網ip地址變動而導致配置無效。

再換一個應用場景,當我們的所有IP攝像機都部署在一個沒有有線網路的環境裡面,所有的流媒體資料都要通過3G/4G網路釋出出去。那麼就必須有這麼一個服務單元,能夠通過先拉後推的方式,將內網的流媒體資料,推送併發布到外網的流媒體伺服器上去:

在實現先拉後推式轉發之前,我們先熟悉下live555的運轉模式,live555主要運轉的是一個source與sink的迴圈,sink想要資料,就呼叫source的getNextFrame,source獲取到資料後,再呼叫afterGettingFrame回撥,返回給sink資料,sink處理完後,再呼叫source的getNextFrame,如此迴圈。那麼我們這裡要實現從攝像機獲取資料,那麼我們的source就是一個RTPSource,我們又需要將資料以RTP的方式傳送給流媒體伺服器,那麼我們的sink就是一個RTPSink,我們需要打通的就是一個RTPSource到一個RTPSink的過程。

ok,live555已經幫我們實現了大部分的功能,我們只需要將已有的部分組合起來就行了,這裡我們主要用到的就是live555的ProxyServerMediaSession類和DarwinInjector類,我們用ProxyServerMediaSession從攝像機獲取流媒體,再用DarwinInjector推送到Darwin Streaming Server,主要實現流程在下面程式碼註釋中:

/*
	功能描述:	一個簡單的RTSP/RTP對接功能,從RTSP源通過基本的RTSPClient流程,獲取到RTP流媒體資料
				再通過標準RTSP推送過程(ANNOUNCE/SETUP/PLAY),將獲取到RTP資料推送給Darwin流媒體
				分發伺服器。
				此Demo只演示了單個源的轉換、推送功能!
				
	Author:	sunpany@qq.com
	時間:		2014/06/25
*/

#include "liveMedia.hh"
#include "BasicUsageEnvironment.hh"
#include "RTSPCommon.hh"

char* server = "www.easydss.com";//RTSP流媒體轉發伺服器地址,<請修改為自己搭建的流媒體伺服器地址>
int port = 8554;				//RTSP流媒體轉發伺服器埠,<請修改為自己搭建的流媒體伺服器埠>
char* streamName = "live.sdp";		//流名稱,推送到Darwin的流名稱必須以.sdp結尾
char* src = "rtsp://218.204.223.237:554/live/1/66251FC11353191F/e7ooqwcfbqjoo80j.sdp";//源端URL

UsageEnvironment* env = NULL;		//live555 global environment
TaskScheduler* scheduler = NULL;
char eventLoopWatchVariable = 0;

DarwinInjector* injector = NULL;	//DarwinInjector
FramedSource* vSource = NULL;		//Video Source
FramedSource* aSource = NULL;		//Audio Source

RTPSink* vSink = NULL;				//Video Sink
RTPSink* aSink = NULL;				//Audio Sink

Groupsock* rtpGroupsockVideo = NULL;//Video Socket
Groupsock* rtpGroupsockAudio = NULL;//Audio Socket

ProxyServerMediaSession* sms = NULL;//proxy session

// 流轉發過程
bool RedirectStream(char const* ip, unsigned port);

// 流轉發結束後處理回撥
void afterPlaying(void* clientData);

// 實現等待功能
void sleep(void* clientSession)  
{
	char* var = (char*)clientSession;
    *var = ~0; 
}  

// Main
int main(int argc, char** argv) 
{
	// 初始化基本的live555環境
	scheduler = BasicTaskScheduler::createNew();
	env = BasicUsageEnvironment::createNew(*scheduler);

	// 新建轉發SESSION
	sms = ProxyServerMediaSession::createNew(*env, NULL, src);
	
	// 迴圈等待轉接程式與源端連線成功
	while(sms->numSubsessions() <= 0 )
	{
		char fWatchVariable  = 0;  
		env->taskScheduler().scheduleDelayedTask(2*1000000,(TaskFunc*)sleep,&fWatchVariable);  
		env->taskScheduler().doEventLoop(&fWatchVariable);  
	}
	
	// 開始轉發流程
	RedirectStream(server, port);

	env->taskScheduler().doEventLoop(&eventLoopWatchVariable);

	return 0;
}


// 推送視訊到流媒體伺服器
bool RedirectStream(char const* ip, unsigned port)
{
	// 轉發SESSION必須保證存在
	if( sms == NULL) return false;

	// 判斷sms是否已經連線上源端
	if( sms->numSubsessions() <= 0 ) 
	{
		*env << "sms numSubsessions() == 0\n";
		return false;
	}

	// DarwinInjector主要用於向Darwin推送RTSP/RTP資料
	injector = DarwinInjector::createNew(*env);

	struct in_addr dummyDestAddress;
	dummyDestAddress.s_addr = 0;
	rtpGroupsockVideo = new Groupsock(*env, dummyDestAddress, 0, 0);

	struct in_addr dummyDestAddressAudio;
	dummyDestAddressAudio.s_addr = 0;
	rtpGroupsockAudio = new Groupsock(*env, dummyDestAddressAudio, 0, 0);

	ServerMediaSubsession* subsession = NULL;
	ServerMediaSubsessionIterator iter(*sms);
    while ((subsession = iter.next()) != NULL)
	{
		ProxyServerMediaSubsession* proxySubsession = (ProxyServerMediaSubsession*)subsession;
						
		unsigned streamBitrate;
		FramedSource* source = proxySubsession->createNewStreamSource(1, streamBitrate);
		
		if (strcmp(proxySubsession->mediumName(), "video") == 0)
		{
			// 用ProxyServerMediaSubsession建立Video的RTPSource
			vSource = source;
			unsigned char rtpPayloadType = proxySubsession->rtpPayloadFormat();
			// 建立Video的RTPSink
			vSink = proxySubsession->createNewRTPSink(rtpGroupsockVideo,rtpPayloadType,source);
			// 將Video的RTPSink賦值給DarwinInjector,推送視訊RTP給Darwin
			injector->addStream(vSink,NULL);
		}
		else
		{
			// 用ProxyServerMediaSubsession建立Audio的RTPSource
			aSource = source;
			unsigned char rtpPayloadType = proxySubsession->rtpPayloadFormat();
			// 建立Audio的RTPSink
			aSink = proxySubsession->createNewRTPSink(rtpGroupsockVideo,rtpPayloadType,source);
			// 將Audio的RTPSink賦值給DarwinInjector,推送音訊RTP給Darwin
			injector->addStream(aSink,NULL);
		}
    }

	// RTSP ANNOUNCE/SETUP/PLAY推送過程
	if (!injector->setDestination(ip, streamName, "live555", "LIVE555", port)) 
	{
		*env << "injector->setDestination() failed: " << env->getResultMsg() << "\n";
		return false;
	}

	// 開始轉發視訊RTP資料
	if((vSink != NULL) && (vSource != NULL))
		vSink->startPlaying(*vSource,afterPlaying,vSink);

	// 開始轉發音訊RTP資料
	if((aSink != NULL) && (aSource != NULL))
		aSink->startPlaying(*aSource,afterPlaying,aSink);

	*env << "\nBeginning to get camera video...\n";
	return true;
}


// 停止推送,釋放所有變數
void afterPlaying(void* clientData) 
{
	if( clientData == NULL ) return;

	if(vSink != NULL)
		vSink->stopPlaying();

	if(aSink != NULL)
		aSink->stopPlaying();

	if(injector != NULL)
	{
		Medium::close(*env, injector->name());
		injector == NULL;
	}

	ServerMediaSubsession* subsession = NULL;
	ServerMediaSubsessionIterator iter(*sms);
    while ((subsession = iter.next()) != NULL)
	{
		ProxyServerMediaSubsession* proxySubsession = (ProxyServerMediaSubsession*)subsession;
		if (strcmp(proxySubsession->mediumName(), "video") == 0)
			proxySubsession->closeStreamSource(vSource);

		else
			proxySubsession->closeStreamSource(aSource);
	}

	if(vSink != NULL)
		Medium::close(vSink);<pre name="code" class="html">
if(aSink != NULL)Medium::close(aSink);if(vSource != NULL)Medium::close(vSource);if(aSource != NULL)Medium::close(aSource);delete rtpGroupsockVideo;rtpGroupsockVideo = NULL;delete rtpGroupsockAudio;rtpGroupsockAudio = NULL;}

程式還有許多要完善的地方,只是一個簡單的實現。

原始碼下載:

http://pan.baidu.com/s/1sj6Ue4l

非常感謝感謝6樓 Boris_Cao_2015 5天前 的回覆,是這樣的!

“按著這個程式碼不同時支援音視訊,要修改LIVE555裡面DarwinInjector原始碼, stream channel id記得加1,因為RTCP instance不存在,所以RTP流的stream channel id必須自動加1, 否則跟RTCP的stream channel id重合,這就是原因。跟樓主和大家分享。嘻嘻!”

------------------------------------------------------------

本文轉自www.easydarwin.org,更多開源流媒體解決方案,請關注我們的微信:EasyDarwin 


相關文章