共计 5390 个字符,预计需要花费 14 分钟才能阅读完成。
常常咱们会在流媒体推送端提到“数据回调”这个词,在多媒体编程中,咱们会比拟罕用到线程数据回调,在 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 这个变量的解决妙用即可!