From ffca21f2743a7b367fa212799c6e2fea6190dd5d Mon Sep 17 00:00:00 2001 From: manuel Date: Tue, 3 Mar 2015 16:53:59 +0100 Subject: initial commit for kodi master --- .../cores/dvdplayer/DVDDemuxers/DVDDemuxFFmpeg.cpp | 1767 ++++++++++++++++++++ 1 file changed, 1767 insertions(+) create mode 100644 xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxFFmpeg.cpp (limited to 'xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxFFmpeg.cpp') diff --git a/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxFFmpeg.cpp b/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxFFmpeg.cpp new file mode 100644 index 0000000..f58472f --- /dev/null +++ b/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxFFmpeg.cpp @@ -0,0 +1,1767 @@ +/* + * 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 + } + } + } +} -- cgit v1.2.3