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()等获取到流信息,这里就不多说了。