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)
, // 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)
, // count of registered media types
&sudMediaTypes[] // list of registered media types
}
};
//注册时候的信息(名称等)
//CLAVSplitter
const AMOVIESETUP_FILTER sudFilterReg =
{
&__uuidof(CLAVSplitter), // filter clsid
L"LAV Splitter", // filter name
MERIT_PREFERRED + , // merit
, // 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 + , // merit
, // 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[]); // 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 ; }
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 -; } // 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, , L"::Load(): Opening file '%s' (extension: %s)", pszFileName, extension)); // BDMV uses the BD demuxer, everything else LAVF
if (_wcsicmp(extension, L".bdmv") == || _wcsicmp(extension, L".mpls") == ) {
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[] = {};
if (pszFileName) {
ret = WideCharToMultiByte(CP_UTF8, , pszFileName, -, fileName, , NULL, NULL);
} if (_strnicmp("mms:", fileName, ) == ) {
memmove(fileName+, fileName, strlen(fileName));
memcpy(fileName, "mmsh", );
} 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 = ; i < countof(wszImageExtensions); i++) {
if (_wcsicmp(extension, wszImageExtensions[i]) == ) {
if (byteContext) {
inputFormat = av_find_input_format("image2pipe");
} else {
inputFormat = av_find_input_format("image2");
}
break;
}
}
for (int i = ; i < countof(wszBlockedExtensions); i++) {
if (_wcsicmp(extension, wszBlockedExtensions[i]) == ) {
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 < ) {
DbgLog((LOG_ERROR, , TEXT("::OpenInputStream(): avformat_open_input failed (%d)"), ret));
if (format) {
DbgLog((LOG_ERROR, , TEXT(" -> trying again without specific format")));
format = NULL;
//实际的关闭
avformat_close_input(&m_avFormat);
goto trynoformat;
}
goto done;
}
DbgLog((LOG_TRACE, , TEXT("::OpenInputStream(): avformat_open_input opened file of type '%S' (took %I64d seconds)"), m_avFormat->iformat->name, time(NULL) - m_timeOpening));
m_timeOpening = ;
//初始化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, , 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", ) == );
m_bOgg = (_strnicmp(m_pszInputFormat, "ogg", ) == );
m_bAVI = (_strnicmp(m_pszInputFormat, "avi", ) == );
m_bMPEGTS = (_strnicmp(m_pszInputFormat, "mpegts", ) == );
m_bMPEGPS = (_stricmp(m_pszInputFormat, "mpeg") == );
m_bRM = (_stricmp(m_pszInputFormat, "rm") == );
m_bPMP = (_stricmp(m_pszInputFormat, "pmp") == );
m_bMP4 = (_stricmp(m_pszInputFormat, "mp4") == ); m_bTSDiscont = m_avFormat->iformat->flags & AVFMT_TS_DISCONT; WCHAR szProt[] = L"file";
if (pszFileName) {
DWORD dwNumChars = ;
hr = UrlGetPart(pszFileName, szProt, &dwNumChars, URL_PART_SCHEME, );
if (SUCCEEDED(hr) && dwNumChars && (_wcsicmp(szProt, L"file") != )) {
m_avFormat->flags |= AVFMT_FLAG_NETWORK;
DbgLog((LOG_TRACE, , 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() * , );
} else {
// And increase it for mpeg-ts/ps files
if (m_bMPEGTS || m_bMPEGPS)
m_avFormat->max_analyze_duration = ;
} av_opt_set_int(m_avFormat, "correct_ts_overflow", !m_pBluRay, ); 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 < ) {
DbgLog((LOG_ERROR, , TEXT("::InitAVFormat(): av_find_stream_info failed (%d)"), ret));
goto done;
}
DbgLog((LOG_TRACE, , TEXT("::InitAVFormat(): avformat_find_stream_info finished, took %I64d seconds"), time(NULL) - m_timeOpening));
m_timeOpening = ; // 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 = ; 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, , L"Stream %d (pid %d) - program: %d, codec: %S; parsing: %S;", idx, st->id, streamProg ? streamProg->pmt_pid : -, 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, , L"::Receive(): Input sample contained media type, dynamic format change..."));
m_Decoder.EndOfStream();
hr = m_pInput->SetMediaType(&mt);
if (FAILED(hr)) {
DbgLog((LOG_ERROR, , L"::Receive(): Setting new media type failed..."));
return hr;
}
}
} m_hrDeliver = S_OK; // Skip over empty packets
if (pIn->GetActualDataLength() == ) {
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(); // 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() : ; }
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[];
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(-, "LAVVideo Decode Thread"); HANDLE hWaitEvents[] = { GetRequestHandle(), m_evInput };
//不停转圈,永不休止
while() {
if (!bEOS && !bReinit) {
// Wait for either an input sample, or an request
WaitForMultipleObjects(, 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 ;
}
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[] = m_NextSample;
m_NextSample = m_FailedSample;
m_FailedSample = NULL;
bReinit = TRUE;
m_evEOSDone.Reset();
Reply(S_OK);
m_bDecoderNeedsReInit = FALSE;
}
break;
default:
ASSERT();
}
} if (m_bDecoderNeedsReInit) {
m_evInput.Reset();
continue;
} if (bReinit && !m_NextSample) {
if (m_TempSample[]) {
m_NextSample = m_TempSample[];
m_TempSample[] = NULL;
} else if (m_TempSample[]) {
m_NextSample = m_TempSample[];
m_TempSample[] = 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 ;
}
先分析到这里了,至于ILAVDecoder接口方面的东西下篇文章再写。
4: LAV Video (2)
在这里继续上篇文章的内容。文章中提到LAVVideo主要通过CDecodeThread这个类进行解码线程的管理,其中有一个关键的管理函数:ThreadProc(),包含了对解码线程的各种操作。函数如下所示:
//包含了对进程的各种操作
DWORD CDecodeThread::ThreadProc()
{
HRESULT hr;
DWORD cmd; BOOL bEOS = FALSE;
BOOL bReinit = FALSE; SetThreadName(-, "LAVVideo Decode Thread"); HANDLE hWaitEvents[] = { GetRequestHandle(), m_evInput };
//不停转圈,永不休止
while() {
if (!bEOS && !bReinit) {
// Wait for either an input sample, or an request
WaitForMultipleObjects(, 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 ;
}
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[] = m_NextSample;
m_NextSample = m_FailedSample;
m_FailedSample = NULL;
bReinit = TRUE;
m_evEOSDone.Reset();
Reply(S_OK);
m_bDecoderNeedsReInit = FALSE;
}
break;
default:
ASSERT();
}
} if (m_bDecoderNeedsReInit) {
m_evInput.Reset();
continue;
} if (bReinit && !m_NextSample) {
if (m_TempSample[]) {
m_NextSample = m_TempSample[];
m_TempSample[] = NULL;
} else if (m_TempSample[]) {
m_NextSample = m_TempSample[];
m_TempSample[] = 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 ;
}
该函数中,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, , 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[] = 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 > ? 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[];
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 = ;
int used_bytes = ;
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 + ) % 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 > ) {
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, );
if (!m_pFFBuffer) {
m_nFFBufferSize = ;
return E_OUTOFMEMORY;
}
} memcpy(m_pFFBuffer, buffer, buflen);
memset(m_pFFBuffer+buflen, , 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[] & )) {
DbgLog((LOG_TRACE, , L"::Decode(): Found VP8 key-frame, resuming decoding"));
m_bWaitingForKeyFrame = FALSE;
} else {
return S_OK;
}
}
} while (buflen > || 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 = ;
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(( << m_pAVCtx->bits_per_coded_sample) << , m_pAVCtx->extradata_size);
uint8_t *pal_src = m_pAVCtx->extradata + m_pAVCtx->extradata_size - pal_size; for (int i = ; i < pal_size/; i++)
pal[i] = 0xFF<< | AV_RL32(pal_src+*i);
}
} else {
avpkt.data = NULL;
avpkt.size = ;
} // Parse the data if a parser is present
// This is mandatory for MPEG-1/2
// 不一定需要
if (m_pParser) {
BYTE *pOut = NULL;
int pOut_size = ; used_bytes = av_parser_parse2(m_pParser, m_pAVCtx, &pOut, &pOut_size, avpkt.data, avpkt.size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, ); if (used_bytes == && pOut_size == && !bFlush) {
DbgLog((LOG_TRACE, , 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 + ) == 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 > ); if (pOut_size > || bFlush) { if (pOut && pOut_size > ) {
if (pOut_size > m_nFFBufferSize2) {
m_nFFBufferSize2 = pOut_size;
m_pFFBuffer2 = (BYTE *)av_realloc_f(m_pFFBuffer2, m_nFFBufferSize2 + FF_INPUT_BUFFER_PADDING_SIZE, );
if (!m_pFFBuffer2) {
m_nFFBufferSize2 = ;
return E_OUTOFMEMORY;
}
}
memcpy(m_pFFBuffer2, pOut, pOut_size);
memset(m_pFFBuffer2+pOut_size, , FF_INPUT_BUFFER_PADDING_SIZE); avpkt.data = m_pFFBuffer2;
avpkt.size = pOut_size;
avpkt.pts = rtStart;
avpkt.duration = ; const uint8_t *eosmarker = CheckForEndOfSequence(m_nCodecId, avpkt.data, avpkt.size, &m_MpegParserState);
if (eosmarker) {
bEndOfSequence = TRUE;
}
} else {
avpkt.data = NULL;
avpkt.size = ;
}
//真正的解码
int ret2 = avcodec_decode_video2 (m_pAVCtx, m_pFrame, &got_picture, &avpkt);
if (ret2 < ) {
DbgLog((LOG_TRACE, , L"::Decode() - decoding failed despite successfull parsing"));
got_picture = ;
}
} else {
got_picture = ;
}
} 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 < ) {
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 == ))) || m_bNoBufferConsumption || bFlush) {
buflen = ;
} 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, , L"::Decode() - Found Key-Frame, resuming decoding at %I64d", m_pFrame->pkt_pts));
m_bWaitingForKeyFrame = FALSE;
} else {
got_picture = ;
}
}
} // 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[]) {
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, << ); 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 = ;
else if (m_pAVCtx->progressive_sequence)
m_iInterlaced = ; pOutFrame->interlaced = (m_pFrame->interlaced_frame || (m_iInterlaced == && 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 = ; i < ; 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[] = m_pFrame->data[];
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 + ) % 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, , 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','','','')) || pmt->subtype == FOURCCMap(MKTAG('h','','',''))) {
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 != && vih->rcTarget.bottom != ) {
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 != && vih2->rcTarget.bottom != ) {
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 = ; 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 == ) {
thread_count = av_cpu_count() * / ;
} m_pAVCtx->thread_count = max(, min(thread_count, AVCODEC_MAX_THREADS));
m_pAVCtx->thread_type = thread_type;
} else {
m_pAVCtx->thread_count = ;
} if (dwDecFlags & LAV_VIDEO_DEC_FLAG_NO_MT) {
m_pAVCtx->thread_count = ;
}
//初始化AVFrame
m_pFrame = av_frame_alloc();
CheckPointer(m_pFrame, E_POINTER); m_h264RandomAccess.SetAVCNALSize(); // Process Extradata
//处理ExtraData
BYTE *extra = NULL;
size_t extralen = ;
getExtraData(*pmt, NULL, &extralen); BOOL bH264avc = FALSE;
if (extralen > ) {
DbgLog((LOG_TRACE, , L"-> Processing extradata of %d bytes", extralen));
// Reconstruct AVC1 extradata format
if (pmt->formattype == FORMAT_MPEG2Video && (m_pAVCtx->codec_tag == MAKEFOURCC('a','v','c','') || m_pAVCtx->codec_tag == MAKEFOURCC('A','V','C','') || m_pAVCtx->codec_tag == MAKEFOURCC('C','C','V',''))) {
MPEG2VIDEOINFO *mp2vi = (MPEG2VIDEOINFO *)pmt->Format();
extralen += ;
extra = (uint8_t *)av_mallocz(extralen + FF_INPUT_BUFFER_PADDING_SIZE);
extra[] = ;
extra[] = (BYTE)mp2vi->dwProfile;
extra[] = ;
extra[] = (BYTE)mp2vi->dwLevel;
extra[] = (BYTE)(mp2vi->dwFlags ? mp2vi->dwFlags : ) - ; // Actually copy the metadata into our new buffer
size_t actual_len;
getExtraData(*pmt, extra+, &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+, *end = extra++actual_len;
BOOL bSPS = FALSE, bPPS = FALSE;
int count = ;
while (p+ < end) {
unsigned len = (((unsigned)p[] << ) | p[]) + ;
if (p + len > end) {
break;
}
if ((p[] & 0x1F) == )
bSPS = TRUE;
if ((p[] & 0x1F) == )
bPPS = TRUE;
count++;
p += len;
}
extra[] = count;
extra[extralen-] = ; bH264avc = TRUE;
m_h264RandomAccess.SetAVCNALSize(mp2vi->dwFlags);
} else if (pmt->subtype == MEDIASUBTYPE_LAV_RAWVIDEO) {
if (extralen < sizeof(m_pAVCtx->pix_fmt)) {
DbgLog((LOG_TRACE, , 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[] == ) {
av_freep(&extra);
extralen = ;
}
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 >= && cropH <= 0x0f && cropV >= && cropV <= 0x0f) {
m_pAVCtx->extradata = (uint8_t *)av_mallocz( + FF_INPUT_BUFFER_PADDING_SIZE);
m_pAVCtx->extradata_size = ;
m_pAVCtx->extradata[] = (cropH << ) | cropV;
}
}
} m_h264RandomAccess.flush(m_pAVCtx->thread_count);
m_CurrentThread = ;
m_rtStartCache = AV_NOPTS_VALUE; LAVPinInfo lavPinInfo = {};
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 <= && 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 >= ) {
DbgLog((LOG_TRACE, , 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 >= ) {
DbgLog((LOG_TRACE, , L"-> ffmpeg codec opened successfully (ret: %d)", ret));
m_nCodecId = codec;
} else {
DbgLog((LOG_TRACE, , L"-> ffmpeg codec failed to open (ret: %d)", ret));
DestroyDecoder();
return VFW_E_UNSUPPORTED_VIDEO;
} m_iInterlaced = ;
for (int i = ; i < countof(ff_interlace_capable); i++) {
if (codec == ff_interlace_capable[i]) {
m_iInterlaced = -;
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 < ) {
m_pAVCtx->pix_fmt = AV_PIX_FMT_YUV420P;
} else if (mpeg2Parser.hdr.chroma == ) {
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+, extralen-, );
else
h264parser.ParseNALs(extra, extralen, );
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 ? - : );
}
} 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, , L"AVCodec init successfull. interlaced: %d", m_iInterlaced)); return S_OK;
}
解码器销毁函数:DestroyDecoder()
//销毁解码器,各种Free
STDMETHODIMP CDecAvcodec::DestroyDecoder()
{
DbgLog((LOG_TRACE, , 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 = ; av_freep(&m_pFFBuffer2);
m_nFFBufferSize2 = ; if (m_pSwsContext) {
sws_freeContext(m_pSwsContext);
m_pSwsContext = NULL;
} m_nCodecId = AV_CODEC_ID_NONE; return S_OK;
}