LAV Filter 源代码分析

1: 总体结构

LAV Filter 是一款视频分离和解码软件,他的分离器封装了FFMPEG中的libavformat,解码器则封装了FFMPEG中的libavcodec。它支持十分广泛的视音频格式。

源代码位于GitHub或Google Code:
https://github.com/Nevcairiel/LAVFilters
http://code.google.com/p/lavfilters/

本文分析了LAV Filter源代码的总体架构。

使用git获取LAV filter源代码之后,使用VC 2010 打开源代码,发现代码目录结构如图所示:

整个解决方案由8个工程组成,介绍一下我目前所知的几个工程:

baseclasses:DirectShow基类,在DirectShow的SDK中也有,是微软为了简化DirectShow开发而提供的。

Demuxers:解封装的基类,LAVSplitter需要调用其中的方法完成解封装操作。

LAVAudio:音频解码Filter。封装了libavcodec。

LAVSplitter:解封装Filter。封装了libavformat。

LAVVideo:视频解码Filter。封装了libavcodec。

libbluray:蓝光的支持。

以上标为咖啡色字体的是要重点分析的,也是最重要的工程。

2: LAV Splitter

LAV Filter 中最著名的就是 LAV Splitter,支持Matroska /WebM,MPEG-TS/PS,MP4/MOV,FLV,OGM / OGG,AVI等其他格式,广泛存在于各种视频播放器(暴风影音这类的)之中。

本文分析一下它的源代码。在分析之前,先看看它是什么样的。

使用GraphEdit随便打开一个视频文件,就可以看见LAV Filter:

可以右键点击这个Filter看一下它的属性页面,如图所示:

属性设置页面:

支持输入格式:

下面我们在 VC 2010 中看一下它的源代码:

从何看起呢?就先从directshow的注册函数看起吧,位于dllmain.cpp之中。部分代码的含义已经用注释标注上了。从代码可以看出,和普通的DirectShow Filter没什么区别。

dllmain.cpp

/*
 *      Copyright (C) 2010-2013 Hendrik Leppkes
 *      http://www.1f0.de
 *
 *  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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

// Based on the SampleParser Template by GDCL
// --------------------------------------------------------------------------------
// Copyright (c) GDCL 2004. All Rights Reserved. 
// You are free to re-use this as the basis for your own filter development,
// provided you retain this copyright notice in the source.
// http://www.gdcl.co.uk
// --------------------------------------------------------------------------------
//各种定义。。。
#include "stdafx.h"

// Initialize the GUIDs
#include <InitGuid.h>

#include <qnetwork.h>
#include "LAVSplitter.h"
#include "moreuuids.h"

#include "registry.h"
#include "IGraphRebuildDelegate.h"

// The GUID we use to register the splitter media types
DEFINE_GUID(MEDIATYPE_LAVSplitter,  
  0x9c53931c, 0x7d5a, 0x4a75, 0xb2, 0x6f, 0x4e, 0x51, 0x65, 0x4d, 0xb2, 0xc0);  

// --- COM factory table and registration code --------------
//注册时候的信息
const AMOVIESETUP_MEDIATYPE   
  sudMediaTypes[] = {  
    { &MEDIATYPE_Stream, &MEDIASUBTYPE_NULL },  
};  
//注册时候的信息(PIN)
const AMOVIESETUP_PIN sudOutputPins[] =   
{  
  {  
    L"Output",            // pin name
      FALSE,              // is rendered?    
      TRUE,               // is output?
      FALSE,              // zero instances allowed?
      TRUE,               // many instances allowed?
      &CLSID_NULL,        // connects to filter (for bridge pins)
      NULL,               // connects to pin (for bridge pins)
      0,                  // count of registered media types
      NULL                // list of registered media types
  },  
  {  
    L"Input",             // pin name
      FALSE,              // is rendered?    
      FALSE,              // is output?
      FALSE,              // zero instances allowed?
      FALSE,              // many instances allowed?
      &CLSID_NULL,        // connects to filter (for bridge pins)
      NULL,               // connects to pin (for bridge pins)
      1,                  // count of registered media types
      &sudMediaTypes[0]   // list of registered media types
  }  
};  
//注册时候的信息(名称等)
//CLAVSplitter
const AMOVIESETUP_FILTER sudFilterReg =  
{  
  &__uuidof(CLAVSplitter),        // filter clsid
  L"LAV Splitter",                // filter name
  MERIT_PREFERRED + 4,            // merit
  2,                              // count of registered pins
  sudOutputPins,                  // list of pins to register
  CLSID_LegacyAmFilterCategory  
};  
//注册时候的信息(名称等)
//CLAVSplitterSource
const AMOVIESETUP_FILTER sudFilterRegSource =  
{  
  &__uuidof(CLAVSplitterSource),  // filter clsid
  L"LAV Splitter Source",         // filter name
  MERIT_PREFERRED + 4,            // merit
  1,                              // count of registered pins
  sudOutputPins,                  // list of pins to register
  CLSID_LegacyAmFilterCategory  
};  

// --- COM factory table and registration code --------------

// DirectShow base class COM factory requires this table, 
// declaring all the COM objects in this DLL
// 注意g_Templates名称是固定的
CFactoryTemplate g_Templates[] = {  
// one entry for each CoCreate-able object
  {  
    sudFilterReg.strName,  
      sudFilterReg.clsID,  
      CreateInstance<CLAVSplitter>,  
      CLAVSplitter::StaticInit,  
      &sudFilterReg  
  },  
  {  
    sudFilterRegSource.strName,  
      sudFilterRegSource.clsID,  
      CreateInstance<CLAVSplitterSource>,  
      NULL,  
      &sudFilterRegSource  
  },  
// This entry is for the property page.
// 属性页
  {   
      L"LAV Splitter Properties",  
      &CLSID_LAVSplitterSettingsProp,  
      CreateInstance<CLAVSplitterSettingsProp>,  
      NULL, NULL  
  },  
  {  
      L"LAV Splitter Input Formats",  
      &CLSID_LAVSplitterFormatsProp,  
      CreateInstance<CLAVSplitterFormatsProp>,  
      NULL, NULL  
  }  
};  
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);  

// self-registration entrypoint
STDAPI DllRegisterServer()  
{  
  std::list<LPCWSTR> chkbytes;  

// BluRay
  chkbytes.clear();  
  chkbytes.push_back(L"0,4,,494E4458"); // INDX (index.bdmv)
  chkbytes.push_back(L"0,4,,4D4F424A"); // MOBJ (MovieObject.bdmv)
  chkbytes.push_back(L"0,4,,4D504C53"); // MPLS
  RegisterSourceFilter(__uuidof(CLAVSplitterSource),  
    MEDIASUBTYPE_LAVBluRay, chkbytes, NULL);  

// base classes will handle registration using the factory template table
return AMovieDllRegisterServer2(true);  
}  

STDAPI DllUnregisterServer()  
{  
  UnRegisterSourceFilter(MEDIASUBTYPE_LAVBluRay);  

// base classes will handle de-registration using the factory template table
return AMovieDllRegisterServer2(false);  
}  

// if we declare the correct C runtime entrypoint and then forward it to the DShow base
// classes we will be sure that both the C/C++ runtimes and the base classes are initialized
// correctly
extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID);  
BOOL WINAPI DllMain(HANDLE hDllHandle, DWORD dwReason, LPVOID lpReserved)  
{  
return DllEntryPoint(reinterpret_cast<HINSTANCE>(hDllHandle), dwReason, lpReserved);  
}  

void CALLBACK OpenConfiguration(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow)  
{  
HRESULT hr = S_OK;  
  CUnknown *pInstance = CreateInstance<CLAVSplitter>(NULL, &hr);  
  IBaseFilter *pFilter = NULL;  
  pInstance->NonDelegatingQueryInterface(IID_IBaseFilter, (void **)&pFilter);  
if (pFilter) {  
    pFilter->AddRef();  
    CBaseDSPropPage::ShowPropPageDialog(pFilter);  
  }  
delete pInstance;  
}

接下来就要进入正题了,看一看核心的分离器(解封装器)的类CLAVSplitter的定义文件LAVSplitter.h。乍一看这个类确实了得,居然继承了那么多的父类,实在是碉堡了。先不管那么多,看看里面都有什么函数吧。主要的函数上面都加了注释。注意还有一个类 CLAVSplitterSource继承了CLAVSplitter。

LAVSplitter.h

/*
 *      Copyright (C) 2010-2013 Hendrik Leppkes
 *      http://www.1f0.de
 *
 *  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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *  Initial design and concept by Gabest and the MPC-HC Team, copyright under GPLv2
 *  Contributions by Ti-BEN from the XBMC DSPlayer Project, also under GPLv2
 */

#pragma once

#include <string>
#include <list>
#include <set>
#include <vector>
#include <map>
#include "PacketQueue.h"

#include "BaseDemuxer.h"

#include "LAVSplitterSettingsInternal.h"
#include "SettingsProp.h"
#include "IBufferInfo.h"

#include "ISpecifyPropertyPages2.h"

#include "LAVSplitterTrayIcon.h"

#define LAVF_REGISTRY_KEY L"Software\\LAV\\Splitter"
#define LAVF_REGISTRY_KEY_FORMATS LAVF_REGISTRY_KEY L"\\Formats"
#define LAVF_LOG_FILE     L"LAVSplitter.txt"

#define MAX_PTS_SHIFT 50000000i64

class CLAVOutputPin;  
class CLAVInputPin;  

#ifdef  _MSC_VER
#pragma warning(disable: 4355)
#endif
//核心解复用(分离器)
//暴漏的接口在ILAVFSettings中
[uuid("171252A0-8820-4AFE-9DF8-5C92B2D66B04")]  
class CLAVSplitter   
  : public CBaseFilter  
  , public CCritSec  
  , protected CAMThread  
  , public IFileSourceFilter  
  , public IMediaSeeking  
  , public IAMStreamSelect  
  , public IAMOpenProgress  
  , public ILAVFSettingsInternal  
  , public ISpecifyPropertyPages2  
  , public IObjectWithSite  
  , public IBufferInfo  
{  
public:  
  CLAVSplitter(LPUNKNOWN pUnk, HRESULT* phr);  
virtual ~CLAVSplitter();  

static void CALLBACK StaticInit(BOOL bLoading, const CLSID *clsid);  

// IUnknown
//
  DECLARE_IUNKNOWN;  
//暴露接口,使外部程序可以QueryInterface,关键!
//翻译(“没有代表的方式查询接口”)
  STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void** ppv);  

// CBaseFilter methods
//输入是一个,输出就不一定了!
int GetPinCount();  
  CBasePin *GetPin(int n);  
  STDMETHODIMP GetClassID(CLSID* pClsID);  

  STDMETHODIMP Stop();  
  STDMETHODIMP Pause();  
  STDMETHODIMP Run(REFERENCE_TIME tStart);  

  STDMETHODIMP JoinFilterGraph(IFilterGraph * pGraph, LPCWSTR pName);  

// IFileSourceFilter
// 源Filter的接口方法
  STDMETHODIMP Load(LPCOLESTR pszFileName, const AM_MEDIA_TYPE * pmt);  
  STDMETHODIMP GetCurFile(LPOLESTR *ppszFileName, AM_MEDIA_TYPE *pmt);  

// IMediaSeeking
  STDMETHODIMP GetCapabilities(DWORD* pCapabilities);  
  STDMETHODIMP CheckCapabilities(DWORD* pCapabilities);  
  STDMETHODIMP IsFormatSupported(const GUID* pFormat);  
  STDMETHODIMP QueryPreferredFormat(GUID* pFormat);  
  STDMETHODIMP GetTimeFormat(GUID* pFormat);  
  STDMETHODIMP IsUsingTimeFormat(const GUID* pFormat);  
  STDMETHODIMP SetTimeFormat(const GUID* pFormat);  
  STDMETHODIMP GetDuration(LONGLONG* pDuration);  
  STDMETHODIMP GetStopPosition(LONGLONG* pStop);  
  STDMETHODIMP GetCurrentPosition(LONGLONG* pCurrent);  
  STDMETHODIMP ConvertTimeFormat(LONGLONG* pTarget, const GUID* pTargetFormat, LONGLONG Source, const GUID* pSourceFormat);  
  STDMETHODIMP SetPositions(LONGLONG* pCurrent, DWORD dwCurrentFlags, LONGLONG* pStop, DWORD dwStopFlags);  
  STDMETHODIMP GetPositions(LONGLONG* pCurrent, LONGLONG* pStop);  
  STDMETHODIMP GetAvailable(LONGLONG* pEarliest, LONGLONG* pLatest);  
  STDMETHODIMP SetRate(double dRate);  
  STDMETHODIMP GetRate(double* pdRate);  
  STDMETHODIMP GetPreroll(LONGLONG* pllPreroll);  

// IAMStreamSelect
  STDMETHODIMP Count(DWORD *pcStreams);  
  STDMETHODIMP Enable(long lIndex, DWORD dwFlags);  
  STDMETHODIMP Info(long lIndex, AM_MEDIA_TYPE **ppmt, DWORD *pdwFlags, LCID *plcid, DWORD *pdwGroup, WCHAR **ppszName, IUnknown **ppObject, IUnknown **ppUnk);  

// IAMOpenProgress
  STDMETHODIMP QueryProgress(LONGLONG *pllTotal, LONGLONG *pllCurrent);  
  STDMETHODIMP AbortOperation();  

// ISpecifyPropertyPages2
  STDMETHODIMP GetPages(CAUUID *pPages);  
  STDMETHODIMP CreatePage(const GUID& guid, IPropertyPage** ppPage);  

// IObjectWithSite
  STDMETHODIMP SetSite(IUnknown *pUnkSite);  
  STDMETHODIMP GetSite(REFIID riid, void **ppvSite);  

// IBufferInfo
  STDMETHODIMP_(int) GetCount();  
  STDMETHODIMP GetStatus(int i, int& samples, int& size);  
  STDMETHODIMP_(DWORD) GetPriority();  

// ILAVFSettings
  STDMETHODIMP SetRuntimeConfig(BOOL bRuntimeConfig);  
  STDMETHODIMP GetPreferredLanguages(LPWSTR *ppLanguages);  
  STDMETHODIMP SetPreferredLanguages(LPCWSTR pLanguages);  
  STDMETHODIMP GetPreferredSubtitleLanguages(LPWSTR *ppLanguages);  
  STDMETHODIMP SetPreferredSubtitleLanguages(LPCWSTR pLanguages);  
  STDMETHODIMP_(LAVSubtitleMode) GetSubtitleMode();  
  STDMETHODIMP SetSubtitleMode(LAVSubtitleMode mode);  
  STDMETHODIMP_(BOOL) GetSubtitleMatchingLanguage();  
  STDMETHODIMP SetSubtitleMatchingLanguage(BOOL dwMode);  
  STDMETHODIMP_(BOOL) GetPGSForcedStream();  
  STDMETHODIMP SetPGSForcedStream(BOOL bFlag);  
  STDMETHODIMP_(BOOL) GetPGSOnlyForced();  
  STDMETHODIMP SetPGSOnlyForced(BOOL bForced);  
  STDMETHODIMP_(int) GetVC1TimestampMode();  
  STDMETHODIMP SetVC1TimestampMode(int iMode);  
  STDMETHODIMP SetSubstreamsEnabled(BOOL bSubStreams);  
  STDMETHODIMP_(BOOL) GetSubstreamsEnabled();  
  STDMETHODIMP SetVideoParsingEnabled(BOOL bEnabled);  
  STDMETHODIMP_(BOOL) GetVideoParsingEnabled();  
  STDMETHODIMP SetFixBrokenHDPVR(BOOL bEnabled);  
  STDMETHODIMP_(BOOL) GetFixBrokenHDPVR();  
  STDMETHODIMP_(HRESULT) SetFormatEnabled(LPCSTR strFormat, BOOL bEnabled);  
  STDMETHODIMP_(BOOL) IsFormatEnabled(LPCSTR strFormat);  
  STDMETHODIMP SetStreamSwitchRemoveAudio(BOOL bEnabled);  
  STDMETHODIMP_(BOOL) GetStreamSwitchRemoveAudio();  
  STDMETHODIMP GetAdvancedSubtitleConfig(LPWSTR *ppAdvancedConfig);  
  STDMETHODIMP SetAdvancedSubtitleConfig(LPCWSTR pAdvancedConfig);  
  STDMETHODIMP SetUseAudioForHearingVisuallyImpaired(BOOL bEnabled);  
  STDMETHODIMP_(BOOL) GetUseAudioForHearingVisuallyImpaired();  
  STDMETHODIMP SetMaxQueueMemSize(DWORD dwMaxSize);  
  STDMETHODIMP_(DWORD) GetMaxQueueMemSize();  
  STDMETHODIMP SetTrayIcon(BOOL bEnabled);  
  STDMETHODIMP_(BOOL) GetTrayIcon();  
  STDMETHODIMP SetPreferHighQualityAudioStreams(BOOL bEnabled);  
  STDMETHODIMP_(BOOL) GetPreferHighQualityAudioStreams();  
  STDMETHODIMP SetLoadMatroskaExternalSegments(BOOL bEnabled);  
  STDMETHODIMP_(BOOL) GetLoadMatroskaExternalSegments();  
  STDMETHODIMP GetFormats(LPSTR** formats, UINT* nFormats);  
  STDMETHODIMP SetNetworkStreamAnalysisDuration(DWORD dwDuration);  
  STDMETHODIMP_(DWORD) GetNetworkStreamAnalysisDuration();  

// ILAVSplitterSettingsInternal
  STDMETHODIMP_(LPCSTR) GetInputFormat() { if (m_pDemuxer) return m_pDemuxer->GetContainerFormat(); return NULL; }  
  STDMETHODIMP_(std::set<FormatInfo>&) GetInputFormats();  
  STDMETHODIMP_(BOOL) IsVC1CorrectionRequired();  
  STDMETHODIMP_(CMediaType *) GetOutputMediatype(int stream);  
  STDMETHODIMP_(IFilterGraph *) GetFilterGraph() { if (m_pGraph) { m_pGraph->AddRef(); return m_pGraph; } return NULL; }  

  STDMETHODIMP_(DWORD) GetStreamFlags(DWORD dwStream) { if (m_pDemuxer) return m_pDemuxer->GetStreamFlags(dwStream); return 0; }  
  STDMETHODIMP_(int) GetPixelFormat(DWORD dwStream) { if (m_pDemuxer) return m_pDemuxer->GetPixelFormat(dwStream); return AV_PIX_FMT_NONE; }  
  STDMETHODIMP_(int) GetHasBFrames(DWORD dwStream){ if (m_pDemuxer) return m_pDemuxer->GetHasBFrames(dwStream); return -1; }  

// Settings helper
  std::list<std::string> GetPreferredAudioLanguageList();  
  std::list<CSubtitleSelector> GetSubtitleSelectors();  

bool IsAnyPinDrying();  
void SetFakeASFReader(BOOL bFlag) { m_bFakeASFReader = bFlag; }  
protected:  
// CAMThread
enum {CMD_EXIT, CMD_SEEK};  
DWORD ThreadProc();  

HRESULT DemuxSeek(REFERENCE_TIME rtStart);  
HRESULT DemuxNextPacket();  
HRESULT DeliverPacket(Packet *pPacket);  

void DeliverBeginFlush();  
void DeliverEndFlush();  

  STDMETHODIMP Close();  
  STDMETHODIMP DeleteOutputs();  
//初始化解复用器
  STDMETHODIMP InitDemuxer();  

friend class CLAVOutputPin;  
  STDMETHODIMP SetPositionsInternal(void *caller, LONGLONG* pCurrent, DWORD dwCurrentFlags, LONGLONG* pStop, DWORD dwStopFlags);  

public:  
  CLAVOutputPin *GetOutputPin(DWORD streamId, BOOL bActiveOnly = FALSE);  
  STDMETHODIMP RenameOutputPin(DWORD TrackNumSrc, DWORD TrackNumDst, std::vector<CMediaType> pmts);  
  STDMETHODIMP UpdateForcedSubtitleMediaType();  

  STDMETHODIMP CompleteInputConnection();  
  STDMETHODIMP BreakInputConnection();  

protected:  
//相关的参数设置
  STDMETHODIMP LoadDefaults();  
  STDMETHODIMP ReadSettings(HKEY rootKey);  
  STDMETHODIMP LoadSettings();  
  STDMETHODIMP SaveSettings();  
//创建图标
  STDMETHODIMP CreateTrayIcon();  

protected:  
  CLAVInputPin *m_pInput;  

private:  
  CCritSec m_csPins;  
//用vector存储输出PIN(解复用的时候是不确定的)
  std::vector<CLAVOutputPin *> m_pPins;  
//活动的
  std::vector<CLAVOutputPin *> m_pActivePins;  
//不用的
  std::vector<CLAVOutputPin *> m_pRetiredPins;  
  std::set<DWORD> m_bDiscontinuitySent;  

  std::wstring m_fileName;  
  std::wstring m_processName;  
//有很多纯虚函数的基本解复用类
//注意:绝大部分信息都是从这获得的
//这里的信息是由其派生类从FFMPEG中获取到的
  CBaseDemuxer *m_pDemuxer;  

BOOL m_bPlaybackStarted;  
BOOL m_bFakeASFReader;  

// Times
  REFERENCE_TIME m_rtStart, m_rtStop, m_rtCurrent, m_rtNewStart, m_rtNewStop;  
  REFERENCE_TIME m_rtOffset;  
double m_dRate;  
BOOL m_bStopValid;  

// Seeking
  REFERENCE_TIME m_rtLastStart, m_rtLastStop;  
  std::set<void *> m_LastSeekers;  

// flushing
bool m_fFlushing;  
  CAMEvent m_eEndFlush;  

  std::set<FormatInfo> m_InputFormats;  

// Settings
//设置
struct Settings {  
BOOL TrayIcon;  
    std::wstring prefAudioLangs;  
    std::wstring prefSubLangs;  
    std::wstring subtitleAdvanced;  
    LAVSubtitleMode subtitleMode;  
BOOL PGSForcedStream;  
BOOL PGSOnlyForced;  
int vc1Mode;  
BOOL substreams;  

BOOL MatroskaExternalSegments;  

BOOL StreamSwitchRemoveAudio;  
BOOL ImpairedAudio;  
BOOL PreferHighQualityAudio;  
DWORD QueueMaxSize;  
DWORD NetworkAnalysisDuration;  

    std::map<std::string, BOOL> formats;  
  } m_settings;  

BOOL m_bRuntimeConfig;  

  IUnknown *m_pSite;  

  CBaseTrayIcon *m_pTrayIcon;  
};  

[uuid("B98D13E7-55DB-4385-A33D-09FD1BA26338")]  
class CLAVSplitterSource : public CLAVSplitter  
{  
public:  
// construct only via class factory
  CLAVSplitterSource(LPUNKNOWN pUnk, HRESULT* phr);  
virtual ~CLAVSplitterSource();  

// IUnknown
  DECLARE_IUNKNOWN;  
//暴露接口,使外部程序可以QueryInterface,关键!
//翻译(“没有代表的方式查询接口”)
  STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void** ppv);  
};

先来看一下查询接口的函数NonDelegatingQueryInterface()吧

//暴露接口,使外部程序可以QueryInterface,关键!
STDMETHODIMP CLAVSplitter::NonDelegatingQueryInterface(REFIID riid, void** ppv)  
{  
  CheckPointer(ppv, E_POINTER);  

  *ppv = NULL;  

if (m_pDemuxer && (riid == __uuidof(IKeyFrameInfo) || riid == __uuidof(ITrackInfo) || riid == IID_IAMExtendedSeeking || riid == IID_IAMMediaContent)) {  
return m_pDemuxer->QueryInterface(riid, ppv);  
  }  
//写法好特别啊,意思是一样的
return
    QI(IMediaSeeking)  
    QI(IAMStreamSelect)  
    QI(ISpecifyPropertyPages)  
    QI(ISpecifyPropertyPages2)  
    QI2(ILAVFSettings)  
    QI2(ILAVFSettingsInternal)  
    QI(IObjectWithSite)  
    QI(IBufferInfo)  
    __super::NonDelegatingQueryInterface(riid, ppv);  
}

这个NonDelegatingQueryInterface()的写法确实够特别的,不过其作用还是一样的:根据不同的REFIID,获得不同的接口指针。在这里就不多说了。

再看一下Load()函数

// IFileSourceFilter
// 打开
STDMETHODIMP CLAVSplitter::Load(LPCOLESTR pszFileName, const AM_MEDIA_TYPE * pmt)  
{  
  CheckPointer(pszFileName, E_POINTER);  

  m_bPlaybackStarted = FALSE;  

  m_fileName = std::wstring(pszFileName);  

HRESULT hr = S_OK;  
  SAFE_DELETE(m_pDemuxer);  
LPWSTR extension = PathFindExtensionW(pszFileName);  

  DbgLog((LOG_TRACE, 10, L"::Load(): Opening file '%s' (extension: %s)", pszFileName, extension));  

// BDMV uses the BD demuxer, everything else LAVF
if (_wcsicmp(extension, L".bdmv") == 0 || _wcsicmp(extension, L".mpls") == 0) {  
    m_pDemuxer = new CBDDemuxer(this, this);  
  } else {  
    m_pDemuxer = new CLAVFDemuxer(this, this);  
  }  
//打开
if(FAILED(hr = m_pDemuxer->Open(pszFileName))) {  
    SAFE_DELETE(m_pDemuxer);  
return hr;  
  }  
  m_pDemuxer->AddRef();  

return InitDemuxer();  
}

在这里我们要注意CLAVSplitter的一个变量:m_pDemuxer。这是一个指向 CBaseDemuxer的指针。因此在这里CLAVSplitter实际上调用了 CBaseDemuxer中的方法。

从代码中的逻辑我们可以看出:

1.寻找文件后缀

2.当文件后缀是:".bdmv"或者".mpls"的时候,m_pDemuxer指向一个CBDDemuxer(我推测这代表目标文件是蓝光文件什么的),其他情况下m_pDemuxer指向一个CLAVFDemuxer。

3.然后m_pDemuxer会调用Open()方法。

4.最后会调用一个InitDemuxer()方法。

在这里我们应该看看m_pDemuxer->Open()这个方法里面有什么。我们先考虑m_pDemuxer指向CLAVFDemuxer的情况。

// Demuxer Functions
// 打开(就是一个封装)
STDMETHODIMP CLAVFDemuxer::Open(LPCOLESTR pszFileName)  
{  
return OpenInputStream(NULL, pszFileName, NULL, TRUE);  
}

发现是一层封装,于是果断决定层层深入。

//实际的打开,使用FFMPEG
STDMETHODIMP CLAVFDemuxer::OpenInputStream(AVIOContext *byteContext, LPCOLESTR pszFileName, const char *format, BOOL bForce)  
{  
  CAutoLock lock(m_pLock);  
HRESULT hr = S_OK;  

int ret; // return code from avformat functions

// Convert the filename from wchar to char for avformat
char fileName[4100] = {0};  
if (pszFileName) {  
    ret = WideCharToMultiByte(CP_UTF8, 0, pszFileName, -1, fileName, 4096, NULL, NULL);  
  }  

if (_strnicmp("mms:", fileName, 4) == 0) {  
    memmove(fileName+1, fileName, strlen(fileName));  
    memcpy(fileName, "mmsh", 4);  
  }  

  AVIOInterruptCB cb = {avio_interrupt_cb, this};  

trynoformat:  
// Create the avformat_context
// FFMPEG中的函数
  m_avFormat = avformat_alloc_context();  
  m_avFormat->pb = byteContext;  
  m_avFormat->interrupt_callback = cb;  

if (m_avFormat->pb)  
    m_avFormat->flags |= AVFMT_FLAG_CUSTOM_IO;  

LPWSTR extension = pszFileName ? PathFindExtensionW(pszFileName) : NULL;  

  AVInputFormat *inputFormat = NULL;  
//如果指定了格式
if (format) {  
//查查有木有
    inputFormat = av_find_input_format(format);  
  } else if (pszFileName) {  
LPWSTR extension = PathFindExtensionW(pszFileName);  
for (int i = 0; i < countof(wszImageExtensions); i++) {  
if (_wcsicmp(extension, wszImageExtensions[i]) == 0) {  
if (byteContext) {  
          inputFormat = av_find_input_format("image2pipe");  
        } else {  
          inputFormat = av_find_input_format("image2");  
        }  
break;  
      }  
    }  
for (int i = 0; i < countof(wszBlockedExtensions); i++) {  
if (_wcsicmp(extension, wszBlockedExtensions[i]) == 0) {  
goto done;  
      }  
    }  
  }  

// Disable loading of external mkv segments, if required
if (!m_pSettings->GetLoadMatroskaExternalSegments())  
    m_avFormat->flags |= AVFMT_FLAG_NOEXTERNAL;  

  m_timeOpening = time(NULL);  
//实际的打开
  ret = avformat_open_input(&m_avFormat, fileName, inputFormat, NULL);  
//出错了
if (ret < 0) {  
    DbgLog((LOG_ERROR, 0, TEXT("::OpenInputStream(): avformat_open_input failed (%d)"), ret));  
if (format) {  
      DbgLog((LOG_ERROR, 0, TEXT(" -> trying again without specific format")));  
      format = NULL;  
//实际的关闭
      avformat_close_input(&m_avFormat);  
goto trynoformat;  
    }  
goto done;  
  }  
  DbgLog((LOG_TRACE, 10, TEXT("::OpenInputStream(): avformat_open_input opened file of type '%S' (took %I64d seconds)"), m_avFormat->iformat->name, time(NULL) - m_timeOpening));  
  m_timeOpening = 0;  
//初始化AVFormat
  CHECK_HR(hr = InitAVFormat(pszFileName, bForce));  

return S_OK;  
done:  
  CleanupAVFormat();  
return E_FAIL;  
}

看到这个函数,立马感受到了一种“拨云见日”的感觉。看到了很多FFMPEG的API函数。最重要的依据当属avformat_open_input()了,通过这个函数,打开了实际的文件。如果出现错误,则调用avformat_close_input()进行清理。

最后,还调用了InitAVFormat()函数:

//初始化AVFormat
STDMETHODIMP CLAVFDemuxer::InitAVFormat(LPCOLESTR pszFileName, BOOL bForce)  
{  
HRESULT hr = S_OK;  
const char *format = NULL;  
//获取InputFormat信息(,短名称,长名称)
  lavf_get_iformat_infos(m_avFormat->iformat, &format, NULL);  
if (!bForce && (!format || !m_pSettings->IsFormatEnabled(format))) {  
    DbgLog((LOG_TRACE, 20, L"::InitAVFormat() - format of type '%S' disabled, failing", format ? format : m_avFormat->iformat->name));  
return E_FAIL;  
  }  

  m_pszInputFormat = format ? format : m_avFormat->iformat->name;  

  m_bVC1SeenTimestamp = FALSE;  

LPWSTR extension = pszFileName ? PathFindExtensionW(pszFileName) : NULL;  

  m_bMatroska = (_strnicmp(m_pszInputFormat, "matroska", 8) == 0);  
  m_bOgg = (_strnicmp(m_pszInputFormat, "ogg", 3) == 0);  
  m_bAVI = (_strnicmp(m_pszInputFormat, "avi", 3) == 0);  
  m_bMPEGTS = (_strnicmp(m_pszInputFormat, "mpegts", 6) == 0);  
  m_bMPEGPS = (_stricmp(m_pszInputFormat, "mpeg") == 0);  
  m_bRM = (_stricmp(m_pszInputFormat, "rm") == 0);  
  m_bPMP = (_stricmp(m_pszInputFormat, "pmp") == 0);  
  m_bMP4 = (_stricmp(m_pszInputFormat, "mp4") == 0);  

  m_bTSDiscont = m_avFormat->iformat->flags & AVFMT_TS_DISCONT;  

WCHAR szProt[24] = L"file";  
if (pszFileName) {  
DWORD dwNumChars = 24;  
    hr = UrlGetPart(pszFileName, szProt, &dwNumChars, URL_PART_SCHEME, 0);  
if (SUCCEEDED(hr) && dwNumChars && (_wcsicmp(szProt, L"file") != 0)) {  
      m_avFormat->flags |= AVFMT_FLAG_NETWORK;  
      DbgLog((LOG_TRACE, 10, TEXT("::InitAVFormat(): detected network protocol: %s"), szProt));  
    }  
  }  

// TODO: make both durations below configurable
// decrease analyze duration for network streams
if (m_avFormat->flags & AVFMT_FLAG_NETWORK || (m_avFormat->flags & AVFMT_FLAG_CUSTOM_IO && !m_avFormat->pb->seekable)) {  
// require at least 0.2 seconds
    m_avFormat->max_analyze_duration = max(m_pSettings->GetNetworkStreamAnalysisDuration() * 1000, 200000);  
  } else {  
// And increase it for mpeg-ts/ps files
if (m_bMPEGTS || m_bMPEGPS)  
      m_avFormat->max_analyze_duration = 10000000;  
  }  

  av_opt_set_int(m_avFormat, "correct_ts_overflow", !m_pBluRay, 0);  

if (m_bMatroska)  
    m_avFormat->flags |= AVFMT_FLAG_KEEP_SIDE_DATA;  

  m_timeOpening = time(NULL);  
//获取媒体流信息
int ret = avformat_find_stream_info(m_avFormat, NULL);  
if (ret < 0) {  
    DbgLog((LOG_ERROR, 0, TEXT("::InitAVFormat(): av_find_stream_info failed (%d)"), ret));  
goto done;  
  }  
  DbgLog((LOG_TRACE, 10, TEXT("::InitAVFormat(): avformat_find_stream_info finished, took %I64d seconds"), time(NULL) - m_timeOpening));  
  m_timeOpening = 0;  

// Check if this is a m2ts in a BD structure, and if it is, read some extra stream properties out of the CLPI files
if (m_pBluRay) {  
    m_pBluRay->ProcessClipLanguages();  
  } else if (pszFileName && m_bMPEGTS) {  
    CheckBDM2TSCPLI(pszFileName);  
  }  

  SAFE_CO_FREE(m_stOrigParser);  
  m_stOrigParser = (enum AVStreamParseType *)CoTaskMemAlloc(m_avFormat->nb_streams * sizeof(enum AVStreamParseType));  
if (!m_stOrigParser)  
return E_OUTOFMEMORY;  

for(unsigned int idx = 0; idx < m_avFormat->nb_streams; ++idx) {  
    AVStream *st = m_avFormat->streams[idx];  

// Disable full stream parsing for these formats
if (st->need_parsing == AVSTREAM_PARSE_FULL) {  
if (st->codec->codec_id == AV_CODEC_ID_DVB_SUBTITLE) {  
        st->need_parsing = AVSTREAM_PARSE_NONE;  
      }  
    }  

if (m_bOgg && st->codec->codec_id == AV_CODEC_ID_H264) {  
      st->need_parsing = AVSTREAM_PARSE_FULL;  
    }  

// Create the parsers with the appropriate flags
    init_parser(m_avFormat, st);  
    UpdateParserFlags(st);  

#ifdef DEBUG
    AVProgram *streamProg = av_find_program_from_stream(m_avFormat, NULL, idx);  
    DbgLog((LOG_TRACE, 30, L"Stream %d (pid %d) - program: %d, codec: %S; parsing: %S;", idx, st->id, streamProg ? streamProg->pmt_pid : -1, avcodec_get_name(st->codec->codec_id), lavf_get_parsing_string(st->need_parsing)));  
#endif
    m_stOrigParser[idx] = st->need_parsing;  

if ((st->codec->codec_id == AV_CODEC_ID_DTS && st->codec->codec_tag == 0xA2)  
     || (st->codec->codec_id == AV_CODEC_ID_EAC3 && st->codec->codec_tag == 0xA1))  
      st->disposition |= LAVF_DISPOSITION_SECONDARY_AUDIO;  

    UpdateSubStreams();  

if (st->codec->codec_type == AVMEDIA_TYPE_ATTACHMENT && (st->codec->codec_id == AV_CODEC_ID_TTF || st->codec->codec_id == AV_CODEC_ID_OTF)) {  
if (!m_pFontInstaller) {  
        m_pFontInstaller = new CFontInstaller();  
      }  
      m_pFontInstaller->InstallFont(st->codec->extradata, st->codec->extradata_size);  
    }  
  }  

  CHECK_HR(hr = CreateStreams());  

return S_OK;  
done:  
//关闭输入
  CleanupAVFormat();  
return E_FAIL;  
}

该函数通过avformat_find_stream_info()等获取到流信息,这里就不多说了。

3: LAV Video (1)

LAV Video 是使用很广泛的DirectShow Filter。它封装了FFMPEG中的libavcodec,支持十分广泛的视频格式的解码。在这里对其源代码进行详细的分析。

LAV Video 工程代码的结构如下图所示

直接看LAV Video最主要的类CLAVVideo吧,它的定义位于LAVVideo.h中。

LAVVideo.h

/* 雷霄骅
 * 中国传媒大学/数字电视技术
 * leixiaohua1020@126.com
 *
 */
/*
 *      Copyright (C) 2010-2013 Hendrik Leppkes
 *      http://www.1f0.de
 *
 *  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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#pragma once

#include "decoders/ILAVDecoder.h"
#include "DecodeThread.h"
#include "ILAVPinInfo.h"

#include "LAVPixFmtConverter.h"
#include "LAVVideoSettings.h"
#include "H264RandomAccess.h"
#include "FloatingAverage.h"

#include "ISpecifyPropertyPages2.h"
#include "SynchronizedQueue.h"

#include "subtitles/LAVSubtitleConsumer.h"
#include "subtitles/LAVVideoSubtitleInputPin.h"

#include "BaseTrayIcon.h"

#define LAVC_VIDEO_REGISTRY_KEY L"Software\\LAV\\Video"
#define LAVC_VIDEO_REGISTRY_KEY_FORMATS L"Software\\LAV\\Video\\Formats"
#define LAVC_VIDEO_REGISTRY_KEY_OUTPUT L"Software\\LAV\\Video\\Output"
#define LAVC_VIDEO_REGISTRY_KEY_HWACCEL L"Software\\LAV\\Video\\HWAccel"

#define LAVC_VIDEO_LOG_FILE     L"LAVVideo.txt"

#define DEBUG_FRAME_TIMINGS 0
#define DEBUG_PIXELCONV_TIMINGS 0

#define LAV_MT_FILTER_QUEUE_SIZE 4

typedef struct {  
  REFERENCE_TIME rtStart;  
  REFERENCE_TIME rtStop;  
} TimingCache;  
//解码核心类
//Transform Filter
[uuid("EE30215D-164F-4A92-A4EB-9D4C13390F9F")]  
class CLAVVideo : public CTransformFilter, public ISpecifyPropertyPages2, public ILAVVideoSettings, public ILAVVideoStatus, public ILAVVideoCallback  
{  
public:  
  CLAVVideo(LPUNKNOWN pUnk, HRESULT* phr);  
  ~CLAVVideo();  

static void CALLBACK StaticInit(BOOL bLoading, const CLSID *clsid);  

// IUnknown
// 查找接口必须实现
  DECLARE_IUNKNOWN;  
  STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void** ppv);  

// ISpecifyPropertyPages2
// 属性页
// 获取或者创建
  STDMETHODIMP GetPages(CAUUID *pPages);  
  STDMETHODIMP CreatePage(const GUID& guid, IPropertyPage** ppPage);  

// ILAVVideoSettings

  STDMETHODIMP SetRuntimeConfig(BOOL bRuntimeConfig);  
  STDMETHODIMP SetFormatConfiguration(LAVVideoCodec vCodec, BOOL bEnabled);  
  STDMETHODIMP_(BOOL) GetFormatConfiguration(LAVVideoCodec vCodec);  
  STDMETHODIMP SetNumThreads(DWORD dwNum);  
  STDMETHODIMP_(DWORD) GetNumThreads();  
  STDMETHODIMP SetStreamAR(DWORD bStreamAR);  
  STDMETHODIMP_(DWORD) GetStreamAR();  
  STDMETHODIMP SetPixelFormat(LAVOutPixFmts pixFmt, BOOL bEnabled);  
  STDMETHODIMP_(BOOL) GetPixelFormat(LAVOutPixFmts pixFmt);  
  STDMETHODIMP SetRGBOutputRange(DWORD dwRange);  
  STDMETHODIMP_(DWORD) GetRGBOutputRange();  

  STDMETHODIMP SetDeintFieldOrder(LAVDeintFieldOrder fieldOrder);  
  STDMETHODIMP_(LAVDeintFieldOrder) GetDeintFieldOrder();  
  STDMETHODIMP SetDeintForce(BOOL bForce);  
  STDMETHODIMP_(BOOL) GetDeintForce();  
  STDMETHODIMP SetDeintAggressive(BOOL bAggressive);  
  STDMETHODIMP_(BOOL) GetDeintAggressive();  

  STDMETHODIMP_(DWORD) CheckHWAccelSupport(LAVHWAccel hwAccel);  
  STDMETHODIMP SetHWAccel(LAVHWAccel hwAccel);  
  STDMETHODIMP_(LAVHWAccel) GetHWAccel();  
  STDMETHODIMP SetHWAccelCodec(LAVVideoHWCodec hwAccelCodec, BOOL bEnabled);  
  STDMETHODIMP_(BOOL) GetHWAccelCodec(LAVVideoHWCodec hwAccelCodec);  
  STDMETHODIMP SetHWAccelDeintMode(LAVHWDeintModes deintMode);  
  STDMETHODIMP_(LAVHWDeintModes) GetHWAccelDeintMode();  
  STDMETHODIMP SetHWAccelDeintOutput(LAVDeintOutput deintOutput);  
  STDMETHODIMP_(LAVDeintOutput) GetHWAccelDeintOutput();  
  STDMETHODIMP SetHWAccelDeintHQ(BOOL bHQ);  
  STDMETHODIMP_(BOOL) GetHWAccelDeintHQ();  
  STDMETHODIMP SetSWDeintMode(LAVSWDeintModes deintMode);  
  STDMETHODIMP_(LAVSWDeintModes) GetSWDeintMode();  
  STDMETHODIMP SetSWDeintOutput(LAVDeintOutput deintOutput);  
  STDMETHODIMP_(LAVDeintOutput) GetSWDeintOutput();  

  STDMETHODIMP SetDeintTreatAsProgressive(BOOL bEnabled);  
  STDMETHODIMP_(BOOL) GetDeintTreatAsProgressive();  

  STDMETHODIMP SetDitherMode(LAVDitherMode ditherMode);  
  STDMETHODIMP_(LAVDitherMode) GetDitherMode();  

  STDMETHODIMP SetUseMSWMV9Decoder(BOOL bEnabled);  
  STDMETHODIMP_(BOOL) GetUseMSWMV9Decoder();  

  STDMETHODIMP SetDVDVideoSupport(BOOL bEnabled);  
  STDMETHODIMP_(BOOL) GetDVDVideoSupport();  

  STDMETHODIMP SetHWAccelResolutionFlags(DWORD dwResFlags);  
  STDMETHODIMP_(DWORD) GetHWAccelResolutionFlags();  

  STDMETHODIMP SetTrayIcon(BOOL bEnabled);  
  STDMETHODIMP_(BOOL) GetTrayIcon();  

  STDMETHODIMP SetDeinterlacingMode(LAVDeintMode deintMode);  
  STDMETHODIMP_(LAVDeintMode) GetDeinterlacingMode();  

// ILAVVideoStatus
  STDMETHODIMP_(const WCHAR *) GetActiveDecoderName() { return m_Decoder.GetDecoderName(); }  

// CTransformFilter
// 核心的
HRESULT CheckInputType(const CMediaType* mtIn);  
HRESULT CheckTransform(const CMediaType* mtIn, const CMediaType* mtOut);  
HRESULT DecideBufferSize(IMemAllocator * pAllocator, ALLOCATOR_PROPERTIES *pprop);  
HRESULT GetMediaType(int iPosition, CMediaType *pMediaType);  

HRESULT SetMediaType(PIN_DIRECTION dir, const CMediaType *pmt);  
HRESULT EndOfStream();  
HRESULT BeginFlush();  
HRESULT EndFlush();  
HRESULT NewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate);  
//处理的核心
//核心一般才有IMediaSample
HRESULT Receive(IMediaSample *pIn);  

HRESULT CheckConnect(PIN_DIRECTION dir, IPin *pPin);  
HRESULT BreakConnect(PIN_DIRECTION dir);  
HRESULT CompleteConnect(PIN_DIRECTION dir, IPin *pReceivePin);  

int GetPinCount();  
  CBasePin* GetPin(int n);  

  STDMETHODIMP JoinFilterGraph(IFilterGraph * pGraph, LPCWSTR pName);  

// ILAVVideoCallback
  STDMETHODIMP AllocateFrame(LAVFrame **ppFrame);  
  STDMETHODIMP ReleaseFrame(LAVFrame **ppFrame);  
  STDMETHODIMP Deliver(LAVFrame *pFrame);  
  STDMETHODIMP_(LPWSTR) GetFileExtension();  
  STDMETHODIMP_(BOOL) FilterInGraph(PIN_DIRECTION dir, const GUID &clsid) { if (dir == PINDIR_INPUT) return FilterInGraphSafe(m_pInput, clsid); else return FilterInGraphSafe(m_pOutput, clsid); }  
  STDMETHODIMP_(DWORD) GetDecodeFlags() { return m_dwDecodeFlags; }  
  STDMETHODIMP_(CMediaType&) GetInputMediaType() { return m_pInput->CurrentMediaType(); }  
  STDMETHODIMP GetLAVPinInfo(LAVPinInfo &info) { if (m_LAVPinInfoValid) { info = m_LAVPinInfo; return S_OK; } return E_FAIL; }  
  STDMETHODIMP_(CBasePin*) GetOutputPin() { return m_pOutput; }  
  STDMETHODIMP_(CMediaType&) GetOutputMediaType() { return m_pOutput->CurrentMediaType(); }  
  STDMETHODIMP DVDStripPacket(BYTE*& p, long& len) { static_cast<CDeCSSTransformInputPin*>(m_pInput)->StripPacket(p, len); return S_OK; }  
  STDMETHODIMP_(LAVFrame*) GetFlushFrame();  
  STDMETHODIMP ReleaseAllDXVAResources() { ReleaseLastSequenceFrame(); return S_OK; }  

public:  
// Pin Configuration
const static AMOVIESETUP_MEDIATYPE    sudPinTypesIn[];  
const static int                      sudPinTypesInCount;  
const static AMOVIESETUP_MEDIATYPE    sudPinTypesOut[];  
const static int                      sudPinTypesOutCount;  

private:  
HRESULT LoadDefaults();  
HRESULT ReadSettings(HKEY rootKey);  
HRESULT LoadSettings();  
HRESULT SaveSettings();  

HRESULT CreateTrayIcon();  

HRESULT CreateDecoder(const CMediaType *pmt);  

HRESULT GetDeliveryBuffer(IMediaSample** ppOut, int width, int height, AVRational ar, DXVA2_ExtendedFormat dxvaExtFormat, REFERENCE_TIME avgFrameDuration);  
HRESULT ReconnectOutput(int width, int height, AVRational ar, DXVA2_ExtendedFormat dxvaExtFlags, REFERENCE_TIME avgFrameDuration, BOOL bDXVA = FALSE);  

HRESULT SetFrameFlags(IMediaSample* pMS, LAVFrame *pFrame);  

HRESULT NegotiatePixelFormat(CMediaType &mt, int width, int height);  
BOOL IsInterlaced();  


HRESULT Filter(LAVFrame *pFrame);  
HRESULT DeliverToRenderer(LAVFrame *pFrame);  

HRESULT PerformFlush();  
HRESULT ReleaseLastSequenceFrame();  

HRESULT GetD3DBuffer(LAVFrame *pFrame);  
HRESULT RedrawStillImage();  
HRESULT SetInDVDMenu(bool menu) { m_bInDVDMenu = menu; return S_OK; }  

enum {CNTRL_EXIT, CNTRL_REDRAW};  
HRESULT ControlCmd(DWORD cmd) {  
return m_ControlThread->CallWorker(cmd);  
  }  

private:  
friend class CVideoOutputPin;  
friend class CDecodeThread;  
friend class CLAVControlThread;  
friend class CLAVSubtitleProvider;  
friend class CLAVSubtitleConsumer;  
//解码线程
  CDecodeThread        m_Decoder;  
  CAMThread            *m_ControlThread;  

  REFERENCE_TIME       m_rtPrevStart;  
  REFERENCE_TIME       m_rtPrevStop;  

BOOL                 m_bForceInputAR;  
BOOL                 m_bSendMediaType;  
BOOL                 m_bFlushing;  

HRESULT              m_hrDeliver;  

  CLAVPixFmtConverter  m_PixFmtConverter;  
  std::wstring         m_strExtension;  

DWORD                m_bDXVAExtFormatSupport;  
DWORD                m_bMadVR;  
DWORD                m_bOverlayMixer;  
DWORD                m_dwDecodeFlags;  

BOOL                 m_bInDVDMenu;  

  AVFilterGraph        *m_pFilterGraph;  
  AVFilterContext      *m_pFilterBufferSrc;  
  AVFilterContext      *m_pFilterBufferSink;  

  LAVPixelFormat       m_filterPixFmt;  
int                  m_filterWidth;  
int                  m_filterHeight;  
  LAVFrame             m_FilterPrevFrame;  

BOOL                 m_LAVPinInfoValid;  
  LAVPinInfo           m_LAVPinInfo;  

  CLAVVideoSubtitleInputPin *m_pSubtitleInput;  
  CLAVSubtitleConsumer *m_SubtitleConsumer;  

  LAVFrame             *m_pLastSequenceFrame;  

  AM_SimpleRateChange  m_DVDRate;  

BOOL                 m_bRuntimeConfig;  
struct VideoSettings {  
BOOL TrayIcon;  
DWORD StreamAR;  
DWORD NumThreads;  
BOOL bFormats[Codec_VideoNB];  
BOOL bMSWMV9DMO;  
BOOL bPixFmts[LAVOutPixFmt_NB];  
DWORD RGBRange;  
DWORD HWAccel;  
BOOL bHWFormats[HWCodec_NB];  
DWORD HWAccelResFlags;  
DWORD HWDeintMode;  
DWORD HWDeintOutput;  
BOOL HWDeintHQ;  
DWORD DeintFieldOrder;  
    LAVDeintMode DeintMode;  
DWORD SWDeintMode;  
DWORD SWDeintOutput;  
DWORD DitherMode;  
BOOL bDVDVideo;  
  } m_settings;  

  CBaseTrayIcon *m_pTrayIcon;  

#ifdef DEBUG
  FloatingAverage<double> m_pixFmtTimingAvg;  
#endif
};

可见该类继承了CTransformFilter,其的功能真的是非常丰富的。在这里肯定无法对其进行一一分析,只能选择其中重点的函数进行一下分析。

该类中包含了解码线程类:CDecodeThread        m_Decoder;,这里封装了解码功能。

同时该类中包含了函数Receive(IMediaSample *pIn);,是发挥解码功能的函数,其中pIn是输入的解码前的视频压缩编码数据。

下面来看看Receive()函数:

//处理的核心
//核心一般才有IMediaSample
HRESULT CLAVVideo::Receive(IMediaSample *pIn)  
{  
  CAutoLock cAutoLock(&m_csReceive);  
HRESULT        hr = S_OK;  

  AM_SAMPLE2_PROPERTIES const *pProps = m_pInput->SampleProps();  
if(pProps->dwStreamId != AM_STREAM_MEDIA) {  
return m_pOutput->Deliver(pIn);  
  }  

  AM_MEDIA_TYPE *pmt = NULL;  
//获取媒体类型等等
if (SUCCEEDED(pIn->GetMediaType(&pmt)) && pmt) {  
    CMediaType mt = *pmt;  
    DeleteMediaType(pmt);  
if (mt != m_pInput->CurrentMediaType() || !(m_dwDecodeFlags & LAV_VIDEO_DEC_FLAG_DVD)) {  
      DbgLog((LOG_TRACE, 10, L"::Receive(): Input sample contained media type, dynamic format change..."));  
      m_Decoder.EndOfStream();  
      hr = m_pInput->SetMediaType(&mt);  
if (FAILED(hr)) {  
        DbgLog((LOG_ERROR, 10, L"::Receive(): Setting new media type failed..."));  
return hr;  
      }  
    }  
  }  

  m_hrDeliver = S_OK;  

// Skip over empty packets
if (pIn->GetActualDataLength() == 0) {  
return S_OK;  
  }  
//解码
  hr = m_Decoder.Decode(pIn);  
if (FAILED(hr))  
return hr;  

if (FAILED(m_hrDeliver))  
return m_hrDeliver;  

return S_OK;  
}

由代码我们可以看出,实际发挥出解码功能的函数是hr = m_Decoder.Decode(pIn);。

下面我们来看看CDecodeThread类的Decode()方法:

//解码线程的解码函数
STDMETHODIMP CDecodeThread::Decode(IMediaSample *pSample)  
{  
  CAutoLock decoderLock(this);  

if (!CAMThread::ThreadExists())  
return E_UNEXPECTED;  

// Wait until the queue is empty
while(HasSample())  
    Sleep(1);  

// Re-init the decoder, if requested
// Doing this inside the worker thread alone causes problems
// when switching from non-sync to sync, so ensure we're in sync.
if (m_bDecoderNeedsReInit) {  
    CAMThread::CallWorker(CMD_REINIT);  
while (!m_evEOSDone.Check()) {  
      m_evSample.Wait();  
      ProcessOutput();  
    }  
  }  

  m_evDeliver.Reset();  
  m_evSample.Reset();  
  m_evDecodeDone.Reset();  

  pSample->AddRef();  

// Send data to worker thread, and wake it (if it was waiting)
  PutSample(pSample);  

// If we don't have thread safe buffers, we need to synchronize
// with the worker thread and deliver them when they are available
// and then let it know that we did so
if (m_bSyncToProcess) {  
while (!m_evDecodeDone.Check()) {  
      m_evSample.Wait();  
      ProcessOutput();  
    }  
  }  

  ProcessOutput();  

return S_OK;  
}

这个方法乍一看感觉很抽象,好像没看见直接调用任何解码的函数。如果LAVVideo的封装的ffmpeg的libavcodec的话,应该是最终调用avcodec_decode_video2()才对啊。。。先来看看CDecodeThread这个类的定义吧!

DecodeThread.h

/* 雷霄骅
 * 中国传媒大学/数字电视技术
 * leixiaohua1020@126.com
 *
 */
/*
 *      Copyright (C) 2010-2013 Hendrik Leppkes
 *      http://www.1f0.de
 *
 *  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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#pragma once

#include "decoders/ILAVDecoder.h"
#include "SynchronizedQueue.h"

class CLAVVideo;  

class CDecodeThread : public ILAVVideoCallback, protected CAMThread, protected CCritSec  
{  
public:  
  CDecodeThread(CLAVVideo *pLAVVideo);  
  ~CDecodeThread();  

// Parts of ILAVDecoder
  STDMETHODIMP_(const WCHAR*) GetDecoderName() { return m_pDecoder ? m_pDecoder->GetDecoderName() : NULL; }  
  STDMETHODIMP_(long) GetBufferCount() { return m_pDecoder ? m_pDecoder->GetBufferCount() : 4; }  
  STDMETHODIMP_(BOOL) IsInterlaced() { return m_pDecoder ? m_pDecoder->IsInterlaced() : TRUE; }  
  STDMETHODIMP GetPixelFormat(LAVPixelFormat *pPix, int *pBpp) { ASSERT(m_pDecoder); return m_pDecoder->GetPixelFormat(pPix, pBpp); }  
  STDMETHODIMP_(REFERENCE_TIME) GetFrameDuration() { ASSERT(m_pDecoder); return m_pDecoder->GetFrameDuration(); }  
  STDMETHODIMP HasThreadSafeBuffers() { return m_pDecoder ? m_pDecoder->HasThreadSafeBuffers() : S_FALSE; }  


  STDMETHODIMP CreateDecoder(const CMediaType *pmt, AVCodecID codec);  
  STDMETHODIMP Close();  
//解码线程的解码函数
  STDMETHODIMP Decode(IMediaSample *pSample);  
  STDMETHODIMP Flush();  
  STDMETHODIMP EndOfStream();  

  STDMETHODIMP InitAllocator(IMemAllocator **ppAlloc);  
  STDMETHODIMP PostConnect(IPin *pPin);  

  STDMETHODIMP_(BOOL) IsHWDecoderActive() { return m_bHWDecoder; }  

// ILAVVideoCallback
  STDMETHODIMP AllocateFrame(LAVFrame **ppFrame);  
  STDMETHODIMP ReleaseFrame(LAVFrame **ppFrame);  
  STDMETHODIMP Deliver(LAVFrame *pFrame);  
  STDMETHODIMP_(LPWSTR) GetFileExtension();  
  STDMETHODIMP_(BOOL) FilterInGraph(PIN_DIRECTION dir, const GUID &clsid);  
  STDMETHODIMP_(DWORD) GetDecodeFlags();  
  STDMETHODIMP_(CMediaType&) GetInputMediaType();  
  STDMETHODIMP GetLAVPinInfo(LAVPinInfo &info);  
  STDMETHODIMP_(CBasePin*) GetOutputPin();  
  STDMETHODIMP_(CMediaType&) GetOutputMediaType();  
  STDMETHODIMP DVDStripPacket(BYTE*& p, long& len);  
  STDMETHODIMP_(LAVFrame*) GetFlushFrame();  
  STDMETHODIMP ReleaseAllDXVAResources();  

protected:  
//包含了对进程的各种操作,重要
DWORD ThreadProc();  

private:  
  STDMETHODIMP CreateDecoderInternal(const CMediaType *pmt, AVCodecID codec);  
  STDMETHODIMP PostConnectInternal(IPin *pPin);  

  STDMETHODIMP DecodeInternal(IMediaSample *pSample);  
  STDMETHODIMP ClearQueues();  
  STDMETHODIMP ProcessOutput();  

bool HasSample();  
void PutSample(IMediaSample *pSample);  
  IMediaSample* GetSample();  
void ReleaseSample();  

bool CheckForEndOfSequence(IMediaSample *pSample);  

private:  
//各种对进程进行的操作
enum {CMD_CREATE_DECODER, CMD_CLOSE_DECODER, CMD_FLUSH, CMD_EOS, CMD_EXIT, CMD_INIT_ALLOCATOR, CMD_POST_CONNECT, CMD_REINIT};  
//注意DecodeThread像是一个处于中间位置的东西
//连接了Filter核心类CLAVVideo和解码器的接口ILAVDecoder
  CLAVVideo    *m_pLAVVideo;  
  ILAVDecoder  *m_pDecoder;  

  AVCodecID    m_Codec;  

BOOL         m_bHWDecoder;  
BOOL         m_bHWDecoderFailed;  

BOOL         m_bSyncToProcess;  
BOOL         m_bDecoderNeedsReInit;  
  CAMEvent     m_evInput;  
  CAMEvent     m_evDeliver;  
  CAMEvent     m_evSample;  
  CAMEvent     m_evDecodeDone;  
  CAMEvent     m_evEOSDone;  

  CCritSec     m_ThreadCritSec;  
struct {  
const CMediaType *pmt;  
    AVCodecID codec;  
    IMemAllocator **allocator;  
    IPin *pin;  
  } m_ThreadCallContext;  
  CSynchronizedQueue<LAVFrame *> m_Output;  

  CCritSec     m_SampleCritSec;  
  IMediaSample *m_NextSample;  

  IMediaSample *m_TempSample[2];  
  IMediaSample *m_FailedSample;  

  std::wstring m_processName;  
};

从名字上我们可以判断,这个类用于管理解码的线程。在这里我们关注该类里面的两个指针变量:

  CLAVVideo    *m_pLAVVideo;
  ILAVDecoder  *m_pDecoder;

其中第一个指针变量就是这个工程中最核心的类CLAVVideo,而第二个指针变量则是解码器的接口。通过这个接口就可以调用具体解码器的相应方法了。(注:在源代码中发现,解码器不光包含libavcodec,也可以是wmv9等等,换句话说,是可以扩展其他种类的解码器的。不过就目前的情况来看,lavvideo似乎不如ffdshow支持的解码器种类多)

该类里面还有一个函数:

ThreadProc()

该函数中包含了对线程的各种操作,其中包含调用了ILAVDecoder接口的各种方法:

//包含了对进程的各种操作
DWORD CDecodeThread::ThreadProc()  
{  
HRESULT hr;  
DWORD cmd;  

BOOL bEOS = FALSE;  
BOOL bReinit = FALSE;  

  SetThreadName(-1, "LAVVideo Decode Thread");  

HANDLE hWaitEvents[2] = { GetRequestHandle(), m_evInput };  
//不停转圈,永不休止
while(1) {  
if (!bEOS && !bReinit) {  
// Wait for either an input sample, or an request
      WaitForMultipleObjects(2, hWaitEvents, FALSE, INFINITE);  
    }  
//根据操作命令的不同
if (CheckRequest(&cmd)) {  
switch (cmd) {  
//创建解码器
case CMD_CREATE_DECODER:  
        {  
          CAutoLock lock(&m_ThreadCritSec);  
//创建
          hr = CreateDecoderInternal(m_ThreadCallContext.pmt, m_ThreadCallContext.codec);  
          Reply(hr);  

          m_ThreadCallContext.pmt = NULL;  
        }  
break;  
case CMD_CLOSE_DECODER:  
        {  
//关闭
          ClearQueues();  
          SAFE_DELETE(m_pDecoder);  
          Reply(S_OK);  
        }  
break;  
case CMD_FLUSH:  
        {  
//清楚
          ClearQueues();  
          m_pDecoder->Flush();  
          Reply(S_OK);  
        }  
break;  
case CMD_EOS:  
        {  
          bEOS = TRUE;  
          m_evEOSDone.Reset();  
          Reply(S_OK);  
        }  
break;  
case CMD_EXIT:  
        {  
//退出
          Reply(S_OK);  
return 0;  
        }  
break;  
case CMD_INIT_ALLOCATOR:  
        {  
          CAutoLock lock(&m_ThreadCritSec);  
          hr = m_pDecoder->InitAllocator(m_ThreadCallContext.allocator);  
          Reply(hr);  

          m_ThreadCallContext.allocator = NULL;  
        }  
break;  
case CMD_POST_CONNECT:  
        {  
          CAutoLock lock(&m_ThreadCritSec);  
          hr = PostConnectInternal(m_ThreadCallContext.pin);  
          Reply(hr);  

          m_ThreadCallContext.pin = NULL;  
        }  
break;  
case CMD_REINIT:  
        {  
//重启
          CMediaType &mt = m_pLAVVideo->GetInputMediaType();  
          CreateDecoderInternal(&mt, m_Codec);  
          m_TempSample[1] = m_NextSample;  
          m_NextSample = m_FailedSample;  
          m_FailedSample = NULL;  
          bReinit = TRUE;  
          m_evEOSDone.Reset();  
          Reply(S_OK);  
          m_bDecoderNeedsReInit = FALSE;  
        }  
break;  
default:  
        ASSERT(0);  
      }  
    }  

if (m_bDecoderNeedsReInit) {  
      m_evInput.Reset();  
continue;  
    }  

if (bReinit && !m_NextSample) {  
if (m_TempSample[0]) {  
        m_NextSample = m_TempSample[0];  
        m_TempSample[0] = NULL;  
      } else if (m_TempSample[1]) {  
        m_NextSample = m_TempSample[1];  
        m_TempSample[1] = NULL;  
      } else {  
        bReinit = FALSE;  
        m_evEOSDone.Set();  
        m_evSample.Set();  
continue;  
      }  
    }  
//获得一份数据
    IMediaSample *pSample = GetSample();  
if (!pSample) {  
// Process the EOS now that the sample queue is empty
if (bEOS) {  
        bEOS = FALSE;  
        m_pDecoder->EndOfStream();  
        m_evEOSDone.Set();  
        m_evSample.Set();  
      }  
continue;  
    }  
//解码
    DecodeInternal(pSample);  

// Release the sample
//释放
    SafeRelease(&pSample);  

// Indicates we're done decoding this sample
    m_evDecodeDone.Set();  

// Set the Sample Event to unblock any waiting threads
    m_evSample.Set();  
  }  

return 0;  
}

先分析到这里了,至于ILAVDecoder接口方面的东西下篇文章再写。

4: LAV Video (2)

在这里继续上篇文章的内容。文章中提到LAVVideo主要通过CDecodeThread这个类进行解码线程的管理,其中有一个关键的管理函数:ThreadProc(),包含了对解码线程的各种操作。函数如下所示:

//包含了对进程的各种操作
DWORD CDecodeThread::ThreadProc()  
{  
HRESULT hr;  
DWORD cmd;  

BOOL bEOS = FALSE;  
BOOL bReinit = FALSE;  

  SetThreadName(-1, "LAVVideo Decode Thread");  

HANDLE hWaitEvents[2] = { GetRequestHandle(), m_evInput };  
//不停转圈,永不休止
while(1) {  
if (!bEOS && !bReinit) {  
// Wait for either an input sample, or an request
      WaitForMultipleObjects(2, hWaitEvents, FALSE, INFINITE);  
    }  
//根据操作命令的不同
if (CheckRequest(&cmd)) {  
switch (cmd) {  
//创建解码器
case CMD_CREATE_DECODER:  
        {  
          CAutoLock lock(&m_ThreadCritSec);  
//创建
          hr = CreateDecoderInternal(m_ThreadCallContext.pmt, m_ThreadCallContext.codec);  
          Reply(hr);  

          m_ThreadCallContext.pmt = NULL;  
        }  
break;  
case CMD_CLOSE_DECODER:  
        {  
//关闭
          ClearQueues();  
          SAFE_DELETE(m_pDecoder);  
          Reply(S_OK);  
        }  
break;  
case CMD_FLUSH:  
        {  
//清楚
          ClearQueues();  
          m_pDecoder->Flush();  
          Reply(S_OK);  
        }  
break;  
case CMD_EOS:  
        {  
          bEOS = TRUE;  
          m_evEOSDone.Reset();  
          Reply(S_OK);  
        }  
break;  
case CMD_EXIT:  
        {  
//退出
          Reply(S_OK);  
return 0;  
        }  
break;  
case CMD_INIT_ALLOCATOR:  
        {  
          CAutoLock lock(&m_ThreadCritSec);  
          hr = m_pDecoder->InitAllocator(m_ThreadCallContext.allocator);  
          Reply(hr);  

          m_ThreadCallContext.allocator = NULL;  
        }  
break;  
case CMD_POST_CONNECT:  
        {  
          CAutoLock lock(&m_ThreadCritSec);  
          hr = PostConnectInternal(m_ThreadCallContext.pin);  
          Reply(hr);  

          m_ThreadCallContext.pin = NULL;  
        }  
break;  
case CMD_REINIT:  
        {  
//重启
          CMediaType &mt = m_pLAVVideo->GetInputMediaType();  
          CreateDecoderInternal(&mt, m_Codec);  
          m_TempSample[1] = m_NextSample;  
          m_NextSample = m_FailedSample;  
          m_FailedSample = NULL;  
          bReinit = TRUE;  
          m_evEOSDone.Reset();  
          Reply(S_OK);  
          m_bDecoderNeedsReInit = FALSE;  
        }  
break;  
default:  
        ASSERT(0);  
      }  
    }  

if (m_bDecoderNeedsReInit) {  
      m_evInput.Reset();  
continue;  
    }  

if (bReinit && !m_NextSample) {  
if (m_TempSample[0]) {  
        m_NextSample = m_TempSample[0];  
        m_TempSample[0] = NULL;  
      } else if (m_TempSample[1]) {  
        m_NextSample = m_TempSample[1];  
        m_TempSample[1] = NULL;  
      } else {  
        bReinit = FALSE;  
        m_evEOSDone.Set();  
        m_evSample.Set();  
continue;  
      }  
    }  
//获得一份数据
    IMediaSample *pSample = GetSample();  
if (!pSample) {  
// Process the EOS now that the sample queue is empty
if (bEOS) {  
        bEOS = FALSE;  
        m_pDecoder->EndOfStream();  
        m_evEOSDone.Set();  
        m_evSample.Set();  
      }  
continue;  
    }  
//解码
    DecodeInternal(pSample);  

// Release the sample
//释放
    SafeRelease(&pSample);  

// Indicates we're done decoding this sample
    m_evDecodeDone.Set();  

// Set the Sample Event to unblock any waiting threads
    m_evSample.Set();  
  }  

return 0;  
}

该函数中,DecodeInternal(pSample)为实际上真正具有解码功能的函数,来看看它的源代码吧:

STDMETHODIMP CDecodeThread::DecodeInternal(IMediaSample *pSample)  
{  
HRESULT hr = S_OK;  

if (!m_pDecoder)  
return E_UNEXPECTED;  
//调用接口进行解码
  hr = m_pDecoder->Decode(pSample);  

// If a hardware decoder indicates a hard failure, we switch back to software
// This is used to indicate incompatible media
if (FAILED(hr) && m_bHWDecoder) {  
    DbgLog((LOG_TRACE, 10, L"::Receive(): Hardware decoder indicates failure, switching back to software"));  
    m_bHWDecoderFailed = TRUE;  

// Store the failed sample for re-try in a moment
    m_FailedSample = pSample;  
    m_FailedSample->AddRef();  

// Schedule a re-init when the main thread goes there the next time
    m_bDecoderNeedsReInit = TRUE;  

// Make room in the sample buffer, to ensure the main thread can get in
    m_TempSample[0] = GetSample();  
  }  

return S_OK;  
}

该函数比较简短,从源代码中可以看出,调用了m_pDecoder的Decode()方法。其中m_pDecoder为ILAVDecoder类型的指针,而ILAVDecoder是一个接口,并不包含实际的方法,如下所示。注意,从程序注释中可以看出,每一个解码器都需要实现该接口规定的函数。

/**
 * Decoder interface
 *
 * Every decoder needs to implement this to interface with the LAV Video core
 */
//接口
interface ILAVDecoder  
{  
/**
   * Virtual destructor
   */
virtual ~ILAVDecoder(void) {};  

/**
   * Initialize interfaces with the LAV Video core
   * This function should also be used to create all interfaces with external DLLs
   *
   * @param pSettings reference to the settings interface
   * @param pCallback reference to the callback interface
   * @return S_OK on success, error code if this decoder is lacking an external support dll
   */
  STDMETHOD(InitInterfaces)(ILAVVideoSettings *pSettings, ILAVVideoCallback *pCallback) PURE;  

/**
   * Check if the decoder is functional
   */
  STDMETHOD(Check)() PURE;  

/**
   * Initialize the codec to decode a stream specified by codec and pmt.
   *
   * @param codec Codec Id
   * @param pmt DirectShow Media Type
   * @return S_OK on success, an error code otherwise
   */
  STDMETHOD(InitDecoder)(AVCodecID codec, const CMediaType *pmt) PURE;  

/**
   * Decode a frame.
   *
   * @param pSample Media Sample to decode
   * @return S_OK if decoding was successfull, S_FALSE if no frame could be extracted, an error code if the decoder is not compatible with the bitstream
   *
   * Note: When returning an actual error code, the filter will switch to the fallback software decoder! This should only be used for catastrophic failures,
   * like trying to decode a unsupported format on a hardware decoder.
   */
  STDMETHOD(Decode)(IMediaSample *pSample) PURE;  

/**
   * Flush the decoder after a seek.
   * The decoder should discard any remaining data.
   *
   * @return unused
   */
  STDMETHOD(Flush)() PURE;  

/**
   * End of Stream
   * The decoder is asked to output any buffered frames for immediate delivery
   *
   * @return unused
   */
  STDMETHOD(EndOfStream)() PURE;  

/**
   * Query the decoder for the current pixel format
   * Mostly used by the media type creation logic before playback starts
   *
   * @return the pixel format used in the decoding process
   */
  STDMETHOD(GetPixelFormat)(LAVPixelFormat *pPix, int *pBpp) PURE;  

/**
   * Get the frame duration.
   *
   * This function is not mandatory, and if you cannot provide any specific duration, return 0.
   */
  STDMETHOD_(REFERENCE_TIME, GetFrameDuration)() PURE;  

/**
   * Query whether the format can potentially be interlaced.
   * This function should return false if the format can 100% not be interlaced, and true if it can be interlaced (but also progressive).
   */
  STDMETHOD_(BOOL, IsInterlaced)() PURE;  

/**
   * Allows the decoder to handle an allocator.
   * Used by DXVA2 decoding
   */
  STDMETHOD(InitAllocator)(IMemAllocator **ppAlloc) PURE;  

/**
   * Function called after connection is established, with the pin as argument
   */
  STDMETHOD(PostConnect)(IPin *pPin) PURE;  

/**
   * Get the number of sample buffers optimal for this decoder
   */
  STDMETHOD_(long, GetBufferCount)() PURE;  

/**
   * Get the name of the decoder
   */
  STDMETHOD_(const WCHAR*, GetDecoderName)() PURE;  

/**
   * Get whether the decoder outputs thread-safe buffers
   */
  STDMETHOD(HasThreadSafeBuffers)() PURE;  

/**
   * Get whether the decoder should sync to the main thread
   */
  STDMETHOD(SyncToProcessThread)() PURE;  
};

下面来看看封装libavcodec库的类吧,该类的定义位于decoders文件夹下,名为avcodec.h,如图所示:

该类名字叫CDecAvcodec,其继承了CDecBase。而CDecBase继承了ILAVDecoder。

/* 雷霄骅
 * 中国传媒大学/数字电视技术
 * leixiaohua1020@126.com
 *
 */
/*
 *      Copyright (C) 2010-2013 Hendrik Leppkes
 *      http://www.1f0.de
 *
 *  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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#pragma once

#include "DecBase.h"
#include "H264RandomAccess.h"

#include <map>

#define AVCODEC_MAX_THREADS 16

typedef struct {  
  REFERENCE_TIME rtStart;  
  REFERENCE_TIME rtStop;  
} TimingCache;  
//解码器(AVCODEC)(其实还有WMV9,CUVID等)
class CDecAvcodec : public CDecBase  
{  
public:  
  CDecAvcodec(void);  
virtual ~CDecAvcodec(void);  

// ILAVDecoder
  STDMETHODIMP InitDecoder(AVCodecID codec, const CMediaType *pmt);  
//解码
  STDMETHODIMP Decode(const BYTE *buffer, int buflen, REFERENCE_TIME rtStart, REFERENCE_TIME rtStop, BOOL bSyncPoint, BOOL bDiscontinuity);  
  STDMETHODIMP Flush();  
  STDMETHODIMP EndOfStream();  
  STDMETHODIMP GetPixelFormat(LAVPixelFormat *pPix, int *pBpp);  
  STDMETHODIMP_(REFERENCE_TIME) GetFrameDuration();  
  STDMETHODIMP_(BOOL) IsInterlaced();  
  STDMETHODIMP_(const WCHAR*) GetDecoderName() { return L"avcodec"; }  
  STDMETHODIMP HasThreadSafeBuffers() { return S_OK; }  
  STDMETHODIMP SyncToProcessThread() { return m_pAVCtx && m_pAVCtx->thread_count > 1 ? S_OK : S_FALSE; }  

// CDecBase
  STDMETHODIMP Init();  

protected:  
virtual HRESULT AdditionaDecoderInit() { return S_FALSE; }  
virtual HRESULT PostDecode() { return S_FALSE; }  
virtual HRESULT HandleDXVA2Frame(LAVFrame *pFrame) { return S_FALSE; }  
//销毁解码器,各种Free
  STDMETHODIMP DestroyDecoder();  

private:  
  STDMETHODIMP ConvertPixFmt(AVFrame *pFrame, LAVFrame *pOutFrame);  

protected:  
  AVCodecContext       *m_pAVCtx;  
  AVFrame              *m_pFrame;  
  AVCodecID            m_nCodecId;  
BOOL                 m_bDXVA;  

private:  
  AVCodec              *m_pAVCodec;  
  AVCodecParserContext *m_pParser;  

BYTE                 *m_pFFBuffer;  
BYTE                 *m_pFFBuffer2;  
int                  m_nFFBufferSize;  
int                  m_nFFBufferSize2;  

  SwsContext           *m_pSwsContext;  

  CH264RandomAccess    m_h264RandomAccess;  

BOOL                 m_bNoBufferConsumption;  
BOOL                 m_bHasPalette;  

// Timing settings
BOOL                 m_bFFReordering;  
BOOL                 m_bCalculateStopTime;  
BOOL                 m_bRVDropBFrameTimings;  
BOOL                 m_bInputPadded;  

BOOL                 m_bBFrameDelay;  
  TimingCache          m_tcBFrameDelay[2];  
int                  m_nBFramePos;  

  TimingCache          m_tcThreadBuffer[AVCODEC_MAX_THREADS];  
int                  m_CurrentThread;  

  REFERENCE_TIME       m_rtStartCache;  
BOOL                 m_bResumeAtKeyFrame;  
BOOL                 m_bWaitingForKeyFrame;  
int                  m_iInterlaced;  
};

从CDecAvcodec类的定义可以看出,包含了各种功能的函数。首先我们看看初始化函数Init()

// ILAVDecoder
STDMETHODIMP CDecAvcodec::Init()  
{  
#ifdef DEBUG
  DbgSetModuleLevel (LOG_CUSTOM1, DWORD_MAX); // FFMPEG messages use custom1
  av_log_set_callback(lavf_log_callback);  
#else
  av_log_set_callback(NULL);  
#endif
//注册
  avcodec_register_all();  
return S_OK;  
}

可见其调用了ffmpeg的API函数avcodec_register_all()进行了解码器的注册。

我们再来看看其解码函数Decode():

//解码
STDMETHODIMP CDecAvcodec::Decode(const BYTE *buffer, int buflen, REFERENCE_TIME rtStartIn, REFERENCE_TIME rtStopIn, BOOL bSyncPoint, BOOL bDiscontinuity)  
{  
int     got_picture = 0;  
int     used_bytes  = 0;  
BOOL    bParserFrame = FALSE;  
BOOL    bFlush = (buffer == NULL);  
BOOL    bEndOfSequence = FALSE;  
//初始化Packet
  AVPacket avpkt;  
  av_init_packet(&avpkt);  

if (m_pAVCtx->active_thread_type & FF_THREAD_FRAME) {  
if (!m_bFFReordering) {  
      m_tcThreadBuffer[m_CurrentThread].rtStart = rtStartIn;  
      m_tcThreadBuffer[m_CurrentThread].rtStop  = rtStopIn;  
    }  

    m_CurrentThread = (m_CurrentThread + 1) % m_pAVCtx->thread_count;  
  } else if (m_bBFrameDelay) {  
    m_tcBFrameDelay[m_nBFramePos].rtStart = rtStartIn;  
    m_tcBFrameDelay[m_nBFramePos].rtStop = rtStopIn;  
    m_nBFramePos = !m_nBFramePos;  
  }  

  uint8_t *pDataBuffer = NULL;  
if (!bFlush && buflen > 0) {  
if (!m_bInputPadded && (!(m_pAVCtx->active_thread_type & FF_THREAD_FRAME) || m_pParser)) {  
// Copy bitstream into temporary buffer to ensure overread protection
// Verify buffer size
if (buflen > m_nFFBufferSize) {  
        m_nFFBufferSize = buflen;  
        m_pFFBuffer = (BYTE *)av_realloc_f(m_pFFBuffer, m_nFFBufferSize + FF_INPUT_BUFFER_PADDING_SIZE, 1);  
if (!m_pFFBuffer) {  
          m_nFFBufferSize = 0;  
return E_OUTOFMEMORY;  
        }  
      }  

      memcpy(m_pFFBuffer, buffer, buflen);  
      memset(m_pFFBuffer+buflen, 0, FF_INPUT_BUFFER_PADDING_SIZE);  
      pDataBuffer = m_pFFBuffer;  
    } else {  
      pDataBuffer = (uint8_t *)buffer;  
    }  

if (m_nCodecId == AV_CODEC_ID_H264) {  
BOOL bRecovered = m_h264RandomAccess.searchRecoveryPoint(pDataBuffer, buflen);  
if (!bRecovered) {  
return S_OK;  
      }  
    } else if (m_nCodecId == AV_CODEC_ID_VP8 && m_bWaitingForKeyFrame) {  
if (!(pDataBuffer[0] & 1)) {  
        DbgLog((LOG_TRACE, 10, L"::Decode(): Found VP8 key-frame, resuming decoding"));  
        m_bWaitingForKeyFrame = FALSE;  
      } else {  
return S_OK;  
      }  
    }  
  }  

while (buflen > 0 || bFlush) {  
    REFERENCE_TIME rtStart = rtStartIn, rtStop = rtStopIn;  

if (!bFlush) {  
//设置AVPacket中的数据
      avpkt.data = pDataBuffer;  
      avpkt.size = buflen;  
      avpkt.pts = rtStartIn;  
if (rtStartIn != AV_NOPTS_VALUE && rtStopIn != AV_NOPTS_VALUE)  
        avpkt.duration = (int)(rtStopIn - rtStartIn);  
else
        avpkt.duration = 0;  
      avpkt.flags = AV_PKT_FLAG_KEY;  

if (m_bHasPalette) {  
        m_bHasPalette = FALSE;  
        uint32_t *pal = (uint32_t *)av_packet_new_side_data(&avpkt, AV_PKT_DATA_PALETTE, AVPALETTE_SIZE);  
int pal_size = FFMIN((1 << m_pAVCtx->bits_per_coded_sample) << 2, m_pAVCtx->extradata_size);  
        uint8_t *pal_src = m_pAVCtx->extradata + m_pAVCtx->extradata_size - pal_size;  

for (int i = 0; i < pal_size/4; i++)  
          pal[i] = 0xFF<<24 | AV_RL32(pal_src+4*i);  
      }  
    } else {  
      avpkt.data = NULL;  
      avpkt.size = 0;  
    }  

// Parse the data if a parser is present
// This is mandatory for MPEG-1/2
// 不一定需要
if (m_pParser) {  
BYTE *pOut = NULL;  
int pOut_size = 0;  

      used_bytes = av_parser_parse2(m_pParser, m_pAVCtx, &pOut, &pOut_size, avpkt.data, avpkt.size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);  

if (used_bytes == 0 && pOut_size == 0 && !bFlush) {  
        DbgLog((LOG_TRACE, 50, L"::Decode() - could not process buffer, starving?"));  
break;  
      }  

// Update start time cache
// If more data was read then output, update the cache (incomplete frame)
// If output is bigger, a frame was completed, update the actual rtStart with the cached value, and then overwrite the cache
if (used_bytes > pOut_size) {  
if (rtStartIn != AV_NOPTS_VALUE)  
          m_rtStartCache = rtStartIn;  
      } else if (used_bytes == pOut_size || ((used_bytes + 9) == pOut_size)) {  
// Why +9 above?
// Well, apparently there are some broken MKV muxers that like to mux the MPEG-2 PICTURE_START_CODE block (which is 9 bytes) in the package with the previous frame
// This would cause the frame timestamps to be delayed by one frame exactly, and cause timestamp reordering to go wrong.
// So instead of failing on those samples, lets just assume that 9 bytes are that case exactly.
        m_rtStartCache = rtStartIn = AV_NOPTS_VALUE;  
      } else if (pOut_size > used_bytes) {  
        rtStart = m_rtStartCache;  
        m_rtStartCache = rtStartIn;  
// The value was used once, don't use it for multiple frames, that ends up in weird timings
        rtStartIn = AV_NOPTS_VALUE;  
      }  

       bParserFrame = (pOut_size > 0);  

if (pOut_size > 0 || bFlush) {  

if (pOut && pOut_size > 0) {  
if (pOut_size > m_nFFBufferSize2) {  
            m_nFFBufferSize2    = pOut_size;  
            m_pFFBuffer2 = (BYTE *)av_realloc_f(m_pFFBuffer2, m_nFFBufferSize2 + FF_INPUT_BUFFER_PADDING_SIZE, 1);  
if (!m_pFFBuffer2) {  
              m_nFFBufferSize2 = 0;  
return E_OUTOFMEMORY;  
            }  
          }  
          memcpy(m_pFFBuffer2, pOut, pOut_size);  
          memset(m_pFFBuffer2+pOut_size, 0, FF_INPUT_BUFFER_PADDING_SIZE);  

          avpkt.data = m_pFFBuffer2;  
          avpkt.size = pOut_size;  
          avpkt.pts = rtStart;  
          avpkt.duration = 0;  

const uint8_t *eosmarker = CheckForEndOfSequence(m_nCodecId, avpkt.data, avpkt.size, &m_MpegParserState);  
if (eosmarker) {  
            bEndOfSequence = TRUE;  
          }  
        } else {  
          avpkt.data = NULL;  
          avpkt.size = 0;  
        }  
//真正的解码
int ret2 = avcodec_decode_video2 (m_pAVCtx, m_pFrame, &got_picture, &avpkt);  
if (ret2 < 0) {  
          DbgLog((LOG_TRACE, 50, L"::Decode() - decoding failed despite successfull parsing"));  
          got_picture = 0;  
        }  
      } else {  
        got_picture = 0;  
      }  
    } else {  
      used_bytes = avcodec_decode_video2 (m_pAVCtx, m_pFrame, &got_picture, &avpkt);  
    }  

if (FAILED(PostDecode())) {  
      av_frame_unref(m_pFrame);  
return E_FAIL;  
    }  

// Decoding of this frame failed ... oh well!
if (used_bytes < 0) {  
      av_frame_unref(m_pFrame);  
return S_OK;  
    }  

// When Frame Threading, we won't know how much data has been consumed, so it by default eats everything.
// In addition, if no data got consumed, and no picture was extracted, the frame probably isn't all that useufl.
// The MJPEB decoder is somewhat buggy and doesn't let us know how much data was consumed really...
if ((!m_pParser && (m_pAVCtx->active_thread_type & FF_THREAD_FRAME || (!got_picture && used_bytes == 0))) || m_bNoBufferConsumption || bFlush) {  
      buflen = 0;  
    } else {  
      buflen -= used_bytes;  
      pDataBuffer += used_bytes;  
    }  

// Judge frame usability
// This determines if a frame is artifact free and can be delivered
// For H264 this does some wicked magic hidden away in the H264RandomAccess class
// MPEG-2 and VC-1 just wait for a keyframe..
if (m_nCodecId == AV_CODEC_ID_H264 && (bParserFrame || !m_pParser || got_picture)) {  
      m_h264RandomAccess.judgeFrameUsability(m_pFrame, &got_picture);  
    } else if (m_bResumeAtKeyFrame) {  
if (m_bWaitingForKeyFrame && got_picture) {  
if (m_pFrame->key_frame) {  
          DbgLog((LOG_TRACE, 50, L"::Decode() - Found Key-Frame, resuming decoding at %I64d", m_pFrame->pkt_pts));  
          m_bWaitingForKeyFrame = FALSE;  
        } else {  
          got_picture = 0;  
        }  
      }  
    }  

// Handle B-frame delay for frame threading codecs
if ((m_pAVCtx->active_thread_type & FF_THREAD_FRAME) && m_bBFrameDelay) {  
      m_tcBFrameDelay[m_nBFramePos] = m_tcThreadBuffer[m_CurrentThread];  
      m_nBFramePos = !m_nBFramePos;  
    }  

if (!got_picture || !m_pFrame->data[0]) {  
if (!avpkt.size)  
        bFlush = FALSE; // End flushing, no more frames
      av_frame_unref(m_pFrame);  
continue;  
    }  

///////////////////////////////////////////////////////////////////////////////////////////////
// Determine the proper timestamps for the frame, based on different possible flags.
///////////////////////////////////////////////////////////////////////////////////////////////
if (m_bFFReordering) {  
      rtStart = m_pFrame->pkt_pts;  
if (m_pFrame->pkt_duration)  
        rtStop = m_pFrame->pkt_pts + m_pFrame->pkt_duration;  
else
        rtStop = AV_NOPTS_VALUE;  
    } else if (m_bBFrameDelay && m_pAVCtx->has_b_frames) {  
      rtStart = m_tcBFrameDelay[m_nBFramePos].rtStart;  
      rtStop  = m_tcBFrameDelay[m_nBFramePos].rtStop;  
    } else if (m_pAVCtx->active_thread_type & FF_THREAD_FRAME) {  
      unsigned index = m_CurrentThread;  
      rtStart = m_tcThreadBuffer[index].rtStart;  
      rtStop  = m_tcThreadBuffer[index].rtStop;  
    }  

if (m_bRVDropBFrameTimings && m_pFrame->pict_type == AV_PICTURE_TYPE_B) {  
      rtStart = AV_NOPTS_VALUE;  
    }  

if (m_bCalculateStopTime)  
      rtStop = AV_NOPTS_VALUE;  

///////////////////////////////////////////////////////////////////////////////////////////////
// All required values collected, deliver the frame
///////////////////////////////////////////////////////////////////////////////////////////////
    LAVFrame *pOutFrame = NULL;  
    AllocateFrame(&pOutFrame);  

    AVRational display_aspect_ratio;  
    int64_t num = (int64_t)m_pFrame->sample_aspect_ratio.num * m_pFrame->width;  
    int64_t den = (int64_t)m_pFrame->sample_aspect_ratio.den * m_pFrame->height;  
    av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den, num, den, 1 << 30);  

    pOutFrame->width        = m_pFrame->width;  
    pOutFrame->height       = m_pFrame->height;  
    pOutFrame->aspect_ratio = display_aspect_ratio;  
    pOutFrame->repeat       = m_pFrame->repeat_pict;  
    pOutFrame->key_frame    = m_pFrame->key_frame;  
    pOutFrame->frame_type   = av_get_picture_type_char(m_pFrame->pict_type);  
    pOutFrame->ext_format   = GetDXVA2ExtendedFlags(m_pAVCtx, m_pFrame);  

if (m_pFrame->interlaced_frame || (!m_pAVCtx->progressive_sequence && (m_nCodecId == AV_CODEC_ID_H264 || m_nCodecId == AV_CODEC_ID_MPEG2VIDEO)))  
      m_iInterlaced = 1;  
else if (m_pAVCtx->progressive_sequence)  
      m_iInterlaced = 0;  

    pOutFrame->interlaced   = (m_pFrame->interlaced_frame || (m_iInterlaced == 1 && m_pSettings->GetDeinterlacingMode() == DeintMode_Aggressive) || m_pSettings->GetDeinterlacingMode() == DeintMode_Force) && !(m_pSettings->GetDeinterlacingMode() == DeintMode_Disable);  

    LAVDeintFieldOrder fo   = m_pSettings->GetDeintFieldOrder();  
    pOutFrame->tff          = (fo == DeintFieldOrder_Auto) ? m_pFrame->top_field_first : (fo == DeintFieldOrder_TopFieldFirst);  

    pOutFrame->rtStart      = rtStart;  
    pOutFrame->rtStop       = rtStop;  

    PixelFormatMapping map  = getPixFmtMapping((AVPixelFormat)m_pFrame->format);  
    pOutFrame->format       = map.lavpixfmt;  
    pOutFrame->bpp          = map.bpp;  

if (m_nCodecId == AV_CODEC_ID_MPEG2VIDEO || m_nCodecId == AV_CODEC_ID_MPEG1VIDEO)  
      pOutFrame->avgFrameDuration = GetFrameDuration();  

if (map.conversion) {  
      ConvertPixFmt(m_pFrame, pOutFrame);  
    } else {  
for (int i = 0; i < 4; i++) {  
        pOutFrame->data[i]   = m_pFrame->data[i];  
        pOutFrame->stride[i] = m_pFrame->linesize[i];  
      }  

      pOutFrame->priv_data = av_frame_alloc();  
      av_frame_ref((AVFrame *)pOutFrame->priv_data, m_pFrame);  
      pOutFrame->destruct  = lav_avframe_free;  
    }  

if (bEndOfSequence)  
      pOutFrame->flags |= LAV_FRAME_FLAG_END_OF_SEQUENCE;  

if (pOutFrame->format == LAVPixFmt_DXVA2) {  
      pOutFrame->data[0] = m_pFrame->data[4];  
      HandleDXVA2Frame(pOutFrame);  
    } else {  
      Deliver(pOutFrame);  
    }  

if (bEndOfSequence) {  
      bEndOfSequence = FALSE;  
if (pOutFrame->format == LAVPixFmt_DXVA2) {  
        HandleDXVA2Frame(m_pCallback->GetFlushFrame());  
      } else {  
        Deliver(m_pCallback->GetFlushFrame());  
      }  
    }  

if (bFlush) {  
      m_CurrentThread = (m_CurrentThread + 1) % m_pAVCtx->thread_count;  
    }  
    av_frame_unref(m_pFrame);  
  }  

return S_OK;  
}

终于,我们从这个函数中看到了很多的ffmpeg的API,结构体,以及变量。比如解码视频的函数avcodec_decode_video2()。

解码器初始化函数:InitDecoder()

//创建解码器
STDMETHODIMP CDecAvcodec::InitDecoder(AVCodecID codec, const CMediaType *pmt)  
{  
//要是有,先销毁
  DestroyDecoder();  
  DbgLog((LOG_TRACE, 10, L"Initializing ffmpeg for codec %S", avcodec_get_name(codec)));  

  BITMAPINFOHEADER *pBMI = NULL;  
  videoFormatTypeHandler((const BYTE *)pmt->Format(), pmt->FormatType(), &pBMI);  
//查找解码器
  m_pAVCodec = avcodec_find_decoder(codec);  
  CheckPointer(m_pAVCodec, VFW_E_UNSUPPORTED_VIDEO);  
//初始化上下文环境
  m_pAVCtx = avcodec_alloc_context3(m_pAVCodec);  
  CheckPointer(m_pAVCtx, E_POINTER);  

if(codec == AV_CODEC_ID_MPEG1VIDEO || codec == AV_CODEC_ID_MPEG2VIDEO || pmt->subtype == FOURCCMap(MKTAG('H','2','6','4')) || pmt->subtype == FOURCCMap(MKTAG('h','2','6','4'))) {  
    m_pParser = av_parser_init(codec);  
  }  

DWORD dwDecFlags = m_pCallback->GetDecodeFlags();  

LONG biRealWidth = pBMI->biWidth, biRealHeight = pBMI->biHeight;  
if (pmt->formattype == FORMAT_VideoInfo || pmt->formattype == FORMAT_MPEGVideo) {  
    VIDEOINFOHEADER *vih = (VIDEOINFOHEADER *)pmt->Format();  
if (vih->rcTarget.right != 0 && vih->rcTarget.bottom != 0) {  
      biRealWidth  = vih->rcTarget.right;  
      biRealHeight = vih->rcTarget.bottom;  
    }  
  } else if (pmt->formattype == FORMAT_VideoInfo2 || pmt->formattype == FORMAT_MPEG2Video) {  
    VIDEOINFOHEADER2 *vih2 = (VIDEOINFOHEADER2 *)pmt->Format();  
if (vih2->rcTarget.right != 0 && vih2->rcTarget.bottom != 0) {  
      biRealWidth  = vih2->rcTarget.right;  
      biRealHeight = vih2->rcTarget.bottom;  
    }  
  }  
//各种赋值
  m_pAVCtx->codec_id              = codec;  
  m_pAVCtx->codec_tag             = pBMI->biCompression;  
  m_pAVCtx->coded_width           = pBMI->biWidth;  
  m_pAVCtx->coded_height          = abs(pBMI->biHeight);  
  m_pAVCtx->bits_per_coded_sample = pBMI->biBitCount;  
  m_pAVCtx->error_concealment     = FF_EC_GUESS_MVS | FF_EC_DEBLOCK;  
  m_pAVCtx->err_recognition       = AV_EF_CAREFUL;  
  m_pAVCtx->workaround_bugs       = FF_BUG_AUTODETECT;  
  m_pAVCtx->refcounted_frames     = 1;  

if (codec == AV_CODEC_ID_H264)  
    m_pAVCtx->flags2             |= CODEC_FLAG2_SHOW_ALL;  

// Setup threading
int thread_type = getThreadFlags(codec);  
if (thread_type) {  
// Thread Count. 0 = auto detect
int thread_count = m_pSettings->GetNumThreads();  
if (thread_count == 0) {  
      thread_count = av_cpu_count() * 3 / 2;  
    }  

    m_pAVCtx->thread_count = max(1, min(thread_count, AVCODEC_MAX_THREADS));  
    m_pAVCtx->thread_type = thread_type;  
  } else {  
    m_pAVCtx->thread_count = 1;  
  }  

if (dwDecFlags & LAV_VIDEO_DEC_FLAG_NO_MT) {  
    m_pAVCtx->thread_count = 1;  
  }  
//初始化AVFrame
  m_pFrame = av_frame_alloc();  
  CheckPointer(m_pFrame, E_POINTER);  

  m_h264RandomAccess.SetAVCNALSize(0);  

// Process Extradata
//处理ExtraData
BYTE *extra = NULL;  
size_t extralen = 0;  
  getExtraData(*pmt, NULL, &extralen);  

BOOL bH264avc = FALSE;  
if (extralen > 0) {  
    DbgLog((LOG_TRACE, 10, L"-> Processing extradata of %d bytes", extralen));  
// Reconstruct AVC1 extradata format
if (pmt->formattype == FORMAT_MPEG2Video && (m_pAVCtx->codec_tag == MAKEFOURCC('a','v','c','1') || m_pAVCtx->codec_tag == MAKEFOURCC('A','V','C','1') || m_pAVCtx->codec_tag == MAKEFOURCC('C','C','V','1'))) {  
      MPEG2VIDEOINFO *mp2vi = (MPEG2VIDEOINFO *)pmt->Format();  
      extralen += 7;  
      extra = (uint8_t *)av_mallocz(extralen + FF_INPUT_BUFFER_PADDING_SIZE);  
      extra[0] = 1;  
      extra[1] = (BYTE)mp2vi->dwProfile;  
      extra[2] = 0;  
      extra[3] = (BYTE)mp2vi->dwLevel;  
      extra[4] = (BYTE)(mp2vi->dwFlags ? mp2vi->dwFlags : 4) - 1;  

// Actually copy the metadata into our new buffer
size_t actual_len;  
      getExtraData(*pmt, extra+6, &actual_len);  

// Count the number of SPS/PPS in them and set the length
// We'll put them all into one block and add a second block with 0 elements afterwards
// The parsing logic does not care what type they are, it just expects 2 blocks.
BYTE *p = extra+6, *end = extra+6+actual_len;  
BOOL bSPS = FALSE, bPPS = FALSE;  
int count = 0;  
while (p+1 < end) {  
        unsigned len = (((unsigned)p[0] << 8) | p[1]) + 2;  
if (p + len > end) {  
break;  
        }  
if ((p[2] & 0x1F) == 7)  
          bSPS = TRUE;  
if ((p[2] & 0x1F) == 8)  
          bPPS = TRUE;  
        count++;  
        p += len;  
      }  
      extra[5] = count;  
      extra[extralen-1] = 0;  

      bH264avc = TRUE;  
      m_h264RandomAccess.SetAVCNALSize(mp2vi->dwFlags);  
    } else if (pmt->subtype == MEDIASUBTYPE_LAV_RAWVIDEO) {  
if (extralen < sizeof(m_pAVCtx->pix_fmt)) {  
        DbgLog((LOG_TRACE, 10, L"-> LAV RAW Video extradata is missing.."));  
      } else {  
        extra = (uint8_t *)av_mallocz(extralen + FF_INPUT_BUFFER_PADDING_SIZE);  
        getExtraData(*pmt, extra, NULL);  
        m_pAVCtx->pix_fmt = *(AVPixelFormat *)extra;  
        extralen -= sizeof(AVPixelFormat);  
        memmove(extra, extra+sizeof(AVPixelFormat), extralen);  
      }  
    } else {  
// Just copy extradata for other formats
      extra = (uint8_t *)av_mallocz(extralen + FF_INPUT_BUFFER_PADDING_SIZE);  
      getExtraData(*pmt, extra, NULL);  
    }  
// Hack to discard invalid MP4 metadata with AnnexB style video
if (codec == AV_CODEC_ID_H264 && !bH264avc && extra[0] == 1) {  
      av_freep(&extra);  
      extralen = 0;  
    }  
    m_pAVCtx->extradata = extra;  
    m_pAVCtx->extradata_size = (int)extralen;  
  } else {  
if (codec == AV_CODEC_ID_VP6 || codec == AV_CODEC_ID_VP6A || codec == AV_CODEC_ID_VP6F) {  
int cropH = pBMI->biWidth - biRealWidth;  
int cropV = pBMI->biHeight - biRealHeight;  
if (cropH >= 0 && cropH <= 0x0f && cropV >= 0 && cropV <= 0x0f) {  
        m_pAVCtx->extradata = (uint8_t *)av_mallocz(1 + FF_INPUT_BUFFER_PADDING_SIZE);  
        m_pAVCtx->extradata_size = 1;  
        m_pAVCtx->extradata[0] = (cropH << 4) | cropV;  
      }  
    }  
  }  

  m_h264RandomAccess.flush(m_pAVCtx->thread_count);  
  m_CurrentThread = 0;  
  m_rtStartCache = AV_NOPTS_VALUE;  

  LAVPinInfo lavPinInfo = {0};  
BOOL bLAVInfoValid = SUCCEEDED(m_pCallback->GetLAVPinInfo(lavPinInfo));  

  m_bInputPadded = dwDecFlags & LAV_VIDEO_DEC_FLAG_LAVSPLITTER;  

// Setup codec-specific timing logic
BOOL bVC1IsPTS = (codec == AV_CODEC_ID_VC1 && !(dwDecFlags & LAV_VIDEO_DEC_FLAG_VC1_DTS));  

// Use ffmpegs logic to reorder timestamps
// This is required for H264 content (except AVI), and generally all codecs that use frame threading
// VC-1 is also a special case. Its required for splitters that deliver PTS timestamps (see bVC1IsPTS above)
  m_bFFReordering        =  ( codec == AV_CODEC_ID_H264 && !(dwDecFlags & LAV_VIDEO_DEC_FLAG_H264_AVI))  
                           || codec == AV_CODEC_ID_VP8  
                           || codec == AV_CODEC_ID_VP3  
                           || codec == AV_CODEC_ID_THEORA  
                           || codec == AV_CODEC_ID_HUFFYUV  
                           || codec == AV_CODEC_ID_FFVHUFF  
                           || codec == AV_CODEC_ID_MPEG2VIDEO  
                           || codec == AV_CODEC_ID_MPEG1VIDEO  
                           || codec == AV_CODEC_ID_DIRAC  
                           || codec == AV_CODEC_ID_UTVIDEO  
                           || codec == AV_CODEC_ID_DNXHD  
                           || codec == AV_CODEC_ID_JPEG2000  
                           || (codec == AV_CODEC_ID_MPEG4 && pmt->formattype == FORMAT_MPEG2Video)  
                           || bVC1IsPTS;  

// Stop time is unreliable, drop it and calculate it
  m_bCalculateStopTime   = (codec == AV_CODEC_ID_H264 || codec == AV_CODEC_ID_DIRAC || (codec == AV_CODEC_ID_MPEG4 && pmt->formattype == FORMAT_MPEG2Video) || bVC1IsPTS);  

// Real Video content has some odd timestamps
// LAV Splitter does them allright with RV30/RV40, everything else screws them up
  m_bRVDropBFrameTimings = (codec == AV_CODEC_ID_RV10 || codec == AV_CODEC_ID_RV20 || ((codec == AV_CODEC_ID_RV30 || codec == AV_CODEC_ID_RV40) && (!(dwDecFlags & LAV_VIDEO_DEC_FLAG_LAVSPLITTER) || (bLAVInfoValid && (lavPinInfo.flags & LAV_STREAM_FLAG_RV34_MKV)))));  

// Enable B-Frame delay handling
  m_bBFrameDelay = !m_bFFReordering && !m_bRVDropBFrameTimings;  

  m_bWaitingForKeyFrame = TRUE;  
  m_bResumeAtKeyFrame =    codec == AV_CODEC_ID_MPEG2VIDEO  
                        || codec == AV_CODEC_ID_VC1  
                        || codec == AV_CODEC_ID_RV30  
                        || codec == AV_CODEC_ID_RV40  
                        || codec == AV_CODEC_ID_VP3  
                        || codec == AV_CODEC_ID_THEORA  
                        || codec == AV_CODEC_ID_MPEG4;  

  m_bNoBufferConsumption =    codec == AV_CODEC_ID_MJPEGB  
                           || codec == AV_CODEC_ID_LOCO  
                           || codec == AV_CODEC_ID_JPEG2000;  

  m_bHasPalette = m_pAVCtx->bits_per_coded_sample <= 8 && m_pAVCtx->extradata_size && !(dwDecFlags & LAV_VIDEO_DEC_FLAG_LAVSPLITTER)  
                  &&  (codec == AV_CODEC_ID_MSVIDEO1  
                    || codec == AV_CODEC_ID_MSRLE  
                    || codec == AV_CODEC_ID_CINEPAK  
                    || codec == AV_CODEC_ID_8BPS  
                    || codec == AV_CODEC_ID_QPEG  
                    || codec == AV_CODEC_ID_QTRLE  
                    || codec == AV_CODEC_ID_TSCC);  

if (FAILED(AdditionaDecoderInit())) {  
return E_FAIL;  
  }  

if (bLAVInfoValid) {  
// Setting has_b_frames to a proper value will ensure smoother decoding of H264
if (lavPinInfo.has_b_frames >= 0) {  
      DbgLog((LOG_TRACE, 10, L"-> Setting has_b_frames to %d", lavPinInfo.has_b_frames));  
      m_pAVCtx->has_b_frames = lavPinInfo.has_b_frames;  
    }  
  }  

// Open the decoder
//打开解码器
int ret = avcodec_open2(m_pAVCtx, m_pAVCodec, NULL);  
if (ret >= 0) {  
    DbgLog((LOG_TRACE, 10, L"-> ffmpeg codec opened successfully (ret: %d)", ret));  
    m_nCodecId = codec;  
  } else {  
    DbgLog((LOG_TRACE, 10, L"-> ffmpeg codec failed to open (ret: %d)", ret));  
    DestroyDecoder();  
return VFW_E_UNSUPPORTED_VIDEO;  
  }  

  m_iInterlaced = 0;  
for (int i = 0; i < countof(ff_interlace_capable); i++) {  
if (codec == ff_interlace_capable[i]) {  
      m_iInterlaced = -1;  
break;  
    }  
  }  

// Detect chroma and interlaced
if (m_pAVCtx->extradata && m_pAVCtx->extradata_size) {  
if (codec == AV_CODEC_ID_MPEG2VIDEO) {  
      CMPEG2HeaderParser mpeg2Parser(extra, extralen);  
if (mpeg2Parser.hdr.valid) {  
if (mpeg2Parser.hdr.chroma < 2) {  
          m_pAVCtx->pix_fmt = AV_PIX_FMT_YUV420P;  
        } else if (mpeg2Parser.hdr.chroma == 2) {  
          m_pAVCtx->pix_fmt = AV_PIX_FMT_YUV422P;  
        }  
        m_iInterlaced = mpeg2Parser.hdr.interlaced;  
      }  
    } else if (codec == AV_CODEC_ID_H264) {  
      CH264SequenceParser h264parser;  
if (bH264avc)  
        h264parser.ParseNALs(extra+6, extralen-6, 2);  
else
        h264parser.ParseNALs(extra, extralen, 0);  
if (h264parser.sps.valid)  
        m_iInterlaced = h264parser.sps.interlaced;  
    } else if (codec == AV_CODEC_ID_VC1) {  
      CVC1HeaderParser vc1parser(extra, extralen);  
if (vc1parser.hdr.valid)  
        m_iInterlaced = (vc1parser.hdr.interlaced ? -1 : 0);  
    }  
  }  

if (codec == AV_CODEC_ID_DNXHD)  
    m_pAVCtx->pix_fmt = AV_PIX_FMT_YUV422P10;  
else if (codec == AV_CODEC_ID_FRAPS)  
    m_pAVCtx->pix_fmt = AV_PIX_FMT_BGR24;  

if (bLAVInfoValid && codec != AV_CODEC_ID_FRAPS && m_pAVCtx->pix_fmt != AV_PIX_FMT_DXVA2_VLD)  
    m_pAVCtx->pix_fmt = lavPinInfo.pix_fmt;  

  DbgLog((LOG_TRACE, 10, L"AVCodec init successfull. interlaced: %d", m_iInterlaced));  

return S_OK;  
}

解码器销毁函数:DestroyDecoder()

//销毁解码器,各种Free
STDMETHODIMP CDecAvcodec::DestroyDecoder()  
{  
  DbgLog((LOG_TRACE, 10, L"Shutting down ffmpeg..."));  
  m_pAVCodec    = NULL;  

if (m_pParser) {  
    av_parser_close(m_pParser);  
    m_pParser = NULL;  
  }  

if (m_pAVCtx) {  
    avcodec_close(m_pAVCtx);  
    av_freep(&m_pAVCtx->extradata);  
    av_freep(&m_pAVCtx);  
  }  
  av_frame_free(&m_pFrame);  

  av_freep(&m_pFFBuffer);  
  m_nFFBufferSize = 0;  

  av_freep(&m_pFFBuffer2);  
  m_nFFBufferSize2 = 0;  

if (m_pSwsContext) {  
    sws_freeContext(m_pSwsContext);  
    m_pSwsContext = NULL;  
  }  

  m_nCodecId = AV_CODEC_ID_NONE;  

return S_OK;  
}
posted @ 2015-05-23 13:55  Mr.Rico  阅读(3263)  评论(0编辑  收藏  举报