/*
* Copyright (C) 2012-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 "DVDInputStreams/DVDInputStream.h"
#include "DVDDemuxPVRClient.h"
#include "DVDDemuxUtils.h"
#include "utils/log.h"
#include "pvr/PVRManager.h"
#include "pvr/addons/PVRClients.h"
#include "../DVDClock.h"
#define FF_MAX_EXTRADATA_SIZE ((1 << 28) - FF_INPUT_BUFFER_PADDING_SIZE)
using namespace PVR;
CDemuxStreamPVRInternal::CDemuxStreamPVRInternal(CDVDDemuxPVRClient *parent)
: m_parent(parent)
, m_parser(NULL)
, m_context(NULL)
, m_parser_split(false)
{
}
CDemuxStreamPVRInternal::~CDemuxStreamPVRInternal()
{
DisposeParser();
}
void CDemuxStreamPVRInternal::DisposeParser()
{
if (m_parser)
{
av_parser_close(m_parser);
m_parser = NULL;
}
if (m_context)
{
avcodec_close(m_context);
m_context = NULL;
}
}
void CDemuxStreamVideoPVRClient::GetStreamInfo(std::string& strInfo)
{
switch (codec)
{
case AV_CODEC_ID_MPEG2VIDEO:
strInfo = "mpeg2video";
break;
case AV_CODEC_ID_H264:
strInfo = "h264";
break;
default:
break;
}
}
void CDemuxStreamAudioPVRClient::GetStreamInfo(std::string& strInfo)
{
switch (codec)
{
case AV_CODEC_ID_AC3:
strInfo = "ac3";
break;
case AV_CODEC_ID_EAC3:
strInfo = "eac3";
break;
case AV_CODEC_ID_MP2:
strInfo = "mpeg2audio";
break;
case AV_CODEC_ID_AAC:
strInfo = "aac";
break;
case AV_CODEC_ID_DTS:
strInfo = "dts";
break;
default:
break;
}
}
void CDemuxStreamSubtitlePVRClient::GetStreamInfo(std::string& strInfo)
{
}
CDVDDemuxPVRClient::CDVDDemuxPVRClient() : CDVDDemux()
{
m_pInput = NULL;
for (int i = 0; i < MAX_STREAMS; i++) m_streams[i] = NULL;
}
CDVDDemuxPVRClient::~CDVDDemuxPVRClient()
{
Dispose();
}
bool CDVDDemuxPVRClient::Open(CDVDInputStream* pInput)
{
Abort();
m_pInput = pInput;
if (!g_PVRClients->GetPlayingClient(m_pvrClient))
return false;
return true;
}
void CDVDDemuxPVRClient::Dispose()
{
for (int i = 0; i < MAX_STREAMS; i++)
{
delete m_streams[i];
m_streams[i] = NULL;
}
m_pInput = NULL;
}
void CDVDDemuxPVRClient::DisposeStream(int iStreamId)
{
if (iStreamId < 0 || iStreamId >= MAX_STREAMS)
return;
delete m_streams[iStreamId];
m_streams[iStreamId] = NULL;
}
void CDVDDemuxPVRClient::Reset()
{
if(m_pInput && g_PVRManager.IsStarted())
m_pvrClient->DemuxReset();
CDVDInputStream* pInputStream = m_pInput;
Dispose();
Open(pInputStream);
}
void CDVDDemuxPVRClient::Abort()
{
if(m_pInput)
m_pvrClient->DemuxAbort();
}
void CDVDDemuxPVRClient::Flush()
{
if(m_pInput && g_PVRManager.IsStarted())
m_pvrClient->DemuxFlush();
}
void CDVDDemuxPVRClient::ParsePacket(DemuxPacket* pkt)
{
CDemuxStream* st = m_streams[pkt->iStreamId];
if (st == NULL)
return;
if (st->ExtraSize)
return;
CDemuxStreamPVRInternal* pvr = dynamic_cast(st);
if(pvr == NULL
|| pvr->m_parser == NULL)
return;
if(pvr->m_context == NULL)
{
AVCodec *codec = avcodec_find_decoder(st->codec);
if (codec == NULL)
{
CLog::Log(LOGERROR, "%s - can't find decoder", __FUNCTION__);
pvr->DisposeParser();
return;
}
pvr->m_context = avcodec_alloc_context3(codec);
if(pvr->m_context == NULL)
{
CLog::Log(LOGERROR, "%s - can't allocate context", __FUNCTION__);
pvr->DisposeParser();
return;
}
pvr->m_context->time_base.num = 1;
pvr->m_context->time_base.den = DVD_TIME_BASE;
}
if(pvr->m_parser_split && pvr->m_parser->parser->split)
{
int len = pvr->m_parser->parser->split(pvr->m_context, pkt->pData, pkt->iSize);
if (len > 0 && len < FF_MAX_EXTRADATA_SIZE)
{
if (st->ExtraData)
delete[] (uint8_t*)st->ExtraData;
st->changes++;
st->disabled = false;
st->ExtraSize = len;
st->ExtraData = new uint8_t[len+FF_INPUT_BUFFER_PADDING_SIZE];
memcpy(st->ExtraData, pkt->pData, len);
memset((uint8_t*)st->ExtraData + len, 0 , FF_INPUT_BUFFER_PADDING_SIZE);
pvr->m_parser_split = false;
}
}
uint8_t *outbuf = NULL;
int outbuf_size = 0;
int len = av_parser_parse2(pvr->m_parser
, pvr->m_context, &outbuf, &outbuf_size
, pkt->pData, pkt->iSize
, (int64_t)(pkt->pts * DVD_TIME_BASE)
, (int64_t)(pkt->dts * DVD_TIME_BASE)
, 0);
/* our parse is setup to parse complete frames, so we don't care about outbufs */
if(len >= 0)
{
#define CHECK_UPDATE(st, trg, src, invalid) do { \
if(src != invalid \
&& src != st->trg) { \
CLog::Log(LOGDEBUG, "%s - {%d} " #trg " changed from %d to %d", __FUNCTION__, st->iId, st->trg, src); \
st->trg = src; \
st->changes++; \
st->disabled = false; \
} \
} while(0)
CHECK_UPDATE(st, profile, pvr->m_context->profile , FF_PROFILE_UNKNOWN);
CHECK_UPDATE(st, level , pvr->m_context->level , FF_LEVEL_UNKNOWN);
switch (st->type)
{
case STREAM_AUDIO: {
CDemuxStreamAudioPVRClient* sta = static_cast(st);
CHECK_UPDATE(sta, iChannels , pvr->m_context->channels , 0);
CHECK_UPDATE(sta, iSampleRate , pvr->m_context->sample_rate, 0);
break;
}
case STREAM_VIDEO: {
CDemuxStreamVideoPVRClient* stv = static_cast(st);
CHECK_UPDATE(stv, iWidth , pvr->m_context->width , 0);
CHECK_UPDATE(stv, iHeight , pvr->m_context->height, 0);
break;
}
default:
break;
}
#undef CHECK_UPDATE
}
else
CLog::Log(LOGDEBUG, "%s - parser returned error %d", __FUNCTION__, len);
return;
}
DemuxPacket* CDVDDemuxPVRClient::Read()
{
if (!g_PVRManager.IsStarted())
return CDVDDemuxUtils::AllocateDemuxPacket(0);
DemuxPacket* pPacket = m_pvrClient->DemuxRead();
if (!pPacket)
{
if (m_pInput)
m_pInput->Close();
return NULL;
}
if (pPacket->iStreamId == DMX_SPECIALID_STREAMINFO)
{
RequestStreams();
CDVDDemuxUtils::FreeDemuxPacket(pPacket);
return CDVDDemuxUtils::AllocateDemuxPacket(0);
}
else if (pPacket->iStreamId == DMX_SPECIALID_STREAMCHANGE)
{
RequestStreams();
}
else if (pPacket->iStreamId >= 0
&& pPacket->iStreamId < MAX_STREAMS
&& m_streams[pPacket->iStreamId])
{
ParsePacket(pPacket);
}
return pPacket;
}
CDemuxStream* CDVDDemuxPVRClient::GetStream(int iStreamId)
{
if (iStreamId < 0 || iStreamId >= MAX_STREAMS) return NULL;
return m_streams[iStreamId];
}
void CDVDDemuxPVRClient::RequestStreams()
{
if (!g_PVRManager.IsStarted())
return;
PVR_STREAM_PROPERTIES props = {};
m_pvrClient->GetStreamProperties(&props);
unsigned int i;
for (i = 0; i < props.iStreamCount; ++i)
{
CDemuxStream *stm = m_streams[i];
if (props.stream[i].iCodecType == XBMC_CODEC_TYPE_AUDIO)
{
CDemuxStreamAudioPVRClient* st = NULL;
if (stm)
{
st = dynamic_cast(stm);
if (!st || (st->codec != (AVCodecID)props.stream[i].iCodecId))
DisposeStream(i);
}
if (!m_streams[i])
{
st = new CDemuxStreamAudioPVRClient(this);
st->m_parser = av_parser_init(props.stream[i].iCodecId);
if(st->m_parser)
st->m_parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
}
st->iChannels = props.stream[i].iChannels;
st->iSampleRate = props.stream[i].iSampleRate;
st->iBlockAlign = props.stream[i].iBlockAlign;
st->iBitRate = props.stream[i].iBitRate;
st->iBitsPerSample = props.stream[i].iBitsPerSample;
m_streams[i] = st;
st->m_parser_split = true;
st->changes++;
}
else if (props.stream[i].iCodecType == XBMC_CODEC_TYPE_VIDEO)
{
CDemuxStreamVideoPVRClient* st = NULL;
if (stm)
{
st = dynamic_cast(stm);
if (!st
|| (st->codec != (AVCodecID)props.stream[i].iCodecId)
|| (st->iWidth != props.stream[i].iWidth)
|| (st->iHeight != props.stream[i].iHeight))
DisposeStream(i);
}
if (!m_streams[i])
{
st = new CDemuxStreamVideoPVRClient(this);
st->m_parser = av_parser_init(props.stream[i].iCodecId);
if(st->m_parser)
st->m_parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
}
st->iFpsScale = props.stream[i].iFPSScale;
st->iFpsRate = props.stream[i].iFPSRate;
st->iHeight = props.stream[i].iHeight;
st->iWidth = props.stream[i].iWidth;
st->fAspect = props.stream[i].fAspect;
st->stereo_mode = "mono";
m_streams[i] = st;
st->m_parser_split = true;
}
else if (props.stream[i].iCodecId == AV_CODEC_ID_DVB_TELETEXT)
{
if (stm)
{
if (stm->codec != (AVCodecID)props.stream[i].iCodecId)
DisposeStream(i);
}
if (!m_streams[i])
m_streams[i] = new CDemuxStreamTeletext();
}
else if (props.stream[i].iCodecType == XBMC_CODEC_TYPE_SUBTITLE)
{
CDemuxStreamSubtitlePVRClient* st = NULL;
if (stm)
{
st = dynamic_cast(stm);
if (!st || (st->codec != (AVCodecID)props.stream[i].iCodecId))
DisposeStream(i);
}
if (!m_streams[i])
{
st = new CDemuxStreamSubtitlePVRClient(this);
}
if(props.stream[i].iIdentifier)
{
st->ExtraData = new uint8_t[4];
st->ExtraSize = 4;
st->ExtraData[0] = (props.stream[i].iIdentifier >> 8) & 0xff;
st->ExtraData[1] = (props.stream[i].iIdentifier >> 0) & 0xff;
st->ExtraData[2] = (props.stream[i].iIdentifier >> 24) & 0xff;
st->ExtraData[3] = (props.stream[i].iIdentifier >> 16) & 0xff;
}
m_streams[i] = st;
}
else
{
if (m_streams[i])
DisposeStream(i);
m_streams[i] = new CDemuxStream();
}
m_streams[i]->codec = (AVCodecID)props.stream[i].iCodecId;
m_streams[i]->iId = i;
m_streams[i]->iPhysicalId = props.stream[i].iPhysicalId;
m_streams[i]->language[0] = props.stream[i].strLanguage[0];
m_streams[i]->language[1] = props.stream[i].strLanguage[1];
m_streams[i]->language[2] = props.stream[i].strLanguage[2];
m_streams[i]->language[3] = props.stream[i].strLanguage[3];
CLog::Log(LOGDEBUG,"CDVDDemuxPVRClient::RequestStreams(): added/updated stream %d:%d with codec_id %d",
m_streams[i]->iId,
m_streams[i]->iPhysicalId,
m_streams[i]->codec);
}
// check if we need to dispose any streams no longer in props
for (unsigned int j = i; j < MAX_STREAMS; j++)
{
if (m_streams[j])
{
CLog::Log(LOGDEBUG,"CDVDDemuxPVRClient::RequestStreams(): disposed stream %d:%d with codec_id %d",
m_streams[j]->iId,
m_streams[j]->iPhysicalId,
m_streams[j]->codec);
DisposeStream(j);
}
}
}
int CDVDDemuxPVRClient::GetNrOfStreams()
{
int i = 0;
while (i < MAX_STREAMS && m_streams[i]) i++;
return i;
}
std::string CDVDDemuxPVRClient::GetFileName()
{
if(m_pInput)
return m_pInput->GetFileName();
else
return "";
}
void CDVDDemuxPVRClient::GetStreamCodecName(int iStreamId, std::string &strName)
{
CDemuxStream *stream = GetStream(iStreamId);
if (stream)
{
if (stream->codec == AV_CODEC_ID_AC3)
strName = "ac3";
else if (stream->codec == AV_CODEC_ID_MP2)
strName = "mp2";
else if (stream->codec == AV_CODEC_ID_AAC)
strName = "aac";
else if (stream->codec == AV_CODEC_ID_DTS)
strName = "dca";
else if (stream->codec == AV_CODEC_ID_MPEG2VIDEO)
strName = "mpeg2video";
else if (stream->codec == AV_CODEC_ID_H264)
strName = "h264";
else if (stream->codec == AV_CODEC_ID_EAC3)
strName = "eac3";
}
}
bool CDVDDemuxPVRClient::SeekTime(int timems, bool backwards, double *startpts)
{
if (m_pInput)
return m_pvrClient->SeekTime(timems, backwards, startpts);
return false;
}
void CDVDDemuxPVRClient::SetSpeed ( int speed )
{
if (m_pInput)
m_pvrClient->SetSpeed(speed);
}