/*
* Copyright (C) 2005-2013 Team XBMC
* http://xbmc.org
*
* 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, 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 XBMC; see the file COPYING. If not, see
* .
*
*/
#include "system.h"
#ifndef __STDC_CONSTANT_MACROS
#define __STDC_CONSTANT_MACROS
#endif
#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
#endif
#ifdef TARGET_POSIX
#include "stdint.h"
#endif
#include "DVDDemuxFFmpeg.h"
#include "DVDInputStreams/DVDInputStream.h"
#include "DVDInputStreams/DVDInputStreamNavigator.h"
#ifdef HAVE_LIBBLURAY
#include "DVDInputStreams/DVDInputStreamBluray.h"
#endif
#include "DVDInputStreams/DVDInputStreamPVRManager.h"
#include "DVDInputStreams/DVDInputStreamFFmpeg.h"
#include "DVDDemuxUtils.h"
#include "DVDClock.h" // for DVD_TIME_BASE
#include "commons/Exception.h"
#include "settings/AdvancedSettings.h"
#include "settings/Settings.h"
#include "filesystem/File.h"
#include "filesystem/CurlFile.h"
#include "filesystem/Directory.h"
#include "utils/log.h"
#include "threads/Thread.h"
#include "threads/SystemClock.h"
#include "utils/TimeUtils.h"
#include "utils/StringUtils.h"
#include "URL.h"
#include "cores/FFmpeg.h"
extern "C" {
#include "libavutil/opt.h"
}
struct StereoModeConversionMap
{
const char* name;
const char* mode;
};
// we internally use the matroska string representation of stereoscopic modes.
// This struct is a conversion map to convert stereoscopic mode values
// from asf/wmv to the internally used matroska ones
static const struct StereoModeConversionMap WmvToInternalStereoModeMap[] =
{
{ "SideBySideRF", "right_left" },
{ "SideBySideLF", "left_right" },
{ "OverUnderRT", "bottom_top" },
{ "OverUnderLT", "top_bottom" },
{}
};
#define FF_MAX_EXTRADATA_SIZE ((1 << 28) - FF_INPUT_BUFFER_PADDING_SIZE)
void CDemuxStreamAudioFFmpeg::GetStreamInfo(std::string& strInfo)
{
if(!m_stream) return;
char temp[128];
avcodec_string(temp, 128, m_stream->codec, 0);
strInfo = temp;
}
void CDemuxStreamAudioFFmpeg::GetStreamName(std::string& strInfo)
{
if(!m_stream) return;
if(!m_description.empty())
strInfo = m_description;
else
CDemuxStream::GetStreamName(strInfo);
}
void CDemuxStreamSubtitleFFmpeg::GetStreamName(std::string& strInfo)
{
if(!m_stream) return;
if(!m_description.empty())
strInfo = m_description;
else
CDemuxStream::GetStreamName(strInfo);
}
void CDemuxStreamVideoFFmpeg::GetStreamInfo(std::string& strInfo)
{
if(!m_stream) return;
char temp[128];
avcodec_string(temp, 128, m_stream->codec, 0);
strInfo = temp;
}
void CDemuxStreamSubtitleFFmpeg::GetStreamInfo(std::string& strInfo)
{
if(!m_stream) return;
char temp[128];
avcodec_string(temp, 128, m_stream->codec, 0);
strInfo = temp;
}
static int interrupt_cb(void* ctx)
{
CDVDDemuxFFmpeg* demuxer = static_cast(ctx);
if(demuxer && demuxer->Aborted())
return 1;
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////
/*
static int dvd_file_open(URLContext *h, const char *filename, int flags)
{
return -1;
}
*/
static int dvd_file_read(void *h, uint8_t* buf, int size)
{
if(interrupt_cb(h))
return AVERROR_EXIT;
CDVDInputStream* pInputStream = static_cast(h)->m_pInput;
return pInputStream->Read(buf, size);
}
/*
static int dvd_file_write(URLContext *h, uint8_t* buf, int size)
{
return -1;
}
*/
static int64_t dvd_file_seek(void *h, int64_t pos, int whence)
{
if(interrupt_cb(h))
return AVERROR_EXIT;
CDVDInputStream* pInputStream = static_cast(h)->m_pInput;
if(whence == AVSEEK_SIZE)
return pInputStream->GetLength();
else
return pInputStream->Seek(pos, whence & ~AVSEEK_FORCE);
}
////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////
CDVDDemuxFFmpeg::CDVDDemuxFFmpeg() : CDVDDemux()
{
m_pFormatContext = NULL;
m_pInput = NULL;
m_ioContext = NULL;
m_currentPts = DVD_NOPTS_VALUE;
m_bMatroska = false;
m_bAVI = false;
m_speed = DVD_PLAYSPEED_NORMAL;
m_program = UINT_MAX;
m_pkt.result = -1;
memset(&m_pkt.pkt, 0, sizeof(AVPacket));
m_streaminfo = true; /* set to true if we want to look for streams before playback */
m_checkvideo = false;
}
CDVDDemuxFFmpeg::~CDVDDemuxFFmpeg()
{
Dispose();
ff_flush_avutil_log_buffers();
}
bool CDVDDemuxFFmpeg::Aborted()
{
if(m_timeout.IsTimePast())
return true;
CDVDInputStreamFFmpeg * input = dynamic_cast(m_pInput);
if(input && input->Aborted())
return true;
return false;
}
bool CDVDDemuxFFmpeg::Open(CDVDInputStream* pInput, bool streaminfo, bool fileinfo)
{
AVInputFormat* iformat = NULL;
std::string strFile;
m_streaminfo = streaminfo;
m_currentPts = DVD_NOPTS_VALUE;
m_speed = DVD_PLAYSPEED_NORMAL;
m_program = UINT_MAX;
const AVIOInterruptCB int_cb = { interrupt_cb, this };
if (!pInput) return false;
m_pInput = pInput;
strFile = m_pInput->GetFileName();
if( m_pInput->GetContent().length() > 0 )
{
std::string content = m_pInput->GetContent();
StringUtils::ToLower(content);
/* check if we can get a hint from content */
if ( content.compare("video/x-vobsub") == 0 )
iformat = av_find_input_format("mpeg");
else if( content.compare("video/x-dvd-mpeg") == 0 )
iformat = av_find_input_format("mpeg");
else if( content.compare("video/mp2t") == 0 )
iformat = av_find_input_format("mpegts");
else if( content.compare("multipart/x-mixed-replace") == 0 )
iformat = av_find_input_format("mjpeg");
}
// open the demuxer
m_pFormatContext = 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();
AVDictionary *options = GetFFMpegOptionsFromURL(url);
int result=-1;
if (url.IsProtocol("mms"))
{
// try mmsh, then mmst
url.SetProtocol("mmsh");
url.SetProtocolOptions("");
result = avformat_open_input(&m_pFormatContext, url.Get().c_str(), iformat, &options);
if (result < 0)
{
url.SetProtocol("mmst");
strFile = url.Get();
}
}
if (result < 0 && 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();
av_dict_free(&options);
return false;
}
av_dict_free(&options);
}
else
{
unsigned char* buffer = (unsigned char*)av_malloc(FFMPEG_FILE_BUFFER_SIZE);
m_ioContext = 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;
std::string content = m_pInput->GetContent();
StringUtils::ToLower(content);
if (StringUtils::StartsWith(content, "audio/l16"))
iformat = av_find_input_format("s16be");
if( iformat == NULL )
{
// let ffmpeg decide which demuxer we have to open
bool trySPDIFonly = (m_pInput->GetContent() == "audio/x-spdif-compressed");
if (!trySPDIFonly)
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();
// av_probe_input_buffer might have changed the buffer_size beyond our allocated amount
int buffer_size = std::min((int) FFMPEG_FILE_BUFFER_SIZE, m_ioContext->buffer_size);
// read data using avformat's buffers
pd.buf_size = avio_read(m_ioContext, pd.buf, m_ioContext->max_packet_size ? m_ioContext->max_packet_size : 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
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 = 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 = 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 = av_find_input_format("aac");
else if( content.compare("audio/aac") == 0 )
iformat = av_find_input_format("aac");
else if( content.compare("video/flv") == 0 )
iformat = av_find_input_format("flv");
else if( content.compare("video/x-flv") == 0 )
iformat = 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;
AVDictionary *options = NULL;
if (iformat->name && (strcmp(iformat->name, "mp3") == 0 || strcmp(iformat->name, "mp2") == 0))
{
CLog::Log(LOGDEBUG, "%s - setting usetoc to 0 for accurate VBR MP3 seek", __FUNCTION__);
av_dict_set(&options, "usetoc", "0", 0);
}
if (StringUtils::StartsWith(content, "audio/l16"))
{
int channels = 2;
int samplerate = 44100;
GetL16Parameters(channels, samplerate);
av_dict_set_int(&options, "channels", channels, 0);
av_dict_set_int(&options, "sample_rate", samplerate, 0);
}
if (avformat_open_input(&m_pFormatContext, strFile.c_str(), iformat, &options) < 0)
{
CLog::Log(LOGERROR, "%s - Error, could not open file %s", __FUNCTION__, CURL::GetRedacted(strFile).c_str());
Dispose();
av_dict_free(&options);
return false;
}
av_dict_free(&options);
}
// 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)
av_opt_set_int(m_pFormatContext, "analyzeduration", 500000, 0);
bool skipCreateStreams = false;
bool isBluray = pInput->IsStreamType(DVDSTREAM_TYPE_BLURAY);
if (iformat && (strcmp(iformat->name, "mpegts") == 0) && !fileinfo && !isBluray)
{
av_opt_set_int(m_pFormatContext, "analyzeduration", 500000, 0);
m_checkvideo = true;
skipCreateStreams = true;
}
else if (!iformat || (strcmp(iformat->name, "mpegts") != 0))
{
m_streaminfo = true;
}
// 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 (m_streaminfo)
{
/* to speed up dvd switches, only analyse very short */
if(m_pInput->IsStreamType(DVDSTREAM_TYPE_DVD))
av_opt_set_int(m_pFormatContext, "analyzeduration", 500000, 0);
CLog::Log(LOGDEBUG, "%s - avformat_find_stream_info starting", __FUNCTION__);
int iErr = 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__);
if (m_checkvideo)
{
// make sure we start video with an i-frame
ResetVideoStreams();
}
}
else
{
m_program = 0;
m_checkvideo = true;
skipCreateStreams = true;
}
// 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
av_dump_format(m_pFormatContext, 0, strFile.c_str(), 0);
UpdateCurrentPTS();
// in case of mpegts and we have not seen pat/pmt, defer creation of streams
if (!skipCreateStreams || m_pFormatContext->nb_programs > 0)
CreateStreams();
// allow IsProgramChange to return true
if (skipCreateStreams && GetNrOfStreams() == 0)
m_program = 0;
return true;
}
void CDVDDemuxFFmpeg::Dispose()
{
m_pkt.result = -1;
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;
}
avformat_close_input(&m_pFormatContext);
}
if(m_ioContext)
{
av_free(m_ioContext->buffer);
av_free(m_ioContext);
}
m_ioContext = NULL;
m_pFormatContext = NULL;
m_speed = DVD_PLAYSPEED_NORMAL;
DisposeStreams();
m_pInput = NULL;
}
void CDVDDemuxFFmpeg::Reset()
{
CDVDInputStream* pInputStream = m_pInput;
Dispose();
Open(pInputStream, m_streaminfo);
}
void CDVDDemuxFFmpeg::Flush()
{
// naughty usage of an internal ffmpeg function
if (m_pFormatContext)
av_read_frame_flush(m_pFormatContext);
m_currentPts = DVD_NOPTS_VALUE;
m_pkt.result = -1;
av_free_packet(&m_pkt.pkt);
}
void CDVDDemuxFFmpeg::Abort()
{
m_timeout.SetExpired();
}
void CDVDDemuxFFmpeg::SetSpeed(int iSpeed)
{
if(!m_pFormatContext)
return;
if(m_speed != DVD_PLAYSPEED_PAUSE && iSpeed == DVD_PLAYSPEED_PAUSE)
{
m_pInput->Pause(m_currentPts);
av_read_pause(m_pFormatContext);
}
else if(m_speed == DVD_PLAYSPEED_PAUSE && iSpeed != DVD_PLAYSPEED_PAUSE)
{
m_pInput->Pause(m_currentPts);
av_read_play(m_pFormatContext);
}
m_speed = iSpeed;
AVDiscard discard = AVDISCARD_NONE;
if(m_speed > 4*DVD_PLAYSPEED_NORMAL)
discard = AVDISCARD_NONKEY;
else if(m_speed > 2*DVD_PLAYSPEED_NORMAL)
discard = AVDISCARD_BIDIR;
else if(m_speed < DVD_PLAYSPEED_PAUSE)
discard = AVDISCARD_NONKEY;
for(unsigned int i = 0; i < m_pFormatContext->nb_streams; i++)
{
if(m_pFormatContext->streams[i])
{
if(m_pFormatContext->streams[i]->discard != AVDISCARD_ALL)
m_pFormatContext->streams[i]->discard = discard;
}
}
}
AVDictionary *CDVDDemuxFFmpeg::GetFFMpegOptionsFromURL(const CURL &url)
{
AVDictionary *options = NULL;
if (url.IsProtocol("http") || url.IsProtocol("https"))
{
std::map protocolOptions;
url.GetProtocolOptions(protocolOptions);
std::string headers;
bool hasUserAgent = false;
for(std::map::const_iterator it = protocolOptions.begin(); it != protocolOptions.end(); ++it)
{
std::string name = it->first; StringUtils::ToLower(name);
const std::string &value = it->second;
if (name == "seekable")
av_dict_set(&options, "seekable", value.c_str(), 0);
else if (name == "user-agent")
{
av_dict_set(&options, "user-agent", value.c_str(), 0);
hasUserAgent = true;
}
else if (name != "auth" && name != "encoding")
// all other protocol options can be added as http header.
headers.append(it->first).append(": ").append(value).append("\r\n");
}
if (!hasUserAgent)
// set default xbmc user-agent.
av_dict_set(&options, "user-agent", g_advancedSettings.m_userAgent.c_str(), 0);
if (!headers.empty())
av_dict_set(&options, "headers", headers.c_str(), 0);
std::string cookies;
if (XFILE::CCurlFile::GetCookies(url, cookies))
av_dict_set(&options, "cookies", cookies.c_str(), 0);
}
return options;
}
double CDVDDemuxFFmpeg::ConvertTimestamp(int64_t pts, int den, int num)
{
if (pts == (int64_t)AV_NOPTS_VALUE)
return DVD_NOPTS_VALUE;
// do calculations in floats as they can easily overflow otherwise
// we don't care for having a completly exact timestamp anyway
double timestamp = (double)pts * num / den;
double starttime = 0.0f;
// for dvd's we need the original time
if(CDVDInputStream::IMenus* menu = dynamic_cast(m_pInput))
starttime = menu->GetTimeStampCorrection() / DVD_TIME_BASE;
else if (m_pFormatContext->start_time != (int64_t)AV_NOPTS_VALUE)
starttime = (double)m_pFormatContext->start_time / AV_TIME_BASE;
if(timestamp > starttime)
timestamp -= starttime;
// allow for largest possible difference in pts and dts for a single packet
else if( timestamp + 0.5f > starttime )
timestamp = 0;
return timestamp*DVD_TIME_BASE;
}
DemuxPacket* CDVDDemuxFFmpeg::Read()
{
DemuxPacket* pPacket = NULL;
// on some cases where the received packet is invalid we will need to return an empty packet (0 length) otherwise the main loop (in CDVDPlayer)
// would consider this the end of stream and stop.
bool bReturnEmpty = false;
{ CSingleLock lock(m_critSection); // open lock scope
if (m_pFormatContext)
{
// assume we are not eof
if(m_pFormatContext->pb)
m_pFormatContext->pb->eof_reached = 0;
// check for saved packet after a program change
if (m_pkt.result < 0)
{
// keep track if ffmpeg doesn't always set these
m_pkt.pkt.size = 0;
m_pkt.pkt.data = NULL;
// timeout reads after 100ms
m_timeout.Set(20000);
m_pkt.result = av_read_frame(m_pFormatContext, &m_pkt.pkt);
m_timeout.SetInfinite();
}
if (m_pkt.result == AVERROR(EINTR) || m_pkt.result == AVERROR(EAGAIN))
{
// timeout, probably no real error, return empty packet
bReturnEmpty = true;
}
else if (m_pkt.result < 0)
{
Flush();
}
else if (IsProgramChange())
{
// update streams
CreateStreams(m_program);
pPacket = CDVDDemuxUtils::AllocateDemuxPacket(0);
pPacket->iStreamId = DMX_SPECIALID_STREAMCHANGE;
return pPacket;
}
// check size and stream index for being in a valid range
else if (m_pkt.pkt.size < 0 ||
m_pkt.pkt.stream_index < 0 ||
m_pkt.pkt.stream_index >= (int)m_pFormatContext->nb_streams)
{
// XXX, in some cases ffmpeg returns a negative packet size
if(m_pFormatContext->pb && !m_pFormatContext->pb->eof_reached)
{
CLog::Log(LOGERROR, "CDVDDemuxFFmpeg::Read() no valid packet");
bReturnEmpty = true;
Flush();
}
else
CLog::Log(LOGERROR, "CDVDDemuxFFmpeg::Read() returned invalid packet and eof reached");
m_pkt.result = -1;
av_free_packet(&m_pkt.pkt);
}
else
{
ParsePacket(&m_pkt.pkt);
AVStream *stream = m_pFormatContext->streams[m_pkt.pkt.stream_index];
if (IsVideoReady())
{
if (m_program != UINT_MAX)
{
/* check so packet belongs to selected program */
for (unsigned int i = 0; i < m_pFormatContext->programs[m_program]->nb_stream_indexes; i++)
{
if(m_pkt.pkt.stream_index == (int)m_pFormatContext->programs[m_program]->stream_index[i])
{
pPacket = CDVDDemuxUtils::AllocateDemuxPacket(m_pkt.pkt.size);
break;
}
}
if (!pPacket)
bReturnEmpty = true;
}
else
pPacket = CDVDDemuxUtils::AllocateDemuxPacket(m_pkt.pkt.size);
}
else
bReturnEmpty = true;
if (pPacket)
{
// lavf sometimes bugs out and gives 0 dts/pts instead of no dts/pts
// since this could only happens on initial frame under normal
// circomstances, let's assume it is wrong all the time
if(m_pkt.pkt.dts == 0)
m_pkt.pkt.dts = AV_NOPTS_VALUE;
if(m_pkt.pkt.pts == 0)
m_pkt.pkt.pts = AV_NOPTS_VALUE;
if(m_bMatroska && stream->codec && stream->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{ // matroska can store different timestamps
// for different formats, for native stored
// stuff it is pts, but for ms compatibility
// tracks, it is really dts. sadly ffmpeg
// sets these two timestamps equal all the
// time, so we select it here instead
if(stream->codec->codec_tag == 0)
m_pkt.pkt.dts = AV_NOPTS_VALUE;
else
m_pkt.pkt.pts = AV_NOPTS_VALUE;
}
// we need to get duration slightly different for matroska embedded text subtitels
if(m_bMatroska && stream->codec && stream->codec->codec_id == AV_CODEC_ID_TEXT && m_pkt.pkt.convergence_duration != 0)
m_pkt.pkt.duration = m_pkt.pkt.convergence_duration;
if(m_bAVI && stream->codec && stream->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
// AVI's always have borked pts, specially if m_pFormatContext->flags includes
// AVFMT_FLAG_GENPTS so always use dts
m_pkt.pkt.pts = AV_NOPTS_VALUE;
}
// copy contents into our own packet
pPacket->iSize = m_pkt.pkt.size;
// maybe we can avoid a memcpy here by detecting where pkt.destruct is pointing too?
if (m_pkt.pkt.data)
memcpy(pPacket->pData, m_pkt.pkt.data, pPacket->iSize);
pPacket->pts = ConvertTimestamp(m_pkt.pkt.pts, stream->time_base.den, stream->time_base.num);
pPacket->dts = ConvertTimestamp(m_pkt.pkt.dts, stream->time_base.den, stream->time_base.num);
pPacket->duration = DVD_SEC_TO_TIME((double)m_pkt.pkt.duration * stream->time_base.num / stream->time_base.den);
// used to guess streamlength
if (pPacket->dts != DVD_NOPTS_VALUE && (pPacket->dts > m_currentPts || m_currentPts == DVD_NOPTS_VALUE))
m_currentPts = pPacket->dts;
// check if stream has passed full duration, needed for live streams
bool bAllowDurationExt = (stream->codec && (stream->codec->codec_type == AVMEDIA_TYPE_VIDEO || stream->codec->codec_type == AVMEDIA_TYPE_AUDIO));
if(bAllowDurationExt && m_pkt.pkt.dts != (int64_t)AV_NOPTS_VALUE)
{
int64_t duration;
duration = m_pkt.pkt.dts;
if(stream->start_time != (int64_t)AV_NOPTS_VALUE)
duration -= stream->start_time;
if(duration > stream->duration)
{
stream->duration = duration;
duration = av_rescale_rnd(stream->duration, (int64_t)stream->time_base.num * AV_TIME_BASE, stream->time_base.den, AV_ROUND_NEAR_INF);
if ((m_pFormatContext->duration == (int64_t)AV_NOPTS_VALUE)
|| (m_pFormatContext->duration != (int64_t)AV_NOPTS_VALUE && duration > m_pFormatContext->duration))
m_pFormatContext->duration = duration;
}
}
// store internal id until we know the continuous id presented to player
// the stream might not have been created yet
pPacket->iStreamId = m_pkt.pkt.stream_index;
}
m_pkt.result = -1;
av_free_packet(&m_pkt.pkt);
}
}
} // end of lock scope
if (bReturnEmpty && !pPacket)
pPacket = CDVDDemuxUtils::AllocateDemuxPacket(0);
if (!pPacket) return NULL;
// check streams, can we make this a bit more simple?
if (pPacket && pPacket->iStreamId >= 0)
{
CDemuxStream *stream = GetStreamInternal(pPacket->iStreamId);
if (!stream ||
stream->pPrivate != m_pFormatContext->streams[pPacket->iStreamId] ||
stream->codec != m_pFormatContext->streams[pPacket->iStreamId]->codec->codec_id)
{
// content has changed, or stream did not yet exist
stream = AddStream(pPacket->iStreamId);
}
// we already check for a valid m_streams[pPacket->iStreamId] above
else if (stream->type == STREAM_AUDIO)
{
if (((CDemuxStreamAudio*)stream)->iChannels != m_pFormatContext->streams[pPacket->iStreamId]->codec->channels ||
((CDemuxStreamAudio*)stream)->iSampleRate != m_pFormatContext->streams[pPacket->iStreamId]->codec->sample_rate)
{
// content has changed
stream = AddStream(pPacket->iStreamId);
}
}
else if (stream->type == STREAM_VIDEO)
{
if (((CDemuxStreamVideo*)stream)->iWidth != m_pFormatContext->streams[pPacket->iStreamId]->codec->width ||
((CDemuxStreamVideo*)stream)->iHeight != m_pFormatContext->streams[pPacket->iStreamId]->codec->height)
{
// content has changed
stream = AddStream(pPacket->iStreamId);
}
}
if (!stream)
{
CLog::Log(LOGERROR, "CDVDDemuxFFmpeg::AddStream - internal error, stream is null");
CDVDDemuxUtils::FreeDemuxPacket(pPacket);
return NULL;
}
// set continuous stream id for player
pPacket->iStreamId = stream->iId;
}
return pPacket;
}
bool CDVDDemuxFFmpeg::SeekTime(int time, bool backwords, double *startpts)
{
if (!m_pInput)
return false;
if(time < 0)
time = 0;
m_pkt.result = -1;
av_free_packet(&m_pkt.pkt);
CDVDInputStream::ISeekTime* ist = dynamic_cast(m_pInput);
if (ist)
{
if (!ist->SeekTime(time))
return false;
if(startpts)
*startpts = DVD_NOPTS_VALUE;
Flush();
// also empty the internal ffmpeg buffer
m_ioContext->buf_ptr = m_ioContext->buf_end;
return true;
}
if(!m_pInput->Seek(0, SEEK_POSSIBLE)
&& !m_pInput->IsStreamType(DVDSTREAM_TYPE_FFMPEG))
{
CLog::Log(LOGDEBUG, "%s - input stream reports it is not seekable", __FUNCTION__);
return false;
}
int64_t seek_pts = (int64_t)time * (AV_TIME_BASE / 1000);
bool ismp3 = m_pFormatContext->iformat && (strcmp(m_pFormatContext->iformat->name, "mp3") == 0);
if (m_pFormatContext->start_time != (int64_t)AV_NOPTS_VALUE && !ismp3)
seek_pts += m_pFormatContext->start_time;
int ret;
{
CSingleLock lock(m_critSection);
ret = av_seek_frame(m_pFormatContext, -1, seek_pts, backwords ? AVSEEK_FLAG_BACKWARD : 0);
// demuxer will return failure, if you seek behind eof
if (ret < 0 && m_pFormatContext->duration && seek_pts >= (m_pFormatContext->duration + m_pFormatContext->start_time))
ret = 0;
else if (ret < 0 && m_pInput->IsEOF())
ret = 0;
if(ret >= 0)
UpdateCurrentPTS();
}
if(m_currentPts == DVD_NOPTS_VALUE)
CLog::Log(LOGDEBUG, "%s - unknown position after seek", __FUNCTION__);
else
CLog::Log(LOGDEBUG, "%s - seek ended up on time %d", __FUNCTION__, (int)(m_currentPts / DVD_TIME_BASE * 1000));
// in this case the start time is requested time
if(startpts)
*startpts = DVD_MSEC_TO_TIME(time);
return (ret >= 0);
}
bool CDVDDemuxFFmpeg::SeekByte(int64_t pos)
{
CSingleLock lock(m_critSection);
int ret = av_seek_frame(m_pFormatContext, -1, pos, AVSEEK_FLAG_BYTE);
if(ret >= 0)
UpdateCurrentPTS();
m_pkt.result = -1;
av_free_packet(&m_pkt.pkt);
return (ret >= 0);
}
void CDVDDemuxFFmpeg::UpdateCurrentPTS()
{
m_currentPts = DVD_NOPTS_VALUE;
int idx = av_find_default_stream_index(m_pFormatContext);
if (idx >= 0)
{
AVStream *stream = m_pFormatContext->streams[idx];
if(stream && stream->cur_dts != (int64_t)AV_NOPTS_VALUE)
{
double ts = ConvertTimestamp(stream->cur_dts, stream->time_base.den, stream->time_base.num);
if(m_currentPts == DVD_NOPTS_VALUE || m_currentPts > ts )
m_currentPts = ts;
}
}
}
int CDVDDemuxFFmpeg::GetStreamLength()
{
if (!m_pFormatContext)
return 0;
if (m_pFormatContext->duration < 0)
return 0;
return (int)(m_pFormatContext->duration / (AV_TIME_BASE / 1000));
}
/**
* @brief Finds stream based on demuxer index
*/
CDemuxStream* CDVDDemuxFFmpeg::GetStream(int iStreamId)
{
if(iStreamId >= 0 && (size_t)iStreamId < m_stream_index.size())
return m_stream_index[iStreamId]->second;
else
return NULL;
}
/**
* @brief Finds stream based on ffmpeg index
*/
CDemuxStream* CDVDDemuxFFmpeg::GetStreamInternal(int iId)
{
std::map::iterator it = m_streams.find(iId);
if (it == m_streams.end())
return NULL;
else
return it->second;
}
int CDVDDemuxFFmpeg::GetNrOfStreams()
{
return m_stream_index.size();
}
static double SelectAspect(AVStream* st, bool* forced)
{
*forced = false;
/* if stream aspect is 1:1 or 0:0 use codec aspect */
if((st->sample_aspect_ratio.den == 1 || st->sample_aspect_ratio.den == 0)
&& (st->sample_aspect_ratio.num == 1 || st->sample_aspect_ratio.num == 0)
&& st->codec->sample_aspect_ratio.num != 0)
return av_q2d(st->codec->sample_aspect_ratio);
*forced = true;
if(st->sample_aspect_ratio.num != 0)
return av_q2d(st->sample_aspect_ratio);
return 0.0;
}
void CDVDDemuxFFmpeg::CreateStreams(unsigned int program)
{
DisposeStreams();
// add the ffmpeg streams to our own stream map
if (m_pFormatContext->nb_programs)
{
// check if desired program is available
if (program < m_pFormatContext->nb_programs && m_pFormatContext->programs[program]->nb_stream_indexes > 0)
{
m_program = program;
}
else
m_program = UINT_MAX;
// look for first non empty stream and discard nonselected programs
for (unsigned int i = 0; i < m_pFormatContext->nb_programs; i++)
{
if(m_program == UINT_MAX && m_pFormatContext->programs[i]->nb_stream_indexes > 0)
{
m_program = i;
}
if(i != m_program)
m_pFormatContext->programs[i]->discard = AVDISCARD_ALL;
}
if(m_program != UINT_MAX)
{
// add streams from selected program
for (unsigned int i = 0; i < m_pFormatContext->programs[m_program]->nb_stream_indexes; i++)
AddStream(m_pFormatContext->programs[m_program]->stream_index[i]);
}
}
else
m_program = UINT_MAX;
// if there were no programs or they were all empty, add all streams
if (m_program == UINT_MAX)
{
for (unsigned int i = 0; i < m_pFormatContext->nb_streams; i++)
AddStream(i);
}
}
void CDVDDemuxFFmpeg::DisposeStreams()
{
std::map::iterator it;
for(it = m_streams.begin(); it != m_streams.end(); ++it)
delete it->second;
m_streams.clear();
m_stream_index.clear();
}
CDemuxStream* CDVDDemuxFFmpeg::AddStream(int iId)
{
AVStream* pStream = m_pFormatContext->streams[iId];
if (pStream)
{
CDemuxStream* stream = NULL;
switch (pStream->codec->codec_type)
{
case AVMEDIA_TYPE_AUDIO:
{
CDemuxStreamAudioFFmpeg* st = new CDemuxStreamAudioFFmpeg(this, pStream);
stream = st;
st->iChannels = pStream->codec->channels;
st->iSampleRate = pStream->codec->sample_rate;
st->iBlockAlign = pStream->codec->block_align;
st->iBitRate = pStream->codec->bit_rate;
st->iBitsPerSample = pStream->codec->bits_per_raw_sample;
if (st->iBitsPerSample == 0)
st->iBitsPerSample = pStream->codec->bits_per_coded_sample;
if(av_dict_get(pStream->metadata, "title", NULL, 0))
st->m_description = av_dict_get(pStream->metadata, "title", NULL, 0)->value;
break;
}
case AVMEDIA_TYPE_VIDEO:
{
CDemuxStreamVideoFFmpeg* st = new CDemuxStreamVideoFFmpeg(this, pStream);
stream = st;
if(strcmp(m_pFormatContext->iformat->name, "flv") == 0)
st->bVFR = true;
else
st->bVFR = false;
// never trust pts in avi files with h264.
if (m_bAVI && pStream->codec->codec_id == AV_CODEC_ID_H264)
st->bPTSInvalid = true;
#if defined(AVFORMAT_HAS_STREAM_GET_R_FRAME_RATE)
AVRational r_frame_rate = av_stream_get_r_frame_rate(pStream);
#else
AVRational r_frame_rate = pStream->r_frame_rate;
#endif
//average fps is more accurate for mkv files
if (m_bMatroska && pStream->avg_frame_rate.den && pStream->avg_frame_rate.num)
{
st->iFpsRate = pStream->avg_frame_rate.num;
st->iFpsScale = pStream->avg_frame_rate.den;
}
else if(r_frame_rate.den && r_frame_rate.num)
{
st->iFpsRate = r_frame_rate.num;
st->iFpsScale = r_frame_rate.den;
}
else
{
st->iFpsRate = 0;
st->iFpsScale = 0;
}
// added for aml hw decoder, mkv frame-rate can be wrong.
if (r_frame_rate.den && r_frame_rate.num)
{
st->irFpsRate = r_frame_rate.num;
st->irFpsScale = r_frame_rate.den;
}
else
{
st->irFpsRate = 0;
st->irFpsScale = 0;
}
if (pStream->codec_info_nb_frames > 0
&& pStream->codec_info_nb_frames <= 2
&& m_pInput->IsStreamType(DVDSTREAM_TYPE_DVD))
{
CLog::Log(LOGDEBUG, "%s - fps may be unreliable since ffmpeg decoded only %d frame(s)", __FUNCTION__, pStream->codec_info_nb_frames);
st->iFpsRate = 0;
st->iFpsScale = 0;
}
st->iWidth = pStream->codec->width;
st->iHeight = pStream->codec->height;
st->fAspect = SelectAspect(pStream, &st->bForcedAspect) * pStream->codec->width / pStream->codec->height;
st->iOrientation = 0;
st->iBitsPerPixel = pStream->codec->bits_per_coded_sample;
AVDictionaryEntry *rtag = av_dict_get(pStream->metadata, "rotate", NULL, 0);
if (rtag)
st->iOrientation = atoi(rtag->value);
// detect stereoscopic mode
std::string stereoMode = GetStereoModeFromMetadata(pStream->metadata);
// check for metadata in file if detection in stream failed
if (stereoMode.empty())
stereoMode = GetStereoModeFromMetadata(m_pFormatContext->metadata);
if (!stereoMode.empty())
st->stereo_mode = stereoMode;
if ( m_pInput->IsStreamType(DVDSTREAM_TYPE_DVD) )
{
if (pStream->codec->codec_id == AV_CODEC_ID_PROBE)
{
// fix MPEG-1/MPEG-2 video stream probe returning AV_CODEC_ID_PROBE for still frames.
// ffmpeg issue 1871, regression from ffmpeg r22831.
if ((pStream->id & 0xF0) == 0xE0)
{
pStream->codec->codec_id = AV_CODEC_ID_MPEG2VIDEO;
pStream->codec->codec_tag = MKTAG('M','P','2','V');
CLog::Log(LOGERROR, "%s - AV_CODEC_ID_PROBE detected, forcing AV_CODEC_ID_MPEG2VIDEO", __FUNCTION__);
}
}
}
break;
}
case AVMEDIA_TYPE_DATA:
{
stream = new CDemuxStream();
stream->type = STREAM_DATA;
break;
}
case AVMEDIA_TYPE_SUBTITLE:
{
if (pStream->codec->codec_id == AV_CODEC_ID_DVB_TELETEXT && CSettings::Get().GetBool("videoplayer.teletextenabled"))
{
CDemuxStreamTeletext* st = new CDemuxStreamTeletext();
stream = st;
stream->type = STREAM_TELETEXT;
break;
}
else
{
CDemuxStreamSubtitleFFmpeg* st = new CDemuxStreamSubtitleFFmpeg(this, pStream);
stream = st;
if(av_dict_get(pStream->metadata, "title", NULL, 0))
st->m_description = av_dict_get(pStream->metadata, "title", NULL, 0)->value;
break;
}
}
case AVMEDIA_TYPE_ATTACHMENT:
{ //mkv attachments. Only bothering with fonts for now.
if(pStream->codec->codec_id == AV_CODEC_ID_TTF
|| pStream->codec->codec_id == AV_CODEC_ID_OTF
)
{
std::string fileName = "special://temp/fonts/";
XFILE::CDirectory::Create(fileName);
AVDictionaryEntry *nameTag = av_dict_get(pStream->metadata, "filename", NULL, 0);
if (!nameTag)
{
CLog::Log(LOGERROR, "%s: TTF attachment has no name", __FUNCTION__);
}
else
{
fileName += nameTag->value;
XFILE::CFile file;
if(pStream->codec->extradata && file.OpenForWrite(fileName))
{
if (file.Write(pStream->codec->extradata, pStream->codec->extradata_size) != pStream->codec->extradata_size)
{
file.Close();
XFILE::CFile::Delete(fileName);
CLog::Log(LOGDEBUG, "%s: Error saving font file \"%s\"", __FUNCTION__, fileName.c_str());
}
}
}
}
stream = new CDemuxStream();
stream->type = STREAM_NONE;
break;
}
default:
{
stream = new CDemuxStream();
stream->type = STREAM_NONE;
break;
}
}
// set ffmpeg type
stream->orig_type = pStream->codec->codec_type;
// generic stuff
if (pStream->duration != (int64_t)AV_NOPTS_VALUE)
stream->iDuration = (int)((pStream->duration / AV_TIME_BASE) & 0xFFFFFFFF);
stream->codec = pStream->codec->codec_id;
stream->codec_fourcc = pStream->codec->codec_tag;
stream->profile = pStream->codec->profile;
stream->level = pStream->codec->level;
stream->source = STREAM_SOURCE_DEMUX;
stream->pPrivate = pStream;
stream->flags = (CDemuxStream::EFlags)pStream->disposition;
AVDictionaryEntry *langTag = av_dict_get(pStream->metadata, "language", NULL, 0);
if (langTag)
strncpy(stream->language, langTag->value, 3);
if( pStream->codec->extradata && pStream->codec->extradata_size > 0 )
{
stream->ExtraSize = pStream->codec->extradata_size;
stream->ExtraData = new uint8_t[pStream->codec->extradata_size];
memcpy(stream->ExtraData, pStream->codec->extradata, pStream->codec->extradata_size);
}
#ifdef HAVE_LIBBLURAY
if( m_pInput->IsStreamType(DVDSTREAM_TYPE_BLURAY) )
static_cast(m_pInput)->GetStreamInfo(pStream->id, stream->language);
#endif
if( m_pInput->IsStreamType(DVDSTREAM_TYPE_DVD) )
{
// this stuff is really only valid for dvd's.
// this is so that the physicalid matches the
// id's reported from libdvdnav
switch(stream->codec)
{
case AV_CODEC_ID_AC3:
stream->iPhysicalId = pStream->id - 128;
break;
case AV_CODEC_ID_DTS:
stream->iPhysicalId = pStream->id - 136;
break;
case AV_CODEC_ID_MP2:
stream->iPhysicalId = pStream->id - 448;
break;
case AV_CODEC_ID_PCM_S16BE:
stream->iPhysicalId = pStream->id - 160;
break;
case AV_CODEC_ID_DVD_SUBTITLE:
stream->iPhysicalId = pStream->id - 0x20;
break;
default:
stream->iPhysicalId = pStream->id & 0x1f;
break;
}
}
else
stream->iPhysicalId = pStream->id;
AddStream(iId, stream);
return stream;
}
else
return NULL;
}
/**
* @brief Adds or updates a demux stream based in ffmpeg id
*/
void CDVDDemuxFFmpeg::AddStream(int iId, CDemuxStream* stream)
{
std::pair::iterator, bool> res;
res = m_streams.insert(std::make_pair(iId, stream));
if(res.second)
{
/* was new stream */
stream->iId = m_stream_index.size();
m_stream_index.push_back(res.first);
}
else
{
/* replace old stream, keeping old index */
stream->iId = res.first->second->iId;
delete res.first->second;
res.first->second = stream;
}
if(g_advancedSettings.m_logLevel > LOG_LEVEL_NORMAL)
CLog::Log(LOGDEBUG, "CDVDDemuxFFmpeg::AddStream(%d, ...) -> %d", iId, stream->iId);
}
std::string CDVDDemuxFFmpeg::GetFileName()
{
if(m_pInput)
return m_pInput->GetFileName();
else
return "";
}
int CDVDDemuxFFmpeg::GetChapterCount()
{
CDVDInputStream::IChapter* ich = dynamic_cast(m_pInput);
if(ich)
return ich->GetChapterCount();
if(m_pFormatContext == NULL)
return 0;
return m_pFormatContext->nb_chapters;
}
int CDVDDemuxFFmpeg::GetChapter()
{
CDVDInputStream::IChapter* ich = dynamic_cast(m_pInput);
if(ich)
return ich->GetChapter();
if(m_pFormatContext == NULL
|| m_currentPts == DVD_NOPTS_VALUE)
return 0;
for(unsigned i = 0; i < m_pFormatContext->nb_chapters; i++)
{
AVChapter *chapter = m_pFormatContext->chapters[i];
if(m_currentPts >= ConvertTimestamp(chapter->start, chapter->time_base.den, chapter->time_base.num)
&& m_currentPts < ConvertTimestamp(chapter->end, chapter->time_base.den, chapter->time_base.num))
return i + 1;
}
return 0;
}
void CDVDDemuxFFmpeg::GetChapterName(std::string& strChapterName, int chapterIdx)
{
if (chapterIdx <= 0 || chapterIdx > GetChapterCount())
chapterIdx = GetChapter();
CDVDInputStream::IChapter* ich = dynamic_cast(m_pInput);
if(ich)
ich->GetChapterName(strChapterName, chapterIdx);
else
{
if(chapterIdx <= 0)
return;
AVDictionaryEntry *titleTag = av_dict_get(m_pFormatContext->chapters[chapterIdx-1]->metadata,
"title", NULL, 0);
if (titleTag)
strChapterName = titleTag->value;
}
}
int64_t CDVDDemuxFFmpeg::GetChapterPos(int chapterIdx)
{
if (chapterIdx <= 0 || chapterIdx > GetChapterCount())
chapterIdx = GetChapter();
if(chapterIdx <= 0)
return 0;
CDVDInputStream::IChapter* ich = dynamic_cast(m_pInput);
if(ich)
return ich->GetChapterPos(chapterIdx);
return m_pFormatContext->chapters[chapterIdx-1]->start*av_q2d(m_pFormatContext->chapters[chapterIdx-1]->time_base);
}
bool CDVDDemuxFFmpeg::SeekChapter(int chapter, double* startpts)
{
if(chapter < 1)
chapter = 1;
CDVDInputStream::IChapter* ich = dynamic_cast(m_pInput);
if(ich)
{
CLog::Log(LOGDEBUG, "%s - chapter seeking using input stream", __FUNCTION__);
if(!ich->SeekChapter(chapter))
return false;
if(startpts)
{
*startpts = DVD_SEC_TO_TIME(ich->GetChapterPos(chapter));
}
Flush();
return true;
}
if(m_pFormatContext == NULL)
return false;
if(chapter < 1 || chapter > (int)m_pFormatContext->nb_chapters)
return false;
AVChapter *ch = m_pFormatContext->chapters[chapter-1];
double dts = ConvertTimestamp(ch->start, ch->time_base.den, ch->time_base.num);
return SeekTime(DVD_TIME_TO_MSEC(dts), true, startpts);
}
void CDVDDemuxFFmpeg::GetStreamCodecName(int iStreamId, std::string &strName)
{
CDemuxStream *stream = GetStream(iStreamId);
if (stream)
{
unsigned int in = stream->codec_fourcc;
// FourCC codes are only valid on video streams, audio codecs in AVI/WAV
// are 2 bytes and audio codecs in transport streams have subtle variation
// e.g AC-3 instead of ac3
if (stream->type == STREAM_VIDEO && in != 0)
{
char fourcc[5];
#if defined(__powerpc__)
fourcc[0] = in & 0xff;
fourcc[1] = (in >> 8) & 0xff;
fourcc[2] = (in >> 16) & 0xff;
fourcc[3] = (in >> 24) & 0xff;
#else
memcpy(fourcc, &in, 4);
#endif
fourcc[4] = 0;
// fourccs have to be 4 characters
if (strlen(fourcc) == 4)
{
strName = fourcc;
StringUtils::ToLower(strName);
return;
}
}
#ifdef FF_PROFILE_DTS_HD_MA
/* use profile to determine the DTS type */
if (stream->codec == AV_CODEC_ID_DTS)
{
if (stream->profile == FF_PROFILE_DTS_HD_MA)
strName = "dtshd_ma";
else if (stream->profile == FF_PROFILE_DTS_HD_HRA)
strName = "dtshd_hra";
else
strName = "dca";
return;
}
#endif
AVCodec *codec = avcodec_find_decoder(stream->codec);
if (codec)
strName = codec->name;
}
}
bool CDVDDemuxFFmpeg::IsProgramChange()
{
if (m_program == UINT_MAX)
return false;
if (m_program == 0 && !m_pFormatContext->nb_programs)
return false;
if(m_pFormatContext->programs[m_program]->nb_stream_indexes != m_streams.size())
return true;
if (m_program >= m_pFormatContext->nb_programs)
return true;
for (unsigned int i = 0; i < m_pFormatContext->programs[m_program]->nb_stream_indexes; i++)
{
int idx = m_pFormatContext->programs[m_program]->stream_index[i];
CDemuxStream *stream = GetStreamInternal(idx);
if(!stream)
return true;
if(m_pFormatContext->streams[idx]->codec->codec_type != stream->orig_type)
return true;
}
return false;
}
std::string CDVDDemuxFFmpeg::GetStereoModeFromMetadata(AVDictionary *pMetadata)
{
std::string stereoMode;
AVDictionaryEntry *tag = NULL;
// matroska
tag = av_dict_get(pMetadata, "stereo_mode", NULL, 0);
if (tag && tag->value)
stereoMode = tag->value;
// asf / wmv
if (stereoMode.empty())
{
tag = av_dict_get(pMetadata, "Stereoscopic", NULL, 0);
if (tag && tag->value)
{
tag = av_dict_get(pMetadata, "StereoscopicLayout", NULL, 0);
if (tag && tag->value)
stereoMode = ConvertCodecToInternalStereoMode(tag->value, WmvToInternalStereoModeMap);
}
}
return stereoMode;
}
std::string CDVDDemuxFFmpeg::ConvertCodecToInternalStereoMode(const std::string &mode, const StereoModeConversionMap *conversionMap)
{
size_t i = 0;
while (conversionMap[i].name)
{
if (mode == conversionMap[i].name)
return conversionMap[i].mode;
i++;
}
return "";
}
void CDVDDemuxFFmpeg::ParsePacket(AVPacket *pkt)
{
AVStream *st = m_pFormatContext->streams[pkt->stream_index];
CDemuxStream *stream = GetStreamInternal(pkt->stream_index);
// if the stream is new, tell ffmpeg to parse the stream
if (!stream && !st->parser)
{
st->need_parsing = AVSTREAM_PARSE_FULL;
}
// split extradata
if(st->parser && st->parser->parser->split && !st->codec->extradata)
{
int i = st->parser->parser->split(st->codec, pkt->data, pkt->size);
if (i > 0 && i < FF_MAX_EXTRADATA_SIZE)
{
// Found extradata, fill it in. This will cause
// a new stream to be created and used.
st->codec->extradata_size = i;
st->codec->extradata = (uint8_t*)av_malloc(st->codec->extradata_size + FF_INPUT_BUFFER_PADDING_SIZE);
if (st->codec->extradata)
{
CLog::Log(LOGDEBUG, "CDVDDemuxFFmpeg::Read() fetching extradata, extradata_size(%d)", st->codec->extradata_size);
memcpy(st->codec->extradata, pkt->data, st->codec->extradata_size);
memset(st->codec->extradata + i, 0, FF_INPUT_BUFFER_PADDING_SIZE);
}
else
{
st->codec->extradata_size = 0;
}
}
}
// for video we need a decoder to get desired information into codec context
if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO && st->codec->extradata &&
(!st->codec->width || st->codec->pix_fmt == PIX_FMT_NONE))
{
// open a decoder, it will be cleared down by ffmpeg on closing the stream
if (!st->codec->codec)
{
const AVCodec* codec;
AVDictionary *thread_opt = NULL;
codec = avcodec_find_decoder(st->codec->codec_id);
// Force thread count to 1 since the h264 decoder will not extract
// SPS and PPS to extradata during multi-threaded decoding
av_dict_set(&thread_opt, "threads", "1", 0);
int res = avcodec_open2(st->codec, codec, &thread_opt);
if(res < 0)
CLog::Log(LOGERROR, "CDVDDemuxFFmpeg::ParsePacket() unable to open codec %d", res);
av_dict_free(&thread_opt);
}
// We don't need to actually decode here
// we just want to transport SPS data into codec context
st->codec->skip_idct = AVDISCARD_ALL;
// extradata is not decoded if skip_frame >= AVDISCARD_NONREF
// st->codec->skip_frame = AVDISCARD_ALL;
st->codec->skip_loop_filter = AVDISCARD_ALL;
// We are looking for an IDR frame
AVFrame picture;
memset(&picture, 0, sizeof(AVFrame));
picture.pts = picture.pkt_dts = picture.pkt_pts = picture.best_effort_timestamp = AV_NOPTS_VALUE;
picture.pkt_pos = -1;
picture.key_frame = 1;
picture.format = -1;
int got_picture = 0;
avcodec_decode_video2(st->codec, &picture, &got_picture, pkt);
}
}
bool CDVDDemuxFFmpeg::IsVideoReady()
{
AVStream *st;
bool hasVideo = false;
if(!m_checkvideo)
return true;
if (m_program == 0 && !m_pFormatContext->nb_programs)
return false;
if(m_program != UINT_MAX)
{
for (unsigned int i = 0; i < m_pFormatContext->programs[m_program]->nb_stream_indexes; i++)
{
int idx = m_pFormatContext->programs[m_program]->stream_index[i];
st = m_pFormatContext->streams[idx];
if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
if (st->codec->width && st->codec->pix_fmt != PIX_FMT_NONE)
return true;
hasVideo = true;
}
}
}
else
{
for (unsigned int i = 0; i < m_pFormatContext->nb_streams; i++)
{
st = m_pFormatContext->streams[i];
if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
if (st->codec->width && st->codec->pix_fmt != PIX_FMT_NONE)
return true;
hasVideo = true;
}
}
}
return !hasVideo;
}
void CDVDDemuxFFmpeg::ResetVideoStreams()
{
AVStream *st;
for (unsigned int i = 0; i < m_pFormatContext->nb_streams; i++)
{
st = m_pFormatContext->streams[i];
if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
if (st->codec->extradata)
av_free(st->codec->extradata);
st->codec->extradata = NULL;
st->codec->width = 0;
}
}
}
void CDVDDemuxFFmpeg::GetL16Parameters(int &channels, int &samplerate)
{
std::string content;
if (XFILE::CCurlFile::GetContentType(m_pInput->GetURL(), content))
{
StringUtils::ToLower(content);
const size_t len = content.length();
size_t pos = content.find(';');
while (pos < len)
{
// move to the next non-whitespace character
pos = content.find_first_not_of(" \t", pos + 1);
if (pos != std::string::npos)
{
if (content.compare(pos, 9, "channels=", 9) == 0)
{
pos += 9; // move position to char after 'channels='
size_t len = content.find(';', pos);
if (len != std::string::npos)
len -= pos;
std::string no_channels(content, pos, len);
// as we don't support any charset with ';' in name
StringUtils::Trim(no_channels, " \t");
if (!no_channels.empty())
{
int val = strtol(no_channels.c_str(), NULL, 0);
if (val > 0)
channels = val;
else
CLog::Log(LOGDEBUG, "CDVDDemuxFFmpeg::%s - no parameter for channels", __FUNCTION__);
}
}
else if (content.compare(pos, 5, "rate=", 5) == 0)
{
pos += 5; // move position to char after 'rate='
size_t len = content.find(';', pos);
if (len != std::string::npos)
len -= pos;
std::string rate(content, pos, len);
// as we don't support any charset with ';' in name
StringUtils::Trim(rate, " \t");
if (!rate.empty())
{
int val = strtol(rate.c_str(), NULL, 0);
if (val > 0)
samplerate = val;
else
CLog::Log(LOGDEBUG, "CDVDDemuxFFmpeg::%s - no parameter for samplerate", __FUNCTION__);
}
}
pos = content.find(';', pos); // find next parameter
}
}
}
}