LAVFilter是一套著名的DirectShow插件,包括Demux,Video Decoder,AudioDecoder,播放文件所需要的几个重要插件都包含进去了,并且支持播放的视音频格式非常广泛,FFmpeg支持的它几乎都支持(因为它底层是调用FFmpeg)。LAVFilter是我们开发Directshow播放器必不可少的插件,值的一提的是它既支持软解又支持硬解,功能非常强大。在Vista以上系统硬解的时候渲染器必须要用EVR,很多关于directshow播放器的例子都是解码器连接VMR进行播放的,连接EVR的很少介绍,这篇文章就给大家讲一下怎么用LAV + EVR来播放视频。
假如我们要播放一个视频文件,那么用Directshow需要构建一个Filter链路图,以下就是播放一个文件的链路图例子:
上图连接的渲染器是Video Renderer(VMR),但是如果要切换到硬解模式,则连接的渲染器要改成EVR(enhanced video renderer)。
我写了一个类,封装了构建和运行DirectShow FilterGraph的一些流程,下面是类的声明:
// CHDVideoPlayGraph
class CHDVideoPlayGraph : public CWnd
{
DECLARE_DYNAMIC(CHDVideoPlayGraph)
public:
CHDVideoPlayGraph();
virtual ~CHDVideoPlayGraph();
void SetNum(int num) { m_Num = num; }
HRESULT BuildGraph(LPCTSTR lpszSrcFile);
void StopCapture();
BOOL GetVideoSize(CSize & size);
PLAYSTATE GetState() { return m_psCurrent; }
BOOL GetVideoStatis(DWORD & dwBitrate, UINT & dwFps);
void UnIntializeVideo();
HRESULT InitializeVideo(HWND hWnd);
HRESULT SetupVideoWindow(HWND hVideoWnd);
void ResizeVideoWindow();
DWORD GetCurrentBitrate();
void SetVideoMediatype(AM_MEDIA_TYPE * pMt);
void SetAudioMediatype(AM_MEDIA_TYPE * pmt);
void SetCaptureCallback(VideoCaptureCB captureCB) { m_CaptureCB = captureCB; }
void CheckRgb24Buffer();
void OnRecvVideo(int nNum,PBYTE pBuffer,long BufferLen); //被回调函数调用
BOOL HasAudioStream() { return m_bHasAudio; }
void SetAudioStream(BOOL bEnable) { m_bHasAudio = bEnable; }
//void SetVideoRecvPort(int port);
LRESULT OnRestartPlaying(WPARAM wParam, LPARAM lParam);
void SetHarewareDecode(BOOL bFlag);
void SetVMRMode(int nVMR);
int GetFrameRate();
protected:
DECLARE_MESSAGE_MAP()
afx_msg HRESULT OnGraphNotify(WPARAM wp, LPARAM lp);
afx_msg void OnPaint();
void DisplayMesg(TCHAR* szFormat, ...);
LRESULT ClearInterfaces(WPARAM wp, LPARAM lp);
void CloseInterfaces();
HRESULT AddGraphToRot(IUnknown* pUnkGraph, DWORD* pdwRegister);
void RemoveGraphFromRot(DWORD pdwRegister);
HRESULT HandleGraphEvent();
HRESULT GetInterfaces();
HRESULT SetSyncClock();
void GetSyncClock();
HRESULT RenderFilter(IBaseFilter * pFilter);
private:
UINT chFullScreen, chAlwaysOnTop;
UINT m_Num;
CBrush m_emptyBrush;
DWORD m_dwGraphRegister;
HWND m_hApp;
IVideoWindow* m_pVW;
IMediaControl* m_pMC;
IMediaEventEx* m_pME;
IGraphBuilder* m_pGraph;
ICaptureGraphBuilder2 *m_pCapture;
VideoCaptureCB m_CaptureCB;
IBaseFilter * m_pRenderer;
IBaseFilter * m_pNullFilter;
IBaseFilter * m_pVideoDec;
IBaseFilter * m_pAudioDec;
PLAYSTATE m_psCurrent;
BOOL m_bHasAudio;
AM_MEDIA_TYPE m_AudioMediaType;
AM_MEDIA_TYPE m_VideoMediaType;
ULONG m_nWidth, m_nHeight;
PBYTE m_pRgb24;
TCHAR m_szFile[256];
BOOL m_bHarewareDecode; //1--硬解码, 0--软解码
INT m_nVMRRenderer; ///1--VMR7, 2--VMR9,3--EVR
DWORD m_dwStartTick;
};
我们首先要在代码中定义要引用到的几个Filter的CLSID,包括LAV和EVR的:
DEFINE_GUID(CLSID_EnhancedVideoRenderer,
0xFA10746C, 0x9B63, 0x4B6C, 0xBC, 0x49, 0xFC, 0x30, 0x0E, 0xA5, 0xF2, 0x56);
DEFINE_GUID(CLSID_LAVSplitter,
0x171252A0, 0x8820, 0x4AFE, 0x9D, 0xF8, 0x5C, 0x92, 0xB2, 0xD6, 0x6B, 0x04);
DEFINE_GUID(CLSID_LAVSource,
0xB98D13E7, 0x55DB, 0x4385, 0xA3, 0x3D, 0x09, 0xFD, 0x1B, 0xA2, 0x63, 0x38);
DEFINE_GUID(CLSID_LAVVideoDec,
0xEE30215D, 0x164F, 0x4A92, 0xA4, 0xEB, 0x9D, 0x4C, 0x13, 0x39, 0x0F, 0x9F);
DEFINE_GUID(CLSID_LAVAudioDec,
0xE8E73B6B, 0x4CB3, 0x44A4, 0xBE, 0x99, 0x4F, 0x7B, 0xCB, 0x96, 0xE4, 0x91);
为了支持软解和硬解,并且能连接不同的渲染器Renderer,我在类声明里定义了两个变量:一个是解码模式,另外一个是连接的渲染器类型。方便对不同的模式进行切换。
BOOL m_bHarewareDecode; //1--硬解码, 0--软解码
INT m_nVMRRenderer; ////0--GDI, 1--VMR7, 2--VMR9
接着,我们看一下怎么构建DirectShow链接图:
HRESULT CHDVideoPlayGraph::BuildGraph(LPCTSTR lpszSrcFile)
{
HRESULT hr;
USES_CONVERSION;
CFileFind find;
if(find.FindFile(lpszSrcFile) == FALSE)
{
AfxMessageBox("Can not Find File");
return E_FAIL;
}
_tcscpy(m_szFile, lpszSrcFile);
// Get DirectShow interfaces
hr = GetInterfaces();
if (FAILED(hr))
{
DisplayMesg(TEXT("Failed to get video interfaces! hr=0x%x"), hr);
return hr;
}
BOOL bIsWin7 = FALSE;
TCHAR sRC[64] = {0};
OSVERSIONINFO versionInfo;
versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
if(::GetVersionEx(&versionInfo))
{
switch(versionInfo.dwPlatformId)
{
case VER_PLATFORM_WIN32_WINDOWS:
if(versionInfo.dwMinorVersion >= 0 && versionInfo.dwMinorVersion <= 9 )
{
_tcscpy( sRC, _T("95"));
}
else if(versionInfo.dwMinorVersion >= 10 && versionInfo.dwMinorVersion <= 89 )
{
_tcscpy(sRC, _T("98"));
}
else if(versionInfo.dwMinorVersion == 90 )
{
_tcscpy(sRC, _T("Me"));
}
break;
case VER_PLATFORM_WIN32_NT:
if(versionInfo.dwMajorVersion == 4)
{
}
else if(versionInfo.dwMajorVersion == 5)
{
switch(versionInfo.dwMinorVersion)
{
case 0:
_tcscpy(sRC, _T("2000"));
break;
case 1:
_tcscpy(sRC, _T("XP"));
break;
case 2:
_tcscpy(sRC, _T("2003"));
break;
}//switch
}// else if(versionInfo.dwMajorVersion == 5)
else
{
_tcscpy(sRC, _T("Windows7"));
bIsWin7 = TRUE;
}
break;
default:
break;
} //switch
}
else
{
OutputDebugString("GetVersionEx Errro!\n");
}
char szSysInfo[100] = {0};
sprintf(szSysInfo, "OSType is: %s \n", sRC);
OutputDebugString(szSysInfo);
CComPtr pSrcBaseFilter;
hr = ::AddFilterByCLSID(m_pGraph, CLSID_LAVSource, L"LAVSource Filter", &pSrcBaseFilter);
if (FAILED(hr))
{
MessageBox("Add LAVSource Filter Failed", "Error");
return hr;
}
IFileSourceFilter * pFileSource = NULL;
hr = pSrcBaseFilter->QueryInterface(IID_IFileSourceFilter, (void**)&pFileSource);
if(pFileSource)
{
hr = pFileSource->Load(A2W(m_szFile), NULL);
pFileSource->Release();
}
if(FAILED(hr))
{
MessageBox("Load File Failed", "Error");
return hr;
}
//Add Video Decode filter
hr = ::AddFilterByCLSID(m_pGraph, CLSID_LAVVideoDec, L"LAVVideo Decoder", &m_pVideoDec);
if (FAILED(hr))
{
return hr;
}
ILAVVideoSettings * pLAVSetting = NULL;
hr = m_pVideoDec->QueryInterface(__uuidof(ILAVVideoSettings), (void**)&pLAVSetting);
if(SUCCEEDED(hr))
{
pLAVSetting->SetHWAccel(m_bHarewareDecode ? HWAccel_DXVA2Native : HWAccel_None);
TRACE("SetHWAccel HW: %d \n", m_bHarewareDecode);
pLAVSetting->Release();
}
if(bIsWin7)
{
CComPtr m_spDisplayCtrl;
CComPtr pGetService;
//Add Enhanced Video Renderer
hr = ::AddFilterByCLSID(m_pGraph, CLSID_EnhancedVideoRenderer, L"Enhanced Video Renderer ", &m_pRenderer);
if(FAILED(hr))
{
return hr;
}
hr = m_pRenderer->QueryInterface((__uuidof(IMFGetService)),(VOID ** )&pGetService);
if(FAILED(hr))
{
return hr;
}
hr = pGetService->GetService(MR_VIDEO_RENDER_SERVICE,__uuidof(IMFVideoDisplayControl), (void **)&m_spDisplayCtrl);
RECT Rect;
::GetClientRect (m_hApp, &Rect);
m_spDisplayCtrl->SetVideoWindow (m_hApp);
m_spDisplayCtrl->SetAspectRatioMode(MFVideoARMode_None);
m_spDisplayCtrl->SetVideoPosition(NULL, &Rect);
}
else
{
//Add Video Renderer
if(m_nVMRRenderer == 0)
{
hr = ::AddFilterByCLSID(m_pGraph, CLSID_VideoRenderer, L"Video Renderer ", &m_pRenderer);
if(FAILED(hr))
{
return hr;
}
}
else if(m_nVMRRenderer == 1)
{
hr = ::AddFilterByCLSID(m_pGraph, CLSID_VideoMixingRenderer, L"Video Renderer ", &m_pRenderer);
if(FAILED(hr))
{
return hr;
}
}
else if(m_nVMRRenderer == 2)
{
hr = ::AddFilterByCLSID(m_pGraph, CLSID_VideoMixingRenderer9, L"Video Renderer ", &m_pRenderer);
if(FAILED(hr))
{
return hr;
}
}
hr = m_pGraph->QueryInterface(IID_IVideoWindow, (LPVOID *) &m_pVW);
if (FAILED(hr))
return hr;
}
hr = RenderFilter(pSrcBaseFilter);
if (FAILED(hr))
{
OutputDebugString(_T("RenderFilter Failed. \n"));
return hr;
}
hr = SetupVideoWindow(m_hWnd);
//SetSyncClock();
IPin * pRenderInputPin = NULL;
pRenderInputPin = GetInPin(m_pRenderer, 0);
if(pRenderInputPin == NULL)
return E_FAIL;
AM_MEDIA_TYPE amType;
AM_MEDIA_TYPE * pmt = &amType;
hr = pRenderInputPin->ConnectionMediaType(pmt);
if(SUCCEEDED(hr))
{
if(pmt->formattype == FORMAT_VideoInfo)
{
VIDEOINFOHEADER * pVidHdr = (VIDEOINFOHEADER*) pmt->pbFormat;
m_nWidth = pVidHdr->bmiHeader.biWidth;
m_nHeight = pVidHdr->bmiHeader.biHeight;
}
else if(pmt->formattype == FORMAT_VideoInfo2)
{
VIDEOINFOHEADER2 * pVidHdr = (VIDEOINFOHEADER2*) pmt->pbFormat;
m_nWidth = pVidHdr->bmiHeader.biWidth;
m_nHeight = pVidHdr->bmiHeader.biHeight;
}
else if (pmt->formattype == FORMAT_MPEGVideo)
{
MPEG1VIDEOINFO *pVidHdr = (MPEG1VIDEOINFO *) pmt->pbFormat;
m_nWidth = pVidHdr->hdr.bmiHeader.biWidth;
m_nHeight = pVidHdr->hdr.bmiHeader.biHeight;
}
else if (pmt->formattype == FORMAT_MPEG2Video)
{
MPEG2VIDEOINFO *pVidHdr = (MPEG2VIDEOINFO *) pmt->pbFormat;
m_nWidth = pVidHdr->hdr.bmiHeader.biWidth;
m_nHeight = pVidHdr->hdr.bmiHeader.biHeight;
}
else
{
}
FreeMediaType(amType);
}
#ifdef REGISTER_FILTERGRAPH
// Add our graph to the running object table, which will allow
// the GraphEdit application to "spy" on our graph
hr = AddGraphToRot(m_pGraph, &m_dwGraphRegister);
if (FAILED(hr))
{
DisplayMesg(TEXT("Failed to register filter graph with ROT! hr=0x%x"), hr);
m_dwGraphRegister = 0;
}
#endif
// Start previewing video data
hr = m_pMC->Run();
if (FAILED(hr))
{
DisplayMesg(TEXT("Couldn't run the graph! hr=0x%x"), hr);
return hr;
}
// Remember current state
m_psCurrent = RUNNING;
m_dwStartTick = GetTickCount();
return S_OK;
}
上面的BuildGraph函数,首先调用GetInterfaces接口获取Directshow组件的一些接口:
HRESULT CHDVideoPlayGraph::GetInterfaces()
{
HRESULT hr;
if(m_pGraph == NULL){
// Create the filter graph
hr = CoCreateInstance (CLSID_FilterGraph, NULL, CLSCTX_INPROC,
IID_IGraphBuilder, (void **) &m_pGraph);
if (FAILED(hr))
{
TRACE("CoCreateInstance() failed \n");
return hr;
}
}
else
return E_FAIL ;
//hr = m_pGraph->QueryInterface(IID_IVideoWindow, (LPVOID *) &m_pVW); //使用VMR时需要获取该接口进行视频窗口控制,但EVR不需要获取该接口
// if (FAILED(hr))
// return hr;
// Obtain interfaces for media control and Video Window
hr = m_pGraph->QueryInterface(IID_IMediaControl,(LPVOID *) &m_pMC);
if (FAILED(hr))
return hr;
hr = m_pGraph->QueryInterface(IID_IMediaEvent, (LPVOID *) &m_pME);
if (FAILED(hr))
return hr;
// Set the window handle used to process graph events
hr = m_pME->SetNotifyWindow((OAHWND)m_hApp, WM_GRAPHNOTIFY, 0);
//m_pME->CancelDefaultHandling(EC_DISPLAY_CHANGED);
return hr;
}
然后,依次把LAV Source Filter,LAV Video Decode Filter加进去。代码中会根据系统版本决定优先插入哪个Video Renderer,如果是Win7以上,则插入EVR;其他系统版本则根据m_nVMRRenderer的类型值来选择对应的Renderer(VMR7,VMR9)。
接着,调用RenderFilter自动将上面的Filter连接起来(可能还会加入其他Filter)。
HRESULT CHDVideoPlayGraph:: RenderFilter(IBaseFilter * pFilter)
{
CComPtr< IEnumPins > pEnum;
if (!pFilter)
return E_POINTER;
HRESULT hr = pFilter->EnumPins(&pEnum);
if(FAILED(hr))
return hr;
ULONG ulFound;
IPin *pPin;
hr = E_FAIL;
int nOutputPinCount = 0;
int nRenderedCount = 0;
while(S_OK == pEnum->Next(1, &pPin, &ulFound))
{
PIN_DIRECTION pindir = (PIN_DIRECTION)3;
pPin->QueryDirection(&pindir);
if(pindir == PINDIR_OUTPUT)
{
IPin * pPin2 = NULL;
pPin->ConnectedTo(&pPin2);
if(pPin2)
{
//pPin->Disconnect(); //先断开连接
pPin2->Release();
pPin2 = NULL;
}
else
{
hr = m_pGraph->Render(pPin); //自动连接Output Pin的下一级链路
if(SUCCEEDED(hr))
nRenderedCount++;
}
nOutputPinCount++;
}
pPin->Release();
}
return nRenderedCount > 0 ? (nOutputPinCount == nRenderedCount ? S_OK : S_FALSE) : E_FAIL;
}
例子代码链接:https://download.csdn.net/download/zhoubotong2012/11874281