XBMC(全称是XBOX Media Center)是一个开源的媒体中心软件。XBMC最初为Xbox而开发,可以运行在Linux、OSX、Windows、Android4.0系统。我自己下载了一个然后体验了一下,感觉确实不错,和Windows自带的媒体中心差不多。
XBMC项目首页:http://xbmc.org/
XBMC差不多是我接触到的开源多媒体项目中体积最大的了。但是它的编译方法却出乎意料的简单。我按照它的Wiki上面说的步骤去做,非常顺利的完成了编译,没有遇到任何错误,赞一个。
下面简述一下它的编译方法。
前提条件:
1.Visual C++ 2010
2.Microsoft DirectX SDK (August 2009 或更晚的版本)
3.Git
4.JRE
编译:
注意:需要下载很多东西,所以需要联网
1.使用Git下载源代码。Git地址:git://github.com/xbmc/xbmc.git
2.运行DownloadBuildDeps.bat (所在目录 project\BuildDependencies):下载编译项目所需要的依赖项
3.运行DownloadMingwBuildEnv.bat (所在目录 project\BuildDependencies) :下载编译ffmpeg库所需要的依赖项
4.运行buildmingwlibs.bat (所在目录 project\Win32BuildSetup): 编译ffmpeg库
5.以下二选一。一般情况下选第二个就可以了。
(1)BuildSetup.bat (所在目录 project\Win32BuildSetup):只有需要直接编译一个打包文件的时候,才推荐使用该批处理。
(2)extract_git_rev.bat : 如果是为了调试,并且使用 VC++ 2010 进行编译,推荐使用该批处理。
6.打开project\VS2010Express\XBMC for Windows.sln,就可以编译了。
下面对XBMC源代码进行一个整体分析:
源代码的目录结构如下图所示。我把其中比较主要的文件夹下面标记了一条红线。
这几个主要文件夹的作用如下(其他文件夹就不再细说了):
addons:附加元件。比如说XBMC的皮肤文件,屏幕保护文件,可视化效果文件等等。
docs:文档。
language:语言文件。
project:项目工程文件。
xbmc:源代码
lib:调用的各个库。比如说libavcodec。
XBMC项目解决方案的目录如下图所示。可以看出项目工程数量是极其巨大的。
其中名字为“XBMC”的工程是主程序。
ImageLib_XXX是图片处理的工程。
libXBMC_XXX是完成XBMC各种功能的工程。
visXXX是各种可视化效果的工程。
我们来看一下“XBMC”工程的目录。该工程下源文件的数量也是十分庞大的。不同功能的类被放到了不同的文件夹中,显得还是比较井然有续的:
其中“core”文件夹中存放核心的类
“addon”文件夹中存放和addon相关的类
“music”文件夹中存放和音乐功能相关的类
“video”文件夹中存放和影视功能相关的类
“settings”文件夹中存放和设置功能相关的类
此处不一一例举
从这篇文章开始,就要对XBMC源代码进行具体分析了。首先先不分析其C++代码,分析一下和其皮肤相关的代码。
XBMC 的和皮肤相关的代码位于 "根目录/addons" 里面。可以从官方网站上下载皮肤文件的压缩包,然后解压到该目录下面即可。皮肤文件夹名称一般是“skin.XXXX”形式的,即以“skin.”开头。
XBMC自带的皮肤存储在文件夹“skin.confluence”中。我从网上下载了4个皮肤,解压后,如下图所示。
系统默认的皮肤:confluence如图所示。
可以在“skin”选项里面选择皮肤,如图所示。
皮肤“simplicity”如图所示。
皮肤“SiO2”如图所示。
可以看出。不同皮肤之间差距非常的大。皮肤囊括了XBMC所有可以看见的界面元素。可以说不修改源代码,只制作皮肤,也可以完全定制出一套非常个性化的系统。
下面我们以系统自带的皮肤“confluence”为例,分析一下皮肤的构成。
skin.confluence文件夹中,目录结构如图所示:
每个文件夹的作用:
720p:界面存放于文件夹里
background:背景图片
font:字体
language:各种语言支持
media:各种图标
sound:声音
例如,background文件夹内容:
media文件夹内容:
下面重点研究720p文件夹中的内容。这个文件夹中存储了界面的布局信息。系统会根据这个文件夹中的布局信息(xml形式)设置窗口的大小,并去其他文件夹中查找相关的素材。
我们以系统的“设置”页面为例研究一下布局信息。系统的布局页面如下图所示。
“设置”页面对应的布局信息文件为Settings.xml。
时间所限,就不逐行注释了。语法理解起来还是比较容易的。总结以下几点:
1.语法与HTML类似。最外层的<window>相当于<html>。<controls>相当于<body>。<control>类似于<div>,是可以嵌套的。<content>相当于<ul>,<item>相当于<li>。当然,这只是打个比方,方便理解。
2.各种组件都是<control>,就是属性“type”不一样。例如“image”,“group”等等。<control>中
<left>,<top>,<width>,<height>表示窗口位置;
<animation>表示其动画效果;
<onleft>2</onleft>表示遥控器按向左键时如果焦点还在控件里面,并且己经是最左边一个元素时,将焦点切换到ID为2的控件;<onright>,<onup>,<ondown>与此类似。
<!-- 雷霄骅 [email protected] 中国传媒大学/数字电视技术 --> <?xml version="1.0" encoding="UTF-8"?> <window> <defaultcontrol always="true">9000</defaultcontrol> <allowoverlay>no</allowoverlay> <controls> <include>CommonBackground</include> <control type="image"> <left>0</left> <top>100r</top> <width>1280</width> <height>100</height> <texture>floor.png</texture> <animation effect="slide" start="0,10" end="0,0" time="200" condition="Window.Previous(Home)">WindowOpen</animation> <animation effect="slide" start="0,0" end="0,10" time="200" condition="Window.Next(Home)">WindowClose</animation> </control> <control type="group"> <left>90</left> <top>30</top> <animation type="WindowOpen" reversible="false"> <effect type="zoom" start="80" end="100" center="640,360" easing="out" tween="back" time="300"/> <effect type="fade" start="0" end="100" time="300"/> </animation> <animation type="WindowClose" reversible="false"> <effect type="zoom" start="100" end="80" center="640,360" easing="in" tween="back" time="300"/> <effect type="fade" start="100" end="0" time="300"/> </animation> <control type="image"> <left>5</left> <top>5</top> <width>1090</width> <height>630</height> <texture border="15">ContentPanel.png</texture> </control> <control type="image"> <left>5</left> <top>625</top> <width>1090</width> <height>64</height> <texture border="15">ContentPanelMirror.png</texture> </control> <control type="button"> <description>Close Window button</description> <left>980</left> <top>11</top> <width>64</width> <height>32</height> <label>-</label> <font>-</font> <onclick>PreviousMenu</onclick> <texturefocus>DialogCloseButton-focus.png</texturefocus> <texturenofocus>DialogCloseButton.png</texturenofocus> <onleft>1</onleft> <onright>1</onright> <onup>1</onup> <ondown>1</ondown> <visible>system.getbool(input.enablemouse)</visible> </control> <control type="image"> <description>LOGO</description> <left>30</left> <top>15</top> <width>220</width> <height>80</height> <aspectratio>keep</aspectratio> <texture>Confluence_Logo.png</texture> </control> <control type="list" id="9000"> <left>10</left> <top>82</top> <width>260</width> <height>541</height> <onleft>9000</onleft> <onright>9001</onright> <onup>9000</onup> <ondown>9000</ondown> <pagecontrol>-</pagecontrol> <scrolltime>300</scrolltime> <itemlayout height="54" width="260"> <control type="image"> <left>0</left> <top>0</top> <width>260</width> <height>55</height> <texture border="5">MenuItemNF.png</texture> </control> <control type="label"> <left>250</left> <top>0</top> <width>380</width> <height>55</height> <font>font24_title</font> <textcolor>grey3</textcolor> <align>right</align> <aligny>center</aligny> <label>$INFO[ListItem.Label]</label> </control> </itemlayout> <focusedlayout height="54" width="260"> <control type="image"> <left>0</left> <top>0</top> <width>260</width> <height>55</height> <texture border="5">MenuItemFO.png</texture> </control> <control type="label"> <left>250</left> <top>0</top> <width>380</width> <height>55</height> <font>font24_title</font> <textcolor>white</textcolor> <align>right</align> <aligny>center</aligny> <label>$INFO[ListItem.Label]</label> </control> </focusedlayout> <content> <item id="1"> <label>480</label> <label2>31400</label2> <onclick>ActivateWindow(AppearanceSettings)</onclick> <icon>-</icon> </item> <item id="2"> <label>157</label> <label2>31401</label2> <onclick>ActivateWindow(VideosSettings)</onclick> <icon>-</icon> </item> <item id="3"> <label>31502</label> <label2>31409</label2> <onclick>ActivateWindow(PVRSettings)</onclick> <icon>special://skin/backgrounds/tv.jpg</icon> </item> <item id="4"> <label>2</label> <label2>31402</label2> <onclick>ActivateWindow(MusicSettings)</onclick> <icon>-</icon> </item> <item id="5"> <label>1</label> <label2>31403</label2> <onclick>ActivateWindow(PicturesSettings)</onclick> <icon>-</icon> </item> <item id="6"> <label>8</label> <label2>31404</label2> <onclick>ActivateWindow(WeatherSettings)</onclick> <icon>-</icon> </item> <item id="7"> <label>24001</label> <label2>31408</label2> <onclick>ActivateWindow(AddonBrowser)</onclick> <icon>-</icon> </item> <item id="8"> <label>14036</label> <label2>31410</label2> <onclick>ActivateWindow(ServiceSettings)</onclick> <icon>-</icon> </item> <item id="9"> <label>13000</label> <label2>31406</label2> <onclick>ActivateWindow(SystemSettings)</onclick> <icon>-</icon> </item> </content> </control> <control type="image"> <left>268</left> <top>10</top> <width>804</width> <height>50</height> <texture border="5">black-back2.png</texture> </control> <control type="image"> <left>268</left> <top>10</top> <width>804</width> <height>70</height> <aspectratio>stretch</aspectratio> <texture>GlassTitleBar.png</texture> </control> <control type="label"> <description>header label</description> <left>300</left> <top>20</top> <width>740</width> <height>30</height> <font>font16</font> <label>$LOCALIZE[31000] $LOCALIZE[5]</label> <align>left</align> <aligny>center</aligny> <textcolor>white</textcolor> <shadowcolor>black</shadowcolor> </control> <control type="image"> <left>270</left> <top>60</top> <width>800</width> <height>450</height> <texture border="5">button-nofocus.png</texture> </control> <control type="image"> <left>272</left> <top>62</top> <width>796</width> <height>446</height> <aspectratio>stretch</aspectratio> <fadetime>600</fadetime> <texture background="true">special://skin/backgrounds/settings.jpg</texture> </control> <control type="image"> <left>272</left> <top>62</top> <width>600</width> <height>340</height> <aspectratio>stretch</aspectratio> <texture>GlassOverlay.png</texture> <colordiffuse>AAFFFFFF</colordiffuse> </control> <control type="image"> <left>268</left> <top>510</top> <width>804</width> <height>118</height> <texture border="5">black-back2.png</texture> </control> <control type="textbox"> <description>Appearance Description</description> <left>300</left> <top>520</top> <width>740</width> <height>100</height> <font>font12</font> <label>$INFO[Container(9000).ListItem.Label2]</label> <align>left</align> <textcolor>white</textcolor> <shadowcolor>black</shadowcolor> </control> </control> <include>CommonNowPlaying</include> <include>MainWindowMouseButtons</include> <include>BehindDialogFadeOut</include> <control type="image"> <description>Section header image</description> <left>20</left> <top>3</top> <width>35</width> <height>35</height> <aspectratio>keep</aspectratio> <texture>icon_system.png</texture> </control> <control type="grouplist"> <left>65</left> <top>5</top> <width>1000</width> <height>30</height> <orientation>horizontal</orientation> <align>left</align> <itemgap>5</itemgap> <control type="label"> <include>WindowTitleCommons</include> <label>$LOCALIZE[5]</label> </control> </control> <include>Clock</include> </controls> </window>
本文以及以后的文章主要分析XBMC的VC工程中的源代码。XBMC源代码体积庞大,想要完全分析所有代码是比较困难的。在这里我们选择它和音视频编解码有关的部分进行分析。在本文里,我们主要分析其核心部分(core)代码。
核心部分(core)源代码结构如图所示:
我目前理解的有以下3个,其他的有时间研究后再补上:
AudioEngine:音频引擎。其封装了所有不同的媒体类型的混音、采样率转换、格式转换、编码、上混、缩混等。
dvdplayer:视频播放器。其中封装了FFMPEG等一些库,是我们分析的重点。
paplayer:XBMC自行开发出来的音频播放器。
本系列文章将会重点分析dvdplayer这个播放器。
下面我们先来看看dvdplayer的代码结构:
先不说一大堆cpp文件。dvdplayer包含以下5个文件夹,我们分析以下3个文件夹中的内容
DVDCodecs:封装各种解码器
DVDDemuxers:封装各种解复用器
DVDHeaders:封装各种Dll的头文件
DVDCodecs里面包含各种解码器的封装,下图列出了封装视频解码器的文件。
DVDDemuxers里面包含了各种解复用器(视音频分离器)的封装,如下图所示。
DVDHeaders里面包含了封装各种Dll的头文件,如下图所示。
详细的分析会在后续文章中完成。
本文我们分析XBMC中视频播放器(dvdplayer)中的解码器部分。由于解码器种类很多,不可能一一分析,因此以ffmpeg解码器为例进行分析。
XBMC解码器部分文件目录如下图所示:
解码器分为音频解码器和视频解码器。在这里我们看一下视频解码器中的FFMPEG解码器。对应DVDVideoCodecFFmpeg.h和DVDVideoCodecFFmpeg.cpp。
DVDVideoCodecFFmpeg.h源代码如下所示:
/* * 雷霄骅 * [email protected] * 中国传媒大学/数字电视技术 * */ #include "DVDVideoCodec.h" #include "DVDResource.h" #include "DllAvCodec.h" #include "DllAvFormat.h" #include "DllAvUtil.h" #include "DllSwScale.h" #include "DllAvFilter.h" #include "DllPostProc.h" class CCriticalSection; //封装的FFMPEG视频解码器 class CDVDVideoCodecFFmpeg : public CDVDVideoCodec { public: class IHardwareDecoder : public IDVDResourceCounted<IHardwareDecoder> { public: IHardwareDecoder() {} virtual ~IHardwareDecoder() {}; virtual bool Open (AVCodecContext* avctx, const enum PixelFormat, unsigned int surfaces) = 0; virtual int Decode (AVCodecContext* avctx, AVFrame* frame) = 0; virtual bool GetPicture(AVCodecContext* avctx, AVFrame* frame, DVDVideoPicture* picture) = 0; virtual int Check (AVCodecContext* avctx) = 0; virtual void Reset () {} virtual unsigned GetAllowedReferences() { return 0; } virtual const std::string Name() = 0; virtual CCriticalSection* Section() { return NULL; } }; CDVDVideoCodecFFmpeg(); virtual ~CDVDVideoCodecFFmpeg(); virtual bool Open(CDVDStreamInfo &hints, CDVDCodecOptions &options);//打开 virtual void Dispose();//关闭 virtual int Decode(uint8_t* pData, int iSize, double dts, double pts);//解码 virtual void Reset(); bool GetPictureCommon(DVDVideoPicture* pDvdVideoPicture); virtual bool GetPicture(DVDVideoPicture* pDvdVideoPicture); virtual void SetDropState(bool bDrop); virtual unsigned int SetFilters(unsigned int filters); virtual const char* GetName() { return m_name.c_str(); }; // m_name is never changed after open virtual unsigned GetConvergeCount(); virtual unsigned GetAllowedReferences(); bool IsHardwareAllowed() { return !m_bSoftware; } IHardwareDecoder * GetHardware() { return m_pHardware; }; void SetHardware(IHardwareDecoder* hardware) { SAFE_RELEASE(m_pHardware); m_pHardware = hardware; UpdateName(); } protected: static enum PixelFormat GetFormat(struct AVCodecContext * avctx, const PixelFormat * fmt); int FilterOpen(const CStdString& filters, bool scale); void FilterClose(); int FilterProcess(AVFrame* frame); void UpdateName() { if(m_pCodecContext->codec->name) m_name = CStdString("ff-") + m_pCodecContext->codec->name; else m_name = "ffmpeg"; if(m_pHardware) m_name += "-" + m_pHardware->Name(); } AVFrame* m_pFrame; AVCodecContext* m_pCodecContext; CStdString m_filters; CStdString m_filters_next; AVFilterGraph* m_pFilterGraph; AVFilterContext* m_pFilterIn; AVFilterContext* m_pFilterOut; #if defined(LIBAVFILTER_AVFRAME_BASED) AVFrame* m_pFilterFrame; #else AVFilterBufferRef* m_pBufferRef; #endif int m_iPictureWidth; int m_iPictureHeight; int m_iScreenWidth; int m_iScreenHeight; int m_iOrientation;// orientation of the video in degress counter clockwise unsigned int m_uSurfacesCount; //封装Dll的各种类 DllAvCodec m_dllAvCodec; DllAvUtil m_dllAvUtil; DllSwScale m_dllSwScale; DllAvFilter m_dllAvFilter; DllPostProc m_dllPostProc; std::string m_name; bool m_bSoftware; bool m_isHi10p; IHardwareDecoder *m_pHardware; int m_iLastKeyframe; double m_dts; bool m_started; std::vector<PixelFormat> m_formats; };
该类中以下几个函数包含了解码器的几种功能:
virtual bool Open(CDVDStreamInfo &hints, CDVDCodecOptions &options);//打开
virtual void Dispose();//关闭
virtual int Decode(uint8_t* pData, int iSize, double dts, double pts);//解码
virtual void Reset();//复位
为了说明这一点,我们可以看一下视频解码器中的libmpeg2解码器,对应DVDVideoCodecLibMpeg2.h。可以看出这几个函数是一样的。
DVDVideoCodecLibMpeg2.h源代码如下:
/* * 雷霄骅 * [email protected] * 中国传媒大学/数字电视技术 * */ #include "DVDVideoCodec.h" #include "DllLibMpeg2.h" class CDVDVideoCodecLibMpeg2 : public CDVDVideoCodec { public: CDVDVideoCodecLibMpeg2(); virtual ~CDVDVideoCodecLibMpeg2(); virtual bool Open(CDVDStreamInfo &hints, CDVDCodecOptions &options); virtual void Dispose(); virtual int Decode(uint8_t* pData, int iSize, double dts, double pts); virtual void Reset(); virtual bool GetPicture(DVDVideoPicture* pDvdVideoPicture); virtual bool GetUserData(DVDVideoUserData* pDvdVideoUserData); virtual void SetDropState(bool bDrop); virtual const char* GetName() { return "libmpeg2"; } protected: DVDVideoPicture* GetBuffer(unsigned int width, unsigned int height); inline void ReleaseBuffer(DVDVideoPicture* pPic); inline void DeleteBuffer(DVDVideoPicture* pPic); static int GuessAspect(const mpeg2_sequence_t *sequence, unsigned int *pixel_width, unsigned int *pixel_height); mpeg2dec_t* m_pHandle; const mpeg2_info_t* m_pInfo; DllLibMpeg2 m_dll; unsigned int m_irffpattern; bool m_bFilm; //Signals that we have film material bool m_bIs422; int m_hurry; double m_dts; double m_dts2; //The buffer of pictures we need DVDVideoPicture m_pVideoBuffer[3]; DVDVideoPicture* m_pCurrentBuffer; };
现在回到DVDVideoCodecFFmpeg.h。我们可以看一下上文所示的4个函数。
Open()
//打开 bool CDVDVideoCodecFFmpeg::Open(CDVDStreamInfo &hints, CDVDCodecOptions &options) { AVCodec* pCodec; if(!m_dllAvUtil.Load() || !m_dllAvCodec.Load() || !m_dllSwScale.Load() || !m_dllPostProc.Load() || !m_dllAvFilter.Load() ) return false; //注册解码器 m_dllAvCodec.avcodec_register_all(); m_dllAvFilter.avfilter_register_all(); m_bSoftware = hints.software; m_iOrientation = hints.orientation; for(std::vector<ERenderFormat>::iterator it = options.m_formats.begin(); it != options.m_formats.end(); ++it) { m_formats.push_back((PixelFormat)CDVDCodecUtils::PixfmtFromEFormat(*it)); if(*it == RENDER_FMT_YUV420P) m_formats.push_back(PIX_FMT_YUVJ420P); } m_formats.push_back(PIX_FMT_NONE); /* always add none to get a terminated list in ffmpeg world */ pCodec = NULL; m_pCodecContext = NULL; if (hints.codec == AV_CODEC_ID_H264) { switch(hints.profile) { case FF_PROFILE_H264_HIGH_10: case FF_PROFILE_H264_HIGH_10_INTRA: case FF_PROFILE_H264_HIGH_422: case FF_PROFILE_H264_HIGH_422_INTRA: case FF_PROFILE_H264_HIGH_444_PREDICTIVE: case FF_PROFILE_H264_HIGH_444_INTRA: case FF_PROFILE_H264_CAVLC_444: // this is needed to not open the decoders m_bSoftware = true; // this we need to enable multithreading for hi10p via advancedsettings m_isHi10p = true; break; } } //查找解码器 if(pCodec == NULL) pCodec = m_dllAvCodec.avcodec_find_decoder(hints.codec); if(pCodec == NULL) { CLog::Log(LOGDEBUG,"CDVDVideoCodecFFmpeg::Open() Unable to find codec %d", hints.codec); return false; } CLog::Log(LOGNOTICE,"CDVDVideoCodecFFmpeg::Open() Using codec: %s",pCodec->long_name ? pCodec->long_name : pCodec->name); if(m_pCodecContext == NULL) m_pCodecContext = m_dllAvCodec.avcodec_alloc_context3(pCodec); m_pCodecContext->opaque = (void*)this; m_pCodecContext->debug_mv = 0; m_pCodecContext->debug = 0; m_pCodecContext->workaround_bugs = FF_BUG_AUTODETECT; m_pCodecContext->get_format = GetFormat; m_pCodecContext->codec_tag = hints.codec_tag; /* Only allow slice threading, since frame threading is more * sensitive to changes in frame sizes, and it causes crashes * during HW accell - so we unset it in this case. * * When we detect Hi10p and user did not disable hi10pmultithreading * via advancedsettings.xml we keep the ffmpeg default thread type. * */ if(m_isHi10p && !g_advancedSettings.m_videoDisableHi10pMultithreading) { CLog::Log(LOGDEBUG,"CDVDVideoCodecFFmpeg::Open() Keep default threading for Hi10p: %d", m_pCodecContext->thread_type); } else if (CSettings::Get().GetBool("videoplayer.useframemtdec")) { CLog::Log(LOGDEBUG,"CDVDVideoCodecFFmpeg::Open() Keep default threading %d by videoplayer.useframemtdec", m_pCodecContext->thread_type); } else m_pCodecContext->thread_type = FF_THREAD_SLICE; #if defined(TARGET_DARWIN_IOS) // ffmpeg with enabled neon will crash and burn if this is enabled m_pCodecContext->flags &= CODEC_FLAG_EMU_EDGE; #else if (pCodec->id != AV_CODEC_ID_H264 && pCodec->capabilities & CODEC_CAP_DR1 && pCodec->id != AV_CODEC_ID_VP8 ) m_pCodecContext->flags |= CODEC_FLAG_EMU_EDGE; #endif // if we don't do this, then some codecs seem to fail. m_pCodecContext->coded_height = hints.height; m_pCodecContext->coded_width = hints.width; m_pCodecContext->bits_per_coded_sample = hints.bitsperpixel; if( hints.extradata && hints.extrasize > 0 ) { m_pCodecContext->extradata_size = hints.extrasize; m_pCodecContext->extradata = (uint8_t*)m_dllAvUtil.av_mallocz(hints.extrasize + FF_INPUT_BUFFER_PADDING_SIZE); memcpy(m_pCodecContext->extradata, hints.extradata, hints.extrasize); } // advanced setting override for skip loop filter (see avcodec.h for valid options) // TODO: allow per video setting? if (g_advancedSettings.m_iSkipLoopFilter != 0) { m_pCodecContext->skip_loop_filter = (AVDiscard)g_advancedSettings.m_iSkipLoopFilter; } // set any special options for(std::vector<CDVDCodecOption>::iterator it = options.m_keys.begin(); it != options.m_keys.end(); ++it) { if (it->m_name == "surfaces") m_uSurfacesCount = std::atoi(it->m_value.c_str()); else m_dllAvUtil.av_opt_set(m_pCodecContext, it->m_name.c_str(), it->m_value.c_str(), 0); } int num_threads = std::min(8 /*MAX_THREADS*/, g_cpuInfo.getCPUCount()); if( num_threads > 1 && !hints.software && m_pHardware == NULL // thumbnail extraction fails when run threaded && ( pCodec->id == AV_CODEC_ID_H264 || pCodec->id == AV_CODEC_ID_MPEG4 )) m_pCodecContext->thread_count = num_threads; //打开解码器 if (m_dllAvCodec.avcodec_open2(m_pCodecContext, pCodec, NULL) < 0) { CLog::Log(LOGDEBUG,"CDVDVideoCodecFFmpeg::Open() Unable to open codec"); return false; } //初始化AVFrame m_pFrame = m_dllAvCodec.avcodec_alloc_frame(); if (!m_pFrame) return false; #if defined(LIBAVFILTER_AVFRAME_BASED) m_pFilterFrame = m_dllAvUtil.av_frame_alloc(); if (!m_pFilterFrame) return false; #endif UpdateName(); return true; }
Dispose()
//关闭 void CDVDVideoCodecFFmpeg::Dispose() { //释放 if (m_pFrame) m_dllAvUtil.av_free(m_pFrame); m_pFrame = NULL; #if defined(LIBAVFILTER_AVFRAME_BASED) m_dllAvUtil.av_frame_free(&m_pFilterFrame); #endif if (m_pCodecContext) { //关闭解码器 if (m_pCodecContext->codec) m_dllAvCodec.avcodec_close(m_pCodecContext); if (m_pCodecContext->extradata) { m_dllAvUtil.av_free(m_pCodecContext->extradata); m_pCodecContext->extradata = NULL; m_pCodecContext->extradata_size = 0; } m_dllAvUtil.av_free(m_pCodecContext); m_pCodecContext = NULL; } SAFE_RELEASE(m_pHardware); FilterClose(); m_dllAvCodec.Unload(); m_dllAvUtil.Unload(); m_dllAvFilter.Unload(); m_dllPostProc.Unload(); }
Decode()
//解码 int CDVDVideoCodecFFmpeg::Decode(uint8_t* pData, int iSize, double dts, double pts) { int iGotPicture = 0, len = 0; if (!m_pCodecContext) return VC_ERROR; if(pData) m_iLastKeyframe++; shared_ptr<CSingleLock> lock; if(m_pHardware) { CCriticalSection* section = m_pHardware->Section(); if(section) lock = shared_ptr<CSingleLock>(new CSingleLock(*section)); int result; if(pData) result = m_pHardware->Check(m_pCodecContext); else result = m_pHardware->Decode(m_pCodecContext, NULL); if(result) return result; } if(m_pFilterGraph) { int result = 0; if(pData == NULL) result = FilterProcess(NULL); if(result) return result; } m_dts = dts; m_pCodecContext->reordered_opaque = pts_dtoi(pts); //初始化AVPacket AVPacket avpkt; m_dllAvCodec.av_init_packet(&avpkt); avpkt.data = pData; avpkt.size = iSize; /* We lie, but this flag is only used by pngdec.c. * Setting it correctly would allow CorePNG decoding. */ avpkt.flags = AV_PKT_FLAG_KEY; //解码 len = m_dllAvCodec.avcodec_decode_video2(m_pCodecContext, m_pFrame, &iGotPicture, &avpkt); if(m_iLastKeyframe < m_pCodecContext->has_b_frames + 2) m_iLastKeyframe = m_pCodecContext->has_b_frames + 2; if (len < 0) { CLog::Log(LOGERROR, "%s - avcodec_decode_video returned failure", __FUNCTION__); return VC_ERROR; } if (!iGotPicture) return VC_BUFFER; if(m_pFrame->key_frame) { m_started = true; m_iLastKeyframe = m_pCodecContext->has_b_frames + 2; } /* put a limit on convergence count to avoid huge mem usage on streams without keyframes */ if(m_iLastKeyframe > 300) m_iLastKeyframe = 300; /* h264 doesn't always have keyframes + won't output before first keyframe anyway */ if(m_pCodecContext->codec_id == AV_CODEC_ID_H264 || m_pCodecContext->codec_id == AV_CODEC_ID_SVQ3) m_started = true; if(m_pHardware == NULL) { bool need_scale = std::find( m_formats.begin() , m_formats.end() , m_pCodecContext->pix_fmt) == m_formats.end(); bool need_reopen = false; if(!m_filters.Equals(m_filters_next)) need_reopen = true; if(m_pFilterIn) { if(m_pFilterIn->outputs[0]->format != m_pCodecContext->pix_fmt || m_pFilterIn->outputs[0]->w != m_pCodecContext->width || m_pFilterIn->outputs[0]->h != m_pCodecContext->height) need_reopen = true; } // try to setup new filters if (need_reopen || (need_scale && m_pFilterGraph == NULL)) { m_filters = m_filters_next; if(FilterOpen(m_filters, need_scale) < 0) FilterClose(); } } int result; if(m_pHardware) result = m_pHardware->Decode(m_pCodecContext, m_pFrame); else if(m_pFilterGraph) result = FilterProcess(m_pFrame); else result = VC_PICTURE | VC_BUFFER; if(result & VC_FLUSHED) Reset(); return result; }
Reset()
//复位 void CDVDVideoCodecFFmpeg::Reset() { m_started = false; m_iLastKeyframe = m_pCodecContext->has_b_frames; m_dllAvCodec.avcodec_flush_buffers(m_pCodecContext); if (m_pHardware) m_pHardware->Reset(); m_filters = ""; FilterClose(); }
本文我们分析XBMC中视频播放器(dvdplayer)中的解复用器部分。由于解复用器种类很多,不可能一一分析,因此以ffmpeg解复用器为例进行分析。
XBMC解复用器部分文件目录如下图所示:
在这里我们看一下解复用器中的FFMPEG解复用器。对应DVDDemuxFFmpeg.h和DVDDemuxFFmpeg.cpp
之前的分析类文章在解复用器这方面已经做过详细的分析了。在此就不多叙述了,代码很清晰。重点的地方已经标上了注释。
DVDDemuxFFmpeg.h源代码如下所示:
/* * 雷霄骅 * [email protected] * 中国传媒大学/数字电视技术 * */ #include "DVDDemux.h" #include "DllAvFormat.h" #include "DllAvCodec.h" #include "DllAvUtil.h" #include "threads/CriticalSection.h" #include "threads/SystemClock.h" #include <map> class CDVDDemuxFFmpeg; class CURL; class CDemuxStreamVideoFFmpeg : public CDemuxStreamVideo { CDVDDemuxFFmpeg *m_parent; AVStream* m_stream; public: CDemuxStreamVideoFFmpeg(CDVDDemuxFFmpeg *parent, AVStream* stream) : m_parent(parent) , m_stream(stream) {} virtual void GetStreamInfo(std::string& strInfo); }; class CDemuxStreamAudioFFmpeg : public CDemuxStreamAudio { CDVDDemuxFFmpeg *m_parent; AVStream* m_stream; public: CDemuxStreamAudioFFmpeg(CDVDDemuxFFmpeg *parent, AVStream* stream) : m_parent(parent) , m_stream(stream) {} std::string m_description; virtual void GetStreamInfo(std::string& strInfo); virtual void GetStreamName(std::string& strInfo); }; class CDemuxStreamSubtitleFFmpeg : public CDemuxStreamSubtitle { CDVDDemuxFFmpeg *m_parent; AVStream* m_stream; public: CDemuxStreamSubtitleFFmpeg(CDVDDemuxFFmpeg *parent, AVStream* stream) : m_parent(parent) , m_stream(stream) {} std::string m_description; virtual void GetStreamInfo(std::string& strInfo); virtual void GetStreamName(std::string& strInfo); }; #define FFMPEG_FILE_BUFFER_SIZE 32768 // default reading size for ffmpeg #define FFMPEG_DVDNAV_BUFFER_SIZE 2048 // for dvd's //FFMPEG解复用 class CDVDDemuxFFmpeg : public CDVDDemux { public: CDVDDemuxFFmpeg(); virtual ~CDVDDemuxFFmpeg(); //打开一个流 bool Open(CDVDInputStream* pInput); void Dispose();//关闭 void Reset();//复位 void Flush(); void Abort(); void SetSpeed(int iSpeed); virtual std::string GetFileName(); DemuxPacket* Read(); bool SeekTime(int time, bool backwords = false, double* startpts = NULL); bool SeekByte(int64_t pos); int GetStreamLength(); CDemuxStream* GetStream(int iStreamId); int GetNrOfStreams(); bool SeekChapter(int chapter, double* startpts = NULL); int GetChapterCount(); int GetChapter(); void GetChapterName(std::string& strChapterName); virtual void GetStreamCodecName(int iStreamId, CStdString &strName); bool Aborted(); AVFormatContext* m_pFormatContext; CDVDInputStream* m_pInput; protected: friend class CDemuxStreamAudioFFmpeg; friend class CDemuxStreamVideoFFmpeg; friend class CDemuxStreamSubtitleFFmpeg; int ReadFrame(AVPacket *packet); CDemuxStream* AddStream(int iId); void AddStream(int iId, CDemuxStream* stream); CDemuxStream* GetStreamInternal(int iStreamId); void CreateStreams(unsigned int program = UINT_MAX); void DisposeStreams(); AVDictionary *GetFFMpegOptionsFromURL(const CURL &url); double ConvertTimestamp(int64_t pts, int den, int num); void UpdateCurrentPTS(); bool IsProgramChange(); CCriticalSection m_critSection; std::map<int, CDemuxStream*> m_streams; std::vector<std::map<int, CDemuxStream*>::iterator> m_stream_index; AVIOContext* m_ioContext; //各种封装的Dll DllAvFormat m_dllAvFormat; DllAvCodec m_dllAvCodec; DllAvUtil m_dllAvUtil; double m_iCurrentPts; // used for stream length estimation bool m_bMatroska; bool m_bAVI; int m_speed; unsigned m_program; XbmcThreads::EndTime m_timeout; // Due to limitations of ffmpeg, we only can detect a program change // with a packet. This struct saves the packet for the next read and // signals STREAMCHANGE to player struct { AVPacket pkt; // packet ffmpeg returned int result; // result from av_read_packet }m_pkt; };
该类中以下几个函数包含了解复用器的几个功能。
bool Open(CDVDInputStream* pInput);//打开
void Dispose();//关闭
void Reset();//复位
void Flush();
我们查看一下这几个函数的源代码。
Open()
//打开一个流 bool CDVDDemuxFFmpeg::Open(CDVDInputStream* pInput) { AVInputFormat* iformat = NULL; std::string strFile; m_iCurrentPts = DVD_NOPTS_VALUE; m_speed = DVD_PLAYSPEED_NORMAL; m_program = UINT_MAX; const AVIOInterruptCB int_cb = { interrupt_cb, this }; if (!pInput) return false; if (!m_dllAvUtil.Load() || !m_dllAvCodec.Load() || !m_dllAvFormat.Load()) { CLog::Log(LOGERROR,"CDVDDemuxFFmpeg::Open - failed to load ffmpeg libraries"); return false; } //注册解复用器 // register codecs m_dllAvFormat.av_register_all(); m_pInput = pInput; strFile = m_pInput->GetFileName(); bool streaminfo = true; /* set to true if we want to look for streams before playback*/ if( m_pInput->GetContent().length() > 0 ) { std::string content = m_pInput->GetContent(); /* check if we can get a hint from content */ if ( content.compare("video/x-vobsub") == 0 ) iformat = m_dllAvFormat.av_find_input_format("mpeg"); else if( content.compare("video/x-dvd-mpeg") == 0 ) iformat = m_dllAvFormat.av_find_input_format("mpeg"); else if( content.compare("video/x-mpegts") == 0 ) iformat = m_dllAvFormat.av_find_input_format("mpegts"); else if( content.compare("multipart/x-mixed-replace") == 0 ) iformat = m_dllAvFormat.av_find_input_format("mjpeg"); } // open the demuxer m_pFormatContext = m_dllAvFormat.avformat_alloc_context(); m_pFormatContext->interrupt_callback = int_cb; // try to abort after 30 seconds m_timeout.Set(30000); if( m_pInput->IsStreamType(DVDSTREAM_TYPE_FFMPEG) ) { // special stream type that makes avformat handle file opening // allows internal ffmpeg protocols to be used CURL url = m_pInput->GetURL(); CStdString protocol = url.GetProtocol(); AVDictionary *options = GetFFMpegOptionsFromURL(url); int result=-1; if (protocol.Equals("mms")) { // try mmsh, then mmst url.SetProtocol("mmsh"); url.SetProtocolOptions(""); //真正地打开 result = m_dllAvFormat.avformat_open_input(&m_pFormatContext, url.Get().c_str(), iformat, &options); if (result < 0) { url.SetProtocol("mmst"); strFile = url.Get(); } } //真正地打开 if (result < 0 && m_dllAvFormat.avformat_open_input(&m_pFormatContext, strFile.c_str(), iformat, &options) < 0 ) { CLog::Log(LOGDEBUG, "Error, could not open file %s", CURL::GetRedacted(strFile).c_str()); Dispose(); m_dllAvUtil.av_dict_free(&options); return false; } m_dllAvUtil.av_dict_free(&options); } else { unsigned char* buffer = (unsigned char*)m_dllAvUtil.av_malloc(FFMPEG_FILE_BUFFER_SIZE); m_ioContext = m_dllAvFormat.avio_alloc_context(buffer, FFMPEG_FILE_BUFFER_SIZE, 0, this, dvd_file_read, NULL, dvd_file_seek); m_ioContext->max_packet_size = m_pInput->GetBlockSize(); if(m_ioContext->max_packet_size) m_ioContext->max_packet_size *= FFMPEG_FILE_BUFFER_SIZE / m_ioContext->max_packet_size; if(m_pInput->Seek(0, SEEK_POSSIBLE) == 0) m_ioContext->seekable = 0; if( iformat == NULL ) { // let ffmpeg decide which demuxer we have to open bool trySPDIFonly = (m_pInput->GetContent() == "audio/x-spdif-compressed"); if (!trySPDIFonly) m_dllAvFormat.av_probe_input_buffer(m_ioContext, &iformat, strFile.c_str(), NULL, 0, 0); // Use the more low-level code in case we have been built against an old // FFmpeg without the above av_probe_input_buffer(), or in case we only // want to probe for spdif (DTS or IEC 61937) compressed audio // specifically, or in case the file is a wav which may contain DTS or // IEC 61937 (e.g. ac3-in-wav) and we want to check for those formats. if (trySPDIFonly || (iformat && strcmp(iformat->name, "wav") == 0)) { AVProbeData pd; uint8_t probe_buffer[FFMPEG_FILE_BUFFER_SIZE + AVPROBE_PADDING_SIZE]; // init probe data pd.buf = probe_buffer; pd.filename = strFile.c_str(); // read data using avformat's buffers pd.buf_size = m_dllAvFormat.avio_read(m_ioContext, pd.buf, m_ioContext->max_packet_size ? m_ioContext->max_packet_size : m_ioContext->buffer_size); if (pd.buf_size <= 0) { CLog::Log(LOGERROR, "%s - error reading from input stream, %s", __FUNCTION__, CURL::GetRedacted(strFile).c_str()); return false; } memset(pd.buf+pd.buf_size, 0, AVPROBE_PADDING_SIZE); // restore position again m_dllAvFormat.avio_seek(m_ioContext , 0, SEEK_SET); // the advancedsetting is for allowing the user to force outputting the // 44.1 kHz DTS wav file as PCM, so that an A/V receiver can decode // it (this is temporary until we handle 44.1 kHz passthrough properly) if (trySPDIFonly || (iformat && strcmp(iformat->name, "wav") == 0 && !g_advancedSettings.m_dvdplayerIgnoreDTSinWAV)) { // check for spdif and dts // This is used with wav files and audio CDs that may contain // a DTS or AC3 track padded for S/PDIF playback. If neither of those // is present, we assume it is PCM audio. // AC3 is always wrapped in iec61937 (ffmpeg "spdif"), while DTS // may be just padded. AVInputFormat *iformat2; iformat2 = m_dllAvFormat.av_find_input_format("spdif"); if (iformat2 && iformat2->read_probe(&pd) > AVPROBE_SCORE_MAX / 4) { iformat = iformat2; } else { // not spdif or no spdif demuxer, try dts iformat2 = m_dllAvFormat.av_find_input_format("dts"); if (iformat2 && iformat2->read_probe(&pd) > AVPROBE_SCORE_MAX / 4) { iformat = iformat2; } else if (trySPDIFonly) { // not dts either, return false in case we were explicitely // requested to only check for S/PDIF padded compressed audio CLog::Log(LOGDEBUG, "%s - not spdif or dts file, fallbacking", __FUNCTION__); return false; } } } } if(!iformat) { std::string content = m_pInput->GetContent(); /* check if we can get a hint from content */ if( content.compare("audio/aacp") == 0 ) iformat = m_dllAvFormat.av_find_input_format("aac"); else if( content.compare("audio/aac") == 0 ) iformat = m_dllAvFormat.av_find_input_format("aac"); else if( content.compare("video/flv") == 0 ) iformat = m_dllAvFormat.av_find_input_format("flv"); else if( content.compare("video/x-flv") == 0 ) iformat = m_dllAvFormat.av_find_input_format("flv"); } if (!iformat) { CLog::Log(LOGERROR, "%s - error probing input format, %s", __FUNCTION__, CURL::GetRedacted(strFile).c_str()); return false; } else { if (iformat->name) CLog::Log(LOGDEBUG, "%s - probing detected format [%s]", __FUNCTION__, iformat->name); else CLog::Log(LOGDEBUG, "%s - probing detected unnamed format", __FUNCTION__); } } m_pFormatContext->pb = m_ioContext; if (m_dllAvFormat.avformat_open_input(&m_pFormatContext, strFile.c_str(), iformat, NULL) < 0) { CLog::Log(LOGERROR, "%s - Error, could not open file %s", __FUNCTION__, CURL::GetRedacted(strFile).c_str()); Dispose(); return false; } } // Avoid detecting framerate if advancedsettings.xml says so if (g_advancedSettings.m_videoFpsDetect == 0) m_pFormatContext->fps_probe_size = 0; // analyse very short to speed up mjpeg playback start if (iformat && (strcmp(iformat->name, "mjpeg") == 0) && m_ioContext->seekable == 0) m_pFormatContext->max_analyze_duration = 500000; // we need to know if this is matroska or avi later m_bMatroska = strncmp(m_pFormatContext->iformat->name, "matroska", 8) == 0; // for "matroska.webm" m_bAVI = strcmp(m_pFormatContext->iformat->name, "avi") == 0; if (streaminfo) { /* too speed up dvd switches, only analyse very short */ if(m_pInput->IsStreamType(DVDSTREAM_TYPE_DVD)) m_pFormatContext->max_analyze_duration = 500000; CLog::Log(LOGDEBUG, "%s - avformat_find_stream_info starting", __FUNCTION__); int iErr = m_dllAvFormat.avformat_find_stream_info(m_pFormatContext, NULL); if (iErr < 0) { CLog::Log(LOGWARNING,"could not find codec parameters for %s", CURL::GetRedacted(strFile).c_str()); if (m_pInput->IsStreamType(DVDSTREAM_TYPE_DVD) || m_pInput->IsStreamType(DVDSTREAM_TYPE_BLURAY) || (m_pFormatContext->nb_streams == 1 && m_pFormatContext->streams[0]->codec->codec_id == AV_CODEC_ID_AC3)) { // special case, our codecs can still handle it. } else { Dispose(); return false; } } CLog::Log(LOGDEBUG, "%s - av_find_stream_info finished", __FUNCTION__); } // reset any timeout m_timeout.SetInfinite(); // if format can be nonblocking, let's use that m_pFormatContext->flags |= AVFMT_FLAG_NONBLOCK; // print some extra information m_dllAvFormat.av_dump_format(m_pFormatContext, 0, strFile.c_str(), 0); UpdateCurrentPTS(); CreateStreams(); return true; }
Dispose()
//关闭 void CDVDDemuxFFmpeg::Dispose() { m_pkt.result = -1; m_dllAvCodec.av_free_packet(&m_pkt.pkt); if (m_pFormatContext) { if (m_ioContext && m_pFormatContext->pb && m_pFormatContext->pb != m_ioContext) { CLog::Log(LOGWARNING, "CDVDDemuxFFmpeg::Dispose - demuxer changed our byte context behind our back, possible memleak"); m_ioContext = m_pFormatContext->pb; } m_dllAvFormat.avformat_close_input(&m_pFormatContext); } if(m_ioContext) { m_dllAvUtil.av_free(m_ioContext->buffer); m_dllAvUtil.av_free(m_ioContext); } m_ioContext = NULL; m_pFormatContext = NULL; m_speed = DVD_PLAYSPEED_NORMAL; DisposeStreams(); m_pInput = NULL; m_dllAvFormat.Unload(); m_dllAvCodec.Unload(); m_dllAvUtil.Unload(); }
Reset()
//复位 void CDVDDemuxFFmpeg::Reset() { CDVDInputStream* pInputStream = m_pInput; Dispose(); Open(pInputStream); }
Flush()
void CDVDDemuxFFmpeg::Flush() { // naughty usage of an internal ffmpeg function if (m_pFormatContext) m_dllAvFormat.av_read_frame_flush(m_pFormatContext); m_iCurrentPts = DVD_NOPTS_VALUE; m_pkt.result = -1; m_dllAvCodec.av_free_packet(&m_pkt.pkt); }
本文我们分析XBMC中视频播放器(dvdplayer)中的文件头部分。文件头部分里包含的是封装Dll用到的头文件。由于文件头种类很多,不可能一一分析,因此还是以ffmpeg文件头为例进行分析。
XBMC中文件头部分文件目录结构如下图所示。
在这里我们看一下封装AVCodec和AVFormat结构体的头文件,分别是DllAvCodec.h和DllAvFormat.h。
DllAvFormat.h内容如下。其中包含了2个主要的类:DllAvFormatInterface和DllAvFormat。
其中DllAvFormatInterface是一个纯虚类,里面全是纯虚函数。
DllAvFormat中包含很多已经定义过的宏,稍后我们分析一下这些宏的含义。
/* * 雷霄骅 * [email protected] * 中国传媒大学/数字电视技术 * */ //接口的作用 class DllAvFormatInterface { public: virtual ~DllAvFormatInterface() {} virtual void av_register_all_dont_call(void)=0; virtual void avformat_network_init_dont_call(void)=0; virtual void avformat_network_deinit_dont_call(void)=0; virtual AVInputFormat *av_find_input_format(const char *short_name)=0; virtual void avformat_close_input(AVFormatContext **s)=0; virtual int av_read_frame(AVFormatContext *s, AVPacket *pkt)=0; virtual void av_read_frame_flush(AVFormatContext *s)=0; virtual int av_read_play(AVFormatContext *s)=0; virtual int av_read_pause(AVFormatContext *s)=0; virtual int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, int flags)=0; #if (!defined USE_EXTERNAL_FFMPEG) && (!defined TARGET_DARWIN) virtual int avformat_find_stream_info_dont_call(AVFormatContext *ic, AVDictionary **options)=0; #endif virtual int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options)=0; virtual AVIOContext *avio_alloc_context(unsigned char *buffer, int buffer_size, int write_flag, void *opaque, int (*read_packet)(void *opaque, uint8_t *buf, int buf_size), int (*write_packet)(void *opaque, uint8_t *buf, int buf_size), offset_t (*seek)(void *opaque, offset_t offset, int whence))=0; virtual AVInputFormat *av_probe_input_format(AVProbeData *pd, int is_opened)=0; virtual AVInputFormat *av_probe_input_format2(AVProbeData *pd, int is_opened, int *score_max)=0; virtual int av_probe_input_buffer(AVIOContext *pb, AVInputFormat **fmt, const char *filename, void *logctx, unsigned int offset, unsigned int max_probe_size)=0; virtual void av_dump_format(AVFormatContext *ic, int index, const char *url, int is_output)=0; virtual int avio_open(AVIOContext **s, const char *filename, int flags)=0; virtual int avio_close(AVIOContext *s)=0; virtual int avio_open_dyn_buf(AVIOContext **s)=0; virtual int avio_close_dyn_buf(AVIOContext *s, uint8_t **pbuffer)=0; virtual offset_t avio_seek(AVIOContext *s, offset_t offset, int whence)=0; virtual int avio_read(AVIOContext *s, unsigned char *buf, int size)=0; virtual void avio_w8(AVIOContext *s, int b)=0; virtual void avio_write(AVIOContext *s, const unsigned char *buf, int size)=0; virtual void avio_wb24(AVIOContext *s, unsigned int val)=0; virtual void avio_wb32(AVIOContext *s, unsigned int val)=0; virtual void avio_wb16(AVIOContext *s, unsigned int val)=0; virtual AVFormatContext *avformat_alloc_context(void)=0; virtual int avformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat, const char *format_name, const char *filename) = 0; virtual AVStream *avformat_new_stream(AVFormatContext *s, AVCodec *c)=0; virtual AVOutputFormat *av_guess_format(const char *short_name, const char *filename, const char *mime_type)=0; virtual int avformat_write_header (AVFormatContext *s, AVDictionary **options)=0; virtual int av_write_trailer(AVFormatContext *s)=0; virtual int av_write_frame (AVFormatContext *s, AVPacket *pkt)=0; #if defined(AVFORMAT_HAS_STREAM_GET_R_FRAME_RATE) virtual AVRational av_stream_get_r_frame_rate(const AVStream *s)=0; #endif }; //封装的Dll,继承了DllDynamic,以及接口 class DllAvFormat : public DllDynamic, DllAvFormatInterface { DECLARE_DLL_WRAPPER(DllAvFormat, DLL_PATH_LIBAVFORMAT) LOAD_SYMBOLS() DEFINE_METHOD0(void, av_register_all_dont_call) DEFINE_METHOD0(void, avformat_network_init_dont_call) DEFINE_METHOD0(void, avformat_network_deinit_dont_call) DEFINE_METHOD1(AVInputFormat*, av_find_input_format, (const char *p1)) DEFINE_METHOD1(void, avformat_close_input, (AVFormatContext **p1)) DEFINE_METHOD1(int, av_read_play, (AVFormatContext *p1)) DEFINE_METHOD1(int, av_read_pause, (AVFormatContext *p1)) DEFINE_METHOD1(void, av_read_frame_flush, (AVFormatContext *p1)) DEFINE_FUNC_ALIGNED2(int, __cdecl, av_read_frame, AVFormatContext *, AVPacket *) DEFINE_FUNC_ALIGNED4(int, __cdecl, av_seek_frame, AVFormatContext*, int, int64_t, int) DEFINE_FUNC_ALIGNED2(int, __cdecl, avformat_find_stream_info_dont_call, AVFormatContext*, AVDictionary **) DEFINE_FUNC_ALIGNED4(int, __cdecl, avformat_open_input, AVFormatContext **, const char *, AVInputFormat *, AVDictionary **) DEFINE_FUNC_ALIGNED2(AVInputFormat*, __cdecl, av_probe_input_format, AVProbeData*, int) DEFINE_FUNC_ALIGNED3(AVInputFormat*, __cdecl, av_probe_input_format2, AVProbeData*, int, int*) DEFINE_FUNC_ALIGNED6(int, __cdecl, av_probe_input_buffer, AVIOContext *, AVInputFormat **, const char *, void *, unsigned int, unsigned int) DEFINE_FUNC_ALIGNED3(int, __cdecl, avio_read, AVIOContext*, unsigned char *, int) DEFINE_FUNC_ALIGNED2(void, __cdecl, avio_w8, AVIOContext*, int) DEFINE_FUNC_ALIGNED3(void, __cdecl, avio_write, AVIOContext*, const unsigned char *, int) DEFINE_FUNC_ALIGNED2(void, __cdecl, avio_wb24, AVIOContext*, unsigned int) DEFINE_FUNC_ALIGNED2(void, __cdecl, avio_wb32, AVIOContext*, unsigned int) DEFINE_FUNC_ALIGNED2(void, __cdecl, avio_wb16, AVIOContext*, unsigned int) DEFINE_METHOD7(AVIOContext *, avio_alloc_context, (unsigned char *p1, int p2, int p3, void *p4, int (*p5)(void *opaque, uint8_t *buf, int buf_size), int (*p6)(void *opaque, uint8_t *buf, int buf_size), offset_t (*p7)(void *opaque, offset_t offset, int whence))) DEFINE_METHOD4(void, av_dump_format, (AVFormatContext *p1, int p2, const char *p3, int p4)) DEFINE_METHOD3(int, avio_open, (AVIOContext **p1, const char *p2, int p3)) DEFINE_METHOD1(int, avio_close, (AVIOContext *p1)) DEFINE_METHOD1(int, avio_open_dyn_buf, (AVIOContext **p1)) DEFINE_METHOD2(int, avio_close_dyn_buf, (AVIOContext *p1, uint8_t **p2)) DEFINE_METHOD3(offset_t, avio_seek, (AVIOContext *p1, offset_t p2, int p3)) DEFINE_METHOD0(AVFormatContext *, avformat_alloc_context) DEFINE_METHOD4(int, avformat_alloc_output_context2, (AVFormatContext **p1, AVOutputFormat *p2, const char *p3, const char *p4)) DEFINE_METHOD2(AVStream *, avformat_new_stream, (AVFormatContext *p1, AVCodec *p2)) DEFINE_METHOD3(AVOutputFormat *, av_guess_format, (const char *p1, const char *p2, const char *p3)) DEFINE_METHOD2(int, avformat_write_header , (AVFormatContext *p1, AVDictionary **p2)) DEFINE_METHOD1(int, av_write_trailer, (AVFormatContext *p1)) DEFINE_METHOD2(int, av_write_frame , (AVFormatContext *p1, AVPacket *p2)) #if defined(AVFORMAT_HAS_STREAM_GET_R_FRAME_RATE) DEFINE_METHOD1(AVRational, av_stream_get_r_frame_rate, (const AVStream *p1)) #endif BEGIN_METHOD_RESOLVE() RESOLVE_METHOD_RENAME(av_register_all, av_register_all_dont_call) RESOLVE_METHOD_RENAME(avformat_network_init, avformat_network_init_dont_call) RESOLVE_METHOD_RENAME(avformat_network_deinit, avformat_network_deinit_dont_call) RESOLVE_METHOD(av_find_input_format) RESOLVE_METHOD(avformat_close_input) RESOLVE_METHOD(av_read_frame) RESOLVE_METHOD(av_read_play) RESOLVE_METHOD(av_read_pause) RESOLVE_METHOD(av_read_frame_flush) RESOLVE_METHOD(av_seek_frame) RESOLVE_METHOD_RENAME(avformat_find_stream_info, avformat_find_stream_info_dont_call) RESOLVE_METHOD(avformat_open_input) RESOLVE_METHOD(avio_alloc_context) RESOLVE_METHOD(av_probe_input_format) RESOLVE_METHOD(av_probe_input_format2) RESOLVE_METHOD(av_probe_input_buffer) RESOLVE_METHOD(av_dump_format) RESOLVE_METHOD(avio_open) RESOLVE_METHOD(avio_close) RESOLVE_METHOD(avio_open_dyn_buf) RESOLVE_METHOD(avio_close_dyn_buf) RESOLVE_METHOD(avio_seek) RESOLVE_METHOD(avio_read) RESOLVE_METHOD(avio_w8) RESOLVE_METHOD(avio_write) RESOLVE_METHOD(avio_wb24) RESOLVE_METHOD(avio_wb32) RESOLVE_METHOD(avio_wb16) RESOLVE_METHOD(avformat_alloc_context) RESOLVE_METHOD(avformat_alloc_output_context2) RESOLVE_METHOD(avformat_new_stream) RESOLVE_METHOD(av_guess_format) RESOLVE_METHOD(avformat_write_header) RESOLVE_METHOD(av_write_trailer) RESOLVE_METHOD(av_write_frame) #if defined(AVFORMAT_HAS_STREAM_GET_R_FRAME_RATE) RESOLVE_METHOD(av_stream_get_r_frame_rate) #endif END_METHOD_RESOLVE() /* dependencies of libavformat */ DllAvCodec m_dllAvCodec; // DllAvUtil loaded implicitely by m_dllAvCodec public: void av_register_all() { CSingleLock lock(DllAvCodec::m_critSection); av_register_all_dont_call(); } int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options) { CSingleLock lock(DllAvCodec::m_critSection); return avformat_find_stream_info_dont_call(ic, options); } virtual bool Load() { if (!m_dllAvCodec.Load()) return false; bool loaded = DllDynamic::Load(); CSingleLock lock(DllAvCodec::m_critSection); if (++m_avformat_refcnt == 1 && loaded) avformat_network_init_dont_call(); return loaded; } virtual void Unload() { CSingleLock lock(DllAvCodec::m_critSection); if (--m_avformat_refcnt == 0 && DllDynamic::IsLoaded()) avformat_network_deinit_dont_call(); DllDynamic::Unload(); } protected: static int m_avformat_refcnt; }; #endif
这些宏的含义如下:
DEFINE_METHOD0(result, name) 定义一个方法(不包含参数)
DEFINE_METHOD1(result, name, args) 定义一个方法(1个参数)
DEFINE_METHOD2(result, name, args) 定义一个方法(2个参数)
DEFINE_METHOD3(result, name, args) 定义一个方法(3个参数)
DEFINE_METHOD4(result, name, args) 定义一个方法(4个参数)
以此类推...
DEFINE_FUNC_ALIGNED0(result, linkage, name) 定义一个方法(不包含参数)
DEFINE_FUNC_ALIGNED1(result, linkage, name, t1) 定义一个方法(1个参数)
DEFINE_FUNC_ALIGNED2(result, linkage, name, t1, t2) 定义一个方法(2个参数)
以此类推...
可以看一下这些宏的定义。看了一会,感觉宏的定义太多了,好乱。在这里仅举一个例子:RESOLVE_METHOD
#define RESOLVE_METHOD(method) \ if (!m_dll->ResolveExport( #method , & m_##method##_ptr )) \ return false;
从定义中可以看出,调用了m_dll的方法ResolveExport()。但是在DllAvFormat中并没有m_dll变量。实际上m_dll位于DllAvFormat的父类DllDynamic里面。
DllAvFormat继承了DllDynamic。DllDynamic是用于加载Dll的类。我们可以看一下它的定义:
//Dll动态加载类 class DllDynamic { public: DllDynamic(); DllDynamic(const CStdString& strDllName); virtual ~DllDynamic(); virtual bool Load();//加载 virtual void Unload();//卸载 virtual bool IsLoaded() const { return m_dll!=NULL; }//是否加载 bool CanLoad(); bool EnableDelayedUnload(bool bOnOff); bool SetFile(const CStdString& strDllName);//设置文件 const CStdString &GetFile() const { return m_strDllName; } protected: virtual bool ResolveExports()=0; virtual bool LoadSymbols() { return false; } bool m_DelayUnload; LibraryLoader* m_dll; CStdString m_strDllName; };
其中有一个变量LibraryLoader* m_dll。是用于加载Dll的。
可以看一DllDynamic中主要的几个函数,就能明白这个类的作用了。
//加载 bool DllDynamic::Load() { if (m_dll) return true; if (!(m_dll=CSectionLoader::LoadDLL(m_strDllName, m_DelayUnload, LoadSymbols()))) return false; if (!ResolveExports()) { CLog::Log(LOGERROR, "Unable to resolve exports from dll %s", m_strDllName.c_str()); Unload(); return false; } return true; }
//卸载 void DllDynamic::Unload() { if(m_dll) CSectionLoader::UnloadDLL(m_strDllName); m_dll=NULL; }
可以看看LibraryLoader的定义。LibraryLoader本身是一个纯虚类,具体方法的实现在其子类里面。
//Dll加载类 class LibraryLoader { public: LibraryLoader(const char* libraryFile); virtual ~LibraryLoader(); virtual bool Load() = 0; virtual void Unload() = 0; virtual int ResolveExport(const char* symbol, void** ptr, bool logging = true) = 0; virtual int ResolveOrdinal(unsigned long ordinal, void** ptr); virtual bool IsSystemDll() = 0; virtual HMODULE GetHModule() = 0; virtual bool HasSymbols() = 0; char* GetName(); // eg "mplayer.dll" char* GetFileName(); // "special://xbmcbin/system/mplayer/players/mplayer.dll" char* GetPath(); // "special://xbmcbin/system/mplayer/players/" int IncrRef(); int DecrRef(); int GetRef(); private: LibraryLoader(const LibraryLoader&); LibraryLoader& operator=(const LibraryLoader&); char* m_sFileName; char* m_sPath; int m_iRefCount; };
LibraryLoader的继承关系如下图所示。
由于自己的操作系统是Windows下的,因此可以看看Win32DllLoader的定义。
//Windows下的Dll加载类 class Win32DllLoader : public LibraryLoader { public: class Import { public: void *table; DWORD function; }; Win32DllLoader(const char *dll); ~Win32DllLoader(); virtual bool Load();//加载 virtual void Unload();//卸载 virtual int ResolveExport(const char* symbol, void** ptr, bool logging = true); virtual bool IsSystemDll(); virtual HMODULE GetHModule(); virtual bool HasSymbols(); private: void OverrideImports(const CStdString &dll); void RestoreImports(); static bool ResolveImport(const char *dllName, const char *functionName, void **fixup); static bool ResolveOrdinal(const char *dllName, unsigned long ordinal, void **fixup); bool NeedsHooking(const char *dllName); HMODULE m_dllHandle; bool bIsSystemDll; std::vector<Import> m_overriddenImports; std::vector<HMODULE> m_referencedDlls; };
其中加载Dll使用Load(),卸载Dll使用Unload()。可以看看这两个函数具体的代码。
//加载 bool Win32DllLoader::Load() { if (m_dllHandle != NULL) return true; //文件路径 CStdString strFileName = GetFileName(); CStdStringW strDllW; g_charsetConverter.utf8ToW(CSpecialProtocol::TranslatePath(strFileName), strDllW, false, false, false); //加载库 m_dllHandle = LoadLibraryExW(strDllW.c_str(), NULL, LOAD_WITH_ALTERED_SEARCH_PATH); if (!m_dllHandle) { LPVOID lpMsgBuf; DWORD dw = GetLastError(); FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, 0, (LPTSTR) &lpMsgBuf, 0, NULL ); CLog::Log(LOGERROR, "%s: Failed to load %s with error %d:%s", __FUNCTION__, CSpecialProtocol::TranslatePath(strFileName).c_str(), dw, lpMsgBuf); LocalFree(lpMsgBuf); return false; } // handle functions that the dll imports if (NeedsHooking(strFileName.c_str())) OverrideImports(strFileName); else bIsSystemDll = true; return true; } //卸载 void Win32DllLoader::Unload() { // restore our imports RestoreImports(); //卸载库 if (m_dllHandle) { if (!FreeLibrary(m_dllHandle)) CLog::Log(LOGERROR, "%s Unable to unload %s", __FUNCTION__, GetName()); } m_dllHandle = NULL; }
本文我们分析XBMC中视频播放器(dvdplayer)中的输入流部分。由于输入流种类很多,因此以RTMP输入流为例进行分析。
XBMC中输入流部分文件目录结构如下图所示。
从目录中文件的名称我们可以看出,XBMC支持多种输入方式:File,HTSP,HTTP,RTMP等等。在这里我们看看RTMP部分的源代码。对应DVDInputStreamRTMP.h和DVDInputStreamRTMP.cpp
先来看看DVDInputStreamRTMP.h
/* * 雷霄骅 * [email protected] * 中国传媒大学/数字电视技术 * */ //如果有libRTMP #ifdef HAS_LIBRTMP #include "DVDInputStream.h" #include "DllLibRTMP.h" //支持RTMP输入流的类,继承CDVDInputStream class CDVDInputStreamRTMP : public CDVDInputStream , public CDVDInputStream::ISeekTime { public: CDVDInputStreamRTMP(); virtual ~CDVDInputStreamRTMP(); virtual bool Open(const char* strFile, const std::string &content);//打开 virtual void Close();//关闭 virtual int Read(uint8_t* buf, int buf_size);//读取 virtual int64_t Seek(int64_t offset, int whence);//跳转到 bool SeekTime(int iTimeInMsec); virtual bool Pause(double dTime);//暂停 virtual bool IsEOF(); virtual int64_t GetLength(); CCriticalSection m_RTMPSection; protected: bool m_eof; bool m_bPaused; char* m_sStreamPlaying; std::vector<CStdString> m_optionvalues; RTMP *m_rtmp; DllLibRTMP m_libRTMP; }; #endif
该类中包含了Open(),Close(),Read(),Seek(),Pause() 这类的方法。实现了对RTMP协议的各种操作。这些方法都是CDVDInputStreamRTMP父类CDVDInputStream中的方法。可以看一下CDVDInputStream的定义,就知道了。
//输入流类 class CDVDInputStream { public: class IChannel { public: virtual ~IChannel() {}; virtual bool NextChannel(bool preview = false) = 0; virtual bool PrevChannel(bool preview = false) = 0; virtual bool SelectChannelByNumber(unsigned int channel) = 0; virtual bool SelectChannel(const PVR::CPVRChannel &channel) { return false; }; virtual bool GetSelectedChannel(PVR::CPVRChannelPtr&) { return false; }; virtual bool UpdateItem(CFileItem& item) = 0; virtual bool CanRecord() = 0; virtual bool IsRecording() = 0; virtual bool Record(bool bOnOff) = 0; virtual bool CanPause() = 0; virtual bool CanSeek() = 0; }; class IDisplayTime { public: virtual ~IDisplayTime() {}; virtual int GetTotalTime() = 0; virtual int GetTime() = 0; }; class ISeekTime { public: virtual ~ISeekTime() {}; virtual bool SeekTime(int ms) = 0; }; class IChapter { public: virtual ~IChapter() {}; virtual int GetChapter() = 0; virtual int GetChapterCount() = 0; virtual void GetChapterName(std::string& name) = 0; virtual bool SeekChapter(int ch) = 0; }; class IMenus { public: virtual ~IMenus() {}; virtual void ActivateButton() = 0; virtual void SelectButton(int iButton) = 0; virtual int GetCurrentButton() = 0; virtual int GetTotalButtons() = 0; virtual void OnUp() = 0; virtual void OnDown() = 0; virtual void OnLeft() = 0; virtual void OnRight() = 0; virtual void OnMenu() = 0; virtual void OnBack() = 0; virtual void OnNext() = 0; virtual void OnPrevious() = 0; virtual bool OnMouseMove(const CPoint &point) = 0; virtual bool OnMouseClick(const CPoint &point) = 0; virtual bool IsInMenu() = 0; virtual void SkipStill() = 0; virtual double GetTimeStampCorrection() = 0; virtual bool GetState(std::string &xmlstate) = 0; virtual bool SetState(const std::string &xmlstate) = 0; }; class ISeekable { public: virtual ~ISeekable() {}; virtual bool CanSeek() = 0; virtual bool CanPause() = 0; }; enum ENextStream { NEXTSTREAM_NONE, NEXTSTREAM_OPEN, NEXTSTREAM_RETRY, }; CDVDInputStream(DVDStreamType m_streamType); virtual ~CDVDInputStream(); virtual bool Open(const char* strFileName, const std::string& content);//打开 virtual void Close() = 0;//关闭 virtual int Read(uint8_t* buf, int buf_size) = 0;//读取 virtual int64_t Seek(int64_t offset, int whence) = 0;//跳转 virtual bool Pause(double dTime) = 0;//暂停 virtual int64_t GetLength() = 0; virtual std::string& GetContent() { return m_content; }; virtual std::string& GetFileName() { return m_strFileName; } virtual CURL &GetURL() { return m_url; } virtual ENextStream NextStream() { return NEXTSTREAM_NONE; } virtual void Abort() {} virtual int GetBlockSize() { return 0; } virtual void ResetScanTimeout(unsigned int iTimeoutMs) { } /*! \brief Indicate expected read rate in bytes per second. * This could be used to throttle caching rate. Should * be seen as only a hint */ virtual void SetReadRate(unsigned rate) {} /*! \brief Get the cache status \return true when cache status was succesfully obtained */ virtual bool GetCacheStatus(XFILE::SCacheStatus *status) { return false; } bool IsStreamType(DVDStreamType type) const { return m_streamType == type; } virtual bool IsEOF() = 0; virtual BitstreamStats GetBitstreamStats() const { return m_stats; } void SetFileItem(const CFileItem& item); protected: DVDStreamType m_streamType; std::string m_strFileName; CURL m_url; BitstreamStats m_stats; std::string m_content; CFileItem m_item; };
回到CDVDInputStreamRTMP类本身。可以看一下Open(),Close(),Read(),Seek(),Pause()这些方法的函数体。这些方方通过调用libRTMP中相应的方法,完成了对RTMP流媒体的各种操作。
/* * 雷霄骅 * [email protected] * 中国传媒大学/数字电视技术 * */ //打开 bool CDVDInputStreamRTMP::Open(const char* strFile, const std::string& content) { if (m_sStreamPlaying) { free(m_sStreamPlaying); m_sStreamPlaying = NULL; } if (!CDVDInputStream::Open(strFile, "video/x-flv")) return false; CSingleLock lock(m_RTMPSection); // libRTMP can and will alter strFile, so take a copy of it m_sStreamPlaying = (char*)calloc(strlen(strFile)+1,sizeof(char)); strcpy(m_sStreamPlaying,strFile); //libRTMP中的设置URL if (!m_libRTMP.SetupURL(m_rtmp, m_sStreamPlaying)) return false; // SetOpt and SetAVal copy pointers to the value. librtmp doesn't use the values until the Connect() call, // so value objects must stay allocated until then. To be extra safe, keep the values around until Close(), // in case librtmp needs them again. m_optionvalues.clear(); for (int i=0; options[i].name; i++) { CStdString tmp = m_item.GetProperty(options[i].name).asString(); if (!tmp.empty()) { m_optionvalues.push_back(tmp); AVal av_tmp; SetAVal(av_tmp, m_optionvalues.back()); m_libRTMP.SetOpt(m_rtmp, &options[i].key, &av_tmp); } } //建立RTMP链接中的NetConnection和NetStream if (!m_libRTMP.Connect(m_rtmp, NULL) || !m_libRTMP.ConnectStream(m_rtmp, 0)) return false; m_eof = false; return true; } //关闭 // close file and reset everything void CDVDInputStreamRTMP::Close() { CSingleLock lock(m_RTMPSection); CDVDInputStream::Close(); //关闭连接 m_libRTMP.Close(m_rtmp); m_optionvalues.clear(); m_eof = true; m_bPaused = false; } //读取 int CDVDInputStreamRTMP::Read(uint8_t* buf, int buf_size) {//读取 int i = m_libRTMP.Read(m_rtmp, (char *)buf, buf_size); if (i < 0) m_eof = true; return i; } //跳转到 int64_t CDVDInputStreamRTMP::Seek(int64_t offset, int whence) { if (whence == SEEK_POSSIBLE) return 0; else return -1; } //暂停 bool CDVDInputStreamRTMP::Pause(double dTime) { CSingleLock lock(m_RTMPSection); m_bPaused = !m_bPaused; CLog::Log(LOGNOTICE, "RTMP Pause %s requested", m_bPaused ? "TRUE" : "FALSE"); m_libRTMP.Pause(m_rtmp, m_bPaused); return true; }