`

LAV Filter 源代码分析 2: LAV Splitter

 
阅读更多

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

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

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

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

属性设置页面:

支持输入格式:

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

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

dllmain.cpp

 

/*
 *      Copyright (C) 2010-2013 Hendrik Leppkes
 *      http://www.1f0.de
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

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

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

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

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

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

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

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

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

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

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

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

STDAPI DllUnregisterServer()
{
  UnRegisterSourceFilter(MEDIASUBTYPE_LAVBluRay);

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

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

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


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

 

LAVSplitter.h

 

/*
 *      Copyright (C) 2010-2013 Hendrik Leppkes
 *      http://www.1f0.de
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *  Initial design and concept by Gabest and the MPC-HC Team, copyright under GPLv2
 *  Contributions by Ti-BEN from the XBMC DSPlayer Project, also under GPLv2
 */

#pragma once

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

#include "BaseDemuxer.h"

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

#include "ISpecifyPropertyPages2.h"

#include "LAVSplitterTrayIcon.h"

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

#define MAX_PTS_SHIFT 50000000i64

class CLAVOutputPin;
class CLAVInputPin;

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

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

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

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

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

  STDMETHODIMP JoinFilterGraph(IFilterGraph * pGraph, LPCWSTR pName);

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

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

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

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

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

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

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

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

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

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

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

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

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

  void DeliverBeginFlush();
  void DeliverEndFlush();

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

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

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

  STDMETHODIMP CompleteInputConnection();
  STDMETHODIMP BreakInputConnection();

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

protected:
  CLAVInputPin *m_pInput;

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

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

  BOOL m_bPlaybackStarted;
  BOOL m_bFakeASFReader;

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

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

  // flushing
  bool m_fFlushing;
  CAMEvent m_eEndFlush;

  std::set<FormatInfo> m_InputFormats;

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

    BOOL MatroskaExternalSegments;

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

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

  BOOL m_bRuntimeConfig;

  IUnknown *m_pSite;

  CBaseTrayIcon *m_pTrayIcon;
};

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

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


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

 

 

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

  *ppv = NULL;

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


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

 

再看一下Load()函数

 

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

  m_bPlaybackStarted = FALSE;

  m_fileName = std::wstring(pszFileName);

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

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

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

  return InitDemuxer();
}


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

 

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

1.寻找文件后缀

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

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

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

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

 

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

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

 

 

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

  int ret; // return code from avformat functions

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

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

  AVIOInterruptCB cb = {avio_interrupt_cb, this};

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

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

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

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

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

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

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


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

 

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

 

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

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

  m_bVC1SeenTimestamp = FALSE;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    UpdateSubStreams();

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

  CHECK_HR(hr = CreateStreams());

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


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

 

 

 

 

分享到:
评论

相关推荐

    FFmpeg基础库编程开发

    LAV Filter 源代码分析 2: LAV Splitter 358 LAV Filter 源代码分析 3: LAV Video (1) 382 LAV Filter 源代码分析 4: LAV Video (2) 400 9.3 MPlayer 427 9.3.1 Mplayer支持的格式 427 9.3.2 Mplayer 中...

    ffmpeg基础开发资料自总结

    LAV Filter 源代码分析 2: LAV Splitter 341 LAV Filter 源代码分析 3: LAV Video (1) 364 LAV Filter 源代码分析 4: LAV Video (2) 382 9.3 MPlayer 408 9.3.1 Mplayer 支持的格式 408 9.3.2 Mplayer 中...

    LAVFilters源码

    lavfiter源代码,可用vs2010编译通过,lavfilter对于视频开发必备。

    lavfilter-0.70.2 windows下的解码器

    lavfilter-0.70.2 下的解码器

    DirectShow调用手动Lav Filter建立Filter Graph播放视频

    使用DirectShow,调用Lav Filter的组件播放视频。需要额外下载lavfilter组件并注册才能正常使用。

    madVR+LAVFilter+OpenCodec.rar

    基于Potplayer, 使用 madVR + LAVFilter + OpenCodec 打造最强观影画质, 任意大片60帧观看,美滋滋

    LAV Filters编解码相关库以及安装脚本

    Qt 中的多媒体播放,底层是使用DirectShowPlayerService,Qt在windows上的多媒体播放功能是使用系统的DirectShow... LAVVideo.ax以及其依赖的所有动态库,另附了命令行的安装bat脚本,可直接加入自己的代码中进行安装。

    LAVFilters0.76支持国标AVS3及AVS2 10位)解码(原始,TS及MKV封装)_Win64

    源码及下载:https://gitee.com/zhengtianbo/LAVFilters-GB-CAVS-AVS2-AVS3-decoder/releases mpc-hc播放器https://gitee.com/zhengtianbo/cavs-avs2-avs3_decoder_added_to_mpc_hc/releases avs2/avs3测试视频:...

    LAVFilters-0.70.2

    QT 视频播放 LAVFilters

    LAVFilters-0.77.2-截止20230529最新版

    LAVFilters-0.77.2-截止20230529最新版

    DirectShow分离器和音视解码器(LAV Filters) v0.60.1.rar

    LAV Filters 包含三个部分:LAV Splitter | LAV Video Decoder | LAV Audio Decoder,LAV Splitter 是源滤镜/分离器,基于 libavformat 和 libbluray 库,负责将文件分离成单独的基本流。 LAV Audio/Video ...

    Lav Filiters 解码器安装包

    LAV Filters中文版是一款非常不错的视频解码工具。LAV Filters中文版是一组基于 ffmpeg 项目中的 libavformat/libavcodec 库的 directshow 分离器和音视频解码器,几乎允许您在 directshow 播放器中播放任何格式的...

    LAVFilters-0.76.1-x86.zip

    视频解码器 LAVFilters,支持绝大部分格式的视频文件解码,0.76.1版本,此为windows平台免安装文件,32位 (x86平台) ,下面链接有32为和64位安装版文件 https://download.csdn.net/download/qq_27898413/86240084 ...

    LAVFilters-0.76.1-Installer.zip

    视频解码器 LAVFilters,支持绝大部分格式的视频文件解码,0.76.1版本,此为windows安装文件,包含32位和64位。 https://github.com/Nevcairiel/LAVFilters/releases

    LAVFilters解码器

    LAVFilters解码器,在Qt开始视频播放是需要用到

    LAV Filters v0.72.0-3 Nightly.zip

    LAV Splitter - 对流协议 (RTSP, RTP, MMS) 提供实验性支持 - 支持 IBufferInfo (MPC-HC“统计栏”面板中的缓冲区状态) - 增加内存限制以避免帧队列过高的内存使用 - 修复 WMP/WMC 在某些情况下对含字幕文件...

    LAVFilters-0.65-Installer

    该安装程序个人主要用于配合SubtitleEdit使用时作为食品的解码插件来安装使用的,如果不安装的话,SubtitleEdit有些视频无法正常打开。该程序的更新可以参考:https://github.com/Nevcairiel/LAVFilters/releases

    基于LAV Filters的全能高质量Delphi媒体播放器源码

    本源码是用Delphi动态调用LAV Filters的例子。在本例中,LAV Filters不用安装、不用注册,纯绿色。LAV Filters的版本替换也很方便,直接把新版拷贝替换便是。 LAV Filters 是一组开源的 DirectShow 滤镜,能播放所有...

    DirectShow视频纵向拉伸Filter源码

    DirectShow视频拉伸Filter(Transform Filter),目前只实现视频纵向2倍拉伸, 可用于视频采集卡按场采集图像时拉伸图像 本工程在WindowsXp Sp2、DirectX9、VC++6.0下编译通过

    LAVFilters-0.77.1-x64

    视频解码器 LAVFilters,支持绝大部分格式的视频文件解码

Global site tag (gtag.js) - Google Analytics