本篇文章自己实现了DirectShow的渲染filter来播放视频,我们设定为只接收RGB32格式的数据,DirectShow会自动帮我们解码、转换颜色格式,只需要拷贝到bitmap里再用GDI显示就行了
使用DirectShow前需要安装解码器,否则只能解码avi、wmv等少数格式,推荐LVAFilters,这是基于FFMPEG的解码器
使用了DirectShow samples里的基类,已经编译好放在baseclasses目录了,参考微软的文档:CBaseRenderer class
Decoder.h:
#pragma once
#include <streams.h>
#include <functional>
class CDecoder : private CBaseVideoRenderer
{
public:
CDecoder(LPCWSTR fileName, HRESULT* phr);
virtual ~CDecoder() = default;
virtual void RunVideo();
virtual void PauseVideo();
virtual void StopVideo();
virtual void GetVideoSize(SIZE& size);
// 设置需要呈现时的回调函数
virtual void SetOnPresent(std::function<void(IMediaSample*)>&& onPresent);
protected:
CComPtr<IGraphBuilder> m_graph;
CComPtr<IMediaControl> m_control;
CComPtr<IBaseFilter> m_source;
SIZE m_videoSize;
// 需要呈现时被调用
std::function<void(IMediaSample*)> m_onPresent;
// CBaseVideoRenderer
private:
HRESULT DoRenderSample(IMediaSample *pMediaSample);
HRESULT CheckMediaType(const CMediaType *);
};
Decoder.cpp:
#include "stdafx.h"
#include "Decoder.h"
CDecoder::CDecoder(LPCWSTR fileName, HRESULT* phr) :
CBaseVideoRenderer(CLSID_NULL, _T("Renderer"), NULL, &(*phr = NOERROR))
{
AddRef(); // 防止多次析构
*phr = m_graph.CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER);
*phr = m_graph.QueryInterface(&m_control);
*phr = m_graph->AddFilter(this, L"Renderer");
*phr = m_graph->AddSourceFilter(fileName, L"Source", &m_source);
// 连接源和渲染器
CComPtr<IEnumPins> enumPins;
CComPtr<IPin> sourcePin;
CBasePin* rendererPin = GetPin(0);
PIN_DIRECTION pinDir;
m_source->EnumPins(&enumPins);
while (enumPins->Next(1, &sourcePin, NULL) == S_OK)
{
sourcePin->QueryDirection(&pinDir);
if (pinDir == PINDIR_OUTPUT)
break;
sourcePin.Release();
}
enumPins.Release();
*phr = m_graph->Connect(sourcePin, rendererPin);
// 获取视频尺寸
CMediaType mediaType;
*phr = rendererPin->ConnectionMediaType(&mediaType);
if (mediaType.formattype == FORMAT_VideoInfo)
{
VIDEOINFOHEADER* info = (VIDEOINFOHEADER*)mediaType.pbFormat;
m_videoSize.cx = info->bmiHeader.biWidth;
m_videoSize.cy = info->bmiHeader.biHeight;
}
else
{
TRACE(_T("未知的FormatType\n"));
m_videoSize.cx = 800;
m_videoSize.cy = 600;
}
}
void CDecoder::RunVideo()
{
TRACE(_T("Run\n"));
HRESULT hr = m_control->Run();
}
void CDecoder::PauseVideo()
{
TRACE(_T("Pause\n"));
HRESULT hr = m_control->Pause();
}
void CDecoder::StopVideo()
{
TRACE(_T("Stop\n"));
HRESULT hr = m_control->Stop();
}
void CDecoder::GetVideoSize(SIZE& size)
{
size = m_videoSize;
}
void CDecoder::SetOnPresent(std::function<void(IMediaSample*)>&& onPresent)
{
m_onPresent = std::move(onPresent);
}
// CBaseVideoRenderer
HRESULT CDecoder::CheckMediaType(const CMediaType * mediaType)
{
if (mediaType->majortype == MEDIATYPE_Video && mediaType->subtype == MEDIASUBTYPE_RGB32)
return S_OK;
return VFW_E_INVALIDMEDIATYPE;
}
HRESULT CDecoder::DoRenderSample(IMediaSample *pMediaSample)
{
if (!m_onPresent._Empty())
m_onPresent(pMediaSample);
return S_OK;
}
使用方法:
HRESULT hr;
m_decoder = new CDecoder(L"E:\\pump it.avi", &hr);
m_decoder->SetOnPresent(std::bind(&CRenderFilterGDIDlg::OnPresent, this, std::placeholders::_1));
m_decoder->GetVideoSize(m_videoSize);
m_dc.Create(m_videoSize.cx, m_videoSize.cy, 32);
m_decoder->RunVideo();
// ......
void CRenderFilterGDIDlg::OnPresent(IMediaSample* mediaSample)
{
BYTE* sampleBuf = NULL;
if (FAILED(mediaSample->GetPointer(&sampleBuf)))
return;
// 假设每行之间没有多余的字节
ASSERT(mediaSample->GetActualDataLength() == m_videoSize.cx * m_videoSize.cy * 4);
m_dcLock.Lock();
// RGB位图都是从下到上储存的
memcpy(m_dc.GetPixelAddress(0, m_videoSize.cy - 1), sampleBuf, m_videoSize.cx * m_videoSize.cy * 4);
m_dcLock.Unlock();
Invalidate(FALSE);
}