SkeyePlayer libSkeyePlayer中的回调机制介绍

2023-04-18 15:27:10 浏览数 (2)

经常我们会在流媒体推送端提到“数据回调”这个词,在多媒体编程中,我们会比较常用到线程数据回调,在SkeyeClient管理类代码中用到了两个数据回调函数,分别是DShow原始音视频数据采集回调函数和SkeyeRTSPClient网络接收线程中回调音视频编码数据回调函数;虽然两者采集到的数据不同,但是我们的用途是一致的,都是用来推送,所以我们通常会用一个数据回调管理函数来进行统一管理。

代码语言:txt复制
int CSourceManager::SourceManager(int _channelId, int *_channelPtr, int _frameType, char *pBuf, RTSP_FRAME_INFO* _frameInfo)

一、DirectShow采集库中的回调

DirectShow采集库中的回调机制在我的另一篇文章SkeyeDarwin SkeyeLive中DirectShow采集音视频流程及几种采集方式介绍中第三点提到过,两种模式都是通过统一的设置回调函数接口函数实现:

代码语言:txt复制
	virtual void WINAPI SetDShowCaptureCallback(RealDataCallback realDataCalBack, void* pMaster) = 0;

回调函数的设置函数通常都带有一个设置参数,该设置参数通常是一个指针变量,主要用于在回调函数体中进行调用控制;最常用的做法是:将其设置为当前类的实例指针this,通过该指针调用不同的实例类的处理函数对回调数据进行处理。

二、libSkeyePlayer库中的回调

libSkeyePlayer库提供的设置回调函数的接口主要来自其所依赖的库SkeyeRTSPClient,该回调函数主要是回调网络接收的Rtsp流解析的音视频编码流数据,用于转发或者解码播放;由于libSkeyePlayer库(及其依赖库)均不是本人的作品(libSkeyePlayer库及其依赖库的作者是SkeyeDarwin团队的Gavin大神,向大神致敬~~~!!!),所以,我对这个库也只有大致的了解,如果有理解不对或者不合理的地方,欢迎指正,大家相互学习!

1、网络Rtsp流回调

流回调函数在SkeyeClient中提供了设置接口函数,底层用libSkeyePlayer提供的接口函数中进行设置,对应SkeyeRTSPClient库提供的接口函数进行设置,三者对应的程序代码如下:

SkeyeLive中的回调设置函数接口:

代码语言:txt复制
int SkeyePlayerManager::Start(char* szURL, HWND hShowWnd, 
	RENDER_FORMAT eRenderFormat, int rtpovertcp, const char *username, const char *password, MediaSourceCallBack callback, void *userPtr) 
{
	//Stop
	if (m_sSourceInfo.rtspSourceId > 0)
	{
		Close();
		return -1;
	}

	m_sSourceInfo.rtspSourceId = SkeyePlayer_OpenStream(szURL, hShowWnd, eRenderFormat, rtpovertcp, username, password, callback, userPtr);
	return	m_sSourceInfo.rtspSourceId ;
}

libSkeyePlayer中主要的功能在类CChannelManager中实现,该类提供了回调设置函数接口:

代码语言:txt复制
int	CChannelManager::OpenStream(const char *url, HWND hWnd, RENDER_FORMAT renderFormat, int _rtpovertcp, const char *username, const char *password, MediaSourceCallBack callback, void *userPtr)
{
	if (NULL == pRealtimePlayThread)			return -1;
	if ( (NULL == url) || (0==strcmp(url, "")))		return -1;

	int iNvsIdx = -1;
	EnterCriticalSection(&crit);
	do
	{
		for (int i=0; i<MAX_CHANNEL_NUM; i  )
		{
			if (NULL == pRealtimePlayThread[i].nvsHandle)
			{
				iNvsIdx = i;
				break;
			}
		}

		if (iNvsIdx == -1)		break;

		SkeyeRTSP_Init(&pRealtimePlayThread[iNvsIdx].nvsHandle);
		if (NULL == pRealtimePlayThread[iNvsIdx].nvsHandle)		break;	//退出while循环

		unsigned int mediaType = MEDIA_TYPE_VIDEO;
		mediaType |= MEDIA_TYPE_AUDIO;		//换为NVSource, 屏蔽声音
		SkeyeRTSP_SetCallback(pRealtimePlayThread[iNvsIdx].nvsHandle, __RTSPSourceCallBack);
		SkeyeRTSP_OpenStream(pRealtimePlayThread[iNvsIdx].nvsHandle, iNvsIdx, (char*)url, _rtpovertcp==0x01?RTP_OVER_TCP:RTP_OVER_UDP, mediaType, (char*)username, (char*)password, (int*)&pRealtimePlayThread[iNvsIdx], 1000, 0);

		pRealtimePlayThread[iNvsIdx].pCallback = callback;
		pRealtimePlayThread[iNvsIdx].pUserPtr = userPtr;

		pRealtimePlayThread[iNvsIdx].hWnd = hWnd;
		pRealtimePlayThread[iNvsIdx].renderFormat = (D3D_SUPPORT_FORMAT)renderFormat;
		CreatePlayThread(&pRealtimePlayThread[iNvsIdx]);

	}while (0);
	LeaveCriticalSection(&crit);

	if (iNvsIdx >= 0)	iNvsIdx  = CHANNEL_ID_GAIN;
	return iNvsIdx;
}

其中,调用的SkeyeRTSPClient库函数接口SkeyeRTSP_SetCallback(pRealtimePlayThreadiNvsIdx.nvsHandle, __RTSPSourceCallBack);实现网络流编码数据的回调函数的设置;在以上代码中,作者除了初始化SkeyeRTSPClient库的操作以及设置回调函数外,还创建了两个线程,分别用于解码和播放;

代码语言:txt复制
void CChannelManager::CreatePlayThread(PLAY_THREAD_OBJ	*_pPlayThread)
{
	if (NULL == _pPlayThread)		return;

	if (_pPlayThread->decodeThread.flag == 0x00)
	{
		//_pPlayThread->ch_tally = 0;
		_pPlayThread->decodeThread.flag = 0x01;
		_pPlayThread->decodeThread.hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)_lpDecodeThread, _pPlayThread, 0, NULL);
		while (_pPlayThread->decodeThread.flag!=0x02 && _pPlayThread->decodeThread.flag!=0x00)	{Sleep(100);}
		if (NULL != _pPlayThread->decodeThread.hThread)
		{
			SetThreadPriority(_pPlayThread->decodeThread.hThread, THREAD_PRIORITY_HIGHEST);
		}
	}
	if (_pPlayThread->displayThread.flag == 0x00)
	{
		_pPlayThread->displayThread.flag = 0x01;
		_pPlayThread->displayThread.hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)_lpDisplayThread, _pPlayThread, 0, NULL);
		while (_pPlayThread->displayThread.flag!=0x02 && _pPlayThread->displayThread.flag!=0x00)	{Sleep(100);}
		if (NULL != _pPlayThread->displayThread.hThread)
		{
			SetThreadPriority(_pPlayThread->displayThread.hThread, THREAD_PRIORITY_HIGHEST);
		}
	}
}

需要重要说明的是,在类CChannelManager中一个重要的成员结构体指针变量pRealtimePlayThread,该变量是贯穿整个程序流程,涉及到网络流数据接收线程->编码数据回调||缓存->写文件||解码线程->播放线程 整个流程,在其中扮演了重要的角色,结构体原型如下:

代码语言:txt复制
typedef struct __PLAY_THREAD_OBJ
{
	THREAD_OBJ		decodeThread;		//解码线程
	THREAD_OBJ		displayThread;		//显示线程


	Skeye_RTSP_Handle		nvsHandle;
	HWND			hWnd;				//显示视频的窗口句柄
	int				channelId;			//通道号
	int				showStatisticalInfo;//显示统计信息

	int				frameCache;		//帧缓存(用于调整流畅度),由上层应用设置
	int				initQueue;		//初始化队列标识
	SS_QUEUE_OBJ_T	*pAVQueue;		//接收rtsp的帧队列
	int				frameQueue;		//队列中的帧数
	int				findKeyframe;	//是否需要查找关键帧标识
	int				decodeYuvIdx;

	DWORD			dwLosspacketTime;	//丢包时间
	DWORD			dwDisconnectTime;	//断线时间


	DECODER_OBJ		decoderObj[MAX_DECODER_NUM];
	D3D_HANDLE		d3dHandle;		//显示句柄
	D3D_SUPPORT_FORMAT	renderFormat;	//显示格式
	int				ShownToScale;		//按比例显示
	int				decodeKeyFrameOnly;	//仅解码显示关键帧

	unsigned int	rtpTimestamp;
	LARGE_INTEGER	cpuFreq;		//cpu频率
	_LARGE_INTEGER	lastRenderTime;	//最后显示时间

	int				yuvFrameNo;		//当前显示的yuv帧号
	YUV_FRAME_INFO	yuvFrame[MAX_YUV_FRAME_NUM];
	CRITICAL_SECTION	crit;
	bool			resetD3d;		//是否需要重建d3dRender
	RECT			rcSrcRender;
	D3D9_LINE		d3d9Line;

	char			manuRecordingFile[MAX_PATH];
	int				manuRecording;
	MP4C_Handler	mp4cHandle;
	int				vidFrameNum;


	MediaSourceCallBack pCallback;
	void			*pUserPtr;
}PLAY_THREAD_OBJ;

其中,编码数据和解码数据分别缓存在队列结构pAVQueue和数组yuvFrame中,程序中用这个结构做了2级缓存,保证接收和解码播放过程的流畅性,其中,解码数据缓存只有3帧,确保播放的实时性,当然在机器性能或者网络资源不够的情况下可能出现卡帧或者花屏的情况,当然,程序中采用了先进的丢帧机制,确保花屏的情况最大限度的减少。

此外,pRealtimePlayThread这个结构指针,创建的是一个最大64的数组,也就是相当于的64个CChannelManager类的实例的Player对应的处理(当然,类实例只有一个CChannelManager *g_pChannelManager);对于libSkeyePlaer库的这种处理方式可能部分程序员不太理解(包括我~~~哈哈哈),但是其中确实有很多处理精妙的地方值得我们去学习,所以,要读懂libSkeyePlaer库中的代码,熟悉和理解pRealtimePlayThread这个变量的处理妙用即可!

0 人点赞