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下载。包括编译好的程序,以及原代码等,下载地址:http://sourceforge.net/projects/ffdshow-tryout/

注意: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 _CBITMAPPAGE_H_
#define _CBITMAPPAGE_H_ #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 it and/or modify
* it 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, , , );
tbrSetRange(IDC_TBR_BITMAP_POSY, , , );
tbrSetRange(IDC_TBR_BITMAP_OPACITY, , );
}
//配置数据传入到对话框界面
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[];
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), * o / );
} 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, m_hwnd, _(-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("."), )) {
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<TbitmapPage> 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,
, , NULL
};
bindHtracks(htbr);
static const TbindCombobox<TbitmapPage> cbx[] = {
IDC_CBX_BITMAP_ALIGN, IDFF_bitmapAlign, BINDCBX_SEL, NULL,
IDC_CBX_BITMAP_MODE, IDFF_bitmapMode, BINDCBX_SEL, NULL, };
bindComboboxes(cbx);
static const TbindButton<TbitmapPage> bt[] = {
IDC_BT_BITMAP_FLNM, &TbitmapPage::onFlnm,
, 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 _TBITMAPSETTINGS_H_
#define _TBITMAPSETTINGS_H_
//各个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 = ,
MODE_DARKEN = ,
MODE_LIGHTEN = ,
MODE_ADD = ,
MODE_SOFTLIGHT = ,
MODE_EXCLUSION =
};
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

该类包含了TbitmapSettings类中函数方法的具体实现。首先看一下构造函数TbitmapSettings()。从构造函数中可以看出,绑定了类中的变量和注册表变量,使它们形成一一对应的关系。其他的函数就不再细说了,比较简单,理解起来比较容易。

/*
* Copyright (c) 2004-2006 Milan Cutka
*
* This program is free software; you can redistribute it and/or modify
* it 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
*/ #include "stdafx.h"
#include "TbitmapSettings.h"
#include "TimgFilterBitmap.h"
#include "Cbitmap.h"
#include "TffdshowPageDec.h"
#include "TsubtitlesSettings.h"
//几种叠加方式
const char_t* TbitmapSettings::modes[] = {
_l("blend"),
_l("darken"),
_l("lighten"),
_l("add"),
_l("softlight"),
_l("exclusion"),
NULL
};
//Filter属性
const TfilterIDFF TbitmapSettings::idffs = {
/*name*/ _l("Bitmap overlay"),
/*id*/ IDFF_filterBitmap,
/*is*/ IDFF_isBitmap,
/*order*/ IDFF_orderBitmap,
/*show*/ IDFF_showBitmap,
/*full*/ IDFF_fullBitmap,
/*half*/ ,
/*dlgId*/ IDD_BITMAP,
};
//构造函数
TbitmapSettings::TbitmapSettings(TintStrColl *Icoll, TfilterIDFFs *filters): TfilterSettingsVideo(sizeof(*this), Icoll, filters, &idffs)
{
half = ;
memset(flnm, , sizeof(flnm));
//绑定变量
static const TintOptionT<TbitmapSettings> iopts[] = {
IDFF_isBitmap , &TbitmapSettings::is , , , _l(""), ,
_l("isBitmap"), ,
IDFF_showBitmap , &TbitmapSettings::show , , , _l(""), ,
_l("showBitmap"), ,
IDFF_orderBitmap , &TbitmapSettings::order , , , _l(""), ,
_l("orderBitmap"), ,
IDFF_fullBitmap , &TbitmapSettings::full , , , _l(""), ,
_l("fullBitmap"), ,
IDFF_bitmapPosx , &TbitmapSettings::posx , -, , _l(""), ,
_l("bitmapPosX"), ,
IDFF_bitmapPosy , &TbitmapSettings::posy , -, , _l(""), ,
_l("bitmapPosY"), ,
IDFF_bitmapPosmode , &TbitmapSettings::posmode , , , _l(""), ,
_l("bitmapPosMode"), ,
IDFF_bitmapAlign , &TbitmapSettings::align , , , _l(""), ,
_l("bitmapAlign"), ALIGN_CENTER,
IDFF_bitmapMode , &TbitmapSettings::mode , , , _l(""), ,
_l("bitmapMode"), ,
IDFF_bitmapStrength , &TbitmapSettings::strength , , , _l(""), ,
_l("bitmapStrength"), , };
addOptions(iopts);
static const TstrOption sopts[] = {
IDFF_bitmapFlnm , (TstrVal)&TbitmapSettings::flnm , MAX_PATH, , _l(""), ,
_l("bitmapFlnm"), _l(""), };
addOptions(sopts); static const TcreateParamList1 listMode(modes);
setParamList(IDFF_bitmapMode, &listMode);
static const TcreateParamList1 listAlign(TsubtitlesSettings::alignments);
setParamList(IDFF_bitmapAlign, &listAlign);
}
//创建Filter
void TbitmapSettings::createFilters(size_t filtersorder, Tfilters *filters, TfilterQueue &queue) const
{
idffOnChange(idffs, filters, queue.temporary);
if (is && show) {
queueFilter<TimgFilterBitmap>(filtersorder, filters, queue);
}
}
//创建属性页面
void TbitmapSettings::createPages(TffdshowPageDec *parent) const
{
parent->addFilterPage<TbitmapPage>(&idffs);
} const int* TbitmapSettings::getResets(unsigned int pageId)
{
static const int idResets[] = {
IDFF_bitmapPosx, IDFF_bitmapPosy, IDFF_bitmapPosmode, IDFF_bitmapAlign, IDFF_bitmapMode, IDFF_bitmapStrength, };
return idResets;
} bool TbitmapSettings::getTip(unsigned int pageId, char_t *tipS, size_t len)
{
if (flnm[]) {
tsnprintf_s(tipS, len, _TRUNCATE, _l("%s %s"), modes[mode], flnm);
tipS[len - ] = '\0';
} else {
tipS[] = '\0';
}
return true;
}

4: 位图覆盖滤镜(滤镜部分Filter)

第一篇文章介绍了ffdshow的位图覆盖滤镜的对话框(Dialog)部分:ffdshow 源代码分析2 : 位图覆盖滤镜(对话框部分Dialog)

第二篇文章介绍了ffdshow的位图覆盖滤镜的设置(Settings)部分:ffdshow 源代码分析 3: 位图覆盖滤镜(设置部分Settings)

此外还有一个滤镜部分(Filter)。这三个部分就可以组成一个ffdshow的滤镜功能了。本文就来简介一下ffdshow的滤镜部分。

滤镜部分(Filter)

ffdshow的滤镜的滤镜部分(怎么感觉名字有点重复 = =,算了先这么叫吧)的功能主要用于完成具体的图像处理功能。具体到位图覆盖滤镜的话,就是用于把图片覆盖到视频上面,他是ffdshow滤镜的核心。

与位图覆盖(Bitmap)滤镜的滤镜处理有关的类位于imgFilters目录下的TimgFilterBitmap.h和TimgFilterBitmap.cpp文件中。

先来看看TimgFilterBitmap.h

这里要注意一下,该类的名字叫TimgFilterBitmap。它的声明方式确实比较奇怪:DECLARE_FILTER(TimgFilterBitmap, public, TimgFilter)

可以看出DECLARE_FILTER是一个宏,具体这个宏的内部代码就先不查看了,否则会感觉很混乱,暂且留下一个小小的谜团。在这里只要知道这是声明了一个滤镜类就可以了。

其实TimgFilterBitmap的核心函数不多,就一个,那就是process(),具体的处理功能都是在这个函数里面实现的。

/*
*雷霄骅
*leixiaohua1020@126.com
*中国传媒大学/数字电视技术
*/ #ifndef _TIMGFILTERBITMAP_H_
#define _TIMGFILTERBITMAP_H_
//叠加一张位图
#include "TimgFilter.h"
#include "Tfont.h" struct TffPict;
struct TbitmapSettings;
//特别的声明方式 = =
DECLARE_FILTER(TimgFilterBitmap, public, TimgFilter)
private:
//图像
TffPict *bitmap;
//内存
Tbuffer bitmapbuf;
char_t oldflnm[MAX_PATH];
typedef void (*Tblendplane)(const TcspInfo &cspInfo, const unsigned int dx[], const unsigned int dy[], unsigned char *dst[], const stride_t dststride[], const unsigned char *src[], const stride_t srcstride[], int strength, int invstrength);
//注意 这个类有一个实例,名字叫w
class TrenderedSubtitleLineBitmap : public TrenderedSubtitleWordBase
{
public:
TrenderedSubtitleLineBitmap(void): TrenderedSubtitleWordBase(false) {}
TffPict *pict;
const TbitmapSettings *cfg;
//叠加
Tblendplane blend;
//打印
virtual void print(int startx, int starty /* not used */, unsigned int dx[], int dy1[], unsigned char *dstLn[], const stride_t stride[], const unsigned char *bmp[], const unsigned char *msk[], REFERENCE_TIME rtStart = REFTIME_INVALID) const;
} w;
TrenderedSubtitleLine l;
//是TrenderedSubtitleLine的一个vector
TrenderedSubtitleLines ls;
int oldmode;
//几种叠加方式
template<class _mm> static void blend(const TcspInfo &cspInfo, const unsigned int dx[], const unsigned int dy[], unsigned char *dst[], const stride_t dststride[], const unsigned char *src[], const stride_t srcstride[], int strength, int invstrength);
template<class _mm> static void add(const TcspInfo &cspInfo, const unsigned int dx[], const unsigned int dy[], unsigned char *dst[], const stride_t dststride[], const unsigned char *src[], const stride_t srcstride[], int strength, int invstrength);
template<class _mm> static void darken(const TcspInfo &cspInfo, const unsigned int dx[], const unsigned int dy[], unsigned char *dst[], const stride_t dststride[], const unsigned char *src[], const stride_t srcstride[], int strength, int invstrength);
template<class _mm> static void lighten(const TcspInfo &cspInfo, const unsigned int dx[], const unsigned int dy[], unsigned char *dst[], const stride_t dststride[], const unsigned char *src[], const stride_t srcstride[], int strength, int invstrength);
template<class _mm> static void softlight(const TcspInfo &cspInfo, const unsigned int dx[], const unsigned int dy[], unsigned char *dst[], const stride_t dststride[], const unsigned char *src[], const stride_t srcstride[], int strength, int invstrength);
template<class _mm> static void exclusion(const TcspInfo &cspInfo, const unsigned int dx[], const unsigned int dy[], unsigned char *dst[], const stride_t dststride[], const unsigned char *src[], const stride_t srcstride[], int strength, int invstrength);
//获取叠加方式
template<class _mm> static Tblendplane getBlend(int mode);
protected:
virtual bool is(const TffPictBase &pict, const TfilterSettingsVideo *cfg);
virtual uint64_t getSupportedInputColorspaces(const TfilterSettingsVideo *cfg) const
{
return FF_CSPS_MASK_YUV_PLANAR;
}
public:
TimgFilterBitmap(IffdshowBase *Ideci, Tfilters *Iparent);
virtual ~TimgFilterBitmap();
//核心函数(Filter配置信息队列,图像,配置信息)
virtual HRESULT process(TfilterQueue::iterator it, TffPict &pict, const TfilterSettingsVideo *cfg0);
}; #endif

再来看看TimgFilterBitmap.cpp

这个文件本身代码量是比较大的,只是其他部分我都还没有仔细分析,确实没那没多时间。。。在这里仅简要分析一下最核心的函数process()。正是这个函数真正实现了滤镜的功能。在这个位图叠加滤镜中,process()实现了位图在视频上面的叠加功能。

//核心函数(Filter配置信息队列,图像,配置信息)
HRESULT TimgFilterBitmap::process(TfilterQueue::iterator it, TffPict &pict, const TfilterSettingsVideo *cfg0)
{
//都有这一句= =
if (is(pict, cfg0)) {
//Bitmap的配置信息
const TbitmapSettings *cfg = (const TbitmapSettings*)cfg0;
init(pict, cfg->full, cfg->half);
unsigned char *dst[];
bool cspChanged = getCurNext(FF_CSPS_MASK_YUV_PLANAR, pict, cfg->full, COPYMODE_DEF, dst);
//处理
if (!bitmap || cspChanged || stricmp(oldflnm, cfg->flnm) != ) {
ff_strncpy(oldflnm, cfg->flnm, countof(oldflnm));
if (bitmap) {
delete bitmap;
}
//新建一张图
//通过cfg->flnm路径
//载入bitmapbuf
bitmap = new TffPict(csp2, cfg->flnm, bitmapbuf, deci);
//3个颜色分量?
for (int i = ; i < ; i++) {
w.dx[i] = bitmap->rectFull.dx >> bitmap->cspInfo.shiftX[i];
w.dy[i] = bitmap->rectFull.dy >> bitmap->cspInfo.shiftY[i];
w.bmp[i] = bitmap->data[i];
w.bmpmskstride[i] = bitmap->stride[i];
}
w.dxChar = w.dx[];
w.dyChar = w.dy[];
} if (bitmap->rectFull.dx != ) {
if (oldmode != cfg->mode)
if (Tconfig::cpu_flags & FF_CPU_SSE2) {
//获取叠加方式(SSE2)
//在cfg的mode里
w.blend = getBlend<Tsse2>(oldmode = cfg->mode);
} else {
//获取叠加方式(MMX)
w.blend = getBlend<Tmmx>(oldmode = cfg->mode);
}
//输出到屏幕上的设置
TprintPrefs prefs(deci, NULL);
//各种参数
prefs.dx = dx2[];
prefs.dy = dy2[];
prefs.xpos = cfg->posx;
prefs.ypos = cfg->posy;
//模式不同的话
if (cfg->posmode == ) {
prefs.xpos *= -;
prefs.ypos *= -;
}
prefs.align = cfg->align;
prefs.linespacing = ;
prefs.csp = pict.csp;
w.pict = &pict;
w.cfg = cfg;
//打印,需要用到TprintPrefs
ls.print(prefs, dst, stride2);
}
}
//最后都是这一句?
return parent->processSample(++it, pict);
}

5: 位图覆盖滤镜(总结)

前面写了三篇文章,介绍了 位图覆盖滤镜的3个部分:

第一篇文章介绍了ffdshow的位图覆盖滤镜的对话框(Dialog)部分:ffdshow 源代码分析2 : 位图覆盖滤镜(对话框部分Dialog)

第二篇文章介绍了ffdshow的位图覆盖滤镜的设置(Settings)部分:ffdshow 源代码分析 3: 位图覆盖滤镜(设置部分Settings)

第三篇文章介绍了ffdshow的位图覆盖滤镜的滤镜(Filter)部分:ffdshow 源代码分析 4: 位图覆盖滤镜(滤镜部分Filter)

在此,用一张图总结他们之间的关系:

如图中所示,设置(Settings)部分是直接和系统上层关联的,它包含两个接口函数:createPages()和createFilters()。分别用于创建对话框(Dialog)和滤镜(Filter)。其中在TbitmapPage中对话框直接和注册表变量关联。而在TbitmapSettings中注册表变量和系统中的变量关联。TimgFilterBitmap最终读取TbitmapSettings中的变量完成相应的操作。

目前来开TimgFilterBitmap是不会直接读取TbitmapPage类中的值的。

6: 对解码器的dll的封装(libavcodec)

ffdshow封装了多个视音频解码器,比如libmpeg2,libavcodec,xvid等等。其中最重要的是libavcodec,这个是 ffmpeg提供的解码器,在ffdshow中起到了“挑大梁”的作用。本文分析ffdshow对解码器的封装方式,就以libavcodec为例。

在ffdshow中,libavcodec的被封装在ffmpeg.dll文件中,通过加载该dll中的函数,就可以使用libavcodec的各种方法。

Ffmpeg对libavcodec的封装类的定义位于codecs->libavcodec->Tlibavcodec.h。实现则位于codecs->libavcodec->Tlibavcodec.cpp。

先来看一看Tlibavcodec.h:

/*
*雷霄骅
*leixiaohua1020@126.com
*中国传媒大学/数字电视技术
*/
#ifndef _TLIBAVCODEC_H_
#define _TLIBAVCODEC_H_
//将FFmpeg的Dll中的方法封装到一个类中,以供使用
#include "../codecs/ffcodecs.h"
#include <dxva.h>
#include "TpostprocSettings.h"
#include "ffImgfmt.h"
#include "libavfilter/vf_yadif.h"
#include "libavfilter/gradfun.h"
#include "libswscale/swscale.h" struct AVCodecContext;
struct AVCodec;
struct AVFrame;
struct AVPacket;
struct AVCodecParserContext;
struct SwsContext;
struct SwsParams;
struct PPMode;
struct AVDictionary; struct Tconfig;
class Tdll;
struct DSPContext;
struct TlibavcodecExt;
//封装FFMPEG
//里面的函数基本上是FFMPEG的API
struct Tlibavcodec {
private:
int (*libswscale_sws_scale)(struct SwsContext *context, const uint8_t* const srcSlice[], const int srcStride[],
int srcSliceY, int srcSliceH, uint8_t* const dst[], const int dstStride[]);
//加载DLL的类
Tdll *dll;
int refcount;
static int get_buffer(AVCodecContext *c, AVFrame *pic);
CCritSec csOpenClose;
public:
Tlibavcodec(const Tconfig *config);
~Tlibavcodec();
static void avlog(AVCodecContext*, int, const char*, va_list);
static void avlogMsgBox(AVCodecContext*, int, const char*, va_list);
void AddRef(void) {
refcount++;
}
void Release(void) {
if (--refcount < ) {
delete this;
}
}
static bool getVersion(const Tconfig *config, ffstring &vers, ffstring &license);
static bool check(const Tconfig *config);
static int ppCpuCaps(uint64_t csp);
static void pp_mode_defaults(PPMode &ppMode);
static int getPPmode(const TpostprocSettings *cfg, int currentq);
static void swsInitParams(SwsParams *params, int resizeMethod);
static void swsInitParams(SwsParams *params, int resizeMethod, int flags); bool ok;
AVCodecContext* avcodec_alloc_context(AVCodec *codec, TlibavcodecExt *ext = NULL); void (*avcodec_register_all)(void);
AVCodecContext* (*avcodec_alloc_context0)(AVCodec *codec);
AVCodec* (*avcodec_find_decoder)(AVCodecID codecId);
AVCodec* (*avcodec_find_encoder)(AVCodecID id);
int (*avcodec_open0)(AVCodecContext *avctx, AVCodec *codec, AVDictionary **options);
int avcodec_open(AVCodecContext *avctx, AVCodec *codec);
AVFrame* (*avcodec_alloc_frame)(void);
int (*avcodec_decode_video2)(AVCodecContext *avctx, AVFrame *picture,
int *got_picture_ptr,
AVPacket *avpkt);
int (*avcodec_decode_audio3)(AVCodecContext *avctx, int16_t *samples,
int *frame_size_ptr,
AVPacket *avpkt);
int (*avcodec_encode_video)(AVCodecContext *avctx, uint8_t *buf, int buf_size, const AVFrame *pict);
int (*avcodec_encode_audio)(AVCodecContext *avctx, uint8_t *buf, int buf_size, const short *samples);
void (*avcodec_flush_buffers)(AVCodecContext *avctx);
int (*avcodec_close0)(AVCodecContext *avctx);
int avcodec_close(AVCodecContext *avctx); void (*av_log_set_callback)(void (*)(AVCodecContext*, int, const char*, va_list));
void* (*av_log_get_callback)(void);
int (*av_log_get_level)(void);
void (*av_log_set_level)(int); void (*av_set_cpu_flags_mask)(int mask); int (*avcodec_default_get_buffer)(AVCodecContext *s, AVFrame *pic);
void (*avcodec_default_release_buffer)(AVCodecContext *s, AVFrame *pic);
int (*avcodec_default_reget_buffer)(AVCodecContext *s, AVFrame *pic);
const char* (*avcodec_get_current_idct)(AVCodecContext *avctx);
void (*avcodec_get_encoder_info)(AVCodecContext *avctx, int *xvid_build, int *divx_version, int *divx_build, int *lavc_build); void* (*av_mallocz)(size_t size);
void (*av_free)(void *ptr); AVCodecParserContext* (*av_parser_init)(int codec_id);
int (*av_parser_parse2)(AVCodecParserContext *s, AVCodecContext *avctx, uint8_t **poutbuf, int *poutbuf_size, const uint8_t *buf, int buf_size, int64_t pts, int64_t dts, int64_t pos);
void (*av_parser_close)(AVCodecParserContext *s); void (*av_init_packet)(AVPacket *pkt);
uint8_t* (*av_packet_new_side_data)(AVPacket *pkt, enum AVPacketSideDataType type, int size); int (*avcodec_h264_search_recovery_point)(AVCodecContext *avctx,
const uint8_t *buf, int buf_size, int *recovery_frame_cnt); static const char_t *idctNames[], *errorRecognitions[], *errorConcealments[];
struct Tdia_size {
int size;
const char_t *descr;
};
static const Tdia_size dia_sizes[]; //libswscale imports
SwsContext* (*sws_getCachedContext)(struct SwsContext *context, int srcW, int srcH, enum PixelFormat srcFormat,
int dstW, int dstH, enum PixelFormat dstFormat, int flags,
SwsFilter *srcFilter, SwsFilter *dstFilter, const double *param, SwsParams *ffdshow_params); void (*sws_freeContext)(SwsContext *c);
SwsFilter* (*sws_getDefaultFilter)(float lumaGBlur, float chromaGBlur,
float lumaSharpen, float chromaSharpen,
float chromaHShift, float chromaVShift,
int verbose);
void (*sws_freeFilter)(SwsFilter *filter);
int sws_scale(struct SwsContext *context, const uint8_t* const srcSlice[], const stride_t srcStride[],
int srcSliceY, int srcSliceH, uint8_t* const dst[], const stride_t dstStride[]);
SwsVector *(*sws_getConstVec)(double c, int length);
SwsVector *(*sws_getGaussianVec)(double variance, double quality);
void (*sws_normalizeVec)(SwsVector *a, double height);
void (*sws_freeVec)(SwsVector *a);
int (*sws_setColorspaceDetails)(struct SwsContext *c, const int inv_table[],
int srcRange, const int table[], int dstRange,
int brightness, int contrast, int saturation);
const int* (*sws_getCoefficients)(int colorspace); int (*GetCPUCount)(void); //libpostproc imports
void (*pp_postprocess)(const uint8_t * src[], const stride_t srcStride[],
uint8_t * dst[], const stride_t dstStride[],
int horizontalSize, int verticalSize,
const /*QP_STORE_T*/int8_t *QP_store, int QP_stride,
/*pp_mode*/void *mode, /*pp_context*/void *ppContext, int pict_type);
/*pp_context*/
void *(*pp_get_context)(int width, int height, int flags);
void (*pp_free_context)(/*pp_context*/void *ppContext);
void (*ff_simple_idct_mmx)(int16_t *block); // DXVA imports
int (*av_h264_decode_frame)(struct AVCodecContext* avctx, uint8_t *buf, int buf_size);
int (*av_vc1_decode_frame)(struct AVCodecContext* avctx, uint8_t *buf, int buf_size); // === H264 functions
int (*FFH264CheckCompatibility)(int nWidth, int nHeight, struct AVCodecContext* pAVCtx, BYTE* pBuffer, UINT nSize, int nPCIVendor, int nPCIDevice, LARGE_INTEGER VideoDriverVersion);
int (*FFH264DecodeBuffer)(struct AVCodecContext* pAVCtx, BYTE* pBuffer, UINT nSize, int* pFramePOC, int* pOutPOC, REFERENCE_TIME* pOutrtStart);
HRESULT(*FFH264BuildPicParams)(DXVA_PicParams_H264* pDXVAPicParams, DXVA_Qmatrix_H264* pDXVAScalingMatrix, int* nFieldType, int* nSliceType, struct AVCodecContext* pAVCtx, int nPCIVendor); void (*FFH264SetCurrentPicture)(int nIndex, DXVA_PicParams_H264* pDXVAPicParams, struct AVCodecContext* pAVCtx);
void (*FFH264UpdateRefFramesList)(DXVA_PicParams_H264* pDXVAPicParams, struct AVCodecContext* pAVCtx);
BOOL (*FFH264IsRefFrameInUse)(int nFrameNum, struct AVCodecContext* pAVCtx);
void (*FF264UpdateRefFrameSliceLong)(DXVA_PicParams_H264* pDXVAPicParams, DXVA_Slice_H264_Long* pSlice, struct AVCodecContext* pAVCtx);
void (*FFH264SetDxvaSliceLong)(struct AVCodecContext* pAVCtx, void* pSliceLong); // === VC1 functions
HRESULT(*FFVC1UpdatePictureParam)(DXVA_PictureParameters* pPicParams, struct AVCodecContext* pAVCtx, int* nFieldType, int* nSliceType, BYTE* pBuffer, UINT nSize, UINT* nFrameSize, BOOL b_SecondField, BOOL* b_repeat_pict);
int (*FFIsSkipped)(struct AVCodecContext* pAVCtx); // === Common functions
char* (*GetFFMpegPictureType)(int nType);
unsigned long(*FFGetMBNumber)(struct AVCodecContext* pAVCtx); // yadif
void (*yadif_init)(YADIFContext *yadctx);
void (*yadif_uninit)(YADIFContext *yadctx);
void (*yadif_filter)(YADIFContext *yadctx, uint8_t *dst[], stride_t dst_stride[], int width, int height, int parity, int tff); // gradfun
int (*gradfunInit)(GradFunContext *ctx, const char *args, void *opaque);
void (*gradfunFilter)(GradFunContext *ctx, uint8_t *dst, uint8_t *src, int width, int height, int dst_linesize, int src_linesize, int r);
}; #endif

从Tlibavcodec定义可以看出,里面包含了大量的ffmpeg中的API,占据了很大的篇幅。通过调用这些API,就可以使用livavcodec的各种功能。

在Tlibavcodec的定义中,有一个变量:Tdll *dll,通过该变量,就可以加载ffmpeg.dll中的方法。

先来看一下Tdll的定义:

/*
*雷霄骅
*leixiaohua1020@126.com
*中国传媒大学/数字电视技术
*/
#ifndef _TDLL_H_
#define _TDLL_H_ #include "Tconfig.h"
//操作Dll的类
class Tdll
{
public:
bool ok;
Tdll(const char_t *dllName1, const Tconfig *config, bool explicitFullPath = false) {
char_t name[MAX_PATH], ext[MAX_PATH];
_splitpath_s(dllName1, NULL, , NULL, , name, countof(name), ext, countof(ext));
if (config && !explicitFullPath) {
char_t dllName2[MAX_PATH]; //installdir+filename+ext
_makepath_s(dllName2, countof(dllName2), NULL, config->pth, name, ext);
hdll = LoadLibrary(dllName2);
} else {
hdll = NULL;
}
if (!hdll) {
hdll = LoadLibrary(dllName1);
if (!hdll && !explicitFullPath) {
if (config) {
char_t dllName3[MAX_PATH]; //ffdshow.ax_path+filename+ext
_makepath_s(dllName3, countof(dllName3), NULL, config->epth, name, ext);
hdll = LoadLibrary(dllName3);
}
if (!hdll) {
char_t dllName0[MAX_PATH]; //only filename+ext - let Windows find it
_makepath_s(dllName0, countof(dllName0), NULL, NULL, name, ext);
hdll = LoadLibrary(dllName0);
}
}
}
ok = (hdll != NULL);
}
~Tdll() {
if (hdll) {
FreeLibrary(hdll);
}
}
HMODULE hdll;
//封装一下直接加载Dll的GetProcAddress
template<class T> __forceinline void loadFunction(T &fnc, const char *name) {
fnc = hdll ? (T)GetProcAddress(hdll, name) : NULL;
ok &= (fnc != NULL);
}
template<class T> __forceinline void loadFunctionByIndex(T &fnc, uint16_t id) {
uint32_t id32 = uint32_t(id);
fnc = hdll ?
(T) GetProcAddress(hdll, (LPCSTR)id32) :
NULL;
ok &= (fnc != NULL);
}
//检查Dll的状态是否正常
static bool check(const char_t *dllName1, const Tconfig *config) {
char_t name[MAX_PATH], ext[MAX_PATH];
_splitpath_s(dllName1, NULL, , NULL, , name, countof(name), ext, countof(ext));
if (config) {
char_t dllName2[MAX_PATH]; //installdir+filename+ext
_makepath_s(dllName2, countof(dllName2), NULL, config->pth, name, ext);
if (fileexists(dllName2)) {
return true;
}
}
if (fileexists(dllName1)) {
return true;
}
if (config) {
char_t dllName3[MAX_PATH]; //ffdshow.ax_path+filename+ext
_makepath_s(dllName3, MAX_PATH, NULL, config->epth, name, ext);
if (fileexists(dllName3)) {
return true;
}
}
char_t dllName0[MAX_PATH]; //only filename+ext - let Windows find it
_makepath_s(dllName0, countof(dllName0), NULL, NULL, name, ext);
char_t dir0[MAX_PATH], *dir0flnm;
if (SearchPath(NULL, dllName0, NULL, MAX_PATH, dir0, &dir0flnm)) {
return true;
}
return false;
}
}; #endif

从Tdll的定义可以看出,该类的loadFunction()函数封装了系统使用Dll功能的函数GetProcAddress()。

该类的构造函数Tdll()封装了系统加载Dll的函数LoadLibrary()。

此外该类还提供了check()用于检查Dll。

对于Tdll的分析先告一段落,现在我们回到Tlibavcodec,来看看它是如何加载libavcodec的函数的。查看一下Tlibavcodec的类的实现,位于codecs->libavcodec->Tlibavcodec.cpp。

该类的实现代码比较长,因此只能选择重要的函数查看一下。首先来看一下构造函数:

//===================================== Tlibavcodec ====================================
//FFMPEG封装类的构造函数
Tlibavcodec::Tlibavcodec(const Tconfig *config): refcount()
{
//加载FFMPEG的Dll
dll = new Tdll(_l("ffmpeg.dll"), config);
//加载各个函数
dll->loadFunction(avcodec_register_all, "avcodec_register_all");
dll->loadFunction(avcodec_find_decoder, "avcodec_find_decoder");
dll->loadFunction(avcodec_open0, "avcodec_open2");
dll->loadFunction(avcodec_alloc_context0, "avcodec_alloc_context3");
dll->loadFunction(avcodec_alloc_frame, "avcodec_alloc_frame");
dll->loadFunction(avcodec_decode_video2, "avcodec_decode_video2");
dll->loadFunction(avcodec_flush_buffers, "avcodec_flush_buffers");
dll->loadFunction(avcodec_close0, "avcodec_close");
dll->loadFunction(av_log_set_callback, "av_log_set_callback");
dll->loadFunction(av_log_get_callback, "av_log_get_callback");
dll->loadFunction(av_log_get_level, "av_log_get_level");
dll->loadFunction(av_log_set_level, "av_log_set_level");
dll->loadFunction(av_set_cpu_flags_mask, "av_set_cpu_flags_mask");
dll->loadFunction(av_mallocz, "av_mallocz");
dll->loadFunction(av_free, "av_free");
dll->loadFunction(avcodec_default_get_buffer, "avcodec_default_get_buffer");
dll->loadFunction(avcodec_default_release_buffer, "avcodec_default_release_buffer");
dll->loadFunction(avcodec_default_reget_buffer, "avcodec_default_reget_buffer");
dll->loadFunction(avcodec_get_current_idct, "avcodec_get_current_idct");
dll->loadFunction(avcodec_get_encoder_info, "avcodec_get_encoder_info");
dll->loadFunction(av_init_packet, "av_init_packet");
dll->loadFunction(av_packet_new_side_data, "av_packet_new_side_data");
dll->loadFunction(avcodec_h264_search_recovery_point, "avcodec_h264_search_recovery_point"); dll->loadFunction(avcodec_decode_audio3, "avcodec_decode_audio3"); dll->loadFunction(avcodec_find_encoder, "avcodec_find_encoder");
dll->loadFunction(avcodec_encode_video, "avcodec_encode_video");
dll->loadFunction(avcodec_encode_audio, "avcodec_encode_audio"); dll->loadFunction(av_parser_init, "av_parser_init");
dll->loadFunction(av_parser_parse2, "av_parser_parse2");
dll->loadFunction(av_parser_close, "av_parser_close"); //libswscale methods
dll->loadFunction(sws_getCachedContext, "sws_getCachedContext");
dll->loadFunction(sws_freeContext, "sws_freeContext");
dll->loadFunction(sws_getDefaultFilter, "sws_getDefaultFilter");
dll->loadFunction(sws_freeFilter, "sws_freeFilter");
dll->loadFunction(libswscale_sws_scale, "sws_scale"); dll->loadFunction(GetCPUCount, "GetCPUCount");
dll->loadFunction(sws_getConstVec, "sws_getConstVec");
dll->loadFunction(sws_getGaussianVec, "sws_getGaussianVec");
dll->loadFunction(sws_normalizeVec, "sws_normalizeVec");
dll->loadFunction(sws_freeVec, "sws_freeVec");
dll->loadFunction(sws_setColorspaceDetails, "sws_setColorspaceDetails");
dll->loadFunction(sws_getCoefficients, "sws_getCoefficients"); //libpostproc methods
dll->loadFunction(pp_postprocess, "pp_postprocess");
dll->loadFunction(pp_get_context, "pp_get_context");
dll->loadFunction(pp_free_context, "pp_free_context");
dll->loadFunction(ff_simple_idct_mmx, "ff_simple_idct_mmx"); //DXVA methods
dll->loadFunction(av_h264_decode_frame, "av_h264_decode_frame");
dll->loadFunction(av_vc1_decode_frame, "av_vc1_decode_frame"); dll->loadFunction(FFH264CheckCompatibility, "FFH264CheckCompatibility");
dll->loadFunction(FFH264DecodeBuffer, "FFH264DecodeBuffer");
dll->loadFunction(FFH264BuildPicParams, "FFH264BuildPicParams");
dll->loadFunction(FFH264SetCurrentPicture, "FFH264SetCurrentPicture");
dll->loadFunction(FFH264UpdateRefFramesList, "FFH264UpdateRefFramesList");
dll->loadFunction(FFH264IsRefFrameInUse, "FFH264IsRefFrameInUse");
dll->loadFunction(FF264UpdateRefFrameSliceLong, "FF264UpdateRefFrameSliceLong");
dll->loadFunction(FFH264SetDxvaSliceLong, "FFH264SetDxvaSliceLong"); dll->loadFunction(FFVC1UpdatePictureParam, "FFVC1UpdatePictureParam");
dll->loadFunction(FFIsSkipped, "FFIsSkipped"); dll->loadFunction(GetFFMpegPictureType, "GetFFMpegPictureType");
dll->loadFunction(FFGetMBNumber, "FFGetMBNumber"); //yadif methods
dll->loadFunction(yadif_init, "yadif_init");
dll->loadFunction(yadif_uninit, "yadif_uninit");
dll->loadFunction(yadif_filter, "yadif_filter"); //gradfun
dll->loadFunction(gradfunInit, "gradfunInit");
dll->loadFunction(gradfunFilter, "gradfunFilter"); ok = dll->ok;
//加载完毕后,进行注册
if (ok) {
avcodec_register_all();
av_log_set_callback(avlog);
}
}

该构造函数尽管篇幅比较长,但是还是比较好理解的,主要完成了3步:

1.      创建一个Tdll类的对象,加载“ffmpeg.dll”。

2.      使用loadFunction()加载各种函数。

3.      最后调用avcodec_register_all()注册各种解码器。

Tlibavcodec的析构函数则比较简单:

Tlibavcodec::~Tlibavcodec()
{
delete dll;
}

检查Dll的函数也比较简单:

bool Tlibavcodec::check(const Tconfig *config)
{
return Tdll::check(_l("ffmpeg.dll"), config);
}

此外,可能是出于某些功能的考虑,ffdshow还自己写了几个函数,但是限于篇幅不能一一介绍,在这里只介绍一个:

获取libavcodec版本:

bool Tlibavcodec::getVersion(const Tconfig *config, ffstring &vers, ffstring &license)
{
Tdll *dl = new Tdll(_l("ffmpeg.dll"), config); void (*av_getVersion)(char **version, char **build, char **datetime, const char* *license);
dl->loadFunction(av_getVersion, "getVersion");
bool res;
if (av_getVersion) {
res = true;
char *version, *build, *datetime;
const char *lic;
av_getVersion(&version, &build, &datetime, &lic);
vers = (const char_t*)text<char_t>(version) + ffstring(_l(" (")) + (const char_t*)text<char_t>(datetime) + _l(")");
license = text<char_t>(lic);
} else {
res = false;
vers.clear();
license.clear();
}
delete dl;
return res;
}

7: libavcodec视频解码器类(TvideoCodecLibavcodec)

在这里我们进一步介绍一下其libavcodec解码器类。注意前一篇文章介绍的类Tlibavcodec仅仅是对 libavcodec所在的“ffmpeg.dll”的函数进行封装的类。但Tlibavcodec并不是一个解码器类,其没有继承任何类,还不能为 ffdshow所用。本文介绍的TvideoCodecLibavcodec才是libavcodec解码器类,其继承了TvideoCodecDec。

先来看一看TvideoCodecLibavcodec的定义吧,位于codecs-> TvideoCodecLibavcodec.h中。

/*
*雷霄骅
*leixiaohua1020@126.com
*中国传媒大学/数字电视技术
*/
#ifndef _TVIDEOCODECLIBAVCODEC_H_
#define _TVIDEOCODECLIBAVCODEC_H_ #include "TvideoCodec.h"
#include "ffmpeg/Tlibavcodec.h"
#include "ffmpeg/libavcodec/avcodec.h" #define MAX_THREADS 8 // FIXME: This is defined in mpegvideo.h. struct Textradata;
class TccDecoder;
//libavcodec解码器(视频)
struct TlibavcodecExt {
private:
static int get_buffer(AVCodecContext *s, AVFrame *pic);
int (*default_get_buffer)(AVCodecContext *s, AVFrame *pic);
static void release_buffer(AVCodecContext *s, AVFrame *pic);
void (*default_release_buffer)(AVCodecContext *s, AVFrame *pic);
static int reget_buffer(AVCodecContext *s, AVFrame *pic);
int (*default_reget_buffer)(AVCodecContext *s, AVFrame *pic);
static void handle_user_data0(AVCodecContext *c, const uint8_t *buf, int buf_len);
public:
virtual ~TlibavcodecExt() {}
void connectTo(AVCodecContext *ctx, Tlibavcodec *libavcodec);
virtual void onGetBuffer(AVFrame *pic) {}
virtual void onRegetBuffer(AVFrame *pic) {}
virtual void onReleaseBuffer(AVFrame *pic) {}
virtual void handle_user_data(const uint8_t *buf, int buf_len) {}
};
//libavcodec解码,不算是Filter?
class TvideoCodecLibavcodec : public TvideoCodecDec, public TvideoCodecEnc, public TlibavcodecExt
{
friend class TDXVADecoderVC1;
friend class TDXVADecoderH264;
protected:
//各种信息(源自AVCodecContext中)
Tlibavcodec *libavcodec;
void create(void);
AVCodec *avcodec;
mutable char_t codecName[];
AVCodecContext *avctx;
uint32_t palette[AVPALETTE_COUNT];
int palette_size;
AVFrame *frame;
FOURCC fcc;
FILE *statsfile;
int cfgcomode;
int psnr;
bool isAdaptive;
int threadcount;
bool dont_use_rtStop_from_upper_stream; // and reordering of timpestams is justified.
bool theorart;
bool codecinited, ownmatrices;
REFERENCE_TIME rtStart, rtStop, avgTimePerFrame, segmentTimeStart;
REFERENCE_TIME prior_in_rtStart, prior_in_rtStop;
REFERENCE_TIME prior_out_rtStart, prior_out_rtStop; struct {
REFERENCE_TIME rtStart, rtStop;
unsigned int srcSize;
} b[MAX_THREADS + ];
int inPosB; Textradata *extradata;
bool sendextradata;
unsigned int mb_width, mb_height, mb_count;
static void line(unsigned char *dst, unsigned int _x0, unsigned int _y0, unsigned int _x1, unsigned int _y1, stride_t strideY);
static void draw_arrow(uint8_t *buf, int sx, int sy, int ex, int ey, stride_t stride, int mulx, int muly, int dstdx, int dstdy);
unsigned char *ffbuf;
unsigned int ffbuflen;
bool wasKey;
virtual void handle_user_data(const uint8_t *buf, int buf_len);
TccDecoder *ccDecoder;
bool autoSkipingLoopFilter;
enum AVDiscard initialSkipLoopFilter;
int got_picture;
bool firstSeek; // firstSeek means start of palyback.
bool mpeg2_in_doubt;
bool mpeg2_new_sequence;
bool bReorderBFrame;
//时长(AVCodecContext中)
REFERENCE_TIME getDuration();
int isReallyMPEG2(const unsigned char *src, size_t srcLen);
protected:
virtual LRESULT beginCompress(int cfgcomode, uint64_t csp, const Trect &r);
virtual bool beginDecompress(TffPictBase &pict, FOURCC infcc, const CMediaType &mt, int sourceFlags);
virtual HRESULT flushDec(void);
AVCodecParserContext *parser;
public:
TvideoCodecLibavcodec(IffdshowBase *Ideci, IdecVideoSink *IsinkD);
TvideoCodecLibavcodec(IffdshowBase *Ideci, IencVideoSink *IsinkE);
virtual ~TvideoCodecLibavcodec();
virtual int getType(void) const {
return IDFF_MOVIE_LAVC;
}
virtual const char_t* getName(void) const;
virtual int caps(void) const {
return CAPS::VIS_MV | CAPS::VIS_QUANTS;
} virtual void end(void); virtual void getCompressColorspaces(Tcsps &csps, unsigned int outDx, unsigned int outDy);
virtual bool supExtradata(void);
//获得ExtraData(AVCodecContext中)
virtual bool getExtradata(const void* *ptr, size_t *len);
virtual HRESULT compress(const TffPict &pict, TencFrameParams ¶ms);
virtual HRESULT flushEnc(const TffPict &pict, TencFrameParams ¶ms) {
return compress(pict, params);
} virtual HRESULT decompress(const unsigned char *src, size_t srcLen, IMediaSample *pIn);
virtual void onGetBuffer(AVFrame *pic);
virtual bool onSeek(REFERENCE_TIME segmentStart);
virtual bool onDiscontinuity(void);
//画出运动矢量(AVCodecContext中)
virtual bool drawMV(unsigned char *dst, unsigned int dx, stride_t stride, unsigned int dy) const;
//编码器信息(AVCodecContext中)
virtual void getEncoderInfo(char_t *buf, size_t buflen) const;
virtual const char* get_current_idct(void);
virtual HRESULT BeginFlush();
bool isReorderBFrame() {
return bReorderBFrame;
};
virtual void reorderBFrames(REFERENCE_TIME& rtStart, REFERENCE_TIME& rtStop); class Th264RandomAccess
{
friend class TvideoCodecLibavcodec;
private:
TvideoCodecLibavcodec* parent;
int recovery_mode; // 0:OK, 1:searching 2: found, 3:waiting for frame_num decoded, 4:waiting for POC outputed
int recovery_frame_cnt;
int recovery_poc;
int thread_delay; public:
Th264RandomAccess(TvideoCodecLibavcodec* Iparent);
int search(uint8_t* buf, int buf_size);
void onSeek(void);
void judgeUsability(int *got_picture_ptr);
} h264RandomAccess;
}; #endif

这里有一个类TlibavcodecExt,我觉得应该是扩展了Tlibavcodec的一些功能,在这里我们先不管它,直接看看TvideoCodecLibavcodec都包含了什么变量:

Tlibavcodec *libavcodec:该类封装了libavcodec的各种函数,在前一篇文章中已经做过介绍,在此不再重复叙述了。可以认为该变量是 TvideoCodecLibavcodec类的灵魂,所有libavcodec中的函数都是通过该类调用的。

AVCodec *avcodec:FFMPEG中的结构体,解码器
AVCodecContext *avctx:FFMPEG中的结构体,解码器上下文

AVFrame *frame FFMPEG中的结构体,视频帧

mutable char_t codecName[100]:解码器名称

FOURCC fcc:FourCC

Textradata *extradata:附加数据

再来看一下TvideoCodecLibavcodec都包含什么方法:

create():创建解码器的时候调用

getDuration():获得时长

getExtradata():获得附加数据

drawMV():画运动矢量

getEncoderInfo():获得编码器信息

此外还包括一些有关解码的方法【这个是最关键的】:

beginDecompress():解码初始化

decompress():解码

下面我们来详细看看这些函数的实现吧:

先来看一下TvideoCodecLibavcodec的构造函数:

//libavcodec解码器(视频)
//内容大部分都很熟悉,因为是FFmpeg的API
TvideoCodecLibavcodec::TvideoCodecLibavcodec(IffdshowBase *Ideci, IdecVideoSink *IsinkD):
Tcodec(Ideci), TcodecDec(Ideci, IsinkD),
TvideoCodec(Ideci),
TvideoCodecDec(Ideci, IsinkD),
TvideoCodecEnc(Ideci, NULL),
h264RandomAccess(this),
bReorderBFrame(true)
{
create();
}

可见构造函数调用了Create(),我们再来看看Create():

void TvideoCodecLibavcodec::create(void)
{
ownmatrices = false;
deci->getLibavcodec(&libavcodec);
ok = libavcodec ? libavcodec->ok : false;
avctx = NULL;
avcodec = NULL;
frame = NULL;
quantBytes = ;
statsfile = NULL;
threadcount = ;
codecinited = false;
extradata = NULL;
theorart = false;
ffbuf = NULL;
ffbuflen = ;
codecName[] = '\0';
ccDecoder = NULL;
autoSkipingLoopFilter = false;
inPosB = ;
firstSeek = true;
mpeg2_new_sequence = true;
parser = NULL;
}

从Create()函数我们可以看出,其完成了各种变量的初始化工作。其中有一行代码:

deci->getLibavcodec(&libavcodec);

完成了Tlibavcodec*libavcodec的初始化工作。

再来看几个函数。

getDuration(),用于从AVCodecContext中获取时长:

REFERENCE_TIME TvideoCodecLibavcodec::getDuration()
{
REFERENCE_TIME duration = REF_SECOND_MULT / ;
if (avctx && avctx->time_base.num && avctx->time_base.den) {
duration = REF_SECOND_MULT * avctx->time_base.num / avctx->time_base.den;
if (codecId == AV_CODEC_ID_H264) {
duration *= ;
}
}
if (duration == ) {
return REF_SECOND_MULT / ;
}
return duration;
}

getExtradata()用于从AVCodecContext中获取附加信息:

bool TvideoCodecLibavcodec::getExtradata(const void* *ptr, size_t *len)
{
if (!avctx || !len) {
return false;
}
*len = avctx->extradata_size;
if (ptr) {
*ptr = avctx->extradata;
}
return true;
}

drawMV()用于从AVFrame中获取运动矢量信息,并画出来(这个函数用于一个名为“可视化”的滤镜里面,用于显示视频的运动矢量信息)。

//画出运动矢量
bool TvideoCodecLibavcodec::drawMV(unsigned char *dst, unsigned int dstdx, stride_t stride, unsigned int dstdy) const
{
if (!frame->motion_val || !frame->mb_type || !frame->motion_val[]) {
return false;
} #define IS_8X8(a) ((a)&MB_TYPE_8x8)
#define IS_16X8(a) ((a)&MB_TYPE_16x8)
#define IS_8X16(a) ((a)&MB_TYPE_8x16)
#define IS_INTERLACED(a) ((a)&MB_TYPE_INTERLACED)
#define USES_LIST(a, list) ((a) & ((MB_TYPE_P0L0|MB_TYPE_P1L0)<<(2*(list)))) const int shift = + ((frame->play_flags & CODEC_FLAG_QPEL) ? : );
const int mv_sample_log2 = - frame->motion_subsample_log2;
const int mv_stride = (frame->mb_width << mv_sample_log2) + (avctx->codec_id == AV_CODEC_ID_H264 ? : );
int direction = ; int mulx = (dstdx << ) / avctx->width;
int muly = (dstdy << ) / avctx->height;
//提取两个方向上的运动矢量信息(根据不同的宏块划分,可以分成几种情况)
//在AVCodecContext的motion_val中
for (int mb_y = ; mb_y < frame->mb_height; mb_y++)
for (int mb_x = ; mb_x < frame->mb_width; mb_x++) {
const int mb_index = mb_x + mb_y * frame->mb_stride;
if (!USES_LIST(frame->mb_type[mb_index], direction)) {
continue;
}
…此处代码太长,略
}
#undef IS_8X8
#undef IS_16X8
#undef IS_8X16
#undef IS_INTERLACED
#undef USES_LIST
return true;
}

下面来看几个很重要的函数,这几个函数继承自TvideoCodecDec类。

beginDecompress()用于解码器的初始化。注:这个函数的代码太长了,因此只选择一点关键的代码。

//----------------------------- decompression -----------------------------
bool TvideoCodecLibavcodec::beginDecompress(TffPictBase &pict, FOURCC fcc, const CMediaType &mt, int sourceFlags)
{
palette_size = ;
prior_out_rtStart = REFTIME_INVALID;
prior_out_rtStop = ;
rtStart = rtStop = REFTIME_INVALID;
prior_in_rtStart = prior_in_rtStop = REFTIME_INVALID;
mpeg2_in_doubt = codecId == AV_CODEC_ID_MPEG2VIDEO; int using_dxva = ; int numthreads = deci->getParam2(IDFF_numLAVCdecThreads);
int thread_type = ;
if (numthreads > && sup_threads_dec_frame(codecId)) {
thread_type = FF_THREAD_FRAME;
} else if (numthreads > && sup_threads_dec_slice(codecId)) {
thread_type = FF_THREAD_SLICE;
} if (numthreads > && thread_type != ) {
threadcount = numthreads;
} else {
threadcount = ;
} if (codecId == CODEC_ID_H264_DXVA) {
codecId = AV_CODEC_ID_H264;
using_dxva = ;
} else if (codecId == CODEC_ID_VC1_DXVA) {
codecId = AV_CODEC_ID_VC1;
using_dxva = ;
} avcodec = libavcodec->avcodec_find_decoder(codecId);
if (!avcodec) {
return false;
}
avctx = libavcodec->avcodec_alloc_context(avcodec, this);
avctx->thread_type = thread_type;
avctx->thread_count = threadcount;
avctx->h264_using_dxva = using_dxva;
if (codecId == AV_CODEC_ID_H264) {
// If we do not set this, first B-frames before the IDR pictures are dropped.
avctx->has_b_frames = ;
} frame = libavcodec->avcodec_alloc_frame();
avctx->width = pict.rectFull.dx;
avctx->height = pict.rectFull.dy;
intra_matrix = avctx->intra_matrix = (uint16_t*)calloc(sizeof(uint16_t), );
inter_matrix = avctx->inter_matrix = (uint16_t*)calloc(sizeof(uint16_t), );
ownmatrices = true; // Fix for new Haali custom media type and fourcc. ffmpeg does not understand it, we have to change it to FOURCC_AVC1
if (fcc == FOURCC_CCV1) {
fcc = FOURCC_AVC1;
} avctx->codec_tag = fcc;
avctx->workaround_bugs = deci->getParam2(IDFF_workaroundBugs);
#if 0
avctx->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK;
avctx->err_recognition = AV_EF_CRCCHECK | AV_EF_BITSTREAM | AV_EF_BUFFER | AV_EF_COMPLIANT | AV_EF_AGGRESSIVE;
#endif
if (codecId == AV_CODEC_ID_MJPEG) {
avctx->flags |= CODEC_FLAG_TRUNCATED;
}
if (mpeg12_codec(codecId) && deci->getParam2(IDFF_fastMpeg2)) {
avctx->flags2 = CODEC_FLAG2_FAST;
}
if (codecId == AV_CODEC_ID_H264)
if (int skip = deci->getParam2(IDFF_fastH264)) {
avctx->skip_loop_filter = skip & ? AVDISCARD_ALL : AVDISCARD_NONREF;
}
initialSkipLoopFilter = avctx->skip_loop_filter; avctx->debug_mv = !using_dxva; //(deci->getParam2(IDFF_isVis) & deci->getParam2(IDFF_visMV)); avctx->idct_algo = limit(deci->getParam2(IDFF_idct), , );
if (extradata) {
delete extradata;
}
extradata = new Textradata(mt, FF_INPUT_BUFFER_PADDING_SIZE);
此处代码太长,略…
}

从代码中可以看出这个函数的流程是:

1.avcodec_find_decoder();
2.avcodec_alloc_context();

3.avcodec_alloc_frame();

4.avcodec_open();

主要做了libavcodec初始化工作。

begin decompress()用于解码器的初始化。 注:这个函数的代码太长了,因此只选择一点关键的代码。

HRESULT TvideoCodecLibavcodec::decompress(const unsigned char *src, size_t srcLen0, IMediaSample *pIn)
{
代码太长,略…
AVPacket avpkt;
libavcodec->av_init_packet(&avpkt);
if (palette_size) {
uint32_t *pal = (uint32_t *)libavcodec->av_packet_new_side_data(&avpkt, AV_PKT_DATA_PALETTE, AVPALETTE_SIZE);
for (int i = ; i < palette_size / ; i++) {
pal[i] = 0xFF << | AV_RL32(palette + i);
}
} while (!src || size > ) {
int used_bytes; avctx->reordered_opaque = rtStart;
avctx->reordered_opaque2 = rtStop;
avctx->reordered_opaque3 = size; if (sendextradata && extradata->data && extradata->size > ) {
avpkt.data = (uint8_t *)extradata->data;
avpkt.size = (int)extradata->size;
used_bytes = libavcodec->avcodec_decode_video2(avctx, frame, &got_picture, &avpkt);
sendextradata = false;
if (used_bytes > ) {
used_bytes = ;
}
if (mpeg12_codec(codecId)) {
avctx->extradata = NULL;
avctx->extradata_size = ;
}
} else {
unsigned int neededsize = size + FF_INPUT_BUFFER_PADDING_SIZE; if (ffbuflen < neededsize) {
ffbuf = (unsigned char*)realloc(ffbuf, ffbuflen = neededsize);
} if (src) {
memcpy(ffbuf, src, size);
memset(ffbuf + size, , FF_INPUT_BUFFER_PADDING_SIZE);
}
if (parser) {
uint8_t *outBuf = NULL;
int out_size = ;
used_bytes = libavcodec->av_parser_parse2(parser, avctx, &outBuf, &out_size, src ? ffbuf : NULL, size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, );
if (prior_in_rtStart == REFTIME_INVALID) {
prior_in_rtStart = rtStart;
prior_in_rtStop = rtStop;
}
if (out_size > || !src) {
mpeg2_in_doubt = false;
avpkt.data = out_size > ? outBuf : NULL;
avpkt.size = out_size;
if (out_size > used_bytes) {
avctx->reordered_opaque = prior_in_rtStart;
avctx->reordered_opaque2 = prior_in_rtStop;
} else {
avctx->reordered_opaque = rtStart;
avctx->reordered_opaque2 = rtStop;
}
prior_in_rtStart = rtStart;
prior_in_rtStop = rtStop;
avctx->reordered_opaque3 = out_size;
if (h264RandomAccess.search(avpkt.data, avpkt.size)) {
libavcodec->avcodec_decode_video2(avctx, frame, &got_picture, &avpkt);
h264RandomAccess.judgeUsability(&got_picture);
} else {
got_picture = ;
}
} else {
got_picture = ;
}
} else {
avpkt.data = src ? ffbuf : NULL;
avpkt.size = size;
if (codecId == AV_CODEC_ID_H264) {
if (h264RandomAccess.search(avpkt.data, avpkt.size)) {
used_bytes = libavcodec->avcodec_decode_video2(avctx, frame, &got_picture, &avpkt);
if (used_bytes < ) {
return S_OK;
}
h264RandomAccess.judgeUsability(&got_picture);
} else {
got_picture = ;
return S_OK;
}
} else {
used_bytes = libavcodec->avcodec_decode_video2(avctx, frame, &got_picture, &avpkt);
}
}
}
代码太长,略…
}

从代码中可以看出这个函数的流程是:

1.AVPacket avpkt;
2.av_init_packet();

3.avcodec_decode_video2();

和ffmpeg的解码流程相差不大。

8: 视频解码器类(TvideoCodecDec)

前面两篇文章介绍了ffdshow中libavcodec的封装类Tlibavcodec,以及libavcodec的解码器类TvideoCodecLibavcodec:

ffdshow 源代码分析 6: 对解码器的dll的封装(libavcodec)

ffdshow 源代码分析 7: 解码器类(TvideoCodecLibavcodec)

其中libavcodec的解码器类TvideoCodecLibavcodec通过调用Tlibavcodec中的方法实现了libavcodec的dll中方法的调用;而它继承了TvideoCodecDec,本文正是要分析它继承的这个类。

TvideoCodecDec是所有视频解码器共有的父类。可以看一下它的继承关系:

可见,除了TvideoCodecLibavcodec继承了TvideoCodecDec之外,还有好几个类继承了TvideoCodecDec,比如说:TvideoCodecLibmpeg2,TvideoCodecXviD4等等…。突然来了兴趣,我们可以看一下其他的解码器类的定义是什么样的。

TvideoCodecLibmpeg2定义如下:

/*
*雷霄骅
*leixiaohua1020@126.com
*中国传媒大学/数字电视技术
*/
#ifndef _TVIDEOCODECLIBMPEG2_H_
#define _TVIDEOCODECLIBMPEG2_H_ #include "TvideoCodec.h"
#include "libmpeg2/include/mpeg2.h" class Tdll;
struct Textradata;
class TccDecoder;
//libmpeg2解码器
class TvideoCodecLibmpeg2 : public TvideoCodecDec
{
private:
Tdll *dll;
uint32_t (*mpeg2_set_accel)(uint32_t accel);
mpeg2dec_t* (*mpeg2_init)(void);
const mpeg2_info_t* (*mpeg2_info)(mpeg2dec_t *mpeg2dec);
mpeg2_state_t (*mpeg2_parse)(mpeg2dec_t *mpeg2dec);
void (*mpeg2_buffer)(mpeg2dec_t *mpeg2dec, const uint8_t *start, const uint8_t *end);
void (*mpeg2_close)(mpeg2dec_t *mpeg2dec);
void (*mpeg2_reset)(mpeg2dec_t *mpeg2dec, int full_reset);
void (*mpeg2_set_rtStart)(mpeg2dec_t *mpeg2dec, int64_t rtStart);
int (*mpeg2_guess_aspect)(const mpeg2_sequence_t * sequence, unsigned int * pixel_width, unsigned int * pixel_height); mpeg2dec_t *mpeg2dec;
const mpeg2_info_t *info;
bool wait4Iframe;
int sequenceFlag;
REFERENCE_TIME avgTimePerFrame;
TffPict oldpict;
Textradata *extradata;
TccDecoder *ccDecoder;
Tbuffer *buffer;
uint32_t oldflags;
bool m_fFilm;
int SetDeinterlaceMethod(void); void init(void);
HRESULT decompressI(const unsigned char *src, size_t srcLen, IMediaSample *pIn); protected:
virtual bool beginDecompress(TffPictBase &pict, FOURCC infcc, const CMediaType &mt, int sourceFlags); public:
TvideoCodecLibmpeg2(IffdshowBase *Ideci, IdecVideoSink *Isink);
virtual ~TvideoCodecLibmpeg2(); static const char_t *dllname;
virtual int getType(void) const {
return IDFF_MOVIE_LIBMPEG2;
}
virtual int caps(void) const {
return CAPS::VIS_QUANTS;
} virtual void end(void);
virtual HRESULT decompress(const unsigned char *src, size_t srcLen, IMediaSample *pIn);
virtual bool onSeek(REFERENCE_TIME segmentStart);
virtual HRESULT BeginFlush();
}; #endif

TvideoCodecXviD4定义如下:

/*
*雷霄骅
*leixiaohua1020@126.com
*中国传媒大学/数字电视技术
*/
#ifndef _TVIDEOCODECXVID4_H_
#define _TVIDEOCODECXVID4_H_ #include "TvideoCodec.h" class Tdll;
struct Textradata;
//xvid解码器
class TvideoCodecXviD4 : public TvideoCodecDec
{
private:
void create(void);
Tdll *dll;
public:
TvideoCodecXviD4(IffdshowBase *Ideci, IdecVideoSink *IsinkD);
virtual ~TvideoCodecXviD4();
int (*xvid_global)(void *handle, int opt, void *param1, void *param2);
int (*xvid_decore)(void *handle, int opt, void *param1, void *param2);
int (*xvid_plugin_single)(void *handle, int opt, void *param1, void *param2);
int (*xvid_plugin_lumimasking)(void *handle, int opt, void *param1, void *param2);
static const char_t *dllname;
private:
void *enchandle, *dechandle;
int psnr;
TffPict pict;
Tbuffer pictbuf;
static int me_hq(int rd3), me_(int me3);
Textradata *extradata;
REFERENCE_TIME rtStart, rtStop;
protected:
virtual bool beginDecompress(TffPictBase &pict, FOURCC infcc, const CMediaType &mt, int sourceFlags);
virtual HRESULT flushDec(void);
public:
virtual int getType(void) const {
return IDFF_MOVIE_XVID4;
}
virtual int caps(void) const {
return CAPS::VIS_QUANTS;
} virtual HRESULT decompress(const unsigned char *src, size_t srcLen, IMediaSample *pIn);
}; #endif

从以上这2种解码器类的定义,我们可以看出一些规律,比如说:

1.  都有Tdll *dll这个变量,用于加载视频解码器的dll
2.  都有beginDecompress()函数,用于初始化解码器

3.  都有decompress()函数,用于解码

好了,闲话不说,回归正题,来看一下这些解码器共有的父类:TvideoCodecDec

//具体 视频 解码器的父类,存一些公共信息
class TvideoCodecDec : virtual public TvideoCodec, virtual public TcodecDec
{
protected:
bool isdvdproc;
comptrQ<IffdshowDecVideo> deciV;
IdecVideoSink *sinkD;
TvideoCodecDec(IffdshowBase *Ideci, IdecVideoSink *Isink);
Rational guessMPEG2sar(const Trect &r, const Rational &sar2, const Rational &containerSar); class TtelecineManager
{
private:
TvideoCodecDec* parent;
int segment_count;
int pos_in_group;
struct {
int fieldtype;
int repeat_pict;
REFERENCE_TIME rtStart;
} group[]; // store information about 2 recent frames.
REFERENCE_TIME group_rtStart;
bool film;
int cfg_softTelecine;
public:
TtelecineManager(TvideoCodecDec* Iparent);
void get_timestamps(TffPict &pict);
void get_fieldtype(TffPict &pict);
void new_frame(int top_field_first, int repeat_pict, const REFERENCE_TIME &rtStart, const REFERENCE_TIME &rtStop);
void onSeek(void);
} telecineManager; public:
static TvideoCodecDec* initDec(IffdshowBase *deci, IdecVideoSink *Isink, AVCodecID codecId, FOURCC fcc, const CMediaType &mt); virtual ~TvideoCodecDec(); virtual int caps(void) const {
return CAPS::NONE;
}
virtual bool testMediaType(FOURCC fcc, const CMediaType &mt) {
return true;
}
virtual void forceOutputColorspace(const BITMAPINFOHEADER *hdr, int *ilace, TcspInfos &forcedCsps) {
*ilace = ; //cspInfos of forced output colorspace, empty when entering function
}
enum {SOURCE_REORDER = };
virtual bool beginDecompress(TffPictBase &pict, FOURCC infcc, const CMediaType &mt, int sourceFlags) = ;
virtual HRESULT decompress(const unsigned char *src, size_t srcLen, IMediaSample *pIn) = ;
virtual bool onDiscontinuity(void) {
return false;
}
virtual HRESULT onEndOfStream(void) {
return S_OK;
} unsigned int quantsDx, quantsStride, quantsDy, quantBytes, quantType;
//QP表
void *quants;
uint16_t *intra_matrix, *inter_matrix;
//计算平均QP
float calcMeanQuant(void);
//画运动矢量
virtual bool drawMV(unsigned char *dst, unsigned int dx, stride_t stride, unsigned int dy) const {
return false;
}
virtual const char* get_current_idct(void) {
return NULL;
}
virtual int useDXVA(void) {
return ;
}; virtual void setOutputPin(IPin * /*pPin*/) {}
};

TvideoCodecDec这个类中,还定义了一个类TtelecineManager。这种在类里面再定义一个类的方式还是不太多见的。TtelecineManager这个类的作用还没有研究,先不管它。

可以看出,TvideoCodecDec类的定义并不复杂,最主要的变量有如下几个,这几个变量都是子类中会用到的:

comptrQ<IffdshowDecVideo>deciV:重要性不言而喻,回头介绍
IdecVideoSink *sinkD:重要性不言而喻,回头介绍

void *quants:QP表(为什么要存在这里还没搞清)

TvideoCodecDec类定义了几个函数:

initDec():初始化解码器(重要)

calcMeanQuant():计算平均QP(为什么要在这里计算还没搞清)

TvideoCodecDec类还定义了一些纯虚函数,作为接口,这些函数的实现都在TvideoCodecDec的子类中完成【这几个函数是最重要的】:

beginDecompress();

decompress();

TvideoCodecDec类中最重要的函数只有一个,就是initDec(),作用主要是初始化解码器。其他的很多函数大多只是定义了一个名称,并没有实现,因为都是打算在具体各种解码器类中再进行实现的。

看一下initDec()的代码:

TvideoCodecDec* TvideoCodecDec::initDec(IffdshowBase *deci, IdecVideoSink *sink, AVCodecID codecId, FOURCC fcc, const CMediaType &mt)
{
// DXVA mode is a preset setting
switch (codecId) {
case AV_CODEC_ID_H264:
if (deci->getParam2(IDFF_filterMode) & IDFF_FILTERMODE_VIDEODXVA) {
if (deci->getParam2(IDFF_dec_DXVA_H264)) {
codecId = CODEC_ID_H264_DXVA;
} else {
return NULL;
}
}
break;
case AV_CODEC_ID_VC1:
case CODEC_ID_WMV9_LIB:
if (deci->getParam2(IDFF_filterMode) & IDFF_FILTERMODE_VIDEODXVA) {
if (deci->getParam2(IDFF_dec_DXVA_VC1)) {
codecId = CODEC_ID_VC1_DXVA;
} else {
return NULL;
}
}
break;
default:
break;
} TvideoCodecDec *movie = NULL; if (is_quicksync_codec(codecId)) {
movie = new TvideoCodecQuickSync(deci, sink, codecId);
} else if (lavc_codec(codecId)) {
movie = new TvideoCodecLibavcodec(deci, sink);
} else if (raw_codec(codecId)) {
movie = new TvideoCodecUncompressed(deci, sink);
} else if (wmv9_codec(codecId)) {
movie = new TvideoCodecWmv9(deci, sink);
} else if (codecId == CODEC_ID_XVID4) {
movie = new TvideoCodecXviD4(deci, sink);
} else if (codecId == CODEC_ID_LIBMPEG2) {
movie = new TvideoCodecLibmpeg2(deci, sink);
} else if (codecId == CODEC_ID_AVISYNTH) {
movie = new TvideoCodecAvisynth(deci, sink);
} else if (codecId == CODEC_ID_H264_DXVA || codecId == CODEC_ID_VC1_DXVA) {
movie = new TvideoCodecLibavcodecDxva(deci, sink, codecId);
} else {
return NULL;
}
if (!movie) {
return NULL;
}
if (movie->ok && movie->testMediaType(fcc, mt)) {
movie->codecId = codecId;
return movie;
} else if (is_quicksync_codec(codecId)) {
// QuickSync decoder init failed, revert to internal decoder.
switch (codecId) {
case CODEC_ID_H264_QUICK_SYNC:
codecId = AV_CODEC_ID_H264;
break;
case CODEC_ID_MPEG2_QUICK_SYNC:
codecId = CODEC_ID_LIBMPEG2;
break;
case CODEC_ID_VC1_QUICK_SYNC:
codecId = CODEC_ID_WMV9_LIB;
break;
default:
ASSERT(FALSE); // this shouldn't happen!
} delete movie; // Call this function again with the new codecId.
return initDec(deci, sink, codecId, fcc, mt);
} else {
delete movie;
return NULL;
}
}

这个函数的功能还是比较好理解的,根据CodecID的不同,创建不同的解码器(从TvideoCodecLibavcodec,TvideoCodecXviD4,TvideoCodecLibmpeg2这些里面选择)。

虽然不知道用途是什么,但是我们可以顺便看一下计算平均QP的函数,就是把quants1指向的QP表里面的数据求了一个平均值:

//计算平均QP
float TvideoCodecDec::calcMeanQuant(void)
{
if (!quants || !quantsDx || !quantsDy) {
return ;
}
unsigned int sum = , num = quantsDx * quantsDy;
unsigned char *quants1 = (unsigned char*)quants;
for (unsigned int y = ; y < quantsDy; y++)
for (unsigned int x = ; x < quantsDx; x++) {
sum += quants1[(y * quantsStride + x) * quantBytes];
}
return float(sum) / num;
}

9: 编解码器有关类的总结

本文再做最后一点的分析。在ffdshow中有如下继承关系:

前文已经分析过TvideoCodecLibavcodec,TvideoCodecDec,在这里我们看一下他们的父类:TvideoCodec,TcodecDec,以及前两个类的父类Tcodec。

其实本文介绍的这3个类充当了接口的作用,TvideoCodecDec继承TvideoCodec,TcodecDec,以及这两个类继承Tcodec,都使用了virtual的方式。

先来看看TvideoCodec。注意这个类强调的是【视频】:

//编解码器的父类
class TvideoCodec : virtual public Tcodec
{
public:
TvideoCodec(IffdshowBase *Ideci);
virtual ~TvideoCodec();
bool ok;
int connectedSplitter;
bool isInterlacedRawVideo;
Rational containerSar; struct CAPS {
enum {
NONE = ,
VIS_MV = ,
VIS_QUANTS =
};
}; virtual void end(void) {}
};

可以看出TvideoCodec定义非常的简单,只包含了视频编解码器会用到的一些变量。注意,是编解码器,不仅仅是解码器。

再来看看TcodecDec。注意这个类强调的是【解码】:

//实现了解码器的祖父类
class TcodecDec : virtual public Tcodec
{
private:
IdecSink *sink;
protected:
comptrQ<IffdshowDec> deciD;
TcodecDec(IffdshowBase *Ideci, IdecSink *Isink);
virtual ~TcodecDec();
virtual HRESULT flushDec(void) {
return S_OK;
}
public:
virtual HRESULT flush(void);
};

可以看出TcodecDec定义非常简单,只包含了解码器需要的一些变量,注意不限于视频解码器,还包含音频解码器。有两个变量比较重要:

IdecSink *sink;
comptrQ<IffdshowDec> deciD;

最后来看一下Tcodec。这个类不再继承任何类:

//编解码器的祖父类,都是虚函数
class Tcodec
{
protected:
const Tconfig *config;
comptr<IffdshowBase> deci;
Tcodec(IffdshowBase *Ideci);
virtual ~Tcodec();
public:
AVCodecID codecId;
virtual int getType(void) const = ;
virtual const char_t* getName(void) const {
return getMovieSourceName(getType());
}
virtual void getEncoderInfo(char_t *buf, size_t buflen) const {
ff_strncpy(buf, _l("unknown"), buflen);
buf[buflen - ] = '\0';
}
static const char_t* getMovieSourceName(int source); virtual HRESULT flush() {
return S_OK;
}
virtual HRESULT BeginFlush() {
return S_OK;
}
virtual HRESULT EndFlush() {
return S_OK;
}
virtual bool onSeek(REFERENCE_TIME segmentStart) {
return false;
}
};

可以看出,该类定义了一些编解码器会用到的公共函数。有几个变量还是比较重要的:

const Tconfig *config;
comptr<IffdshowBase> deci;

Tcodec(IffdshowBase *Ideci);

AVCodecID codecId

自此,我们可以总结出ffdshow编解码器这部分继承关系如下(图太大了,截成两张):

从TcodecDec继承下来的如下图所示。包含视频解码器以及音频解码器。

从TvideoCodec继承下来的如下图所示。包含了解码器类和编码器类。

总算大体上完成了,关于ffdshow解码器封装的内容就先告一段落吧。

转:ffdshow 源代码分析的更多相关文章

  1. ffdshow 源代码分析 9: 编解码器有关类的总结

    ===================================================== ffdshow源代码分析系列文章列表: ffdshow 源代码分析 1: 整体结构 ffds ...

  2. ffdshow 源代码分析 8: 视频解码器类(TvideoCodecDec)

    ===================================================== ffdshow源代码分析系列文章列表: ffdshow 源代码分析 1: 整体结构 ffds ...

  3. ffdshow 源代码分析 7: libavcodec视频解码器类(TvideoCodecLibavcodec)

    ===================================================== ffdshow源代码分析系列文章列表: ffdshow 源代码分析 1: 整体结构 ffds ...

  4. ffdshow 源代码分析 6: 对解码器的dll的封装(libavcodec)

    ===================================================== ffdshow源代码分析系列文章列表: ffdshow 源代码分析 1: 整体结构 ffds ...

  5. ffdshow 源代码分析 5: 位图覆盖滤镜(总结)

    ===================================================== ffdshow源代码分析系列文章列表: ffdshow 源代码分析 1: 整体结构 ffds ...

  6. ffdshow 源代码分析 4: 位图覆盖滤镜(滤镜部分Filter)

    ===================================================== ffdshow源代码分析系列文章列表: ffdshow 源代码分析 1: 整体结构 ffds ...

  7. ffdshow 源代码分析 3: 位图覆盖滤镜(设置部分Settings)

    ===================================================== ffdshow源代码分析系列文章列表: ffdshow 源代码分析 1: 整体结构 ffds ...

  8. ffdshow 源代码分析 2: 位图覆盖滤镜(对话框部分Dialog)

    ===================================================== ffdshow源代码分析系列文章列表: ffdshow 源代码分析 1: 整体结构 ffds ...

  9. ffdshow 源代码分析1 : 整体结构

    ffdshow是一个非常强大的DirectShow解码器,封装了ffmpeg,libmpeg2等解码库.它也提供了丰富的加工处理选项,可以锐化画面,调节画面的亮度等等.不止是视频,FFDShow现在同 ...

随机推荐

  1. shell学习记录003-cat命令

    cat 命令一般用于文件的查看 cat -s file   #可以去除文件中多余的上下空行 cat -T file   #Python编程中会用到的制表符会在该命令中体现出来 cat -n file  ...

  2. redis命令参考

    http://doc.redisfans.com/ 进入redis命令行模式方式: 1.进入redis安装目录 2.运行redis-cli

  3. poj3211

    Washing Clothes Time Limit: 1000MS   Memory Limit: 131072K Total Submissions: 9654   Accepted: 3095 ...

  4. C# 添加,修改,删除文件夹/文件集合

    C#追加文件 StreamWriter sw = File.AppendText(Server.MapPath(".")+"\\myText.txt"); sw ...

  5. git常用命令二、:git stash

    Git stash 储藏工作现场(当你不得不新建分支,或者切换分支,但是当前工作区的修改并不想提交) git stash Saved working directory and index state ...

  6. Java初学者容易犯的代码错误

    1. 不会判断空 空指针异常是所有Java初学者接触最多的异常,没有之一.原因是,你们拿到一个对象后容易不假思索的直接使用(直接给这个对象的属性赋值,直接调用这个对象的方法等),不报异常才怪呢!下面是 ...

  7. AngularJS基于模块化的MVC实现

    AngularJS基于模块化的MVC实现 <!DOCTYPE html> <html> <head> <meta charset="UTF-8&qu ...

  8. Viewer.js 是一款强大的 jQuery 图像浏览插件。

    https://blog.csdn.net/qq_29132907/article/details/80136023 一.效果图  二.代码<!DOCTYPE html><html ...

  9. QQ空间留言的JS

    直接上代码吧... var i=0; var time; function test(str) { i++; document.getElementById('tgb').contentWindow. ...

  10. ECShop全系列版本远程代码执行漏洞复现

    前言 问题发生在user.php的display函数,模版变量可控,导致注入,配合注入可达到远程代码执行 漏洞分析 0x01-SQL注入 先看user.php $back_act变量来源于HTTP_R ...