【视频处理工程】7、一个基于LavFilter的对话框视频播放器

在实现了利用控制台程序播放指定视频文件之后,接下来开始尝试编写一个带有界面的视频播放器,可以选择播放的视频,控制音量等更多的功能。为简单起见,界面的框架用MFC实现。

1、建立工程,生成默认界面

这一步很简单,打开Visual Studio 2010,选择MFC Application,选择基于对话框的工程然后一路next就完成了,没有任何需要更改的地方。之后可以编译运行,生成一个默认的对话框。对工程进行与DirectShow相关的设置,具体方法参考这里

2、添加播放器内核类

将上文中的DirectShowApi.h+.cpp文件添加到新的工程中,代码如下。

//DirectShowApi.h
#pragma once
#include <DShow.h>
#include <assert.h>

HRESULT AddToRot(IUnknown *pUnkGraph, DWORD *pdwRegister);
void RemoveFromRot(DWORD pdwRegister);
HRESULT AddFilterByCLSID(IGraphBuilder *pGraph, const GUID& clsid, LPCWCHAR wszName, IBaseFilter **ppF);
HRESULT GetUnconectedPin(IBaseFilter *pFilter, PIN_DIRECTION PinDir, IPin **ppPin);
HRESULT ConnectFilters(IGraphBuilder *pGraph, IPin *pOut, IBaseFilter *pDest);
HRESULT ConnectFilters( IGraphBuilder *pGraph, IBaseFilter *pSrc, IBaseFilter *pDest);

//DirectShowApi.cpp
#include "stdafx.h"
#include "DirectShowAPI.h"

HRESULT AddToRot(IUnknown *pUnkGraph, DWORD *pdwRegister)
{
	IMoniker * pMoniker = NULL;
	IRunningObjectTable *pROT = NULL;

	if (FAILED(GetRunningObjectTable(0, &pROT)))
	{
		return E_FAIL;
	}

	const size_t STRING_LENGTH = 256;

	WCHAR wsz[STRING_LENGTH];

	StringCchPrintfW(
		wsz, STRING_LENGTH,
		L"FilterGraph %08x pid %08x",
		(DWORD_PTR)pUnkGraph,
		GetCurrentProcessId()
		);

	HRESULT hr = CreateItemMoniker(L"!", wsz, &pMoniker);
	if (SUCCEEDED(hr))
	{
		hr = pROT->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, pUnkGraph,
			pMoniker, pdwRegister);
		pMoniker->Release();
	}
	pROT->Release();

	return hr;
}

void RemoveFromRot(DWORD pdwRegister)
{
	IRunningObjectTable *pROT = NULL;
	if(SUCCEEDED(GetRunningObjectTable(0,&pROT)))
	{
		pROT->Revoke(pdwRegister);
		pROT->Release();
	}
}

HRESULT AddFilterByCLSID( IGraphBuilder *pGraph, const GUID& clsid, LPCWCHAR wszName, IBaseFilter **ppF )
{
	if (!pGraph || !ppF)
		return E_POINTER;
	*ppF = 0;
	IBaseFilter *pF = 0;
	HRESULT hr = CoCreateInstance(clsid, 0, CLSCTX_INPROC_SERVER, IID_IBaseFilter, reinterpret_cast<void**>(&pF));
	if (SUCCEEDED(hr))
	{
		hr = pGraph->AddFilter(pF, wszName);
		if (SUCCEEDED(hr))
			*ppF = pF;
		else
			pF->Release();
	}
	return hr;
}

HRESULT GetUnconectedPin( IBaseFilter *pFilter, PIN_DIRECTION PinDir, IPin **ppPin )
{
	*ppPin = 0;
	IEnumPins *pEnum = 0;
	IPin *pPin = 0;

	HRESULT hr = pFilter->EnumPins(&pEnum);
	if (FAILED(hr))
	{
		return hr;
	}
	hr = pEnum->Reset();
	while (pEnum->Next(1,&pPin,NULL) == S_OK)
	{
		PIN_DIRECTION ThisPinDirection;
		pPin->QueryDirection(&ThisPinDirection);
		if (ThisPinDirection == PinDir)
		{
			IPin *pTemp = 0;
			hr = pPin->ConnectedTo(&pTemp);
			if (SUCCEEDED(hr))
			{
				//当前pin已经连接,无效;
				pTemp->Release();
			}
			else
			{
				pEnum->Release();
				*ppPin = pPin;
				return S_OK;
			}
		}
		pPin->Release();
	}
	pEnum->Release();
	return E_FAIL;
}

HRESULT ConnectFilters( IGraphBuilder *pGraph, IPin *pOut, IBaseFilter *pDest )
{
	if ((pGraph == NULL)||(pOut == NULL)||(pDest == NULL))
		return E_POINTER;

#ifdef _DEBUG
	PIN_DIRECTION PinDir;
	pOut->QueryDirection(&PinDir);
	assert(PinDir == PINDIR_OUTPUT);
#endif // _DEBUG

	//得到下级filter的输入pin
	IPin *pIn = 0;
	HRESULT hr = GetUnconectedPin(pDest,PINDIR_INPUT,&pIn);
	if (FAILED(hr))
		return hr;

	hr = pGraph->Connect(pOut,pIn);
	pIn->Release();
	return hr;
}

HRESULT ConnectFilters( IGraphBuilder *pGraph, IBaseFilter *pSrc, IBaseFilter *pDest)
{
	if ((pGraph == NULL)||(pSrc == NULL)||(pDest == NULL))
		return E_POINTER;

	IPin *pOut = 0;
	HRESULT hr = GetUnconectedPin(pSrc,PINDIR_OUTPUT,&pOut);
	if (FAILED(hr))
		return hr;
	hr = ConnectFilters(pGraph,pOut,pDest);
	pOut->Release();
	return hr;
}

在工程中添加类CFilterGraph,并在定义类的头文件中添加跟Filter Graph相关的接口成员变量;随后定义创建和删除Filter Graph的方法,代码如下:

定义:

//Filter Graph.h
#pragma once
#include "DirectShowApi.h"

class CFilterGraph
{
public:
	CFilterGraph(void);
	~CFilterGraph(void);

public:

	//DirectShow相关接口成员
	IGraphBuilder	*pGraph;		//滤波器链表管理器
	IMediaControl	*pMediaControl;	//媒体控制接口,如run、stop、pause
	IMediaEventEx	*pMediaEvent;	//媒体事件接口
	IBasicVideo		*pBasicVideo;	//视频基本接口
	IBasicAudio		*pBasicAudio;	//音频基本接口
	IVideoWindow	*pVideoWindow;	//视频窗口接口

	DWORD			m_dwGraphRegister;

public:
	//CFilterGraph API
	virtual bool Create(void);		//生成滤波器链表管理器
 	virtual void Release(void);		//释放所有接口

private:
	//内部方法
	bool QueryInterfaces(void);
};

实现:

//Filter Graph.cpp
#include "StdAfx.h"
#include "FilterGraph.h"

CFilterGraph::CFilterGraph(void)
{
}

CFilterGraph::~CFilterGraph(void)
{
}

bool CFilterGraph::Create( void )
{
	if (!pGraph)
	{
		if (SUCCEEDED(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pGraph)))
		{
			::AddToRot(pGraph,&m_dwGraphRegister);
			return QueryInterfaces();
		}
	}

	return false;
}

bool CFilterGraph::QueryInterfaces( void )
{
	if (pGraph)
	{
		HRESULT hr = NOERROR;
		hr |= pGraph->QueryInterface(IID_IMediaControl, (void **)&pMediaControl);
		hr |= pGraph->QueryInterface(IID_IMediaEventEx, (void **)&pMediaEvent);
		hr |= pGraph->QueryInterface(IID_IBasicVideo, (void **)&pBasicVideo);
		hr |= pGraph->QueryInterface(IID_IBasicAudio, (void **)&pBasicAudio);
		hr |= pGraph->QueryInterface(IID_IVideoWindow, (void **)&pVideoWindow);

		return SUCCEEDED(hr);
	}

	return false;
}

void CFilterGraph::Release( void )
{
	if (pMediaControl)
	{
		pMediaControl->Release();
		pMediaControl = NULL;
	}
	if (pMediaEvent)
	{
		pMediaEvent->Release();
		pMediaEvent = NULL;
	}
	if (pBasicVideo)
	{
		pBasicVideo->Release();
		pBasicVideo = NULL;
	}
	if (pBasicAudio)
	{
		pBasicAudio->Release();
		pBasicAudio = NULL;
	}
	if (pVideoWindow)
	{
		pVideoWindow->put_Visible(OAFALSE);
		pVideoWindow->put_MessageDrain((OAHWND)NULL);
		pVideoWindow->put_Owner(OAHWND(0));
		pVideoWindow->Release();
		pVideoWindow = NULL;
	}
	::RemoveFromRot(m_dwGraphRegister);
	if (pGraph)
	{
		pGraph->Release();
		pGraph = NULL;
	}
}

现在这个类还不完整,稍后将会把这个类补全。

3、简要设计对话框的界面

在MFC默认的对话框界面中,添加一个按键用于选择视频文件,以及一个标签用于显示选定的文件名,如下图所示。

为按键添加相应函数:

void CLavPlayerDlg::OnBnClickedButtonSelectfile()
{
	// TODO: Add your control notification handler code here
	CString strFormatFilter = _T("AVI file (*.avi)|*.avi|");
	strFormatFilter += _T("MPEG file (*.mpg;*.mpeg;*.mp4)|*.mpg;*.mpeg;*.mp4|");
	strFormatFilter += _T("HD file (*.mkv;*.ts)|*.mkv;*.ts|");
	strFormatFilter += _T("Audio file (*.mp3;*.aac)|*.mp3;*.aac|");
	strFormatFilter += _T("All file (*.*)|*.*|");

	CFileDialog dlg(TRUE,NULL,NULL,OFN_FILEMUSTEXIST | OFN_HIDEREADONLY,strFormatFilter,this);
	if (dlg.DoModal() == IDOK)
	{
		m_VideoFilePath = dlg.GetPathName();
		m_VideoFileName = GetFileTitleFromFileName(m_VideoFilePath,1);
		CreateGraph();
	}
}

CString CLavPlayerDlg::GetFileTitleFromFileName( CString FileName, bool Ext )
{
	int Where;
	Where = FileName.ReverseFind('\\');
	if (Where == -1)
		Where = FileName.ReverseFind('/');
	CString FileTitle = FileName.Right(FileName.GetLength() - 1 - Where);
	if (!Ext)
	{
		int Which = FileTitle.ReverseFind('.');
		if (Which != -1)
			FileTitle = FileTitle.Left(Which);
	}
	return FileTitle;
}

其中的m_VideoFilePath和m_VideoFileName是定义在dlg类中的两个cstring类的成员,用于接收选定视频的文件路径和文件名。CreateGraph()用于创建完整的filter graph。

void CLavPlayerDlg::CreateGraph()
{
	DestroyGraph();
	m_pFilterGraph = new CFilterGraph();
	if (m_pFilterGraph->Create())
	{
		if (m_pFilterGraph->RenderFile(m_VideoFilePath))
		{
			AfxMessageBox(_T("无法播放文件,因为文件已损坏或缺少解码器!"));
			return;
		}
//		m_pFilterGraph->SetDisplayWindow(m_videoWindow.GetSafeHwnd());
		m_pFilterGraph->SetNotifyWindow(this->GetSafeHwnd());
		m_pFilterGraph->Pause();
	}
}

void CLavPlayerDlg::DestroyGraph()
{
	if (m_pFilterGraph != NULL)
	{
		m_pFilterGraph->Stop();
		m_pFilterGraph->SetNotifyWindow(NULL);
		delete m_pFilterGraph;
		m_pFilterGraph = NULL;
	}
}

播放器核心类中添加了多个函数。如Stop、Pause、SetNotifyWindow、SetDisplayWindow、RenderFile等,分别用于停止视频播放、暂停视频播放、设置通知窗口、设置显示窗口、解析播放视频文件等。下面首先考虑实现RenderFile方法。

首先需要添加头文件定义lavfilter组件的clsid,并在播放器核心类中include这个头文件:

//CLSID.h
#pragma once
#include <InitGuid.h>

// {B98D13E7-55DB-4385-A33D-09FD1BA26338}
static const GUID CLSID_LavSplitter_Source =
{ 0xB98D13E7, 0x55DB, 0x4385, { 0xA3, 0x3D, 0x09, 0xFD, 0x1B, 0xA2, 0x63, 0x38 } };

//{EE30215D-164F-4A92-A4EB-9D4C13390F9F}
static const GUID CLSID_LavVideoDecoder =
{ 0xEE30215D, 0x164F, 0x4A92, { 0xA4, 0xEB, 0x9D, 0x4C, 0x13, 0x39, 0x0F, 0x9F } };

//{E8E73B6B-4CB3-44A4-BE99-4F7BCB96E491}
static const GUID CLSID_LavAudioDecoder =
{ 0xE8E73B6B, 0x4CB3, 0x44A4, { 0xBE, 0x99, 0x4F, 0x7B, 0xCB, 0x96, 0xE4, 0x91 } };

同时参考上篇文章中的控制台应用,在RenderFile函数中添加lavfilter的组件:

bool CFilterGraph::RenderFile( CString fileName )
{
	HRESULT hr = NOERROR;

	LPTSTR fileToPlay = fileName.GetBuffer();
	hr |= ::AddFilterByCLSID(pGraph,CLSID_LavSplitter_Source,L"Lav Splitter Source",&m_pLavSplitterSource);
	hr |= m_pLavSplitterSource->QueryInterface(IID_IFileSourceFilter,(void **)&m_pFileSourceFilter);
	hr |= m_pFileSourceFilter->Load(fileToPlay,NULL);
	fileName.ReleaseBuffer();

	hr |= AddFilterByCLSID(pGraph,CLSID_LavVideoDecoder,L"Lav Video Decoder",&m_pLavVideoDecoder);
	hr |= ConnectFilters(pGraph,m_pLavSplitterSource,m_pLavVideoDecoder);

	hr |= AddFilterByCLSID(pGraph,CLSID_LavAudioDecoder,L"Lav Audio Decoder",&m_pLavAudioDecoder);
	hr |= ConnectFilters(pGraph,m_pLavSplitterSource,m_pLavAudioDecoder);

	hr |= AddFilterByCLSID(pGraph,CLSID_VideoMixingRenderer9,L"Video Mixing Renderer-9",&m_pVideoRenderer);
	hr |= ConnectFilters(pGraph,m_pLavVideoDecoder,m_pVideoRenderer);

	hr |= AddFilterByCLSID(pGraph,CLSID_AudioRender,L"Audio render",&m_pAudioRender);
	hr |= ConnectFilters(pGraph,m_pLavAudioDecoder,m_pAudioRender);

	return SUCCEEDED(hr);
}

同时在界面上新增加一个按钮,命名为“播放”,初始显示设置为FALSE,在其响应函数中调用播放内核类的run方法,使Filter Graph Manager开始运行:

void CLavPlayerDlg::OnBnClickedBtnPlay()
{
	// TODO: Add your control notification handler code here
	m_pFilterGraph->Run();
}
bool CFilterGraph::Run( void )
{
	if (pGraph && pMediaControl)
	{
		if (!IsRunning())
		{
			if (SUCCEEDED(pMediaControl->Run()))
			{
				return true;
			}
		}
		else
		{
			return true;
		}
	}
	return false;
}

bool CFilterGraph::IsRunning( void )
{
	if (pGraph && pMediaControl)
	{
		OAFilterState state = State_Stopped;
		if (SUCCEEDED(pMediaControl->GetState(10, &state)))
		{
			return state == State_Running;
		}
	}
	return false;
}

除此之外还需要在CreateGraph中添加Label显示文件名,以及显示播放按钮的功能,完成后效果如下:

void CLavPlayerDlg::CreateGraph()
{
	DestroyGraph();
	m_pFilterGraph = new CFilterGraph();
	if (m_pFilterGraph->Create())
	{
		if (m_pFilterGraph->RenderFile(m_VideoFilePath) == false)
		{
			AfxMessageBox(_T("无法播放文件,因为文件已损坏或缺少解码器!"));
			return;
		}
		m_pFilterGraph->SetNotifyWindow(this->GetSafeHwnd());
		m_pFilterGraph->Pause();
		GetDlgItem(IDC_VIDEOFILE)->SetWindowText(m_VideoFileName);
		GetDlgItem(IDC_BTN_PLAY)->ShowWindow(TRUE);
	}
}

编译运行,初始界面如:

选择文件后,出现“播放”按钮:

播放之后的界面:

虽然视频的播放功能已经实现但是应用却还是十分的不完善(比如没有播放控制功能,没有音量调节等),还有很多漏洞,以后会在学习的过程中逐步改进。

本文DEMO下载请点击这里

时间: 2024-12-26 04:25:15

【视频处理工程】7、一个基于LavFilter的对话框视频播放器的相关文章

100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)【转】

转自:http://blog.csdn.net/leixiaohua1020/article/details/8652605 版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[-] 简介 流程图 simplest_ffmpeg_player标准版代码 simplest_ffmpeg_player_suSU版代码 结果 FFMPEG相关学习资料 补充问题     ===================================================== 最简单的

c++播放器-qt基于mplayer的多路视频播放器

问题描述 qt基于mplayer的多路视频播放器 主要功能是同步播放最多四路视频,也可以放大某一通道进行单路回放,如果单纯只为了播放就没有问题,但是在多路回放的时候我要实时显示播放进度以及进度条的跟进和进度条拉动进行视频的跳转不好实现,如果是单一通道播放就没问题,求大神指教,其他只要是基于c++的开发工具都可以,求条明路,如果可以的话,大神加我qq941290482或者留下您的QQ!

用C/C++开发基于VLC SDK的视频播放器

在windows系统如果开发万能播放器,一般都是基本DirectShow来开发,开发也很简单,但缺点也很多,一个文件格式是否能够播放完全取决于你 是否安装了正确的解析器和解码器,即使现在有了万能解器安装包也会出现很多问题,不过自从很多开源软件的出现,比如大名鼎鼎的ffmpeg,vlc等,一 切都变得简单起来,基于ffmpeg sdk开发就不多介绍了,本文主要介绍基于 VLC SDK来开发自己的播放器,一切都是那么的简单.   1.先下载VLC player, http://www.videola

Ranger – 给命令行用户一个基于文本的文件管理器

图形文件管理器是每个人日常电脑工作的一部分.多数用户都乐于使用默认的文件管理器,并且没有那么多困扰让他们去探索替代的文件管理器.但是,当使用命令行(CLI)的文件管理器,用户在找到一个最好的之前,可能有兴趣尝试各种可用的文件管理器,以适合他们的需求.在这篇文章中,我们将来看看Ranger,一个基于命令行的文件管理器. ranger-main  注释:这篇文章中的所有示例和使用说明在ubuntu13.04上已通过测试. Ranger - 命令行文件管理器 Ranger是一个基于ncurses库的命

用JSP实现基于Web的RSS阅读器

js|rss|web 一: RSS介绍 根据维基百科(http://zh.wikipedia.org/wiki/RSS)的定义,"RSS是一种用于共享新闻和其他Web内容的数据交换规范 ",它是一系列的规范的组合,采用XML格式.目前国内RSS应用最多的是在新闻网站和博客网站上. 许多网站可以用RSS阅读器来个性化自己的网页,比如显示最新的新浪新闻,显示自己好朋友最新的博客文章,显示最新的Google论坛内容.除此之外,利用RSS阅读器还可以实现其它用途,比如: 获得天气预报 接收邮件

Android基于Service的音乐播放器_Android

本文开发一个基于Service的音乐播放器,音乐由后台运行的Service负责播放,当后台的播放状态发生变化时,程序将会通过发送广播通知前台Activity更新界面:当点击Activity的界面按钮时,系统将通过发送广播通知后台Service来改变播放状态. 前台Activity界面有两个按钮,分别用于控制播放/暂停.停止,另外还有两个文本框,用于显示正在播放的歌曲名.歌手名.前台Activity的代码如下: public class MainActivity extends AppCompat

Android基于Service的音乐播放器

本文开发一个基于Service的音乐播放器,音乐由后台运行的Service负责播放,当后台的播放状态发生变化时,程序将会通过发送广播通知前台Activity更新界面:当点击Activity的界面按钮时,系统将通过发送广播通知后台Service来改变播放状态. 前台Activity界面有两个按钮,分别用于控制播放/暂停.停止,另外还有两个文本框,用于显示正在播放的歌曲名.歌手名.前台Activity的代码如下: public class MainActivity extends AppCompat

linux-想做一个基于图像对比视频监控的监控系统应该看哪些书籍或者推荐学习什么?

问题描述 想做一个基于图像对比视频监控的监控系统应该看哪些书籍或者推荐学习什么? 基于mini2440来做的,主要是通过图像对比看当前采集到的数据和之前的有没有变化,来决定采集到的数据要不要存储.

设计一个基于CSS的网页模板

css|模板|设计|网页 这是一个教你如何一步一步学习建立基于CSS制作网站的开始,这个教程将由几个部分组成.第一部分是讲述如何在photoshop中制作导航按扭的:第二部分将讲述背景的制作,再下一个是讲述标题(header)和页面的设计规划的,在最后是CSS和XHTML的应用的执行.现在也许有些人想知道为什么在我的教程里要以导航按扭的制作来开始,呵呵,其实我最初的目的是要讲述一段关于这些简单按扭的制作方法的小教程的,但是即然这个想法开始了,为什么不做一个全面的讲解呢!建立一个像玻璃面一样的导航