常常咱们会在流媒体推送端提到“数据回调”这个词,在多媒体编程中,咱们会比拟罕用到线程数据回调,在SkeyeClient治理类代码中用到了两个数据回调函数,别离是DShow原始音视频数据采集回调函数和SkeyeRTSPClient网络接管线程中回调音视频编码数据回调函数;尽管两者采集到的数据不同,然而咱们的用处是统一的,都是用来推送,所以咱们通常会用一个数据回调治理函数来进行对立治理。
int CSourceManager::SourceManager(int _channelId, int *_channelPtr, int _frameType, char *pBuf, RTSP_FRAME_INFO* _frameInfo)
一、DirectShow采集库中的回调
DirectShow采集库中的回调机制在我的另一篇文章SkeyeDarwin SkeyeLive中DirectShow采集音视频流程及几种采集形式介绍中第三点提到过,两种模式都是通过对立的设置回调函数接口函数实现:
virtual void WINAPI SetDShowCaptureCallback(RealDataCallback realDataCalBack, void* pMaster) = 0;
回调函数的设置函数通常都带有一个设置参数,该设置参数通常是一个指针变量,次要用于在回调函数体中进行调用管制;最罕用的做法是:将其设置为以后类的实例指针this,通过该指针调用不同的实例类的处理函数对回调数据进行解决。
二、libSkeyePlayer库中的回调
libSkeyePlayer库提供的设置回调函数的接口次要来自其所依赖的库SkeyeRTSPClient,该回调函数次要是回调网络接管的Rtsp流解析的音视频编码流数据,用于转发或者解码播放;因为libSkeyePlayer库(及其依赖库)均不是自己的作品(libSkeyePlayer库及其依赖库的作者是SkeyeDarwin团队的Gavin大神,向大神致敬~~~!!!),所以,我对这个库也只有大抵的理解,如果有了解不对或者不合理的中央,欢送斧正,大家互相学习!
1、网络Rtsp流回调
流回调函数在SkeyeClient中提供了设置接口函数,底层用libSkeyePlayer提供的接口函数中进行设置,对应SkeyeRTSPClient库提供的接口函数进行设置,三者对应的程序代码如下:
SkeyeLive中的回调设置函数接口:
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中实现,该类提供了回调设置函数接口:
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, "\0"))) 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(pRealtimePlayThread[iNvsIdx].nvsHandle, __RTSPSourceCallBack);实现网络流编码数据的回调函数的设置;在以上代码中,作者除了初始化SkeyeRTSPClient库的操作以及设置回调函数外,还创立了两个线程,别离用于解码和播放;
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,该变量是贯通整个程序流程,波及到网络流数据接管线程->编码数据回调||缓存->写文件||解码线程->播放线程 整个流程,在其中表演了重要的角色,构造体原型如下:
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这个变量的解决妙用即可!