使用自己实现的Renderer Filter+GDI解码和播放视频

完整源码

本篇文章自己实现了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);
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值