DirectShow播放器(LAVFilter + EVR)开发例子

LAVFilter是一套著名的DirectShow插件,包括Demux,Video Decoder,AudioDecoder,播放文件所需要的几个重要插件都包含进去了,并且支持播放的视音频格式非常广泛,FFmpeg支持的它几乎都支持(因为它底层是调用FFmpeg)。LAVFilter是我们开发Directshow播放器必不可少的插件,值的一提的是它既支持软解又支持硬解,功能非常强大。在Vista以上系统硬解的时候渲染器必须要用EVR,很多关于directshow播放器的例子都是解码器连接VMR进行播放的,连接EVR的很少介绍,这篇文章就给大家讲一下怎么用LAV + EVR来播放视频。

假如我们要播放一个视频文件,那么用Directshow需要构建一个Filter链路图,以下就是播放一个文件的链路图例子:

DirectShow播放器(LAVFilter + EVR)开发例子_第1张图片

上图连接的渲染器是Video Renderer(VMR),但是如果要切换到硬解模式,则连接的渲染器要改成EVR(enhanced video renderer)。

DirectShow播放器(LAVFilter + EVR)开发例子_第2张图片

我写了一个类,封装了构建和运行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

你可能感兴趣的:(directshow)