XBMC分析系列文章:
XBMC源代码分析 4:视频播放器(dvdplayer)-解码器(以ffmpeg为例)
本文我们分析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);
}