本文我们分析XBMC中视频播放器(dvdplayer)中的解复用器部分。由于解复用器种类很多,不可能一一分析,因此以ffmpeg解复用器为例进行分析。
XBMC解复用器部分文件目录如下图所示:
在这里我们看一下解复用器中的FFMPEG解复用器。对应DVDDemuxFFmpeg.h和DVDDemuxFFmpeg.cpp
之前的分析类文章在解复用器这方面已经做过详细的分析了。在此就不多叙述了,代码很清晰。重点的地方已经标上了注释。
DVDDemuxFFmpeg.h源代码如下所示:
- /*
- * 雷霄骅
- * leixiaohua1020@126.com
- * 中国传媒大学/数字电视技术
- *
- */
- #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);
- }