基于live555的RtspClient

基于live555的RtspClient

前言

很早之前做的一个监控客户端使用的一个庞大的SDK,现在把RtspClient这端独立出来。后面可能还会写一些列关于音视频相关的Blog,包括视频解码、视频显示、在YUV数据中叠加图片等等。

音视频系列

正文

开发环境

  • Visual Studio 2013
  • Windows 10

开发工具

  • Live555源码

开发过程

一、编译Live555库

使用vs2013编译live555库网上应该有一大把资料。
参考这个Live555编译,
使用方法二,编译出来4个静态库:

  • BasicUsageEnvironmentlib.lib
  • groupsocklib.lib
  • liveMedialib.lib
  • UsageEnvironmentlib.lib
二、开发RtspClient项目

在编译好了live555的库后,用vs创建项目引入静态库,这里就不详细的介绍了,主要来讲讲RtspClient的代码。
live555的源码里面,testProgs\testRTSPClient.cpp 和 testProgs\openRTSP.cpp 详细的介绍了具体流程。
那我们慢慢根据代码来慢慢探索live555 RtspClient 整个连接过程。

创建RTSPClient对象。

m_pScheduler = BasicTaskScheduler::createNew();
m_pEnv = BasicUsageEnvironment::createNew(*m_pScheduler);
m_pRtspClient = RTSPClientEx::createNew(*m_pEnv, m_szUrl.c_str(), 0, "RTSP Client");

首先是创建TaskScheduler对象和UsageEnvironment对象,然后根据RTSP地址m_szUrl 创建一个RTSPClient对象,一个RTSPClient对象代表一个RTSP客户端。

发送DESCRIBE命令

Authenticator auth;
auth.setUsernameAndPassword(m_szAccount.c_str(), m_szPassword.c_str());
m_pRtspClient->sendDescribeCommand(continueAfterDESCRIBE, &auth);
if (!wait_Live555_response(wait_live555_time))
{
    continue;
}

调用sendDescribeCommand函数发送DESCRIBE命令,回调函数是continueAfterDESCRIBE函数,在收到RTSP服务器端对DESCRIBE命令的回复时调用。大家看到这里可能发现下面的wait_Live555_response怎么和testRTSPClient.cpp里面的不同了,这里我是参考了vlc代码里面的实现。这在里调用wait_Live555_response堵塞线程等待continueAfterDESCRIBE返回结果才执行下一步。wait_Live555_response这个函数会在接下来的代码中发挥同样的作用 wait_live555_time 是超时时间。

static void TaskInterruptRTSP(void *p_private)
{
	CRtspPullClass *pPullClass = (CRtspPullClass*)p_private;
	pPullClass->m_event_rtsp = 0xff;
}

bool CRtspPullClass::wait_Live555_response(int i_timeout)
{
	TaskToken task;
	m_event_rtsp = 0;
	if (i_timeout > 0)
	{
		task = m_pScheduler->scheduleDelayedTask(i_timeout * 1000,
			TaskInterruptRTSP,
			this);
	}
	m_event_rtsp = 0;
	m_b_error = true;
	m_ilive555_ret = 0;
	m_pScheduler->doEventLoop(&m_event_rtsp);
	if (i_timeout > 0)
		m_pScheduler->unscheduleDelayedTask(task);
	return !m_b_error;
}

上面是wait_Live555_response()的具体实现,先通过scheduleDelayedTask设置一个超时回调,然后doEventLoop堵塞Live555的线程,这里m_event_rtsp = 0的时候调用doEventLoop就是堵塞,如果当m_event_rtsp = 1的时候这里就继续往下执行。

然后让我们来看看continueAfterDESCRIBE()回调里的实现

static void continueAfterDESCRIBE(RTSPClient* rtspClient, int resultCode, char* resultString)
{
	CRtspPullClass* pPullClass = ((RTSPClientEx*)rtspClient)->GetPullClass();
	UsageEnvironment& env = rtspClient->envir();
	pPullClass->m_ilive555_ret = resultCode;
	if (resultCode == 0)
	{
		char* sdpDescription = resultString;
		free(pPullClass->m_p_sdp);
		pPullClass->m_p_sdp = NULL;
		if (sdpDescription)
		{
			pPullClass->m_p_sdp = strdup(sdpDescription);
			pPullClass->m_b_error = false;
		}
		else
			pPullClass->m_b_error = true;
	}
	else
		pPullClass->m_b_error = true;
	delete[] resultString;
	pPullClass->m_event_rtsp = 1;
}

如果回调返回成功的话就会获取到sdp信息。然后我们就会更具sdp信息创建一个MediaSession对象,MediaSession表示客户端请求服务器端某个媒体资源的会话。MediaSubsession,表示MediaSession的子会话,创建MediaSession的同时也创建了包含的MediaSubsession对象。然后客户端对服务器端的每个ServerMediaSubsession发送SETUP命令请求建立连接。让我们看接下来创建MediaSession的代码

typedef struct _VIDEO_PARAM
{
	char codec[256];
	int width;
	int height;
	int colorbits;
	int framerate;
	int bitrate;
	char vol_data[256];
	int vol_length;
}VIDEO_PARAM;



typedef struct  _AUDIO_PARAM
{
	char codec[256];
	int samplerate;
	int bitspersample;
	int channels;
	int framerate;
	int bitrate;
}AUDIO_PARAM;


typedef struct  __STREAM_AV_PARAM
{
	unsigned char	ProtocolName[32];
	short  bHaveVideo;//0 表示没有视频参数
	short  bHaveAudio;//0 表示没有音频参数
	VIDEO_PARAM videoParam;//视频参数
	AUDIO_PARAM audioParam;//音频参数
	char		szUrlInfo[512];//注意长度
}STREAM_AV_PARAM;

if (!(m_pSession = MediaSession::createNew(*m_pEnv, m_p_sdp)))
{
	continue;
}
if (m_pSession->hasSubsessions() ==false)
{
	continue;
}
m_dwLastRecvDataTick = GetTickCount();
STREAM_AV_PARAM * pAvParam = &m_avParam;
memset(pAvParam, 0, sizeof(STREAM_AV_PARAM));
memset(&m_AudioParam, 0, sizeof(AUDIO_PARAM));
memset(&m_VideoParam, 0, sizeof(VIDEO_PARAM));
sprintf((char *)pAvParam->ProtocolName, "%s", "AjVisionHD");
MediaSubsession* subsession = NULL;
MediaSubsessionIterator iter(*m_pSession);

STREAM_AV_PARAM 是一个包含媒体流参数的结构体,因为在解码的时候需要知道音视频的类型是H.265 还是H.264 是AAC还是G.711。音频的采样率、码率、通道数。视频的长宽、码率、和帧率。
取得MediaSubsession,然后客户端对服务器端的每个MediaSubsessionn发送SETUP命令请求建立连接。

while ((subsession = iter.next()) != NULL)
{
	if (subsession->initiate() == false)
	{
		continue;
	}
	if (subsession->rtpSource() != NULL)
	{
		desiredPortNum = subsession->clientPortNum();
		if (desiredPortNum != 0)
		{
			subsession->setClientPortNum(desiredPortNum);
			desiredPortNum += 2;
		}
		subsession->rtpSource()->setPacketReorderingThresholdTime(200000);
		if (strcmp(subsession->mediumName(), "video") == 0)
		{
			int nIsH264 = -1;
			if ((strcmp(subsession->codecName(), "h264") == 0)
				|| (strcmp(subsession->codecName(), "H264") == 0))
				nIsH264 = 1;
			else if ((strcmp(subsession->codecName(), "h265") == 0)
				|| (strcmp(subsession->codecName(), "H265") == 0))
				nIsH264 = 0;
			if (nIsH264 == -1)
				continue;
			VIDEO_PARAM *pVideoParam = &m_VideoParam;
			memset(pVideoParam, 0, sizeof(VIDEO_PARAM));
			strcpy(pVideoParam->codec, subsession->codecName());
			pVideoParam->width = subsession->videoWidth();
			pVideoParam->height = subsession->videoHeight();
			pVideoParam->framerate = subsession->videoFPS();
			pVideoParam->bitrate = 0;
			if (subsession->fmtp_config() != NULL)
			{
				unsigned configLen;
				unsigned char* configData = parseGeneralConfigStr(subsession->fmtp_config(), configLen);
				if (configData != NULL)
				{
					memcpy(pVideoParam->vol_data, configData, configLen);
					pVideoParam->vol_length = configLen;
					delete[] configData; configData = NULL;
				}
			}
			pAvParam->bHaveVideo = 1;
			memcpy(&pAvParam->videoParam, pVideoParam, sizeof(VIDEO_PARAM));
			if (m_pVideoSink)
			{
				CVideoStreamSink::close(m_pVideoSink);
				m_pVideoSink = NULL;
			}
			try
			{
				m_pVideoSink = new CVideoStreamSink(*m_pEnv, nIsH264, this, (LONG)this, 4 * 1024 * 1024);
			}
			catch (...)
			{
				m_pVideoSink = NULL;
				continue;
			}
			if (m_pVideoSink)
			{
				m_pVideoSink->SetStreamName("");
				if ( m_nLinkMode== 2)
					m_pRtspClient->sendSetupCommand(*subsession, default_live555_callback, false, false, true);
				else
					m_pRtspClient->sendSetupCommand(*subsession, default_live555_callback, false, m_nLinkMode, false);
				m_dwLastRecvDataTick = GetTickCount();
				if (!IsRunning())
					break;
				if (!wait_Live555_response(wait_live555_time))
				{
					continue;
				}
				m_dwLastRecvDataTick = GetTickCount();
				if (m_nLinkMode == 2)
				{//组播
					subsession->setDestinations(subsession->connectionEndpointAddress());
				}
				subsession->miscPtr = m_pRtspClient;
				m_pVideoSink->startPlaying(*(subsession->readSource()), subsessionAfterPlaying, subsession);
				if (subsession->rtcpInstance() != NULL)
			    	subsession->rtcpInstance()->setByeHandler(subsessionByeHandler, subsession);
				subsession->sink = m_pVideoSink;
			}

		}
		else if (strcmp(subsession->mediumName(), "audio") == 0)
		{
			AUDIO_PARAM *pAudioParam = &m_AudioParam;
			memset(pAudioParam, 0, sizeof(AUDIO_PARAM));
			strcpy(pAudioParam->codec, subsession->codecName());
			DebugLog("CRtspPullClass::RtspLinkThread() %s recv audio type = %s", m_szUrl.c_str(), subsession->codecName());
			if (strstr(pAudioParam->codec, "PCMU") || strstr(pAudioParam->codec, "PCMA")) // for G711
			{
				pAudioParam->bitrate = 64000;
				pAudioParam->bitspersample = 16;
				pAudioParam->channels = 1;//subsession->numChannels();
				pAudioParam->framerate = 8;
				pAudioParam->samplerate = subsession->rtpTimestampFrequency();

			}
			else //for AAC
			{
				unsigned configLen;
				unsigned char* configData = parseGeneralConfigStr(subsession->fmtp_config(), configLen);
				if (configData != NULL)
				{
					unsigned char samplingFrequencyIndex = (configData[0] & 0x07) << 1;
					samplingFrequencyIndex |= (configData[1] & 0x80) >> 7;
					if (samplingFrequencyIndex > 15)
					{
						samplingFrequencyIndex = 0;
					}

					pAudioParam->bitspersample = 16;
					pAudioParam->samplerate = samplingFrequencyTable[samplingFrequencyIndex];
					pAudioParam->channels = 2;
					pAudioParam->bitrate = pAudioParam->samplerate * pAudioParam->bitspersample / 8;
					pAudioParam->framerate = 8;
					delete[] configData; configData = NULL;
				}
				else
				{
					pAudioParam->bitrate = 16000;
					pAudioParam->bitspersample = 16;
					pAudioParam->channels = 2;
					pAudioParam->framerate = 16;
					pAudioParam->samplerate = 16000;
				}
			}
			pAvParam->bHaveAudio = 1;
			memcpy(&pAvParam->audioParam, pAudioParam, sizeof(AUDIO_PARAM));
			if (m_pAudioSink)
			{
				CAudioStreamSink::close(m_pAudioSink);
				m_pAudioSink = NULL;
			}
			try
			{
				m_pAudioSink = new CAudioStreamSink(*m_pEnv, this, (LONG)this, 64 * 1024);
			}
			catch (...)
			{
				CAudioStreamSink::close(m_pAudioSink);
				m_pAudioSink = NULL;
				continue;
			}
			if (m_pAudioSink)
			{
				if (m_nLinkMode == 2)
				{
					m_pRtspClient->sendSetupCommand(*subsession, default_live555_callback, false, false, true);//,TRUE);
				}
				else
					m_pRtspClient->sendSetupCommand(*subsession, default_live555_callback, false, m_nLinkMode);//,TRUE);			
				m_dwLastRecvDataTick = GetTickCount();
				if (!IsRunning())
					break;
				if (!wait_Live555_response(wait_live555_time))
				{
					if (m_pAudioSink)
					{
						CAudioStreamSink::close(m_pAudioSink);
						m_pAudioSink = NULL;
					}
					subsession->sink = NULL;
					continue;
				}
				if (m_nLinkMode == 2)
				{//组播
					subsession->setDestinations(subsession->connectionEndpointAddress());
				}
				subsession->miscPtr = m_pRtspClient;
				m_pAudioSink->startPlaying(*(subsession->readSource()), subsessionAfterPlaying, subsession);
				if (subsession->rtcpInstance() != NULL)
					subsession->rtcpInstance()->setByeHandler(subsessionByeHandler, subsession);
				subsession->sink = m_pAudioSink;
				m_dwLastRecvDataTick = GetTickCount();
			}
		}
	}
}
if (m_pVideoSink || m_pAudioSink)
{
	m_pRtspClient->sendPlayCommand(*m_pSession, default_live555_callback, 0, -1, 1);
	if (!wait_Live555_response(wait_live555_time))
	{
		continue;
	}
	m_isStoping = 0;
	m_pScheduler->doEventLoop(&m_isStoping);
}

在上面的代码中,首先调用MediaSubsession的initiate函数初始化MediaSubsession,然后通过MediaSubsession获取音视频的参数填充结构体。最后为MediaSubsession创建MediaSink对象来请求和保存服务器端发送的数据。然后对MediaSubsession发送SETUP命令。同样调用wait_Live555_response等待回调返回数据,然后调用MediaSink::startPlaying函数开始准备播放对应的MediaSubsession。如果建立连接成功了最后会发送Play命令请求开始传送数据同样等到回复后调用m_pScheduler->doEventLoop(&m_isStoping)堵塞线程;

下面的代码是MediaSink里面的实现了



void CMediaSinkSink::afterGettingFrame(void* clientData, unsigned frameSize,
 unsigned /*numTruncatedBytes*/,
 struct timeval presentationTime,
 unsigned /*durationInMicroseconds*/) 
{
	CVideoStreamSink* sink = (CVideoStreamSink*)clientData;	
	fSource->getNextFrame(fBuffer, fBufferSize,afterGettingFrame, this,onSourceClosure, this);
} 

在DummySink::afterGettingFrame函数中只是简单地打印出了某个MediaSubsession接收到了多少字节的数据,然后接着利用FramedSource去读取数据。可以看出,在RTSP客户端,Live555也是在MediaSink和FramedSource之间形成了一个循环,不停地从服务器端读取数据。

参考文献

最后的话

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值