ffdshow神奇的功能:视频播放时显示运动矢量和QP
FFDShow可以称得上是全能的解码、编码器.最初FFDShow只是mpeg视频解码器,不过现在他能做到的远不止于此.它能够解码的视频格式已经远远超出了mpeg4的范围,包括indeo video,WMV,mpeg2等等.同时,它也提供了丰富的加工处理选项,可以锐化画面,调节画面的亮度等等.不止是视频,FFDShow现在同样可以解码音频,AC3、MP3等音频格式都可支持.并且可以外挂winamp 的DSP插件,来改善听觉效果.可以说现在的FFDShow已经是windows平台多媒体播放的非常出色的工具了。
FFDshow功能十分强大,我们只要打开它的设置界面就会发现,它有大量的视频滤镜等,配置页面如图所示。
我发现它竟然可以在视频播放时显示运动矢量和QP!很神奇
在左边栏中有一个是"可视化效果"(英文版的是"Visualization")
注意:我修改过FFDshow源代码,所以”可视化效果“里面多了两项,一般情况下为3项
勾选”运动矢量“之后的效果。可以发现视频激烈运动的地方有大量的箭头
勾选”量化“后的效果。可见每个宏块的量化值都被显示了出来
勾选”图示“后的效果。可见视频每一帧的帧大小都被画在视频的最下面
FFDshow直接让播放器变成码流分析软件了!
1 : 整体结构
ffdshow是一个非常强大的DirectShow解码器,封装了ffmpeg,libmpeg2等解码库。它也提供了丰富的加工处理选项,可以锐化画面,调节画面的亮度等等。不止是视频,FFDShow现在同样可以解码音频,AC3、MP3等音频格式都可支持。并且可以外挂winamp 的DSP插件,来改善听觉效果。一个词形容:强大。
因为项目的要求,需要对ffdshow进行二次开发,正好有这个机会,分析研究一下ffdshow的源代码。
ffdshow项目的资源可以从sourceforge下载。包括编译好的程序,以及原代码等,下载地址:
注意:sourceforge上有两个版本的ffdshow:ffdshow以及ffdshow-tryout。其中前一个版本很早之前已经停止开发了,因此我们需要选择后一个(ffdshow-tryout)。
下载源代码的方法不再赘述,下面直接进入正题。源代码下载后,需要进行编译,推荐使用源代码根目录下的bat脚本一次性完成所有的资源编译。
编译完成后我们就可以打开源代码根目录里的工程了。我自己的开发环境是VC2010,打开后工程如下图所示(解决方案的名字被我修改了= =):
由图可见,ffdshow由一大堆工程组成,乍一看给人一种杂论无章的感觉,其实大部分工程我们不用去理会,我们重点研究最重要的工程就是那个名字叫“ffdshow”的工程。
下面我介绍几个最重要的文件夹里包含的代码的功能:
audiofilters:音频滤镜都在这里面(例如EQ,调节高低频等)
baseclasses:微软自带directshow的sdk里面就有,主要是微软为了方便DirectShow开发而提供的一些基本的类
codecs:支持的解码器都在这里(例如libavcodec,libmpeg2等)
convert:色彩转换的一些功能(没太用过)
decss:解除版权加密的一些功能(没太用过)
dialog:音频视频滤镜的配置页面
doc:文档,不是程序
ffvfw:VFW相关(目前没太用过)
Header Files:核心代码的头文件
imgfilters:视频滤镜都在这里(显示QP/MV,加LOGO,显示视频信息等)
Resource Files:资源文件
settings:音频视频滤镜的配置信息
Source Files:核心代码的源文件
subtitles:字幕相关的功能
以上用红色标出的,是我们二次开发中最有可能会涉及到的三个部分。掌握了这三个部分,就可以往ffdhow中添加自己写的滤镜(注意:这里说的是视频滤镜,音频的方法是一样的)
黄色背景标出的部分,虽然我们可能不需要做出什么改变,但是为了了解ffdshow的架构,我们需要分析其中的代码。
2: 位图覆盖滤镜(对话框部分Dialog)
本文我们介绍ffdshow的滤镜功能。ffdshow支持很多种滤镜,可以支持多种视频和音频的后期效果。例如OSD滤镜支持在视频的左上角显示视频相关的信息。而可视化滤镜则支持显示视频每一帧的运动矢量以及量化参数。在这里我们介绍一种位图覆盖(Bitmap)滤镜(Filter)。
效果
编译完ffdshow之后,在“项目属性->调试->命令”里面将GraphEdit.exe所在位置设置为调试程序,例如在这里我设置了《终极解码》里面自带GraphEdit.exe,路径为“C:\Program Files\Final Codecs\Codecs\GraphEdit.exe”。这样就可以使用GraphEdit.exe调试ffdshow了。
向GraphEdit.exe里面拖入一个文件“五月天 咸鱼.mp4”,结果如下图所示:
注:有的时候默认的视频解码器可能不是ffdshow,可能是CoreAVC等,可以先删除视频解码器然后添加ffdshow。
点击绿色三角形按钮就可以开始播放视频。
右键点击ffdshow组件,打开属性对话框之后,可以看见右边栏中有很多的滤镜。
勾选“位图覆盖”滤镜,然后选择一张用于覆盖的图片(在这里我选择了一张bmp格式的专辑封面)。
注:可以调整位图所在的水平位置,垂直位置,不透明度,并且可以修改位图叠加模式(在这里用混合)。
添加了该滤镜之后,播放窗口的显示内容为:
可见在右上角显示出了叠加的位图。
源代码分析
1.对话框部分
与位图覆盖(Bitmap)滤镜的对话框有关的类位于dialog目录下的Cbitmap.cpp和Cbitmap.h文件中。
先来看看Cbitmap.h中类的声明:
需要注意的是,里面类的名字居然叫TbitmapPage,而没有和头文件名字一致。= =
#ifndef _CBITMAPPAGEH
#define _CBITMAPPAGEH
#include "TconfPageDecVideo.h"
//Bitmap配置页面
class TbitmapPage : public TconfPageDecVideo
{
private:
void pos2dlg(void), opacity2dlg(void);
//设置文件路径
void onFlnm(void);
protected:
virtual INT_PTR msgProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
public:
//构造函数
TbitmapPage(TffdshowPageDec Iparent, const TfilterIDFF idff);
//初始化
virtual void init(void);
//配置数据传入到对话框界面
virtual void cfg2dlg(void);
virtual void translate(void);
};
#endif
再看看Cbitmap.cpp文件吧。关键的代码都已经加上了注释。
/
Copyright (c) 2004-2006 Milan Cutka
This program is free software; you can redistribute //代码效果参考:http://www.zidongmutanji.com/zsjx/1459.html
it and/or modifyit under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
/
//Bitmap配置页面
#include "stdafx.h"
#include "TsubtitlesSettings.h"
#include "TbitmapSettings.h"
#include "Cbitmap.h"
//初始化
void TbitmapPage::init(void)
{
//设置滑动条范围
edLimitText(IDC_ED_BITMAP_FLNM, MAX_PATH);
tbrSetRange(IDC_TBR_BITMAP_POSX, 0, 100, 10);
tbrSetRange(IDC_TBR_BITMAP_POSY, 0, 100, 10);
tbrSetRange(IDC_TBR_BITMAP_OPACITY, 0, 256);
}
//配置数据传入到对话框界面
void TbitmapPage::cfg2dlg(void)
{
//各种设置
//EditControl设置
setDlgItemText(m_hwnd, IDC_ED_BITMAP_FLNM, cfgGetStr(IDFF_bitmapFlnm));
pos2dlg();
cbxSetCurSel(IDC_CBX_BITMAP_ALIGN, cfgGet(IDFF_bitmapAlign));
cbxSetCurSel(IDC_CBX_BITMAP_MODE, cfgGet(IDFF_bitmapMode));
opacity2dlg();
}
//Bitmap位置信息
void TbitmapPage::pos2dlg(void)
{
char_t s【260】;
int x;
//获取
x = cfgGet(IDFF_bitmapPosx);
TsubtitlesSettings::getPosHoriz(x, s, this, IDC_LBL_BITMAP_POSX, countof(s));
setDlgItemText(m_hwnd, IDC_LBL_BITMAP_POSX, s);
//设置
tbrSet(IDC_TBR_BITMAP_POSX, x);
x = cfgGet(IDFF_bitmapPosy);
TsubtitlesSettings::getPosVert(x, s, this, IDC_LBL_BITMAP_POSY, countof(s));
setDlgItemText(m_hwnd, IDC_LBL_BITMAP_POSY, s);
tbrSet(IDC_TBR_BITMAP_POSY, x);
}
void TbitmapPage::opacity2dlg(void)
{
int o = cfgGet(IDFF_bitmapStrength);
tbrSet(IDC_TBR_BITMAP_OPACITY, o);
setText(IDC_LBL_BITMAP_OPACITY, l("%s %i%%"), (IDC_LBL_BITMAP_OPACITY), 100 o / 256);
}
INT_PTR TbitmapPage::msgProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDC_ED_BITMAP_FLNM:
if (HIWORD(wParam) == EN_CHANGE !isSetWindowText) {
char_t flnm【MAX_PATH】;
GetDlgItemText(m_hwnd, IDC_ED_BITMAP_FLNM, flnm, MAX_PATH);
cfgSet(IDFF_bitmapFlnm, flnm);
}
return TRUE;
}
break;
}
return TconfPageDecVideo::msgProc(uMsg, wParam, lParam);
}
//设置文件路径
void TbitmapPage::onFlnm(void)
{
char_t flnm【MAX_PATH】;
cfgGet(IDFF_bitmapFlnm, flnm, MAX_PATH);
if (dlgGetFile(false, mhwnd, (-IDD_BITMAP, _l("Load image file")), _l("All supported (.jpg,.bmp,.gif,.png)\0.bmp;.jpg;.jpeg;.gif;.png\0Windows Bitmap (.bmp)\0.bmp\0JPEG (.jpg)\0.jpg\0Compuserve Graphics Interchange (.gif)\0.gif\0Portable Network Graphics (.png)\0.png"), _l("bmp"), flnm, _l("."), 0)) {
setDlgItemText(m_hwnd, IDC_ED_BITMAP_FLNM, flnm);
//设置
cfgSet(IDFF_bitmapFlnm, flnm);
}
}
void TbitmapPage::translate(void)
{
TconfPageBase::translate();
cbxTranslate(IDC_CBX_BITMAP_ALIGN, TsubtitlesSettings::alignments);
cbxTranslate(IDC_CBX_BITMAP_MODE, TbitmapSettings::modes);
}
//构造函数
TbitmapPage::TbitmapPage(TffdshowPageDec Iparent, const TfilterIDFF idff): TconfPageDecVideo(Iparent, idff)
{
//各种绑定
resInter = IDC_CHB_BITMAP;
static const TbindTrackbar htbr【】 = {
IDC_TBR_BITMAP_POSX, IDFF_bitmapPosx, TbitmapPage::pos2dlg,
IDC_TBR_BITMAP_POSY, IDFF_bitmapPosy, TbitmapPage::pos2dlg,
IDC_TBR_BITMAP_OPACITY, IDFF_bitmapStrength, TbitmapPage::opacity2dlg,
0, 0, NULL
};
bindHtracks(htbr);
static const TbindCombobox cbx【】 = {
IDC_CBX_BITMAP_ALIGN, IDFF_bitmapAlign, BINDCBX_SEL, NULL,
IDC_CBX_BITMAP_MODE, IDFF_bitmapMode, BINDCBX_SEL, NULL,
0
};
bindComboboxes(cbx);
static const TbindButton bt【】 = {
IDC_BT_BITMAP_FLNM, TbitmapPage::onFlnm,
0, NULL
};
bindButtons(bt);
}
看 ffdshow源代码的时候,开始会比较费劲。为什么?因为它使用了大量自己写的API函数,以及自己定义的结构体。这些API函数的种类繁多,如果一个一个都看完,估计就精疲力竭了。经过一段时间的学习之后,我发现最方便的方法还是根据函数名字推测其用法。因此我就不深入剖析ffdshow的API函数了。
以上源代码中包含以下API(大致按出现先后次序,可能没有例举全,在这里只是举例子):
edLimitText();//限制输入字符串长度
tbrSetRange();//设置滑动条范围
setDlgItemText();//设置组件名称
cbxSetCurSel();//设置下拉框当前选项
cfgGet();//从注册表中读取变量的值
tbrSet();//设置滑动条的值
bindHtracks();//绑定注册表变量和滑动条
bindComboboxes();//绑定注册表变量和下拉框
bindButtons();//绑定函数和按钮
从以上函数大致可以看出tbr**()基本上都是操作滑动条的,cbx()基本上都是操作下拉框的,函数基本上可以从名称上理解其的意思。 bind()就是绑定注册表变量和控件的。注意ffdshow里面有注册表变量这么一个概念。这些变量的值存在系统的注册表里面,不会因为程序结束运行而消失。就目前我的观察来看,绝大部分注册表变量存的是一个整数值。这些注册表变量都以IDFF_xxx的名称预编译定义在 ffdshow_constants.h头文件中。与MFC控件可以直接与CString,int等变量绑定不同,ffdshow控件只可以和注册表变量绑定。即每次运行的时候都从注册表加载变量的值到界面上。存储的时候把界面上的值存储到注册表中。
注:注册表变量如下所示(截取了一小段)
#define IDFF_filterBitmap 1650
#define IDFF_isBitmap 1651
#define IDFF_showBitmap 1652
#define IDFF_orderBitmap 1653
#define IDFF_fullBitmap 1654
#define IDFF_bitmapFlnm 1655
#define IDFF_bitmapPosx 1656
#define IDFF_bitmapPosy 1657
#define IDFF_bitmapPosmode 1658
#define IDFF_bitmapAlign 1659
#define IDFF_bitmapMode 1660
#define IDFF_bitmapStrength 1661
此外需要注意的是,ffdshow尽管包含了图形化的属性界面,却没有使用MFC类库,因而MFC的很多函数都不能使用,对此我还不甚了解为什么要这样,以后有机会要探究探究。
3: 位图覆盖滤镜(设置部分Settings)
上一篇文章介绍了ffdshow的位图覆盖滤镜的对话框(Dialog)部分:ffdshow 源代码分析2 : 位图覆盖滤镜(对话框部分Dialog)
在这里再介绍一下设置部分(Settings),此外还有一个滤镜部分(Filter)。这三个部分就可以组成一个ffdshow的滤镜功能了。
设置部分(Settings)
在ffdshow中滤镜的设置部分(Settings)主要用于存储滤镜运行过程中需要用到的各种变量。一般情况下通过读取注册表变量并赋值给该类当中的变量从而达到操作相应滤镜的功能。
与位图覆盖(Bitmap)滤镜的设置有关的类位于settings->filters->video目录下(隐藏的很深啊)的TbitmapSettings.cpp和TbitmapSettings.h文件中。
先来看看TbitmapSettings.h
该类的名字叫TbitmapSettings,从类的定义我们可以看出,
flnm【】存储了打开的位图的路径
posx,posy存储了位图在屏幕上显示的位置
mode存储了显示的方式
等等,所有跟该滤镜(Filter)相关的数据都存储在该类之中。
该类包含一个TfilterIDFF类型的结构体idffs,用于存储该滤镜的一些属性信息(名称,ID,属性对话框ID等等)
此外,有两个函数至关重要。createFilters()用于创建滤镜(Filter)。 createPages()用于创建滤镜的配置对话框(Dialog)。
#ifndef _TBITMAPSETTINGSH
#define _TBITMAPSETTINGSH
//各个Filter预设值
#include "TfilterSettings.h"
#include "Tfont.h"
//Bitmap的配置信息
struct TbitmapSettings : TfilterSettingsVideo {
private:
static const TfilterIDFF idffs;
protected:
virtual const int getResets(unsigned int pageId);
public:
TbitmapSettings(TintStrColl Icoll = NULL, TfilterIDFFs filters = NULL);
//Bitmap文件路径
char_t flnm【MAX_PATH】;
//x,y坐标,以及坐标的模式
int posx, posy, posmode;
int align;
//叠加方式
enum {
MODE_BLEND = 0,
MODE_DARKEN = 1,
MODE_LIGHTEN = 2,
MODE_ADD = 3,
MODE_SOFTLIGHT = 4,
MODE_EXCLUSION = 5
};
int mode;
static const char_t modes【】;
int strength;
//创建Filter
virtual void createFilters(size_t filtersorder, Tfilters filters, TfilterQueue queue) const;
//创建属性页面
virtual void createPages(TffdshowPageDec parent) const;
virtual bool getTip(unsigned int pageId, char_t *buf, size_t buflen);
};
#endif
再来看看TbitmapSettings.cpp