diff options
author | gimli <ebsi4711@gmail.com> | 2012-08-10 22:15:58 +0200 |
---|---|---|
committer | gimli <ebsi4711@gmail.com> | 2012-08-10 22:15:58 +0200 |
commit | a91e8749e76f61e2c24ee87c16bae375674f8476 (patch) | |
tree | 5811e8fe4c0fe159714a7341d95e7e7c40ec4606 | |
parent | 18c401cbfaa7ca339734b41d52fb4460454496ad (diff) |
[rbp] added raspberrypi omxplayer
30 files changed, 12219 insertions, 3 deletions
diff --git a/Makefile.in b/Makefile.in index 856a341def..895adf57df 100644 --- a/Makefile.in +++ b/Makefile.in @@ -143,6 +143,10 @@ ifeq (@USE_AMLPLAYER@,1) DIRECTORY_ARCHIVES += xbmc/cores/amlplayer/amlplayer.a endif +ifeq (@USE_OMXPLAYER@,1) +DIRECTORY_ARCHIVES += xbmc/cores/omxplayer/omxplayer.a +endif + PAPCODECS_DIRS= \ lib/xbadpcm \ lib/nosefart \ diff --git a/Makefile.include.in b/Makefile.include.in index a517dd269e..704a4bc7f2 100644 --- a/Makefile.include.in +++ b/Makefile.include.in @@ -40,6 +40,10 @@ ifneq (@USE_EXTERNAL_FFMPEG@,1) endif INCLUDES+=-I@abs_top_srcdir@/xbmc/linux INCLUDES+=-I@abs_top_srcdir@/xbmc/cores/dvdplayer +ifeq (@USE_OMXPLAYER@,1) +INCLUDES+=-I@abs_top_srcdir@/xbmc/cores/AudioEngine +INCLUDES+=-I@abs_top_srcdir@/xbmc/cores/AudioEngine/Utils +endif DEFINES+= \ @ARCH_DEFINES@ \ -D_FILE_DEFINED \ diff --git a/tools/rbp/depends/xbmc/Makefile b/tools/rbp/depends/xbmc/Makefile index 34ddd6ade7..66dc8dff58 100644 --- a/tools/rbp/depends/xbmc/Makefile +++ b/tools/rbp/depends/xbmc/Makefile @@ -12,7 +12,7 @@ CONFIGURE=./configure --prefix=$(PREFIX) --build=$(BUILD) --host=$(HOST) \ --disable-optical-drive --disable-dvdcss --disable-joystick --disable-debug \ --disable-crystalhd --disable-vtbdecoder --disable-vaapi --disable-vdpau \ --disable-pulse --disable-projectm --with-platform=raspberry-pi --disable-optimizations \ - --enable-rpi-cec-api + --enable-rpi-cec-api --enable-player=omxplayer all: configure diff --git a/xbmc/Application.cpp b/xbmc/Application.cpp index a5f288530e..f737abfd38 100644 --- a/xbmc/Application.cpp +++ b/xbmc/Application.cpp @@ -5428,6 +5428,10 @@ void CApplication::SetHardwareVolume(float hardwareVolume) value = 1.0f; CAEFactory::SetVolume(value); + + /* for platforms where we do not have AE */ + if (m_pPlayer) + m_pPlayer->SetVolume(g_settings.m_fVolumeLevel); } int CApplication::GetVolume() const diff --git a/xbmc/cores/dvdplayer/DVDMessageQueue.cpp b/xbmc/cores/dvdplayer/DVDMessageQueue.cpp index 75b663d5a4..cbce73b046 100644 --- a/xbmc/cores/dvdplayer/DVDMessageQueue.cpp +++ b/xbmc/cores/dvdplayer/DVDMessageQueue.cpp @@ -163,7 +163,9 @@ MsgQueueReturnCode CDVDMessageQueue::Get(CDVDMsg** pMsg, unsigned int iTimeoutIn if(m_list.empty() && m_bEmptied == false && priority == 0 && m_owner != "teletext") { +#if !defined(TARGET_RASPBERRY_PI) CLog::Log(LOGWARNING, "CDVDMessageQueue(%s)::Get - asked for new data packet, with nothing available", m_owner.c_str()); +#endif m_bEmptied = true; } diff --git a/xbmc/cores/omxplayer/BitstreamConverter.cpp b/xbmc/cores/omxplayer/BitstreamConverter.cpp new file mode 100644 index 0000000000..13b6f1fdbb --- /dev/null +++ b/xbmc/cores/omxplayer/BitstreamConverter.cpp @@ -0,0 +1,911 @@ +/* + * Copyright (C) 2010 Team XBMC + * http://www.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, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif + +#include "BitstreamConverter.h" + +void CBitstreamConverter::bits_reader_set( bits_reader_t *br, uint8_t *buf, int len ) +{ + br->buffer = br->start = buf; + br->offbits = 0; + br->length = len; + br->oflow = 0; +} + +uint32_t CBitstreamConverter::read_bits( bits_reader_t *br, int nbits ) +{ + int i, nbytes; + uint32_t ret = 0; + uint8_t *buf; + + buf = br->buffer; + nbytes = (br->offbits + nbits)/8; + if ( ((br->offbits + nbits) %8 ) > 0 ) + nbytes++; + if ( (buf + nbytes) > (br->start + br->length) ) { + br->oflow = 1; + return 0; + } + for ( i=0; i<nbytes; i++ ) + ret += buf[i]<<((nbytes-i-1)*8); + i = (4-nbytes)*8+br->offbits; + ret = ((ret<<i)>>i)>>((nbytes*8)-nbits-br->offbits); + + br->offbits += nbits; + br->buffer += br->offbits / 8; + br->offbits %= 8; + + return ret; +} + +void CBitstreamConverter::skip_bits( bits_reader_t *br, int nbits ) +{ + br->offbits += nbits; + br->buffer += br->offbits / 8; + br->offbits %= 8; + if ( br->buffer > (br->start + br->length) ) { + br->oflow = 1; + } +} + +uint32_t CBitstreamConverter::get_bits( bits_reader_t *br, int nbits ) +{ + int i, nbytes; + uint32_t ret = 0; + uint8_t *buf; + + buf = br->buffer; + nbytes = (br->offbits + nbits)/8; + if ( ((br->offbits + nbits) %8 ) > 0 ) + nbytes++; + if ( (buf + nbytes) > (br->start + br->length) ) { + br->oflow = 1; + return 0; + } + for ( i=0; i<nbytes; i++ ) + ret += buf[i]<<((nbytes-i-1)*8); + i = (4-nbytes)*8+br->offbits; + ret = ((ret<<i)>>i)>>((nbytes*8)-nbits-br->offbits); + + return ret; +} + +//////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////// +// GStreamer h264 parser +// Copyright (C) 2005 Michal Benes <michal.benes@itonis.tv> +// (C) 2008 Wim Taymans <wim.taymans@gmail.com> +// gsth264parse.c: +// * License as published by the Free Software Foundation; either +// * version 2.1 of the License, or (at your option) any later version. +void CBitstreamConverter::nal_bs_init(nal_bitstream *bs, const uint8_t *data, size_t size) +{ + bs->data = data; + bs->end = data + size; + bs->head = 0; + // fill with something other than 0 to detect + // emulation prevention bytes + bs->cache = 0xffffffff; +} + +uint32_t CBitstreamConverter::nal_bs_read(nal_bitstream *bs, int n) +{ + uint32_t res = 0; + int shift; + + if (n == 0) + return res; + + // fill up the cache if we need to + while (bs->head < n) + { + uint8_t a_byte; + bool check_three_byte; + + check_three_byte = true; +next_byte: + if (bs->data >= bs->end) + { + // we're at the end, can't produce more than head number of bits + n = bs->head; + break; + } + // get the byte, this can be an emulation_prevention_three_byte that we need + // to ignore. + a_byte = *bs->data++; + if (check_three_byte && a_byte == 0x03 && ((bs->cache & 0xffff) == 0)) + { + // next byte goes unconditionally to the cache, even if it's 0x03 + check_three_byte = false; + goto next_byte; + } + // shift bytes in cache, moving the head bits of the cache left + bs->cache = (bs->cache << 8) | a_byte; + bs->head += 8; + } + + // bring the required bits down and truncate + if ((shift = bs->head - n) > 0) + res = bs->cache >> shift; + else + res = bs->cache; + + // mask out required bits + if (n < 32) + res &= (1 << n) - 1; + bs->head = shift; + + return res; +} + +bool CBitstreamConverter::nal_bs_eos(nal_bitstream *bs) +{ + return (bs->data >= bs->end) && (bs->head == 0); +} + +// read unsigned Exp-Golomb code +int CBitstreamConverter::nal_bs_read_ue(nal_bitstream *bs) +{ + int i = 0; + + while (nal_bs_read(bs, 1) == 0 && !nal_bs_eos(bs) && i < 32) + i++; + + return ((1 << i) - 1 + nal_bs_read(bs, i)); +} + +void CBitstreamConverter::parseh264_sps(uint8_t *sps, uint32_t sps_size, bool *interlaced, int32_t *max_ref_frames) +{ + nal_bitstream bs; + sps_info_struct sps_info; + + nal_bs_init(&bs, sps, sps_size); + + sps_info.profile_idc = nal_bs_read(&bs, 8); + nal_bs_read(&bs, 1); // constraint_set0_flag + nal_bs_read(&bs, 1); // constraint_set1_flag + nal_bs_read(&bs, 1); // constraint_set2_flag + nal_bs_read(&bs, 1); // constraint_set3_flag + nal_bs_read(&bs, 4); // reserved + sps_info.level_idc = nal_bs_read(&bs, 8); + sps_info.sps_id = nal_bs_read_ue(&bs); + + if (sps_info.profile_idc == 100 || + sps_info.profile_idc == 110 || + sps_info.profile_idc == 122 || + sps_info.profile_idc == 244 || + sps_info.profile_idc == 44 || + sps_info.profile_idc == 83 || + sps_info.profile_idc == 86) + { + sps_info.chroma_format_idc = nal_bs_read_ue(&bs); + if (sps_info.chroma_format_idc == 3) + sps_info.separate_colour_plane_flag = nal_bs_read(&bs, 1); + sps_info.bit_depth_luma_minus8 = nal_bs_read_ue(&bs); + sps_info.bit_depth_chroma_minus8 = nal_bs_read_ue(&bs); + sps_info.qpprime_y_zero_transform_bypass_flag = nal_bs_read(&bs, 1); + + sps_info.seq_scaling_matrix_present_flag = nal_bs_read (&bs, 1); + if (sps_info.seq_scaling_matrix_present_flag) + { + /* TODO: unfinished */ + } + } + sps_info.log2_max_frame_num_minus4 = nal_bs_read_ue(&bs); + if (sps_info.log2_max_frame_num_minus4 > 12) + { // must be between 0 and 12 + return; + } + sps_info.pic_order_cnt_type = nal_bs_read_ue(&bs); + if (sps_info.pic_order_cnt_type == 0) + { + sps_info.log2_max_pic_order_cnt_lsb_minus4 = nal_bs_read_ue(&bs); + } + else if (sps_info.pic_order_cnt_type == 1) + { // TODO: unfinished + /* + delta_pic_order_always_zero_flag = gst_nal_bs_read (bs, 1); + offset_for_non_ref_pic = gst_nal_bs_read_se (bs); + offset_for_top_to_bottom_field = gst_nal_bs_read_se (bs); + + num_ref_frames_in_pic_order_cnt_cycle = gst_nal_bs_read_ue (bs); + for( i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++ ) + offset_for_ref_frame[i] = gst_nal_bs_read_se (bs); + */ + } + + sps_info.max_num_ref_frames = nal_bs_read_ue(&bs); + sps_info.gaps_in_frame_num_value_allowed_flag = nal_bs_read(&bs, 1); + sps_info.pic_width_in_mbs_minus1 = nal_bs_read_ue(&bs); + sps_info.pic_height_in_map_units_minus1 = nal_bs_read_ue(&bs); + + sps_info.frame_mbs_only_flag = nal_bs_read(&bs, 1); + if (!sps_info.frame_mbs_only_flag) + sps_info.mb_adaptive_frame_field_flag = nal_bs_read(&bs, 1); + + sps_info.direct_8x8_inference_flag = nal_bs_read(&bs, 1); + + sps_info.frame_cropping_flag = nal_bs_read(&bs, 1); + if (sps_info.frame_cropping_flag) + { + sps_info.frame_crop_left_offset = nal_bs_read_ue(&bs); + sps_info.frame_crop_right_offset = nal_bs_read_ue(&bs); + sps_info.frame_crop_top_offset = nal_bs_read_ue(&bs); + sps_info.frame_crop_bottom_offset = nal_bs_read_ue(&bs); + } + + *interlaced = !sps_info.frame_mbs_only_flag; + *max_ref_frames = sps_info.max_num_ref_frames; +} + +const uint8_t *CBitstreamConverter::avc_find_startcode_internal(const uint8_t *p, const uint8_t *end) +{ + const uint8_t *a = p + 4 - ((intptr_t)p & 3); + + for (end -= 3; p < a && p < end; p++) + { + if (p[0] == 0 && p[1] == 0 && p[2] == 1) + return p; + } + + for (end -= 3; p < end; p += 4) + { + uint32_t x = *(const uint32_t*)p; + if ((x - 0x01010101) & (~x) & 0x80808080) // generic + { + if (p[1] == 0) + { + if (p[0] == 0 && p[2] == 1) + return p; + if (p[2] == 0 && p[3] == 1) + return p+1; + } + if (p[3] == 0) + { + if (p[2] == 0 && p[4] == 1) + return p+2; + if (p[4] == 0 && p[5] == 1) + return p+3; + } + } + } + + for (end += 3; p < end; p++) + { + if (p[0] == 0 && p[1] == 0 && p[2] == 1) + return p; + } + + return end + 3; +} + +const uint8_t *CBitstreamConverter::avc_find_startcode(const uint8_t *p, const uint8_t *end) +{ + const uint8_t *out= avc_find_startcode_internal(p, end); + if (p<out && out<end && !out[-1]) + out--; + return out; +} + +const int CBitstreamConverter::avc_parse_nal_units(AVIOContext *pb, const uint8_t *buf_in, int size) +{ + const uint8_t *p = buf_in; + const uint8_t *end = p + size; + const uint8_t *nal_start, *nal_end; + + size = 0; + nal_start = avc_find_startcode(p, end); + + for (;;) { + while (nal_start < end && !*(nal_start++)); + if (nal_start == end) + break; + + nal_end = avc_find_startcode(nal_start, end); + m_dllAvFormat->avio_wb32(pb, nal_end - nal_start); + m_dllAvFormat->avio_write(pb, nal_start, nal_end - nal_start); + size += 4 + nal_end - nal_start; + nal_start = nal_end; + } + return size; +} + +const int CBitstreamConverter::avc_parse_nal_units_buf(const uint8_t *buf_in, uint8_t **buf, int *size) +{ + AVIOContext *pb; + int ret = m_dllAvFormat->avio_open_dyn_buf(&pb); + if (ret < 0) + return ret; + + avc_parse_nal_units(pb, buf_in, *size); + + m_dllAvUtil->av_freep(buf); + *size = m_dllAvFormat->avio_close_dyn_buf(pb, buf); + return 0; +} + +const int CBitstreamConverter::isom_write_avcc(AVIOContext *pb, const uint8_t *data, int len) +{ + // extradata from bytestream h264, convert to avcC atom data for bitstream + if (len > 6) + { + /* check for h264 start code */ + if (OMX_RB32(data) == 0x00000001 || OMX_RB24(data) == 0x000001) + { + uint8_t *buf=NULL, *end, *start; + uint32_t sps_size=0, pps_size=0; + uint8_t *sps=0, *pps=0; + + int ret = avc_parse_nal_units_buf(data, &buf, &len); + if (ret < 0) + return ret; + start = buf; + end = buf + len; + + /* look for sps and pps */ + while (end - buf > 4) + { + uint32_t size; + uint8_t nal_type; + size = FFMIN(OMX_RB32(buf), end - buf - 4); + buf += 4; + nal_type = buf[0] & 0x1f; + if (nal_type == 7) /* SPS */ + { + sps = buf; + sps_size = size; + } + else if (nal_type == 8) /* PPS */ + { + pps = buf; + pps_size = size; + } + buf += size; + } + if (!sps || !pps || sps_size < 4 || sps_size > UINT16_MAX || pps_size > UINT16_MAX) + assert(0); + + m_dllAvFormat->avio_w8(pb, 1); /* version */ + m_dllAvFormat->avio_w8(pb, sps[1]); /* profile */ + m_dllAvFormat->avio_w8(pb, sps[2]); /* profile compat */ + m_dllAvFormat->avio_w8(pb, sps[3]); /* level */ + m_dllAvFormat->avio_w8(pb, 0xff); /* 6 bits reserved (111111) + 2 bits nal size length - 1 (11) */ + m_dllAvFormat->avio_w8(pb, 0xe1); /* 3 bits reserved (111) + 5 bits number of sps (00001) */ + + m_dllAvFormat->avio_wb16(pb, sps_size); + m_dllAvFormat->avio_write(pb, sps, sps_size); + if (pps) + { + m_dllAvFormat->avio_w8(pb, 1); /* number of pps */ + m_dllAvFormat->avio_wb16(pb, pps_size); + m_dllAvFormat->avio_write(pb, pps, pps_size); + } + m_dllAvUtil->av_free(start); + } + else + { + m_dllAvFormat->avio_write(pb, data, len); + } + } + return 0; +} + +CBitstreamConverter::CBitstreamConverter() +{ + m_convert_bitstream = false; + m_convertBuffer = NULL; + m_convertSize = 0; + m_inputBuffer = NULL; + m_inputSize = 0; + m_to_annexb = false; + m_extradata = NULL; + m_extrasize = 0; + m_convert_3byteTo4byteNALSize = false; + m_dllAvUtil = NULL; + m_dllAvFormat = NULL; + m_convert_bytestream = false; + m_convert_vc1 = false; +} + +CBitstreamConverter::~CBitstreamConverter() +{ + Close(); +} + +bool CBitstreamConverter::Open(enum CodecID codec, uint8_t *in_extradata, int in_extrasize, bool to_annexb) +{ + m_to_annexb = to_annexb; + m_convert_vc1 = false; + + m_codec = codec; + + switch(codec) + { + case CODEC_ID_VC1: + m_extradata = (uint8_t *)malloc(in_extrasize); + memcpy(m_extradata, in_extradata, in_extrasize); + m_extrasize = in_extrasize; + m_dllAvUtil = new DllAvUtil; + m_dllAvFormat = new DllAvFormat; + if (!m_dllAvUtil->Load() || !m_dllAvFormat->Load()) + return false; + + return true; + break; + case CODEC_ID_H264: + if (in_extrasize < 7 || in_extradata == NULL) + { + CLog::Log(LOGERROR, "CBitstreamConverter::Open avcC data too small or missing\n"); + return false; + } + // valid avcC data (bitstream) always starts with the value 1 (version) + if(m_to_annexb) + { + if ( *(char*)in_extradata == 1 ) + { + CLog::Log(LOGINFO, "CBitstreamConverter::Open bitstream to annexb init\n"); + m_convert_bitstream = BitstreamConvertInit(in_extradata, in_extrasize); + return true; + } + } + else + { + // valid avcC atom data always starts with the value 1 (version) + if ( *in_extradata != 1 ) + { + if (in_extradata[0] == 0 && in_extradata[1] == 0 && in_extradata[2] == 0 && in_extradata[3] == 1) + { + CLog::Log(LOGINFO, "CBitstreamConverter::Open annexb to bitstream init\n"); + // video content is from x264 or from bytestream h264 (AnnexB format) + // NAL reformating to bitstream format needed + m_dllAvUtil = new DllAvUtil; + m_dllAvFormat = new DllAvFormat; + if (!m_dllAvUtil->Load() || !m_dllAvFormat->Load()) + return false; + + AVIOContext *pb; + if (m_dllAvFormat->avio_open_dyn_buf(&pb) < 0) + return false; + m_convert_bytestream = true; + // create a valid avcC atom data from ffmpeg's extradata + isom_write_avcc(pb, in_extradata, in_extrasize); + // unhook from ffmpeg's extradata + in_extradata = NULL; + // extract the avcC atom data into extradata then write it into avcCData for VDADecoder + in_extrasize = m_dllAvFormat->avio_close_dyn_buf(pb, &in_extradata); + // make a copy of extradata contents + m_extradata = (uint8_t *)malloc(in_extrasize); + memcpy(m_extradata, in_extradata, in_extrasize); + m_extrasize = in_extrasize; + // done with the converted extradata, we MUST free using av_free + m_dllAvUtil->av_free(in_extradata); + return true; + } + else + { + CLog::Log(LOGNOTICE, "CBitstreamConverter::Open invalid avcC atom data"); + return false; + } + } + else + { + if (in_extradata[4] == 0xFE) + { + CLog::Log(LOGINFO, "CBitstreamConverter::Open annexb to bitstream init 3 byte to 4 byte nal\n"); + // video content is from so silly encoder that think 3 byte NAL sizes + // are valid, setup to convert 3 byte NAL sizes to 4 byte. + m_dllAvUtil = new DllAvUtil; + m_dllAvFormat = new DllAvFormat; + if (!m_dllAvUtil->Load() || !m_dllAvFormat->Load()) + return false; + + in_extradata[4] = 0xFF; + m_convert_3byteTo4byteNALSize = true; + + m_extradata = (uint8_t *)malloc(in_extrasize); + memcpy(m_extradata, in_extradata, in_extrasize); + m_extrasize = in_extrasize; + return true; + } + } + } + return false; + break; + default: + return false; + break; + } + return false; +} + +void CBitstreamConverter::Close(void) +{ + if (m_convert_bitstream) + { + if (m_sps_pps_context.sps_pps_data) + { + free(m_sps_pps_context.sps_pps_data); + m_sps_pps_context.sps_pps_data = NULL; + } + if(m_convertBuffer) + free(m_convertBuffer); + m_convertSize = 0; + } + + if (m_convert_bytestream || m_convert_vc1) + { + if(m_convertBuffer) + { + m_dllAvUtil->av_free(m_convertBuffer); + m_convertBuffer = NULL; + } + m_convertSize = 0; + } + + if(m_extradata) + free(m_extradata); + m_extradata = NULL; + m_extrasize = 0; + + m_inputBuffer = NULL; + m_inputSize = 0; + m_convert_3byteTo4byteNALSize = false; + + m_convert_bitstream = false; + + if (m_dllAvUtil) + { + delete m_dllAvUtil; + m_dllAvUtil = NULL; + } + if (m_dllAvFormat) + { + delete m_dllAvFormat; + m_dllAvFormat = NULL; + } +} + +bool CBitstreamConverter::Convert(uint8_t *pData, int iSize) +{ + if(m_convertBuffer) + free(m_convertBuffer); + m_convertBuffer = NULL; + m_convertSize = 0; + m_inputBuffer = NULL; + m_inputSize = 0; + + if (pData) + { + if(m_codec == CODEC_ID_H264) + { + if(m_to_annexb) + { + int demuxer_bytes = iSize; + + uint8_t *demuxer_content = pData; + + if (m_convert_bitstream) + { + // convert demuxer packet from bitstream to bytestream (AnnexB) + int bytestream_size = 0; + uint8_t *bytestream_buff = NULL; + + BitstreamConvert(demuxer_content, demuxer_bytes, &bytestream_buff, &bytestream_size); + if (bytestream_buff && (bytestream_size > 0)) + { + m_convertSize = bytestream_size; + m_convertBuffer = bytestream_buff; + } + else + { + Close(); + m_inputBuffer = pData; + m_inputSize = iSize; + CLog::Log(LOGERROR, "CBitstreamConverter::Convert error converting. disable converter\n"); + } + } + else + { + m_inputBuffer = pData; + m_inputSize = iSize; + } + + return true; + } + else + { + m_inputBuffer = pData; + m_inputSize = iSize; + + if (m_convert_bytestream) + { + if(m_convertBuffer) + { + m_dllAvUtil->av_free(m_convertBuffer); + m_convertBuffer = NULL; + } + m_convertSize = 0; + + // convert demuxer packet from bytestream (AnnexB) to bitstream + AVIOContext *pb; + + if(m_dllAvFormat->avio_open_dyn_buf(&pb) < 0) + { + return false; + } + m_convertSize = avc_parse_nal_units(pb, pData, iSize); + m_convertSize = m_dllAvFormat->avio_close_dyn_buf(pb, &m_convertBuffer); + } + else if (m_convert_3byteTo4byteNALSize) + { + if(m_convertBuffer) + { + m_dllAvUtil->av_free(m_convertBuffer); + m_convertBuffer = NULL; + } + m_convertSize = 0; + + // convert demuxer packet from 3 byte NAL sizes to 4 byte + AVIOContext *pb; + if (m_dllAvFormat->avio_open_dyn_buf(&pb) < 0) + return false; + + uint32_t nal_size; + uint8_t *end = pData + iSize; + uint8_t *nal_start = pData; + while (nal_start < end) + { + nal_size = OMX_RB24(nal_start); + m_dllAvFormat->avio_wb16(pb, nal_size); + nal_start += 3; + m_dllAvFormat->avio_write(pb, nal_start, nal_size); + nal_start += nal_size; + } + + m_convertSize = m_dllAvFormat->avio_close_dyn_buf(pb, &m_convertBuffer); + } + return true; + } + } + else if (m_codec == CODEC_ID_VC1) + { + if(!(iSize >= 3 && !pData[0] && !pData[1] && pData[2] == 1) && !m_convert_vc1) + m_convert_vc1 = true; + + if(m_convert_vc1) + { + + m_inputBuffer = pData; + m_inputSize = iSize; + + if(m_convertBuffer) + { + m_dllAvUtil->av_free(m_convertBuffer); + m_convertBuffer = NULL; + } + m_convertSize = 0; + + AVIOContext *pb; + if (m_dllAvFormat->avio_open_dyn_buf(&pb) < 0) + return false; + + m_dllAvFormat->avio_w8(pb, 0); + m_dllAvFormat->avio_w8(pb, 0); + m_dllAvFormat->avio_w8(pb, !m_convert_vc1 ? 0 : 1); + m_dllAvFormat->avio_w8(pb, !m_convert_vc1 ? 0 : 0xd); + m_dllAvFormat->avio_write(pb, pData, iSize); + m_convertSize = m_dllAvFormat->avio_close_dyn_buf(pb, &m_convertBuffer); + return true; + } + else + { + m_inputBuffer = pData; + m_inputSize = iSize; + return true; + } + } + } + + return false; +} + + +uint8_t *CBitstreamConverter::GetConvertBuffer() +{ + if((m_convert_bitstream || m_convert_bytestream || m_convert_3byteTo4byteNALSize || m_convert_vc1) && m_convertBuffer != NULL) + return m_convertBuffer; + else + return m_inputBuffer; +} + +int CBitstreamConverter::GetConvertSize() +{ + if((m_convert_bitstream || m_convert_bytestream || m_convert_3byteTo4byteNALSize || m_convert_vc1) && m_convertBuffer != NULL) + return m_convertSize; + else + return m_inputSize; +} + +uint8_t *CBitstreamConverter::GetExtraData() +{ + return m_extradata; +} +int CBitstreamConverter::GetExtraSize() +{ + return m_extrasize; +} + +bool CBitstreamConverter::BitstreamConvertInit(void *in_extradata, int in_extrasize) +{ + // based on h264_mp4toannexb_bsf.c (ffmpeg) + // which is Copyright (c) 2007 Benoit Fouet <benoit.fouet@free.fr> + // and Licensed GPL 2.1 or greater + + m_sps_pps_size = 0; + m_sps_pps_context.sps_pps_data = NULL; + + // nothing to filter + if (!in_extradata || in_extrasize < 6) + return false; + + uint16_t unit_size; + uint32_t total_size = 0; + uint8_t *out = NULL, unit_nb, sps_done = 0; + const uint8_t *extradata = (uint8_t*)in_extradata + 4; + static const uint8_t nalu_header[4] = {0, 0, 0, 1}; + + // retrieve length coded size + m_sps_pps_context.length_size = (*extradata++ & 0x3) + 1; + if (m_sps_pps_context.length_size == 3) + return false; + + // retrieve sps and pps unit(s) + unit_nb = *extradata++ & 0x1f; // number of sps unit(s) + if (!unit_nb) + { + unit_nb = *extradata++; // number of pps unit(s) + sps_done++; + } + while (unit_nb--) + { + unit_size = extradata[0] << 8 | extradata[1]; + total_size += unit_size + 4; + if ( (extradata + 2 + unit_size) > ((uint8_t*)in_extradata + in_extrasize) ) + { + free(out); + return false; + } + out = (uint8_t*)realloc(out, total_size); + if (!out) + return false; + + memcpy(out + total_size - unit_size - 4, nalu_header, 4); + memcpy(out + total_size - unit_size, extradata + 2, unit_size); + extradata += 2 + unit_size; + + if (!unit_nb && !sps_done++) + unit_nb = *extradata++; // number of pps unit(s) + } + + m_sps_pps_context.sps_pps_data = out; + m_sps_pps_context.size = total_size; + m_sps_pps_context.first_idr = 1; + + return true; +} + +bool CBitstreamConverter::BitstreamConvert(uint8_t* pData, int iSize, uint8_t **poutbuf, int *poutbuf_size) +{ + // based on h264_mp4toannexb_bsf.c (ffmpeg) + // which is Copyright (c) 2007 Benoit Fouet <benoit.fouet@free.fr> + // and Licensed GPL 2.1 or greater + + + uint8_t *buf = pData; + uint32_t buf_size = iSize; + uint8_t unit_type; + int32_t nal_size; + uint32_t cumul_size = 0; + const uint8_t *buf_end = buf + buf_size; + + do + { + if (buf + m_sps_pps_context.length_size > buf_end) + goto fail; + + if (m_sps_pps_context.length_size == 1) + nal_size = buf[0]; + else if (m_sps_pps_context.length_size == 2) + nal_size = buf[0] << 8 | buf[1]; + else + nal_size = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]; + + buf += m_sps_pps_context.length_size; + unit_type = *buf & 0x1f; + + if (buf + nal_size > buf_end || nal_size < 0) + goto fail; + + // prepend only to the first type 5 NAL unit of an IDR picture + if (m_sps_pps_context.first_idr && unit_type == 5) + { + BitstreamAllocAndCopy(poutbuf, poutbuf_size, + m_sps_pps_context.sps_pps_data, m_sps_pps_context.size, buf, nal_size); + m_sps_pps_context.first_idr = 0; + } + else + { + BitstreamAllocAndCopy(poutbuf, poutbuf_size, NULL, 0, buf, nal_size); + if (!m_sps_pps_context.first_idr && unit_type == 1) + m_sps_pps_context.first_idr = 1; + } + + buf += nal_size; + cumul_size += nal_size + m_sps_pps_context.length_size; + } while (cumul_size < buf_size); + + return true; + +fail: + free(*poutbuf); + *poutbuf = NULL; + *poutbuf_size = 0; + return false; +} + +void CBitstreamConverter::BitstreamAllocAndCopy( uint8_t **poutbuf, int *poutbuf_size, + const uint8_t *sps_pps, uint32_t sps_pps_size, const uint8_t *in, uint32_t in_size) +{ + // based on h264_mp4toannexb_bsf.c (ffmpeg) + // which is Copyright (c) 2007 Benoit Fouet <benoit.fouet@free.fr> + // and Licensed GPL 2.1 or greater + + #define CHD_WB32(p, d) { \ + ((uint8_t*)(p))[3] = (d); \ + ((uint8_t*)(p))[2] = (d) >> 8; \ + ((uint8_t*)(p))[1] = (d) >> 16; \ + ((uint8_t*)(p))[0] = (d) >> 24; } + + uint32_t offset = *poutbuf_size; + uint8_t nal_header_size = offset ? 3 : 4; + + *poutbuf_size += sps_pps_size + in_size + nal_header_size; + *poutbuf = (uint8_t*)realloc(*poutbuf, *poutbuf_size); + if (sps_pps) + memcpy(*poutbuf + offset, sps_pps, sps_pps_size); + + memcpy(*poutbuf + sps_pps_size + nal_header_size + offset, in, in_size); + if (!offset) + { + CHD_WB32(*poutbuf + sps_pps_size, 1); + } + else + { + (*poutbuf + offset + sps_pps_size)[0] = 0; + (*poutbuf + offset + sps_pps_size)[1] = 0; + (*poutbuf + offset + sps_pps_size)[2] = 1; + } +} + + diff --git a/xbmc/cores/omxplayer/BitstreamConverter.h b/xbmc/cores/omxplayer/BitstreamConverter.h new file mode 100644 index 0000000000..85b0e44e95 --- /dev/null +++ b/xbmc/cores/omxplayer/BitstreamConverter.h @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2010 Team XBMC + * http://www.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, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef _BITSTREAMCONVERTER_H_ +#define _BITSTREAMCONVERTER_H_ + +#include <stdint.h> +#include "DllAvUtil.h" +#include "DllAvFormat.h" +#include "DllAvFilter.h" +#include "DllAvCodec.h" + +typedef struct { + uint8_t *buffer, *start; + int offbits, length, oflow; +} bits_reader_t; + +//////////////////////////////////////////////////////////////////////////////////////////// +// TODO: refactor this so as not to need these ffmpeg routines. +// These are not exposed in ffmpeg's API so we dupe them here. +// AVC helper functions for muxers, +// * Copyright (c) 2006 Baptiste Coudurier <baptiste.coudurier@smartjog.com> +// This is part of FFmpeg +// * License as published by the Free Software Foundation; either +// * version 2.1 of the License, or (at your option) any later version. +#define OMX_RB16(x) \ + ((((const uint8_t*)(x))[0] << 8) | \ + ((const uint8_t*)(x)) [1]) + +#define OMX_RB24(x) \ + ((((const uint8_t*)(x))[0] << 16) | \ + (((const uint8_t*)(x))[1] << 8) | \ + ((const uint8_t*)(x))[2]) + +#define OMX_RB32(x) \ + ((((const uint8_t*)(x))[0] << 24) | \ + (((const uint8_t*)(x))[1] << 16) | \ + (((const uint8_t*)(x))[2] << 8) | \ + ((const uint8_t*)(x))[3]) + +#define OMX_WB32(p, d) { \ + ((uint8_t*)(p))[3] = (d); \ + ((uint8_t*)(p))[2] = (d) >> 8; \ + ((uint8_t*)(p))[1] = (d) >> 16; \ + ((uint8_t*)(p))[0] = (d) >> 24; } + +typedef struct +{ + const uint8_t *data; + const uint8_t *end; + int head; + uint64_t cache; +} nal_bitstream; + +typedef struct +{ + int profile_idc; + int level_idc; + int sps_id; + + int chroma_format_idc; + int separate_colour_plane_flag; + int bit_depth_luma_minus8; + int bit_depth_chroma_minus8; + int qpprime_y_zero_transform_bypass_flag; + int seq_scaling_matrix_present_flag; + + int log2_max_frame_num_minus4; + int pic_order_cnt_type; + int log2_max_pic_order_cnt_lsb_minus4; + + int max_num_ref_frames; + int gaps_in_frame_num_value_allowed_flag; + int pic_width_in_mbs_minus1; + int pic_height_in_map_units_minus1; + + int frame_mbs_only_flag; + int mb_adaptive_frame_field_flag; + + int direct_8x8_inference_flag; + + int frame_cropping_flag; + int frame_crop_left_offset; + int frame_crop_right_offset; + int frame_crop_top_offset; + int frame_crop_bottom_offset; +} sps_info_struct; + +class CBitstreamConverter +{ +public: + CBitstreamConverter(); + ~CBitstreamConverter(); + // Required overrides + static void bits_reader_set( bits_reader_t *br, uint8_t *buf, int len ); + static uint32_t read_bits( bits_reader_t *br, int nbits ); + static void skip_bits( bits_reader_t *br, int nbits ); + static uint32_t get_bits( bits_reader_t *br, int nbits ); + + bool Open(enum CodecID codec, uint8_t *in_extradata, int in_extrasize, bool to_annexb); + void Close(void); + bool NeedConvert(void) { return m_convert_bitstream; }; + bool Convert(uint8_t *pData, int iSize); + uint8_t *GetConvertBuffer(void); + int GetConvertSize(); + uint8_t *GetExtraData(void); + int GetExtraSize(); + void parseh264_sps(uint8_t *sps, uint32_t sps_size, bool *interlaced, int32_t *max_ref_frames); +protected: + // bytestream (Annex B) to bistream conversion support. + void nal_bs_init(nal_bitstream *bs, const uint8_t *data, size_t size); + uint32_t nal_bs_read(nal_bitstream *bs, int n); + bool nal_bs_eos(nal_bitstream *bs); + int nal_bs_read_ue(nal_bitstream *bs); + const uint8_t *avc_find_startcode_internal(const uint8_t *p, const uint8_t *end); + const uint8_t *avc_find_startcode(const uint8_t *p, const uint8_t *end); + const int avc_parse_nal_units(AVIOContext *pb, const uint8_t *buf_in, int size); + const int avc_parse_nal_units_buf(const uint8_t *buf_in, uint8_t **buf, int *size); + const int isom_write_avcc(AVIOContext *pb, const uint8_t *data, int len); + // bitstream to bytestream (Annex B) conversion support. + bool BitstreamConvertInit(void *in_extradata, int in_extrasize); + bool BitstreamConvert(uint8_t* pData, int iSize, uint8_t **poutbuf, int *poutbuf_size); + void BitstreamAllocAndCopy( uint8_t **poutbuf, int *poutbuf_size, + const uint8_t *sps_pps, uint32_t sps_pps_size, const uint8_t *in, uint32_t in_size); + + typedef struct omx_bitstream_ctx { + uint8_t length_size; + uint8_t first_idr; + uint8_t *sps_pps_data; + uint32_t size; + } omx_bitstream_ctx; + + uint8_t *m_convertBuffer; + int m_convertSize; + uint8_t *m_inputBuffer; + int m_inputSize; + + uint32_t m_sps_pps_size; + omx_bitstream_ctx m_sps_pps_context; + bool m_convert_bitstream; + bool m_to_annexb; + bool m_convert_vc1; + + uint8_t *m_extradata; + int m_extrasize; + bool m_convert_3byteTo4byteNALSize; + bool m_convert_bytestream; + DllAvUtil *m_dllAvUtil; + DllAvFormat *m_dllAvFormat; + CodecID m_codec; +}; + +#endif diff --git a/xbmc/cores/omxplayer/DllOMX.h b/xbmc/cores/omxplayer/DllOMX.h new file mode 100644 index 0000000000..4bad4604c1 --- /dev/null +++ b/xbmc/cores/omxplayer/DllOMX.h @@ -0,0 +1,123 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.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, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#if defined(HAVE_OMXLIB) + +#if (defined HAVE_CONFIG_H) && (!defined WIN32) + #include "config.h" +#endif +#ifndef __GNUC__ +#pragma warning(push) +#pragma warning(disable:4244) +#endif + +#include "DynamicDll.h" +#include "utils/log.h" + +#include <IL/OMX_Core.h> +#include <IL/OMX_Component.h> +#include <IL/OMX_Index.h> +#include <IL/OMX_Image.h> +#include <IL/OMX_Video.h> +#include <IL/OMX_Broadcom.h> + +//////////////////////////////////////////////////////////////////////////////////////////// + +class DllOMXInterface +{ +public: + virtual ~DllOMXInterface() {} + + virtual OMX_ERRORTYPE OMX_Init(void) = 0; + virtual OMX_ERRORTYPE OMX_Deinit(void) = 0; + virtual OMX_ERRORTYPE OMX_GetHandle(OMX_HANDLETYPE *pHandle, OMX_STRING cComponentName, OMX_PTR pAppData, OMX_CALLBACKTYPE *pCallBacks) = 0; + virtual OMX_ERRORTYPE OMX_FreeHandle(OMX_HANDLETYPE hComponent) = 0; + virtual OMX_ERRORTYPE OMX_GetComponentsOfRole(OMX_STRING role, OMX_U32 *pNumComps, OMX_U8 **compNames) = 0; + virtual OMX_ERRORTYPE OMX_GetRolesOfComponent(OMX_STRING compName, OMX_U32 *pNumRoles, OMX_U8 **roles) = 0; + virtual OMX_ERRORTYPE OMX_ComponentNameEnum(OMX_STRING cComponentName, OMX_U32 nNameLength, OMX_U32 nIndex) = 0; + virtual OMX_ERRORTYPE OMX_SetupTunnel(OMX_HANDLETYPE hOutput, OMX_U32 nPortOutput, OMX_HANDLETYPE hInput, OMX_U32 nPortInput) = 0; + +}; + +#if (defined USE_EXTERNAL_OMX) +class DllOMX : public DllDynamic, DllOMXInterface +{ +public: + virtual OMX_ERRORTYPE OMX_Init(void) + { return ::OMX_Init(); }; + virtual OMX_ERRORTYPE OMX_Deinit(void) + { return ::OMX_Deinit(); }; + virtual OMX_ERRORTYPE OMX_GetHandle(OMX_HANDLETYPE *pHandle, OMX_STRING cComponentName, OMX_PTR pAppData, OMX_CALLBACKTYPE *pCallBacks) + { return ::OMX_GetHandle(pHandle, cComponentName, pAppData, pCallBacks); }; + virtual OMX_ERRORTYPE OMX_FreeHandle(OMX_HANDLETYPE hComponent) + { return ::OMX_FreeHandle(hComponent); }; + virtual OMX_ERRORTYPE OMX_GetComponentsOfRole(OMX_STRING role, OMX_U32 *pNumComps, OMX_U8 **compNames) + { return ::OMX_GetComponentsOfRole(role, pNumComps, compNames); }; + virtual OMX_ERRORTYPE OMX_GetRolesOfComponent(OMX_STRING compName, OMX_U32 *pNumRoles, OMX_U8 **roles) + { return ::OMX_GetRolesOfComponent(compName, pNumRoles, roles); }; + virtual OMX_ERRORTYPE OMX_ComponentNameEnum(OMX_STRING cComponentName, OMX_U32 nNameLength, OMX_U32 nIndex) + { return ::OMX_ComponentNameEnum(cComponentName, nNameLength, nIndex); }; + virtual OMX_ERRORTYPE OMX_SetupTunnel(OMX_HANDLETYPE hOutput, OMX_U32 nPortOutput, OMX_HANDLETYPE hInput, OMX_U32 nPortInput) + { return ::OMX_SetupTunnel(hOutput, nPortOutput, hInput, nPortInput); }; + virtual bool ResolveExports() + { return true; } + virtual bool Load() + { + CLog::Log(LOGDEBUG, "DllOMX: Using omx system library"); + return true; + } + virtual void Unload() {} +}; +#else +class DllOMX : public DllDynamic, DllOMXInterface +{ + //DECLARE_DLL_WRAPPER(DllLibOpenMax, "/usr/lib/libnvomx.so") + DECLARE_DLL_WRAPPER(DllOMX, "/opt/vc/lib/libopenmaxil.so") + + DEFINE_METHOD0(OMX_ERRORTYPE, OMX_Init) + DEFINE_METHOD0(OMX_ERRORTYPE, OMX_Deinit) + DEFINE_METHOD4(OMX_ERRORTYPE, OMX_GetHandle, (OMX_HANDLETYPE *p1, OMX_STRING p2, OMX_PTR p3, OMX_CALLBACKTYPE *p4)) + DEFINE_METHOD1(OMX_ERRORTYPE, OMX_FreeHandle, (OMX_HANDLETYPE p1)) + DEFINE_METHOD3(OMX_ERRORTYPE, OMX_GetComponentsOfRole, (OMX_STRING p1, OMX_U32 *p2, OMX_U8 **p3)) + DEFINE_METHOD3(OMX_ERRORTYPE, OMX_GetRolesOfComponent, (OMX_STRING p1, OMX_U32 *p2, OMX_U8 **p3)) + DEFINE_METHOD3(OMX_ERRORTYPE, OMX_ComponentNameEnum, (OMX_STRING p1, OMX_U32 p2, OMX_U32 p3)) + DEFINE_METHOD4(OMX_ERRORTYPE, OMX_SetupTunnel, (OMX_HANDLETYPE p1, OMX_U32 p2, OMX_HANDLETYPE p3, OMX_U32 p4)); + BEGIN_METHOD_RESOLVE() + RESOLVE_METHOD(OMX_Init) + RESOLVE_METHOD(OMX_Deinit) + RESOLVE_METHOD(OMX_GetHandle) + RESOLVE_METHOD(OMX_FreeHandle) + RESOLVE_METHOD(OMX_GetComponentsOfRole) + RESOLVE_METHOD(OMX_GetRolesOfComponent) + RESOLVE_METHOD(OMX_ComponentNameEnum) + RESOLVE_METHOD(OMX_SetupTunnel) + END_METHOD_RESOLVE() + +public: + virtual bool Load() + { + return DllDynamic::Load(); + } +}; +#endif + +#endif diff --git a/xbmc/cores/omxplayer/Makefile.in b/xbmc/cores/omxplayer/Makefile.in new file mode 100644 index 0000000000..0d6ec5c00e --- /dev/null +++ b/xbmc/cores/omxplayer/Makefile.in @@ -0,0 +1,20 @@ +CXXFLAGS += -D__STDC_FORMAT_MACROS + +SRCS= \ + OMXPlayer.cpp \ + OMXAudio.cpp \ + OMXVideo.cpp \ + OMXAudioCodecOMX.cpp \ + OMXPlayerAudio.cpp \ + OMXPlayerVideo.cpp \ + OMXImage.cpp \ + BitstreamConverter.cpp + +LIB= omxplayer.a + +@abs_top_srcdir@/system/advancedsettings.xml: $(LIB) + cp -f omxplayer_advancedsettings.xml $@ + +include @abs_top_srcdir@/Makefile.include +-include $(patsubst %.cpp,%.P,$(patsubst %.c,%.P,$(SRCS))) + diff --git a/xbmc/cores/omxplayer/OMXAudio.cpp b/xbmc/cores/omxplayer/OMXAudio.cpp new file mode 100644 index 0000000000..8018826eee --- /dev/null +++ b/xbmc/cores/omxplayer/OMXAudio.cpp @@ -0,0 +1,1478 @@ +/* +* XBMC Media Center +* Copyright (c) 2002 d7o3g4q and RUNTiME +* Portions Copyright (c) by the authors of ffmpeg and xvid +* +* 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 of the License, 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 this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#if (defined HAVE_CONFIG_H) && (!defined WIN32) + #include "config.h" +#elif defined(_WIN32) +#include "system.h" +#endif + +#include "OMXAudio.h" +#include "utils/log.h" + +#define CLASSNAME "COMXAudio" + +#include "linux/XMemUtils.h" + +#include "settings/AdvancedSettings.h" +#include "settings/GUISettings.h" +#include "settings/Settings.h" +#include "guilib/LocalizeStrings.h" +#include "cores/AudioEngine/Utils/AEConvert.h" + +#ifndef VOLUME_MINIMUM +#define VOLUME_MINIMUM -6000 // -60dB +#endif + +using namespace std; + +#define OMX_MAX_CHANNELS 9 + +static enum AEChannel OMXChannelMap[OMX_MAX_CHANNELS] = +{ + AE_CH_FL , AE_CH_FR, + AE_CH_FC , AE_CH_LFE, + AE_CH_BL , AE_CH_BR, + AE_CH_SL , AE_CH_SR, + AE_CH_RAW +}; + +static enum OMX_AUDIO_CHANNELTYPE OMXChannels[OMX_MAX_CHANNELS] = +{ + OMX_AUDIO_ChannelLF, OMX_AUDIO_ChannelRF, + OMX_AUDIO_ChannelCF, OMX_AUDIO_ChannelLFE, + OMX_AUDIO_ChannelLR, OMX_AUDIO_ChannelRR, + OMX_AUDIO_ChannelLS, OMX_AUDIO_ChannelRS, + OMX_AUDIO_ChannelNone +}; + +static unsigned int WAVEChannels[OMX_MAX_CHANNELS] = +{ + SPEAKER_FRONT_LEFT, SPEAKER_FRONT_RIGHT, + SPEAKER_TOP_FRONT_CENTER, SPEAKER_LOW_FREQUENCY, + SPEAKER_BACK_LEFT, SPEAKER_BACK_RIGHT, + SPEAKER_SIDE_LEFT, SPEAKER_SIDE_RIGHT, + SPEAKER_SIDE_RIGHT +}; + +static const uint16_t AC3Bitrates[] = {32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 576, 640}; +static const uint16_t AC3FSCod [] = {48000, 44100, 32000, 0}; + +static const uint16_t DTSFSCod [] = {0, 8000, 16000, 32000, 0, 0, 11025, 22050, 44100, 0, 0, 12000, 24000, 48000, 0, 0}; + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// +//*********************************************************************************************** +COMXAudio::COMXAudio() : + m_pCallback (NULL ), + m_Initialized (false ), + m_Pause (false ), + m_CanPause (false ), + m_CurrentVolume (0 ), + m_Passthrough (false ), + m_HWDecode (false ), + m_BytesPerSec (0 ), + m_BufferLen (0 ), + m_ChunkLen (0 ), + m_OutputChannels (0 ), + m_BitsPerSample (0 ), + m_omx_clock (NULL ), + m_av_clock (NULL ), + m_external_clock (false ), + m_first_frame (true ), + m_LostSync (true ), + m_SampleRate (0 ), + m_eEncoding (OMX_AUDIO_CodingPCM), + m_extradata (NULL ), + m_extrasize (0 ), + m_last_pts (DVD_NOPTS_VALUE) +{ + m_vizBufferSize = m_vizRemapBufferSize = 4096; + m_vizRemapBuffer = (uint8_t *)_aligned_malloc(m_vizRemapBufferSize,16); + m_vizBuffer = (uint8_t *)_aligned_malloc(m_vizBufferSize,16); +} + +COMXAudio::~COMXAudio() +{ + if(m_Initialized) + Deinitialize(); + + _aligned_free(m_vizRemapBuffer); + _aligned_free(m_vizBuffer); +} + + +CAEChannelInfo COMXAudio::GetChannelLayout(AEAudioFormat format) +{ + unsigned int count = 0; + + if(format.m_dataFormat == AE_FMT_AC3 || + format.m_dataFormat == AE_FMT_DTS || + format.m_dataFormat == AE_FMT_EAC3) + count = 2; + else if (format.m_dataFormat == AE_FMT_TRUEHD || + format.m_dataFormat == AE_FMT_DTSHD) + count = 8; + else + { + for (unsigned int c = 0; c < 8; ++c) + { + for (unsigned int i = 0; i < format.m_channelLayout.Count(); ++i) + { + if (format.m_channelLayout[i] == OMXChannelMap[c]) + { + count = c + 1; + break; + } + } + } + } + + CAEChannelInfo info; + for (unsigned int i = 0; i < count; ++i) + info += OMXChannelMap[i]; + + return info; +} + +bool COMXAudio::Initialize(AEAudioFormat format, std::string& device, OMXClock *clock, CDVDStreamInfo &hints, bool bUsePassthrough, bool bUseHWDecode) +{ + m_HWDecode = bUseHWDecode; + m_Passthrough = bUsePassthrough; + + m_format = format; + + if(hints.samplerate == 0) + return false; + + /* passthrough overwrites hw decode */ + if(m_Passthrough) + { + m_HWDecode = false; + } + else if(m_HWDecode) + { + /* check again if we are capable to hw decode the format */ + m_HWDecode = CanHWDecode(hints.codec); + } + SetCodingType(format.m_dataFormat); + + SetClock(clock); + + if(hints.extrasize > 0 && hints.extradata != NULL) + { + m_extrasize = hints.extrasize; + m_extradata = (uint8_t *)malloc(m_extrasize); + memcpy(m_extradata, hints.extradata, hints.extrasize); + } + + return Initialize(format, device); +} + +bool COMXAudio::Initialize(AEAudioFormat format, std::string& device) +{ + if(m_Initialized) + Deinitialize(); + + m_format = format; + + if(m_format.m_channelLayout.Count() == 0) + return false; + + if(!m_dllAvUtil.Load()) + return false; + + if(m_av_clock == NULL) + { + /* no external clock set. generate one */ + m_external_clock = false; + + m_av_clock = new OMXClock(); + + if(!m_av_clock->OMXInitialize(false, true)) + { + delete m_av_clock; + m_av_clock = NULL; + CLog::Log(LOGERROR, "COMXAudio::Initialize error creating av clock\n"); + return false; + } + } + + m_omx_clock = m_av_clock->GetOMXClock(); + + /* + m_Passthrough = false; + + if(OMX_IS_RAW(m_format.m_dataFormat)) + m_Passthrough =true; + */ + + m_drc = 0; + + m_CurrentVolume = g_settings.m_fVolumeLevel; + + memset(m_input_channels, 0x0, sizeof(m_input_channels)); + memset(m_output_channels, 0x0, sizeof(m_output_channels)); + memset(&m_wave_header, 0x0, sizeof(m_wave_header)); + + m_output_channels[0] = OMX_AUDIO_ChannelLF; + m_output_channels[1] = OMX_AUDIO_ChannelRF; + m_output_channels[2] = OMX_AUDIO_ChannelMax; + + m_input_channels[0] = OMX_AUDIO_ChannelLF; + m_input_channels[1] = OMX_AUDIO_ChannelRF; + m_input_channels[2] = OMX_AUDIO_ChannelMax; + + m_OutputChannels = 2; + m_wave_header.Format.nChannels = m_OutputChannels; + m_wave_header.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; + + if (!m_Passthrough) + { + /* setup output channel map */ + /* + int ch = 0, map; + int chan = 0; + m_OutputChannels = 0; + + for (unsigned int ch = 0; ch < m_format.m_channelLayout.Count(); ++ch) + { + for(map = 0; map < OMX_MAX_CHANNELS; ++map) + { + if (m_output_channels[ch] == OMXChannelMap[map]) + { + printf("output %d\n", chan); + m_output_channels[chan] = OMXChannels[map]; + chan++; + break; + } + } + } + + m_OutputChannels = chan; + */ + + /* setup input channel map */ + int map = 0; + int chan = 0; + + for (unsigned int ch = 0; ch < m_format.m_channelLayout.Count(); ++ch) + { + for(map = 0; map < OMX_MAX_CHANNELS; ++map) + { + if (m_format.m_channelLayout[ch] == OMXChannelMap[map]) + { + m_input_channels[chan] = OMXChannels[map]; + m_wave_header.dwChannelMask |= WAVEChannels[map]; + chan++; + break; + } + } + } + + m_vizRemap.Initialize(m_format.m_channelLayout, CAEChannelInfo(AE_CH_LAYOUT_2_0), false, true); + } + + OMX_INIT_STRUCTURE(m_pcm_output); + OMX_INIT_STRUCTURE(m_pcm_input); + + memcpy(m_pcm_output.eChannelMapping, m_output_channels, sizeof(m_output_channels)); + memcpy(m_pcm_input.eChannelMapping, m_input_channels, sizeof(m_input_channels)); + + // set the m_pcm_output parameters + m_pcm_output.eNumData = OMX_NumericalDataSigned; + m_pcm_output.eEndian = OMX_EndianLittle; + m_pcm_output.bInterleaved = OMX_TRUE; + m_pcm_output.nBitPerSample = CAEUtil::DataFormatToBits(m_format.m_dataFormat); + m_pcm_output.ePCMMode = OMX_AUDIO_PCMModeLinear; + m_pcm_output.nChannels = m_OutputChannels; + m_pcm_output.nSamplingRate = m_format.m_sampleRate; + + m_SampleRate = m_format.m_sampleRate; + m_BitsPerSample = CAEUtil::DataFormatToBits(m_format.m_dataFormat); + m_BufferLen = m_BytesPerSec = m_format.m_sampleRate * + (CAEUtil::DataFormatToBits(m_format.m_dataFormat) >> 3) * + m_format.m_channelLayout.Count(); + m_BufferLen *= AUDIO_BUFFER_SECONDS; + m_ChunkLen = 6144; + + m_wave_header.Samples.wSamplesPerBlock = 0; + m_wave_header.Format.nChannels = m_format.m_channelLayout.Count(); + m_wave_header.Format.nBlockAlign = m_format.m_channelLayout.Count() * + (CAEUtil::DataFormatToBits(m_format.m_dataFormat) >> 3); + m_wave_header.Format.wFormatTag = WAVE_FORMAT_PCM; + m_wave_header.Format.nSamplesPerSec = m_format.m_sampleRate; + m_wave_header.Format.nAvgBytesPerSec = m_BytesPerSec; + m_wave_header.Format.wBitsPerSample = CAEUtil::DataFormatToBits(m_format.m_dataFormat); + m_wave_header.Samples.wValidBitsPerSample = CAEUtil::DataFormatToBits(m_format.m_dataFormat); + m_wave_header.Format.cbSize = 0; + m_wave_header.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + + m_pcm_input.eNumData = OMX_NumericalDataSigned; + m_pcm_input.eEndian = OMX_EndianLittle; + m_pcm_input.bInterleaved = OMX_TRUE; + m_pcm_input.nBitPerSample = CAEUtil::DataFormatToBits(m_format.m_dataFormat); + m_pcm_input.ePCMMode = OMX_AUDIO_PCMModeLinear; + m_pcm_input.nChannels = m_format.m_channelLayout.Count(); + m_pcm_input.nSamplingRate = m_format.m_sampleRate; + + OMX_ERRORTYPE omx_err = OMX_ErrorNone; + std::string componentName = ""; + + componentName = "OMX.broadcom.audio_render"; + if(!m_omx_render.Initialize((const std::string)componentName, OMX_IndexParamAudioInit)) + return false; + + OMX_CONFIG_BRCMAUDIODESTINATIONTYPE audioDest; + OMX_INIT_STRUCTURE(audioDest); + strncpy((char *)audioDest.sName, device.c_str(), strlen(device.c_str())); + + omx_err = m_omx_render.SetConfig(OMX_IndexConfigBrcmAudioDestination, &audioDest); + if (omx_err != OMX_ErrorNone) + return false; + + OMX_CONFIG_BOOLEANTYPE configBool; + OMX_INIT_STRUCTURE(configBool); + configBool.bEnabled = OMX_FALSE; + + omx_err = m_omx_render.SetConfig(OMX_IndexConfigBrcmClockReferenceSource, &configBool); + if (omx_err != OMX_ErrorNone) + return false; + + componentName = "OMX.broadcom.audio_decode"; + if(!m_omx_decoder.Initialize((const std::string)componentName, OMX_IndexParamAudioInit)) + return false; + + if(!m_Passthrough) + { + componentName = "OMX.broadcom.audio_mixer"; + if(!m_omx_mixer.Initialize((const std::string)componentName, OMX_IndexParamAudioInit)) + return false; + } + + if(m_Passthrough) + { + OMX_CONFIG_BOOLEANTYPE boolType; + OMX_INIT_STRUCTURE(boolType); + boolType.bEnabled = OMX_TRUE; + omx_err = m_omx_decoder.SetParameter(OMX_IndexParamBrcmDecoderPassThrough, &boolType); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXAudio::Initialize - Error OMX_IndexParamBrcmDecoderPassThrough 0x%08x", omx_err); + return false; + } + } + + // set up the number/size of buffers + OMX_PARAM_PORTDEFINITIONTYPE port_param; + OMX_INIT_STRUCTURE(port_param); + port_param.nPortIndex = m_omx_decoder.GetInputPort(); + + omx_err = m_omx_decoder.GetParameter(OMX_IndexParamPortDefinition, &port_param); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXAudio::Initialize error get OMX_IndexParamPortDefinition omx_err(0x%08x)\n", omx_err); + return false; + } + + port_param.format.audio.eEncoding = m_eEncoding; + + port_param.nBufferSize = m_ChunkLen; + port_param.nBufferCountActual = m_BufferLen / m_ChunkLen; + + omx_err = m_omx_decoder.SetParameter(OMX_IndexParamPortDefinition, &port_param); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXAudio::Initialize error set OMX_IndexParamPortDefinition omx_err(0x%08x)\n", omx_err); + return false; + } + + if(m_HWDecode) + { + OMX_AUDIO_PARAM_PORTFORMATTYPE formatType; + OMX_INIT_STRUCTURE(formatType); + formatType.nPortIndex = m_omx_decoder.GetInputPort(); + + formatType.eEncoding = m_eEncoding; + + omx_err = m_omx_decoder.SetParameter(OMX_IndexParamAudioPortFormat, &formatType); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXAudio::Initialize error OMX_IndexParamAudioPortFormat omx_err(0x%08x)\n", omx_err); + return false; + } + } + + m_omx_tunnel_clock.Initialize(m_omx_clock, m_omx_clock->GetInputPort(), &m_omx_render, m_omx_render.GetInputPort()+1); + + omx_err = m_omx_tunnel_clock.Establish(false); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXAudio::Initialize m_omx_tunnel_clock.Establish\n"); + return false; + } + + if(!m_external_clock) + { + omx_err = m_omx_clock->SetStateForComponent(OMX_StateExecuting); + if (omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXAudio::Initialize m_omx_clock.SetStateForComponent\n"); + return false; + } + } + + omx_err = m_omx_decoder.AllocInputBuffers(); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXAudio::Initialize - Error alloc buffers 0x%08x", omx_err); + return false; + } + + if(!m_Passthrough) + { + m_omx_tunnel_decoder.Initialize(&m_omx_decoder, m_omx_decoder.GetOutputPort(), &m_omx_mixer, m_omx_mixer.GetInputPort()); + omx_err = m_omx_tunnel_decoder.Establish(false); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXAudio::Initialize - Error m_omx_tunnel_decoder.Establish 0x%08x", omx_err); + return false; + } + + omx_err = m_omx_decoder.SetStateForComponent(OMX_StateExecuting); + if(omx_err != OMX_ErrorNone) { + CLog::Log(LOGERROR, "COMXAudio::Initialize - Error setting OMX_StateExecuting 0x%08x", omx_err); + return false; + } + + m_omx_tunnel_mixer.Initialize(&m_omx_mixer, m_omx_mixer.GetOutputPort(), &m_omx_render, m_omx_render.GetInputPort()); + omx_err = m_omx_tunnel_mixer.Establish(false); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXAudio::Initialize - Error m_omx_tunnel_decoder.Establish 0x%08x", omx_err); + return false; + } + + omx_err = m_omx_mixer.SetStateForComponent(OMX_StateExecuting); + if(omx_err != OMX_ErrorNone) { + CLog::Log(LOGERROR, "COMXAudio::Initialize - Error setting OMX_StateExecuting 0x%08x", omx_err); + return false; + } + } + else + { + m_omx_tunnel_decoder.Initialize(&m_omx_decoder, m_omx_decoder.GetOutputPort(), &m_omx_render, m_omx_render.GetInputPort()); + omx_err = m_omx_tunnel_decoder.Establish(false); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXAudio::Initialize - Error m_omx_tunnel_decoder.Establish 0x%08x", omx_err); + return false; + } + + omx_err = m_omx_decoder.SetStateForComponent(OMX_StateExecuting); + if(omx_err != OMX_ErrorNone) { + CLog::Log(LOGERROR, "COMXAudio::Initialize - Error setting OMX_StateExecuting 0x%08x", omx_err); + return false; + } + } + + omx_err = m_omx_render.SetStateForComponent(OMX_StateExecuting); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXAudio::Initialize - Error setting OMX_StateExecuting 0x%08x", omx_err); + return false; + } + + if(m_eEncoding == OMX_AUDIO_CodingPCM) + { + OMX_BUFFERHEADERTYPE *omx_buffer = m_omx_decoder.GetInputBuffer(); + if(omx_buffer == NULL) + { + CLog::Log(LOGERROR, "COMXAudio::Initialize - buffer error 0x%08x", omx_err); + return false; + } + + omx_buffer->nOffset = 0; + omx_buffer->nFilledLen = sizeof(m_wave_header); + if(omx_buffer->nFilledLen > omx_buffer->nAllocLen) + { + CLog::Log(LOGERROR, "COMXAudio::Initialize - omx_buffer->nFilledLen > omx_buffer->nAllocLen"); + return false; + } + memset((unsigned char *)omx_buffer->pBuffer, 0x0, omx_buffer->nAllocLen); + memcpy((unsigned char *)omx_buffer->pBuffer, &m_wave_header, omx_buffer->nFilledLen); + omx_buffer->nFlags = OMX_BUFFERFLAG_CODECCONFIG | OMX_BUFFERFLAG_ENDOFFRAME; + + omx_err = m_omx_decoder.EmptyThisBuffer(omx_buffer); + if (omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s - OMX_EmptyThisBuffer() failed with result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + } + else if(m_HWDecode) + { + // send decoder config + if(m_extrasize > 0 && m_extradata != NULL) + { + OMX_BUFFERHEADERTYPE *omx_buffer = m_omx_decoder.GetInputBuffer(); + + if(omx_buffer == NULL) + { + CLog::Log(LOGERROR, "%s::%s - buffer error 0x%08x", CLASSNAME, __func__, omx_err); + return false; + } + + omx_buffer->nOffset = 0; + omx_buffer->nFilledLen = m_extrasize; + if(omx_buffer->nFilledLen > omx_buffer->nAllocLen) + { + CLog::Log(LOGERROR, "%s::%s - omx_buffer->nFilledLen > omx_buffer->nAllocLen", CLASSNAME, __func__); + return false; + } + + memset((unsigned char *)omx_buffer->pBuffer, 0x0, omx_buffer->nAllocLen); + memcpy((unsigned char *)omx_buffer->pBuffer, m_extradata, omx_buffer->nFilledLen); + omx_buffer->nFlags = OMX_BUFFERFLAG_CODECCONFIG | OMX_BUFFERFLAG_ENDOFFRAME; + + omx_err = m_omx_decoder.EmptyThisBuffer(omx_buffer); + if (omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s - OMX_EmptyThisBuffer() failed with result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + } + } + + m_Initialized = true; + m_first_frame = true; + m_last_pts = DVD_NOPTS_VALUE; + + SetCurrentVolume(m_CurrentVolume); + + CLog::Log(LOGDEBUG, "COMXAudio::Initialize Ouput bps %d samplerate %d channels %d buffer size %d bytes per second %d", + (int)m_pcm_output.nBitPerSample, (int)m_pcm_output.nSamplingRate, (int)m_pcm_output.nChannels, m_BufferLen, m_BytesPerSec); + CLog::Log(LOGDEBUG, "COMXAudio::Initialize Input bps %d samplerate %d channels %d buffer size %d bytes per second %d", + (int)m_pcm_input.nBitPerSample, (int)m_pcm_input.nSamplingRate, (int)m_pcm_input.nChannels, m_BufferLen, m_BytesPerSec); + CLog::Log(LOGDEBUG, "COMXAudio::Initialize device %s passthrough %d hwdecode %d external clock %d", + device.c_str(), m_Passthrough, m_HWDecode, m_external_clock); + + m_av_clock->OMXStateExecute(false); + + return true; +} + +//*********************************************************************************************** +bool COMXAudio::Deinitialize() +{ + if(!m_Initialized) + return true; + + /* + if(m_av_clock && !m_external_clock) + m_av_clock->OMXStop(); + */ + + if(m_av_clock && !m_external_clock) + { + m_av_clock->Lock(); + m_av_clock->OMXStop(false); + } + + m_omx_tunnel_decoder.Flush(); + if(!m_Passthrough) + m_omx_tunnel_mixer.Flush(); + m_omx_tunnel_clock.Flush(); + + m_omx_tunnel_clock.Deestablish(); + if(!m_Passthrough) + m_omx_tunnel_mixer.Deestablish(); + m_omx_tunnel_decoder.Deestablish(); + + m_omx_decoder.FlushInput(); + + m_omx_render.Deinitialize(); + if(!m_Passthrough) + m_omx_mixer.Deinitialize(); + m_omx_decoder.Deinitialize(); + + m_Initialized = false; + m_BytesPerSec = 0; + m_BufferLen = 0; + + if(m_av_clock && !m_external_clock) + { + m_av_clock->OMXReset(false); + m_av_clock->UnLock(); + } + + if(!m_external_clock && m_av_clock != NULL) + { + delete m_av_clock; + m_av_clock = NULL; + m_external_clock = false; + } + + m_omx_clock = NULL; + m_av_clock = NULL; + + m_Initialized = false; + m_LostSync = true; + m_HWDecode = false; + + if(m_extradata) + free(m_extradata); + m_extradata = NULL; + m_extrasize = 0; + + m_dllAvUtil.Unload(); + + m_first_frame = true; + m_last_pts = DVD_NOPTS_VALUE; + + return true; +} + +void COMXAudio::Flush() +{ + if(!m_Initialized) + return; + + m_omx_decoder.FlushInput(); + m_omx_tunnel_decoder.Flush(); + if(!m_Passthrough) + m_omx_tunnel_mixer.Flush(); + + m_last_pts = DVD_NOPTS_VALUE; + m_LostSync = true; + //m_first_frame = true; +} + +//*********************************************************************************************** +bool COMXAudio::Pause() +{ + if (!m_Initialized) + return -1; + + if(m_Pause) return true; + m_Pause = true; + + m_omx_decoder.SetStateForComponent(OMX_StatePause); + + return true; +} + +//*********************************************************************************************** +bool COMXAudio::Resume() +{ + if (!m_Initialized) + return -1; + + if(!m_Pause) return true; + m_Pause = false; + + m_omx_decoder.SetStateForComponent(OMX_StateExecuting); + + return true; +} + +//*********************************************************************************************** +bool COMXAudio::Stop() +{ + if (!m_Initialized) + return -1; + + Flush(); + + m_Pause = false; + + return true; +} + +//*********************************************************************************************** +long COMXAudio::GetCurrentVolume() const +{ + return m_CurrentVolume; +} + +//*********************************************************************************************** +void COMXAudio::Mute(bool bMute) +{ + if(!m_Initialized) + return; + + if (bMute) + SetCurrentVolume(VOLUME_MINIMUM); + else + SetCurrentVolume(m_CurrentVolume); +} + +//*********************************************************************************************** +bool COMXAudio::SetCurrentVolume(float fVolume) +{ + if(!m_Initialized || m_Passthrough) + return -1; + + m_CurrentVolume = fVolume; + + OMX_AUDIO_CONFIG_VOLUMETYPE volume; + OMX_INIT_STRUCTURE(volume); + volume.nPortIndex = m_omx_render.GetInputPort(); + + volume.bLinear = OMX_TRUE; + float hardwareVolume = std::max(VOLUME_MINIMUM, std::min(VOLUME_MAXIMUM, fVolume)) * 100.0f; + volume.sVolume.nValue = (int)hardwareVolume; + + m_omx_render.SetConfig(OMX_IndexConfigAudioVolume, &volume); + + return true; +} + + +//*********************************************************************************************** +unsigned int COMXAudio::AddPackets(const void* data, unsigned int len) +{ + return AddPackets(data, len, 0, 0); +} + +//*********************************************************************************************** +unsigned int COMXAudio::AddPackets(const void* data, unsigned int len, double dts, double pts) +{ + if(!m_Initialized) + { + CLog::Log(LOGERROR,"COMXAudio::AddPackets - sanity failed. no valid play handle!"); + return len; + } + + m_vizBufferSamples = 0; + + if (!m_Passthrough && m_pCallback && len) + { + /* unput samples */ + m_vizBufferSamples = len / (CAEUtil::DataFormatToBits(AE_FMT_S16LE) >> 3); + CAEConvert::AEConvertToFn m_convertFn = CAEConvert::ToFloat(AE_FMT_S16LE); + /* input frames */ + unsigned int frames = m_vizBufferSamples / m_format.m_channelLayout.Count(); + + /* check convert buffer */ + CheckOutputBufferSize((void **)&m_vizBuffer, &m_vizBufferSize, m_vizBufferSamples * (CAEUtil::DataFormatToBits(AE_FMT_FLOAT) >> 3)); + + /* convert to float */ + m_convertFn((uint8_t *)data, m_vizBufferSamples, (float *)m_vizBuffer); + + /* check remap buffer */ + CheckOutputBufferSize((void **)&m_vizRemapBuffer, &m_vizRemapBufferSize, frames * 2 * (CAEUtil::DataFormatToBits(AE_FMT_FLOAT) >> 3)); + + /* remap */ + m_vizRemap.Remap((float *)m_vizBuffer, (float*)m_vizRemapBuffer, frames); + + /* output samples */ + m_vizBufferSamples = m_vizBufferSamples / m_format.m_channelLayout.Count() * 2; + + /* viz size is limited */ + if(m_vizBufferSamples > VIS_PACKET_SIZE) + m_vizBufferSamples = VIS_PACKET_SIZE; + + if(m_pCallback) + m_pCallback->OnAudioData((float *)m_vizRemapBuffer, m_vizBufferSamples); + } + + if(m_eEncoding == OMX_AUDIO_CodingDTS && m_LostSync && (m_Passthrough || m_HWDecode)) + { + int skip = SyncDTS((uint8_t *)data, len); + if(skip > 0) + return len; + } + + if(m_eEncoding == OMX_AUDIO_CodingDDP && m_LostSync && (m_Passthrough || m_HWDecode)) + { + int skip = SyncAC3((uint8_t *)data, len); + if(skip > 0) + return len; + } + + unsigned int demuxer_bytes = (unsigned int)len; + uint8_t *demuxer_content = (uint8_t *)data; + + OMX_ERRORTYPE omx_err; + + OMX_BUFFERHEADERTYPE *omx_buffer = NULL; + + while(demuxer_bytes) + { + // 200ms timeout + omx_buffer = m_omx_decoder.GetInputBuffer(200); + + if(omx_buffer == NULL) + { + CLog::Log(LOGERROR, "COMXAudio::Decode timeout\n"); + return len; + } + + omx_buffer->nOffset = 0; + omx_buffer->nFlags = 0; + + omx_buffer->nFilledLen = (demuxer_bytes > omx_buffer->nAllocLen) ? omx_buffer->nAllocLen : demuxer_bytes; + memcpy(omx_buffer->pBuffer, demuxer_content, omx_buffer->nFilledLen); + + uint64_t val = (uint64_t)(pts == DVD_NOPTS_VALUE) ? 0 : pts; + + if(m_av_clock->AudioStart()) + { + omx_buffer->nFlags = OMX_BUFFERFLAG_STARTTIME; + + m_last_pts = pts; + + CLog::Log(LOGDEBUG, "ADec : setStartTime %f\n", (float)val / DVD_TIME_BASE); + m_av_clock->AudioStart(false); + } + else + { + if(pts == DVD_NOPTS_VALUE) + { + omx_buffer->nFlags = OMX_BUFFERFLAG_TIME_UNKNOWN; + m_last_pts = pts; + } + else if (m_last_pts != pts) + { + if(pts > m_last_pts) + m_last_pts = pts; + else + omx_buffer->nFlags = OMX_BUFFERFLAG_TIME_UNKNOWN;; + } + else if (m_last_pts == pts) + { + omx_buffer->nFlags = OMX_BUFFERFLAG_TIME_UNKNOWN; + } + } + + omx_buffer->nTimeStamp = ToOMXTime(val); + + demuxer_bytes -= omx_buffer->nFilledLen; + demuxer_content += omx_buffer->nFilledLen; + + if(demuxer_bytes == 0) + omx_buffer->nFlags |= OMX_BUFFERFLAG_ENDOFFRAME; + + int nRetry = 0; + while(true) + { + omx_err = m_omx_decoder.EmptyThisBuffer(omx_buffer); + if (omx_err == OMX_ErrorNone) + { + break; + } + else + { + CLog::Log(LOGERROR, "%s::%s - OMX_EmptyThisBuffer() failed with result(0x%x)\n", CLASSNAME, __func__, omx_err); + nRetry++; + } + if(nRetry == 5) + { + CLog::Log(LOGERROR, "%s::%s - OMX_EmptyThisBuffer() finaly failed\n", CLASSNAME, __func__); + return 0; + } + } + + if(m_first_frame) + { + m_first_frame = false; + //m_omx_render.WaitForEvent(OMX_EventPortSettingsChanged); + + m_omx_render.DisablePort(m_omx_render.GetInputPort(), false); + if(!m_Passthrough) + { + m_omx_mixer.DisablePort(m_omx_mixer.GetOutputPort(), false); + m_omx_mixer.DisablePort(m_omx_mixer.GetInputPort(), false); + } + m_omx_decoder.DisablePort(m_omx_decoder.GetOutputPort(), false); + + if(!m_Passthrough) + { + OMX_INIT_STRUCTURE(m_pcm_input); + m_pcm_input.nPortIndex = m_omx_decoder.GetOutputPort(); + omx_err = m_omx_decoder.GetParameter(OMX_IndexParamAudioPcm, &m_pcm_input); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXAudio::AddPackets error GetParameter 1 omx_err(0x%08x)\n", omx_err); + } + + if (g_guiSettings.GetBool("audiooutput.normalizelevels")) + { + OMX_AUDIO_CONFIG_VOLUMETYPE volume; + OMX_INIT_STRUCTURE(volume); + volume.nPortIndex = m_omx_mixer.GetInputPort(); + volume.bLinear = OMX_FALSE; + volume.sVolume.nValue = (int)(g_advancedSettings.m_ac3Gain*100.0f+0.5f); + m_omx_mixer.SetConfig(OMX_IndexConfigAudioVolume, &volume); + } + + memcpy(m_pcm_input.eChannelMapping, m_input_channels, sizeof(m_input_channels)); + m_pcm_input.nSamplingRate = m_format.m_sampleRate; + + /* setup mixer input */ + m_pcm_input.nPortIndex = m_omx_mixer.GetInputPort(); + omx_err = m_omx_mixer.SetParameter(OMX_IndexParamAudioPcm, &m_pcm_input); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXAudio::AddPackets error SetParameter 1 input omx_err(0x%08x)\n", omx_err); + } + omx_err = m_omx_mixer.GetParameter(OMX_IndexParamAudioPcm, &m_pcm_input); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXAudio::AddPackets error GetParameter 2 input omx_err(0x%08x)\n", omx_err); + } + + m_pcm_output.nSamplingRate = m_format.m_sampleRate; + + /* setup mixer output */ + m_pcm_output.nPortIndex = m_omx_mixer.GetOutputPort(); + omx_err = m_omx_mixer.SetParameter(OMX_IndexParamAudioPcm, &m_pcm_output); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXAudio::AddPackets error SetParameter 1 output omx_err(0x%08x)\n", omx_err); + } + omx_err = m_omx_mixer.GetParameter(OMX_IndexParamAudioPcm, &m_pcm_output); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXAudio::AddPackets error GetParameter 2 output omx_err(0x%08x)\n", omx_err); + } + + m_pcm_output.nSamplingRate = m_format.m_sampleRate; + + m_pcm_output.nPortIndex = m_omx_render.GetInputPort(); + omx_err = m_omx_render.SetParameter(OMX_IndexParamAudioPcm, &m_pcm_output); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXAudio::AddPackets error SetParameter 1 render omx_err(0x%08x)\n", omx_err); + } + omx_err = m_omx_render.GetParameter(OMX_IndexParamAudioPcm, &m_pcm_output); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXAudio::AddPackets error GetParameter 2 render omx_err(0x%08x)\n", omx_err); + } + + PrintPCM(&m_pcm_input, std::string("input")); + PrintPCM(&m_pcm_output, std::string("output")); + } + else + { + m_pcm_output.nPortIndex = m_omx_decoder.GetOutputPort(); + m_omx_decoder.GetParameter(OMX_IndexParamAudioPcm, &m_pcm_output); + PrintPCM(&m_pcm_output, std::string("output")); + + OMX_AUDIO_PARAM_PORTFORMATTYPE formatType; + OMX_INIT_STRUCTURE(formatType); + formatType.nPortIndex = m_omx_render.GetInputPort(); + + omx_err = m_omx_render.GetParameter(OMX_IndexParamAudioPortFormat, &formatType); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXAudio::AddPackets error OMX_IndexParamAudioPortFormat omx_err(0x%08x)\n", omx_err); + assert(0); + } + + formatType.eEncoding = m_eEncoding; + + omx_err = m_omx_render.SetParameter(OMX_IndexParamAudioPortFormat, &formatType); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXAudio::AddPackets error OMX_IndexParamAudioPortFormat omx_err(0x%08x)\n", omx_err); + assert(0); + } + + if(m_eEncoding == OMX_AUDIO_CodingDDP) + { + OMX_AUDIO_PARAM_DDPTYPE m_ddParam; + OMX_INIT_STRUCTURE(m_ddParam); + + m_ddParam.nPortIndex = m_omx_render.GetInputPort(); + + m_ddParam.nChannels = m_format.m_channelLayout.Count(); //(m_InputChannels == 6) ? 8 : m_InputChannels; + m_ddParam.nSampleRate = m_SampleRate; + m_ddParam.eBitStreamId = OMX_AUDIO_DDPBitStreamIdAC3; + m_ddParam.nBitRate = 0; + + for(unsigned int i = 0; i < OMX_MAX_CHANNELS; i++) + { + if(i >= m_ddParam.nChannels) + break; + + m_ddParam.eChannelMapping[i] = OMXChannels[i]; + } + + m_omx_render.SetParameter(OMX_IndexParamAudioDdp, &m_ddParam); + m_omx_render.GetParameter(OMX_IndexParamAudioDdp, &m_ddParam); + PrintDDP(&m_ddParam); + } + else if(m_eEncoding == OMX_AUDIO_CodingDTS) + { + m_dtsParam.nPortIndex = m_omx_render.GetInputPort(); + + m_dtsParam.nChannels = m_format.m_channelLayout.Count(); //(m_InputChannels == 6) ? 8 : m_InputChannels; + m_dtsParam.nBitRate = 0; + + for(unsigned int i = 0; i < OMX_MAX_CHANNELS; i++) + { + if(i >= m_dtsParam.nChannels) + break; + + m_dtsParam.eChannelMapping[i] = OMXChannels[i]; + } + + m_omx_render.SetParameter(OMX_IndexParamAudioDts, &m_dtsParam); + m_omx_render.GetParameter(OMX_IndexParamAudioDts, &m_dtsParam); + PrintDTS(&m_dtsParam); + } + } + + m_omx_render.EnablePort(m_omx_render.GetInputPort(), false); + if(!m_Passthrough) + { + m_omx_mixer.EnablePort(m_omx_mixer.GetOutputPort(), false); + m_omx_mixer.EnablePort(m_omx_mixer.GetInputPort(), false); + } + m_omx_decoder.EnablePort(m_omx_decoder.GetOutputPort(), false); + } + + } + + return len; +} + +//*********************************************************************************************** +unsigned int COMXAudio::GetSpace() +{ + int free = m_omx_decoder.GetInputBufferSpace(); + return free; +} + +float COMXAudio::GetDelay() +{ + unsigned int free = m_omx_decoder.GetInputBufferSize() - m_omx_decoder.GetInputBufferSpace(); + return (float)free / (float)m_BytesPerSec; +} + +float COMXAudio::GetCacheTime() +{ + float fBufferLenFull = (float)m_BufferLen - (float)GetSpace(); + if(fBufferLenFull < 0) + fBufferLenFull = 0; + float ret = fBufferLenFull / (float)m_BytesPerSec; + return ret; +} + +float COMXAudio::GetCacheTotal() +{ + return (float)m_BufferLen / (float)m_BytesPerSec; +} + +//*********************************************************************************************** +unsigned int COMXAudio::GetChunkLen() +{ + return m_ChunkLen; +} +//*********************************************************************************************** +int COMXAudio::SetPlaySpeed(int iSpeed) +{ + return 0; +} + +void COMXAudio::RegisterAudioCallback(IAudioCallback *pCallback) +{ + m_pCallback = pCallback; + if (m_pCallback && !m_Passthrough && !m_HWDecode) + m_pCallback->OnInitialize(m_OutputChannels, m_SampleRate, m_BitsPerSample); +} + +void COMXAudio::UnRegisterAudioCallback() +{ + m_pCallback = NULL; +} + +void COMXAudio::DoAudioWork() +{ + if (m_pCallback && m_vizBufferSamples) + { + m_pCallback->OnAudioData((float*)m_vizBuffer, m_vizBufferSamples); + m_vizBufferSamples = 0; + } +} + +void COMXAudio::WaitCompletion() +{ + if(!m_Initialized || m_Pause) + return; + + OMX_ERRORTYPE omx_err = OMX_ErrorNone; + OMX_BUFFERHEADERTYPE *omx_buffer = m_omx_decoder.GetInputBuffer(); + struct timespec starttime, endtime; + + if(omx_buffer == NULL) + { + CLog::Log(LOGERROR, "%s::%s - buffer error 0x%08x", CLASSNAME, __func__, omx_err); + return; + } + + omx_buffer->nOffset = 0; + omx_buffer->nFilledLen = 0; + omx_buffer->nTimeStamp = ToOMXTime(0LL); + + omx_buffer->nFlags = OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_EOS | OMX_BUFFERFLAG_TIME_UNKNOWN; + + omx_err = m_omx_decoder.EmptyThisBuffer(omx_buffer); + if (omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s - OMX_EmptyThisBuffer() failed with result(0x%x)\n", CLASSNAME, __func__, omx_err); + return; + } + + clock_gettime(CLOCK_REALTIME, &starttime); + + while(true) + { + if(m_omx_render.IsEOS()) + break; + clock_gettime(CLOCK_REALTIME, &endtime); + if((endtime.tv_sec - starttime.tv_sec) > 2) + { + CLog::Log(LOGERROR, "%s::%s - wait for eos timed out\n", CLASSNAME, __func__); + break; + } + Sleep(50); + } + + return; +} + +void COMXAudio::SwitchChannels(int iAudioStream, bool bAudioOnAllSpeakers) +{ + return ; +} + +bool COMXAudio::SetClock(OMXClock *clock) +{ + if(m_av_clock != NULL) + return false; + + m_av_clock = clock; + m_external_clock = true; + return true; +} + +void COMXAudio::SetCodingType(AEDataFormat dataFormat) +{ + switch(dataFormat) + { + case AE_FMT_DTS: + CLog::Log(LOGDEBUG, "COMXAudio::SetCodingType OMX_AUDIO_CodingDTS\n"); + m_eEncoding = OMX_AUDIO_CodingDTS; + break; + case AE_FMT_AC3: + case AE_FMT_EAC3: + CLog::Log(LOGDEBUG, "COMXAudio::SetCodingType OMX_AUDIO_CodingDDP\n"); + m_eEncoding = OMX_AUDIO_CodingDDP; + break; + default: + CLog::Log(LOGDEBUG, "COMXAudio::SetCodingType OMX_AUDIO_CodingPCM\n"); + m_eEncoding = OMX_AUDIO_CodingPCM; + break; + } +} + +bool COMXAudio::CanHWDecode(CodecID codec) +{ + bool ret = false; + switch(codec) + { + case CODEC_ID_DTS: + CLog::Log(LOGDEBUG, "COMXAudio::CanHWDecode OMX_AUDIO_CodingDTS\n"); + ret = true; + break; + case CODEC_ID_AC3: + case CODEC_ID_EAC3: + CLog::Log(LOGDEBUG, "COMXAudio::CanHWDecode OMX_AUDIO_CodingDDP\n"); + ret = true; + break; + default: + CLog::Log(LOGDEBUG, "COMXAudio::CanHWDecode OMX_AUDIO_CodingPCM\n"); + ret = false; + break; + } + + return ret; +} + +void COMXAudio::PrintChannels(OMX_AUDIO_CHANNELTYPE eChannelMapping[]) +{ + for(int i = 0; i < OMX_AUDIO_MAXCHANNELS; i++) + { + switch(eChannelMapping[i]) + { + case OMX_AUDIO_ChannelLF: + CLog::Log(LOGDEBUG, "OMX_AUDIO_ChannelLF\n"); + break; + case OMX_AUDIO_ChannelRF: + CLog::Log(LOGDEBUG, "OMX_AUDIO_ChannelRF\n"); + break; + case OMX_AUDIO_ChannelCF: + CLog::Log(LOGDEBUG, "OMX_AUDIO_ChannelCF\n"); + break; + case OMX_AUDIO_ChannelLS: + CLog::Log(LOGDEBUG, "OMX_AUDIO_ChannelLS\n"); + break; + case OMX_AUDIO_ChannelRS: + CLog::Log(LOGDEBUG, "OMX_AUDIO_ChannelRS\n"); + break; + case OMX_AUDIO_ChannelLFE: + CLog::Log(LOGDEBUG, "OMX_AUDIO_ChannelLFE\n"); + break; + case OMX_AUDIO_ChannelCS: + CLog::Log(LOGDEBUG, "OMX_AUDIO_ChannelCS\n"); + break; + case OMX_AUDIO_ChannelLR: + CLog::Log(LOGDEBUG, "OMX_AUDIO_ChannelLR\n"); + break; + case OMX_AUDIO_ChannelRR: + CLog::Log(LOGDEBUG, "OMX_AUDIO_ChannelRR\n"); + break; + case OMX_AUDIO_ChannelNone: + case OMX_AUDIO_ChannelKhronosExtensions: + case OMX_AUDIO_ChannelVendorStartUnused: + case OMX_AUDIO_ChannelMax: + default: + break; + } + } +} + +void COMXAudio::PrintPCM(OMX_AUDIO_PARAM_PCMMODETYPE *pcm, std::string direction) +{ + CLog::Log(LOGDEBUG, "pcm->direction : %s\n", direction.c_str()); + CLog::Log(LOGDEBUG, "pcm->nPortIndex : %d\n", (int)pcm->nPortIndex); + CLog::Log(LOGDEBUG, "pcm->eNumData : %d\n", pcm->eNumData); + CLog::Log(LOGDEBUG, "pcm->eEndian : %d\n", pcm->eEndian); + CLog::Log(LOGDEBUG, "pcm->bInterleaved : %d\n", (int)pcm->bInterleaved); + CLog::Log(LOGDEBUG, "pcm->nBitPerSample : %d\n", (int)pcm->nBitPerSample); + CLog::Log(LOGDEBUG, "pcm->ePCMMode : %d\n", pcm->ePCMMode); + CLog::Log(LOGDEBUG, "pcm->nChannels : %d\n", (int)pcm->nChannels); + CLog::Log(LOGDEBUG, "pcm->nSamplingRate : %d\n", (int)pcm->nSamplingRate); + + PrintChannels(pcm->eChannelMapping); +} + +void COMXAudio::PrintDDP(OMX_AUDIO_PARAM_DDPTYPE *ddparm) +{ + CLog::Log(LOGDEBUG, "ddparm->nPortIndex : %d\n", (int)ddparm->nPortIndex); + CLog::Log(LOGDEBUG, "ddparm->nChannels : %d\n", (int)ddparm->nChannels); + CLog::Log(LOGDEBUG, "ddparm->nBitRate : %d\n", (int)ddparm->nBitRate); + CLog::Log(LOGDEBUG, "ddparm->nSampleRate : %d\n", (int)ddparm->nSampleRate); + CLog::Log(LOGDEBUG, "ddparm->eBitStreamId : %d\n", (int)ddparm->eBitStreamId); + CLog::Log(LOGDEBUG, "ddparm->eBitStreamMode : %d\n", (int)ddparm->eBitStreamMode); + CLog::Log(LOGDEBUG, "ddparm->eDolbySurroundMode : %d\n", (int)ddparm->eDolbySurroundMode); + + PrintChannels(ddparm->eChannelMapping); +} + +void COMXAudio::PrintDTS(OMX_AUDIO_PARAM_DTSTYPE *dtsparam) +{ + CLog::Log(LOGDEBUG, "dtsparam->nPortIndex : %d\n", (int)dtsparam->nPortIndex); + CLog::Log(LOGDEBUG, "dtsparam->nChannels : %d\n", (int)dtsparam->nChannels); + CLog::Log(LOGDEBUG, "dtsparam->nBitRate : %d\n", (int)dtsparam->nBitRate); + CLog::Log(LOGDEBUG, "dtsparam->nSampleRate : %d\n", (int)dtsparam->nSampleRate); + CLog::Log(LOGDEBUG, "dtsparam->nFormat : 0x%08x\n", (int)dtsparam->nFormat); + CLog::Log(LOGDEBUG, "dtsparam->nDtsType : %d\n", (int)dtsparam->nDtsType); + CLog::Log(LOGDEBUG, "dtsparam->nDtsFrameSizeBytes : %d\n", (int)dtsparam->nDtsFrameSizeBytes); + + PrintChannels(dtsparam->eChannelMapping); +} + +/* ========================== SYNC FUNCTIONS ========================== */ +unsigned int COMXAudio::SyncDTS(BYTE* pData, unsigned int iSize) +{ + OMX_INIT_STRUCTURE(m_dtsParam); + + unsigned int skip; + unsigned int srCode; + unsigned int dtsBlocks; + bool littleEndian; + + for(skip = 0; iSize - skip > 8; ++skip, ++pData) + { + if (pData[0] == 0x7F && pData[1] == 0xFE && pData[2] == 0x80 && pData[3] == 0x01) + { + /* 16bit le */ + littleEndian = true; + dtsBlocks = ((pData[4] >> 2) & 0x7f) + 1; + m_dtsParam.nFormat = 0x1 | 0x2; + } + else if (pData[0] == 0x1F && pData[1] == 0xFF && pData[2] == 0xE8 && pData[3] == 0x00 && pData[4] == 0x07 && (pData[5] & 0xF0) == 0xF0) + { + /* 14bit le */ + littleEndian = true; + dtsBlocks = (((pData[4] & 0x7) << 4) | (pData[7] & 0x3C) >> 2) + 1; + m_dtsParam.nFormat = 0x1 | 0x0; + } + else if (pData[1] == 0x7F && pData[0] == 0xFE && pData[3] == 0x80 && pData[2] == 0x01) + { + /* 16bit be */ + littleEndian = false; + dtsBlocks = ((pData[5] >> 2) & 0x7f) + 1; + m_dtsParam.nFormat = 0x0 | 0x2; + } + else if (pData[1] == 0x1F && pData[0] == 0xFF && pData[3] == 0xE8 && pData[2] == 0x00 && pData[5] == 0x07 && (pData[4] & 0xF0) == 0xF0) + { + /* 14bit be */ + littleEndian = false; + dtsBlocks = (((pData[5] & 0x7) << 4) | (pData[6] & 0x3C) >> 2) + 1; + m_dtsParam.nFormat = 0x0 | 0x0; + } + else + { + continue; + } + + if (littleEndian) + { + /* if it is not a termination frame, check the next 6 bits are set */ + if ((pData[4] & 0x80) == 0x80 && (pData[4] & 0x7C) != 0x7C) + continue; + + /* get the frame size */ + m_dtsParam.nDtsFrameSizeBytes = ((((pData[5] & 0x3) << 8 | pData[6]) << 4) | ((pData[7] & 0xF0) >> 4)) + 1; + srCode = (pData[8] & 0x3C) >> 2; + } + else + { + /* if it is not a termination frame, check the next 6 bits are set */ + if ((pData[5] & 0x80) == 0x80 && (pData[5] & 0x7C) != 0x7C) + continue; + + /* get the frame size */ + m_dtsParam.nDtsFrameSizeBytes = ((((pData[4] & 0x3) << 8 | pData[7]) << 4) | ((pData[6] & 0xF0) >> 4)) + 1; + srCode = (pData[9] & 0x3C) >> 2; + } + + /* make sure the framesize is sane */ + if (m_dtsParam.nDtsFrameSizeBytes < 96 || m_dtsParam.nDtsFrameSizeBytes > 16384) + continue; + + m_dtsParam.nSampleRate = DTSFSCod[srCode]; + + switch(dtsBlocks << 5) + { + case 512 : + m_dtsParam.nDtsType = 1; + break; + case 1024: + m_dtsParam.nDtsType = 2; + break; + case 2048: + m_dtsParam.nDtsType = 3; + break; + default: + m_dtsParam.nDtsType = 0; + break; + } + + //m_dtsParam.nFormat = 1; + m_dtsParam.nDtsType = 1; + + m_LostSync = false; + + return skip; + } + + m_LostSync = true; + return iSize; +} + +unsigned int COMXAudio::SyncAC3(BYTE* pData, unsigned int iSize) +{ + unsigned int skip = 0; + //unsigned int fSize = 0; + + for(skip = 0; iSize - skip > 6; ++skip, ++pData) + { + /* search for an ac3 sync word */ + if(pData[0] != 0x0b || pData[1] != 0x77) + continue; + + uint8_t fscod = pData[4] >> 6; + uint8_t frmsizecod = pData[4] & 0x3F; + uint8_t bsid = pData[5] >> 3; + + /* sanity checks on the header */ + if ( + fscod == 3 || + frmsizecod > 37 || + bsid > 0x11 + ) continue; + + /* get the details we need to check crc1 and framesize */ + uint16_t bitrate = AC3Bitrates[frmsizecod >> 1]; + unsigned int framesize = 0; + switch(fscod) + { + case 0: framesize = bitrate * 2; break; + case 1: framesize = (320 * bitrate / 147 + (frmsizecod & 1 ? 1 : 0)); break; + case 2: framesize = bitrate * 4; break; + } + + //fSize = framesize * 2; + m_SampleRate = AC3FSCod[fscod]; + + /* dont do extensive testing if we have not lost sync */ + if (!m_LostSync && skip == 0) + return 0; + + unsigned int crc_size; + /* if we have enough data, validate the entire packet, else try to validate crc2 (5/8 of the packet) */ + if (framesize <= iSize - skip) + crc_size = framesize - 1; + else crc_size = (framesize >> 1) + (framesize >> 3) - 1; + + if (crc_size <= iSize - skip) + if(m_dllAvUtil.av_crc(m_dllAvUtil.av_crc_get_table(AV_CRC_16_ANSI), 0, &pData[2], crc_size * 2)) + continue; + + /* if we get here, we can sync */ + m_LostSync = false; + return skip; + } + + /* if we get here, the entire packet is invalid and we have lost sync */ + m_LostSync = true; + return iSize; +} + +void COMXAudio::CheckOutputBufferSize(void **buffer, int *oldSize, int newSize) +{ + if (newSize > *oldSize) + { + if (*buffer) + _aligned_free(*buffer); + *buffer = _aligned_malloc(newSize, 16); + *oldSize = newSize; + } + memset(*buffer, 0x0, *oldSize); +} + diff --git a/xbmc/cores/omxplayer/OMXAudio.h b/xbmc/cores/omxplayer/OMXAudio.h new file mode 100644 index 0000000000..55caef178e --- /dev/null +++ b/xbmc/cores/omxplayer/OMXAudio.h @@ -0,0 +1,155 @@ +/* +* XBMC Media Center +* Copyright (c) 2002 d7o3g4q and RUNTiME +* Portions Copyright (c) by the authors of ffmpeg and xvid +* +* 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 of the License, 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 this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +////////////////////////////////////////////////////////////////////// + +#ifndef __OPENMAXAUDIORENDER_H__ +#define __OPENMAXAUDIORENDER_H__ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include "cores/AudioEngine/AEAudioFormat.h" +#include "cores/AudioEngine/Utils/AEUtil.h" +#include "cores/AudioEngine/Utils/AERemap.h" +#include "cores/IAudioCallback.h" +#include "linux/PlatformDefs.h" +#include "DllAvCodec.h" +#include "DllAvUtil.h" +#include "OMXCore.h" +#include "OMXClock.h" +#include "DVDStreamInfo.h" +#include "BitstreamConverter.h" + +#define AUDIO_BUFFER_SECONDS 2 +#define VIS_PACKET_SIZE 512 + +#define OMX_IS_RAW(x) \ +( \ + (x) == AE_FMT_AC3 || \ + (x) == AE_FMT_DTS \ +) + +class CAERemap; + +class COMXAudio +{ +public: + void UnRegisterAudioCallback(); + void RegisterAudioCallback(IAudioCallback* pCallback); + unsigned int GetChunkLen(); + float GetDelay(); + float GetCacheTime(); + float GetCacheTotal(); + COMXAudio(); + bool Initialize(AEAudioFormat format, std::string& device, OMXClock *clock, CDVDStreamInfo &hints, bool bUsePassthrough, bool bUseHWDecode); + bool Initialize(AEAudioFormat format, std::string& device); + ~COMXAudio(); + + unsigned int AddPackets(const void* data, unsigned int len); + unsigned int AddPackets(const void* data, unsigned int len, double dts, double pts); + unsigned int GetSpace(); + bool Deinitialize(); + bool Pause(); + bool Stop(); + bool Resume(); + + long GetCurrentVolume() const; + void Mute(bool bMute); + bool SetCurrentVolume(float fVolume); + void SetDynamicRangeCompression(long drc) { m_drc = drc; } + int SetPlaySpeed(int iSpeed); + void WaitCompletion(); + void SwitchChannels(int iAudioStream, bool bAudioOnAllSpeakers); + + void Flush(); + void DoAudioWork(); + + void Process(); + + bool SetClock(OMXClock *clock); + void SetCodingType(AEDataFormat dataFormat); + static bool CanHWDecode(CodecID codec); + + void PrintChannels(OMX_AUDIO_CHANNELTYPE eChannelMapping[]); + void PrintPCM(OMX_AUDIO_PARAM_PCMMODETYPE *pcm, std::string direction); + void PrintDDP(OMX_AUDIO_PARAM_DDPTYPE *ddparm); + void PrintDTS(OMX_AUDIO_PARAM_DTSTYPE *dtsparam); + unsigned int SyncDTS(BYTE* pData, unsigned int iSize); + unsigned int SyncAC3(BYTE* pData, unsigned int iSize); + +private: + IAudioCallback* m_pCallback; + bool m_Initialized; + bool m_Pause; + bool m_CanPause; + float m_CurrentVolume; + long m_drc; + bool m_Passthrough; + bool m_HWDecode; + unsigned int m_BytesPerSec; + unsigned int m_BufferLen; + unsigned int m_ChunkLen; + unsigned int m_OutputChannels; + unsigned int m_BitsPerSample; + COMXCoreComponent *m_omx_clock; + OMXClock *m_av_clock; + bool m_external_clock; + bool m_first_frame; + bool m_LostSync; + int m_SampleRate; + OMX_AUDIO_CODINGTYPE m_eEncoding; + uint8_t *m_extradata; + int m_extrasize; + // stuff for visualisation + unsigned int m_vizBufferSamples; + double m_last_pts; + int m_vizBufferSize; + uint8_t *m_vizBuffer; + int m_vizRemapBufferSize; + uint8_t *m_vizRemapBuffer; + CAERemap m_vizRemap; + + OMX_AUDIO_PARAM_PCMMODETYPE m_pcm_output; + OMX_AUDIO_PARAM_PCMMODETYPE m_pcm_input; + OMX_AUDIO_PARAM_DTSTYPE m_dtsParam; + WAVEFORMATEXTENSIBLE m_wave_header; + AEAudioFormat m_format; +protected: + COMXCoreComponent m_omx_render; + COMXCoreComponent m_omx_mixer; + COMXCoreComponent m_omx_decoder; + COMXCoreTunel m_omx_tunnel_clock; + COMXCoreTunel m_omx_tunnel_mixer; + COMXCoreTunel m_omx_tunnel_decoder; + DllAvUtil m_dllAvUtil; + + OMX_AUDIO_CHANNELTYPE m_input_channels[OMX_AUDIO_MAXCHANNELS]; + OMX_AUDIO_CHANNELTYPE m_output_channels[OMX_AUDIO_MAXCHANNELS]; + + CAEChannelInfo m_channelLayout; + + CAEChannelInfo GetChannelLayout(AEAudioFormat format); + + void CheckOutputBufferSize(void **buffer, int *oldSize, int newSize); +}; +#endif + diff --git a/xbmc/cores/omxplayer/OMXAudioCodec.h b/xbmc/cores/omxplayer/OMXAudioCodec.h new file mode 100644 index 0000000000..76d7049829 --- /dev/null +++ b/xbmc/cores/omxplayer/OMXAudioCodec.h @@ -0,0 +1,119 @@ +#pragma once + +/* + * Copyright (C) 2005-2008 Team XBMC + * http://www.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, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "system.h" + +#if (defined HAVE_CONFIG_H) && (!defined WIN32) + #include "config.h" +#endif +#include <vector> +#include "DllAvCodec.h" + +struct AVStream; + +class COMXStreamInfo; + +class COMXAudioCodec +{ +public: + + COMXAudioCodec() {} + virtual ~COMXAudioCodec() {} + + /* + * Open the decoder, returns true on success + */ + virtual bool Open(COMXStreamInfo &hints) = 0; + + /* + * Dispose, Free all resources + */ + virtual void Dispose() = 0; + + /* + * returns bytes used or -1 on error + * + */ + virtual int Decode(BYTE* pData, int iSize) = 0; + + /* + * returns nr of bytes used or -1 on error + * the data is valid until the next Decode call + */ + virtual int GetData(BYTE** dst) = 0; + + /* + * resets the decoder + */ + virtual void Reset() = 0; + + /* + * returns the nr of channels for the decoded audio stream + */ + virtual int GetChannels() = 0; + + /* + * returns the channel mapping + */ + virtual enum PCMChannels* GetChannelMap() = 0; + + /* + * returns the samplerate for the decoded audio stream + */ + virtual int GetSampleRate() = 0; + + /* + * returns the bitspersample for the decoded audio stream (eg 16 bits) + */ + virtual int GetBitsPerSample() = 0; + + /* + * returns the framesize for bitstreams + */ + virtual int GetFrameSize() = 0; + + /* + * returns the syncword for bitstreams + */ + virtual uint32_t GetSyncWord() = 0; + + /* + * should return the average input bit rate + */ + virtual int GetBitRate() { return 0; } + + /* + * returns if the codec requests to use passtrough + */ + virtual bool NeedPassthrough() { return false; } + + /* + * should return codecs name + */ + virtual const char* GetName() = 0; + + /* + * should return amount of data decoded has buffered in preparation for next audio frame + */ + virtual int GetBufferSize() { return 0; } +}; diff --git a/xbmc/cores/omxplayer/OMXAudioCodecOMX.cpp b/xbmc/cores/omxplayer/OMXAudioCodecOMX.cpp new file mode 100644 index 0000000000..b8b3372e48 --- /dev/null +++ b/xbmc/cores/omxplayer/OMXAudioCodecOMX.cpp @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2005-2008 Team XBMC + * http://www.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, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "OMXAudioCodecOMX.h" +#ifdef _LINUX +#include "XMemUtils.h" +#endif +#include "utils/log.h" + +#include "cores/AudioEngine/Utils/AEUtil.h" + +#define MAX_AUDIO_FRAME_SIZE (AVCODEC_MAX_AUDIO_FRAME_SIZE*2) + +template <class AudioDataType> +static inline void _Upmix(AudioDataType *input, + unsigned int channelsInput, AudioDataType *output, + unsigned int channelsOutput, unsigned int frames) +{ + unsigned int unused = channelsOutput - channelsInput; + AudioDataType *_input = input; + AudioDataType *_output = output; + + for (unsigned int i = 0; i < frames; i++) + { + // get input channels + for(unsigned int j = 0; j < channelsInput; j++) + *_output++ = *_input++; + // set unused channels + for(unsigned int j = 0; j < unused; j++) + *_output++ = 0; + } +} + +void COMXAudioCodecOMX::Upmix(void *input, + unsigned int channelsInput, void *output, + unsigned int channelsOutput, unsigned int frames, AEDataFormat dataFormat) +{ + // input channels must be less than output channels + if (channelsInput >= channelsOutput) + return; + + switch (CAEUtil::DataFormatToBits(dataFormat)) + { + case 8: _Upmix ( (unsigned char *) input, channelsInput, (unsigned char *) output, channelsOutput, frames ); break; + case 16: _Upmix ( (short *) input, channelsInput, (short *) output, channelsOutput, frames ); break; + case 32: _Upmix ( (float *) input, channelsInput, (float *) output, channelsOutput, frames ); break; + default: _Upmix ( (int *) input, channelsInput, (int *) output, channelsOutput, frames ); break; + } +} + +COMXAudioCodecOMX::COMXAudioCodecOMX() +{ + m_iBufferSize2 = 0; + m_pBuffer2 = (BYTE*)_aligned_malloc(MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE, 16); + memset(m_pBuffer2, 0, MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE); + + m_iBufferUpmixSize = 0; + m_pBufferUpmix = (BYTE*)_aligned_malloc(MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE, 16); + memset(m_pBufferUpmix, 0, MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE); + + m_iBuffered = 0; + m_pCodecContext = NULL; + m_pConvert = NULL; + m_bOpenedCodec = false; + + m_channels = 0; + m_layout = 0; + m_pFrame1 = NULL; +} + +COMXAudioCodecOMX::~COMXAudioCodecOMX() +{ + _aligned_free(m_pBuffer2); + _aligned_free(m_pBufferUpmix); + Dispose(); +} + +bool COMXAudioCodecOMX::Open(CDVDStreamInfo &hints) +{ + AVCodec* pCodec; + m_bOpenedCodec = false; + + if (!m_dllAvUtil.Load() || !m_dllAvCodec.Load()) + return false; + + m_dllAvCodec.avcodec_register_all(); + + pCodec = m_dllAvCodec.avcodec_find_decoder(hints.codec); + if (!pCodec) + { + CLog::Log(LOGDEBUG,"COMXAudioCodecOMX::Open() Unable to find codec %d", hints.codec); + return false; + } + + m_pCodecContext = m_dllAvCodec.avcodec_alloc_context3(pCodec); + m_pCodecContext->debug_mv = 0; + m_pCodecContext->debug = 0; + m_pCodecContext->workaround_bugs = 1; + + if (pCodec->capabilities & CODEC_CAP_TRUNCATED) + m_pCodecContext->flags |= CODEC_FLAG_TRUNCATED; + + m_channels = 0; + m_pCodecContext->channels = hints.channels; + m_pCodecContext->sample_rate = hints.samplerate; + m_pCodecContext->block_align = hints.blockalign; + m_pCodecContext->bit_rate = hints.bitrate; + m_pCodecContext->bits_per_coded_sample = hints.bitspersample; + + if(m_pCodecContext->bits_per_coded_sample == 0) + m_pCodecContext->bits_per_coded_sample = 16; + + if( hints.extradata && hints.extrasize > 0 ) + { + m_pCodecContext->extradata_size = hints.extrasize; + m_pCodecContext->extradata = (uint8_t*)m_dllAvUtil.av_mallocz(hints.extrasize + FF_INPUT_BUFFER_PADDING_SIZE); + memcpy(m_pCodecContext->extradata, hints.extradata, hints.extrasize); + } + + if (m_dllAvCodec.avcodec_open2(m_pCodecContext, pCodec, NULL) < 0) + { + CLog::Log(LOGDEBUG,"COMXAudioCodecOMX::Open() Unable to open codec"); + Dispose(); + return false; + } + + m_pFrame1 = m_dllAvCodec.avcodec_alloc_frame(); + m_bOpenedCodec = true; + m_iSampleFormat = AV_SAMPLE_FMT_NONE; + return true; +} + +void COMXAudioCodecOMX::Dispose() +{ + if (m_pFrame1) m_dllAvUtil.av_free(m_pFrame1); + m_pFrame1 = NULL; + + if (m_pConvert) + { + m_dllAvCodec.av_audio_convert_free(m_pConvert); + m_pConvert = NULL; + } + + if (m_pCodecContext) + { + if (m_bOpenedCodec) m_dllAvCodec.avcodec_close(m_pCodecContext); + m_bOpenedCodec = false; + m_dllAvUtil.av_free(m_pCodecContext); + m_pCodecContext = NULL; + } + + m_dllAvCodec.Unload(); + m_dllAvUtil.Unload(); + + m_iBufferSize1 = 0; + m_iBufferSize2 = 0; + m_iBuffered = 0; +} + +int COMXAudioCodecOMX::Decode(BYTE* pData, int iSize) +{ + int iBytesUsed, got_frame; + if (!m_pCodecContext) return -1; + + m_iBufferSize1 = AVCODEC_MAX_AUDIO_FRAME_SIZE; + m_iBufferSize2 = 0; + + AVPacket avpkt; + m_dllAvCodec.av_init_packet(&avpkt); + avpkt.data = pData; + avpkt.size = iSize; + iBytesUsed = m_dllAvCodec.avcodec_decode_audio4( m_pCodecContext + , m_pFrame1 + , &got_frame + , &avpkt); + if (iBytesUsed < 0 || !got_frame) + { + m_iBufferSize1 = 0; + m_iBufferSize2 = 0; + return iBytesUsed; + } + m_iBufferSize1 = m_dllAvUtil.av_samples_get_buffer_size(NULL, m_pCodecContext->channels, m_pFrame1->nb_samples, m_pCodecContext->sample_fmt, 1); + + /* some codecs will attempt to consume more data than what we gave */ + if (iBytesUsed > iSize) + { + CLog::Log(LOGWARNING, "COMXAudioCodecOMX::Decode - decoder attempted to consume more data than given"); + iBytesUsed = iSize; + } + + if(m_iBufferSize1 == 0 && iBytesUsed >= 0) + m_iBuffered += iBytesUsed; + else + m_iBuffered = 0; + + if(m_pCodecContext->sample_fmt != AV_SAMPLE_FMT_S16 && m_iBufferSize1 > 0) + { + if(m_pConvert && m_pCodecContext->sample_fmt != m_iSampleFormat) + { + m_dllAvCodec.av_audio_convert_free(m_pConvert); + m_pConvert = NULL; + } + + if(!m_pConvert) + { + m_iSampleFormat = m_pCodecContext->sample_fmt; + m_pConvert = m_dllAvCodec.av_audio_convert_alloc(AV_SAMPLE_FMT_S16, 1, m_pCodecContext->sample_fmt, 1, NULL, 0); + } + + if(!m_pConvert) + { + CLog::Log(LOGERROR, "COMXAudioCodecOMX::Decode - Unable to convert %d to AV_SAMPLE_FMT_S16", m_pCodecContext->sample_fmt); + m_iBufferSize1 = 0; + m_iBufferSize2 = 0; + return iBytesUsed; + } + + const void *ibuf[6] = { m_pFrame1->data[0] }; + void *obuf[6] = { m_pBuffer2 }; + int istr[6] = { m_dllAvUtil.av_get_bytes_per_sample(m_pCodecContext->sample_fmt) }; + int ostr[6] = { (int) (CAEUtil::DataFormatToBits(AE_FMT_S16LE) >> 3) }; + int len = m_iBufferSize1 / istr[0]; + if(m_dllAvCodec.av_audio_convert(m_pConvert, obuf, ostr, ibuf, istr, len) < 0) + { + CLog::Log(LOGERROR, "COMXAudioCodecOMX::Decode - Unable to convert %d to AV_SAMPLE_FMT_S16", (int)m_pCodecContext->sample_fmt); + m_iBufferSize1 = 0; + m_iBufferSize2 = 0; + return iBytesUsed; + } + + m_iBufferSize1 = 0; + m_iBufferSize2 = len * ostr[0]; + } + + return iBytesUsed; +} + +int COMXAudioCodecOMX::GetData(BYTE** dst) +{ + unsigned int size = 0; + BYTE *src = NULL; + + if(m_iBufferSize1) + { + *dst = m_pFrame1->data[0]; + src = m_pFrame1->data[0]; + size = m_iBufferSize1; + } + if(m_iBufferSize2) + { + *dst = m_pBuffer2; + src = m_pBuffer2; + size = m_iBufferSize2; + } + + if(m_pCodecContext->channels > 4 && size) + { + unsigned int m_frameSize = (CAEUtil::DataFormatToBits(AE_FMT_S16LE) >> 3) * m_pCodecContext->channels; + unsigned int frames = size / m_frameSize; + + memset(m_pBufferUpmix, 0, MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE); + + Upmix(src, m_pCodecContext->channels, m_pBufferUpmix, 8, frames, AE_FMT_S16LE); + + m_iBufferUpmixSize = frames * 8 * (CAEUtil::DataFormatToBits(AE_FMT_S16LE) >> 3); + + *dst = m_pBufferUpmix; + size = m_iBufferUpmixSize; + } + + return size; +} + +void COMXAudioCodecOMX::Reset() +{ + if (m_pCodecContext) m_dllAvCodec.avcodec_flush_buffers(m_pCodecContext); + m_iBufferSize1 = 0; + m_iBufferSize2 = 0; + m_iBuffered = 0; +} + +int COMXAudioCodecOMX::GetChannels() +{ + return (m_pCodecContext->channels > 4) ? 8 : m_pCodecContext->channels; +} + +int COMXAudioCodecOMX::GetSampleRate() +{ + if (m_pCodecContext) return m_pCodecContext->sample_rate; + return 0; +} + +int COMXAudioCodecOMX::GetBitsPerSample() +{ + return 16; +} + +int COMXAudioCodecOMX::GetBitRate() +{ + if (m_pCodecContext) return m_pCodecContext->bit_rate; + return 0; +} + +static unsigned count_bits(int64_t value) +{ + unsigned bits = 0; + for(;value;++bits) + value &= value - 1; + return bits; +} + +void COMXAudioCodecOMX::BuildChannelMap() +{ + if (m_channels == m_pCodecContext->channels && m_layout == m_pCodecContext->channel_layout) + return; //nothing to do here + + m_channels = m_pCodecContext->channels; + m_layout = m_pCodecContext->channel_layout; + + int64_t layout; + + int bits = count_bits(m_pCodecContext->channel_layout); + if (bits == m_pCodecContext->channels) + layout = m_pCodecContext->channel_layout; + else + { + CLog::Log(LOGINFO, "COMXAudioCodecOMX::GetChannelMap - FFmpeg reported %d channels, but the layout contains %d ignoring", m_pCodecContext->channels, bits); + layout = m_dllAvUtil.av_get_default_channel_layout(m_pCodecContext->channels); + } + + m_channelLayout.Reset(); + + if (layout & AV_CH_FRONT_LEFT ) m_channelLayout += AE_CH_FL ; + if (layout & AV_CH_FRONT_RIGHT ) m_channelLayout += AE_CH_FR ; + if (layout & AV_CH_FRONT_CENTER ) m_channelLayout += AE_CH_FC ; + if (layout & AV_CH_LOW_FREQUENCY ) m_channelLayout += AE_CH_LFE ; + if (layout & AV_CH_BACK_LEFT ) m_channelLayout += AE_CH_BL ; + if (layout & AV_CH_BACK_RIGHT ) m_channelLayout += AE_CH_BR ; + if (layout & AV_CH_FRONT_LEFT_OF_CENTER ) m_channelLayout += AE_CH_FLOC; + if (layout & AV_CH_FRONT_RIGHT_OF_CENTER) m_channelLayout += AE_CH_FROC; + if (layout & AV_CH_BACK_CENTER ) m_channelLayout += AE_CH_BC ; + if (layout & AV_CH_SIDE_LEFT ) m_channelLayout += AE_CH_SL ; + if (layout & AV_CH_SIDE_RIGHT ) m_channelLayout += AE_CH_SR ; + if (layout & AV_CH_TOP_CENTER ) m_channelLayout += AE_CH_TC ; + if (layout & AV_CH_TOP_FRONT_LEFT ) m_channelLayout += AE_CH_TFL ; + if (layout & AV_CH_TOP_FRONT_CENTER ) m_channelLayout += AE_CH_TFC ; + if (layout & AV_CH_TOP_FRONT_RIGHT ) m_channelLayout += AE_CH_TFR ; + if (layout & AV_CH_TOP_BACK_LEFT ) m_channelLayout += AE_CH_BL ; + if (layout & AV_CH_TOP_BACK_CENTER ) m_channelLayout += AE_CH_BC ; + if (layout & AV_CH_TOP_BACK_RIGHT ) m_channelLayout += AE_CH_BR ; + + //terminate the channel map + if(m_pCodecContext->channels > 4) + { + for(int i = m_pCodecContext->channels; i < 8; i++) + m_channelLayout += AE_CH_RAW; + } +} + +CAEChannelInfo COMXAudioCodecOMX::GetChannelMap() +{ + BuildChannelMap(); + return m_channelLayout; +} diff --git a/xbmc/cores/omxplayer/OMXAudioCodecOMX.h b/xbmc/cores/omxplayer/OMXAudioCodecOMX.h new file mode 100644 index 0000000000..e740e5b47c --- /dev/null +++ b/xbmc/cores/omxplayer/OMXAudioCodecOMX.h @@ -0,0 +1,77 @@ +#pragma once + +/* + * Copyright (C) 2005-2008 Team XBMC + * http://www.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, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "cores/AudioEngine/AEAudioFormat.h" +#include "DllAvCodec.h" +#include "DllAvFormat.h" +#include "DllAvUtil.h" + +#include "DVDStreamInfo.h" +#include "linux/PlatformDefs.h" + +class COMXAudioCodecOMX +{ +public: + void Upmix(void *input, unsigned int channelsInput, void *output, + unsigned int channelsOutput, unsigned int frames, AEDataFormat dataFormat); + COMXAudioCodecOMX(); + virtual ~COMXAudioCodecOMX(); + bool Open(CDVDStreamInfo &hints); + void Dispose(); + int Decode(BYTE* pData, int iSize); + int GetData(BYTE** dst); + void Reset(); + int GetChannels(); + virtual CAEChannelInfo GetChannelMap(); + int GetSampleRate(); + int GetBitsPerSample(); + const char* GetName() { return "FFmpeg"; } + int GetBufferSize() { return m_iBuffered; } + int GetBitRate(); + +protected: + AVCodecContext* m_pCodecContext; + AVAudioConvert* m_pConvert;; + enum AVSampleFormat m_iSampleFormat; + CAEChannelInfo m_channelLayout; + + AVFrame* m_pFrame1; + int m_iBufferSize1; + + BYTE *m_pBuffer2; + int m_iBufferSize2; + + BYTE *m_pBufferUpmix; + int m_iBufferUpmixSize; + + bool m_bOpenedCodec; + int m_iBuffered; + + int m_channels; + uint64_t m_layout; + + DllAvCodec m_dllAvCodec; + DllAvUtil m_dllAvUtil; + + void BuildChannelMap(); +}; diff --git a/xbmc/cores/omxplayer/OMXImage.cpp b/xbmc/cores/omxplayer/OMXImage.cpp new file mode 100644 index 0000000000..7eb2e99bd3 --- /dev/null +++ b/xbmc/cores/omxplayer/OMXImage.cpp @@ -0,0 +1,1114 @@ +/* + * Copyright (C) 2010 Team XBMC + * http://www.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, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#if (defined HAVE_CONFIG_H) && (!defined WIN32) + #include "config.h" +#elif defined(_WIN32) +#include "system.h" +#endif + +#include "OMXImage.h" + +#include "utils/log.h" +#include "linux/XMemUtils.h" + +#include "BitstreamConverter.h" + +#include <sys/time.h> +#include <inttypes.h> +#ifndef STANDALONE +#include "settings/GUISettings.h" +#include "settings/Settings.h" +#include "settings/AdvancedSettings.h" +#endif + +#ifdef CLASSNAME +#undef CLASSNAME +#endif +#define CLASSNAME "COMXImage" + +#define CONTENTURI_MAXLEN 256 + +#define EXIF_TAG_ORIENTATION 0x0112 + +static CCriticalSection g_OMXSection; + +COMXImage::COMXImage() +{ + m_is_open = false; + m_image_size = 0; + m_image_buffer = NULL; + m_progressive = false; + m_alpha = false; + m_orientation = 0; + m_width = 0; + m_height = 0; + + m_is_open = false; + m_decoded_buffer = NULL; + m_encoded_buffer = NULL; + m_decoder_open = false; + m_encoder_open = false; + + OMX_INIT_STRUCTURE(m_decoded_format); + OMX_INIT_STRUCTURE(m_encoded_format); + memset(&m_omx_image, 0x0, sizeof(OMX_IMAGE_PORTDEFINITIONTYPE)); +} + +COMXImage::~COMXImage() +{ + Close(); +} + +void COMXImage::Close() +{ + OMX_INIT_STRUCTURE(m_decoded_format); + OMX_INIT_STRUCTURE(m_encoded_format); + memset(&m_omx_image, 0x0, sizeof(OMX_IMAGE_PORTDEFINITIONTYPE)); + + if(m_image_buffer) + free(m_image_buffer); + + m_image_buffer = NULL; + m_image_size = 0; + m_width = 0; + m_height = 0; + m_is_open = false; + m_progressive = false; + m_orientation = 0; + m_decoded_buffer = NULL; + m_encoded_buffer = NULL; + + if(m_decoder_open) + { + m_omx_decoder.FlushInput(); + m_omx_decoder.FreeInputBuffers(true); + m_omx_resize.FlushOutput(); + m_omx_resize.FreeOutputBuffers(true); + + m_omx_tunnel_decode.Flush(); + m_omx_tunnel_decode.Flush(); + m_omx_tunnel_decode.Deestablish(); + m_omx_decoder.Deinitialize(); + m_omx_resize.Deinitialize(); + m_decoder_open = false; + } + + if(m_encoder_open) + { + m_omx_encoder.Deinitialize(); + m_encoder_open = false; + } + + m_pFile.Close(); +} + +typedef enum { /* JPEG marker codes */ + M_SOF0 = 0xc0, + M_SOF1 = 0xc1, + M_SOF2 = 0xc2, + M_SOF3 = 0xc3, + M_SOF5 = 0xc5, + M_SOF6 = 0xc6, + M_SOF7 = 0xc7, + M_JPG = 0xc8, + M_SOF9 = 0xc9, + M_SOF10 = 0xca, + M_SOF11 = 0xcb, + M_SOF13 = 0xcd, + M_SOF14 = 0xce, + M_SOF15 = 0xcf, + + M_DHT = 0xc4, + + M_RST0 = 0xd0, + M_RST1 = 0xd1, + M_RST2 = 0xd2, + M_RST3 = 0xd3, + M_RST4 = 0xd4, + M_RST5 = 0xd5, + M_RST6 = 0xd6, + M_RST7 = 0xd7, + + M_SOI = 0xd8, + M_EOI = 0xd9, + M_SOS = 0xda, + M_DQT = 0xdb, + M_DNL = 0xdc, + M_DRI = 0xdd, + M_DHP = 0xde, + M_EXP = 0xdf, + + M_APP0 = 0xe0, + M_APP1 = 0xe1, + M_APP2 = 0xe2, + M_APP3 = 0xe3, + M_APP4 = 0xe4, + M_APP5 = 0xe5, + M_APP6 = 0xe6, + M_APP7 = 0xe7, + M_APP8 = 0xe8, + M_APP9 = 0xe9, + M_APP10 = 0xea, + M_APP11 = 0xeb, + M_APP12 = 0xec, + M_APP13 = 0xed, + M_APP14 = 0xee, + M_APP15 = 0xef, + + M_TEM = 0x01, +} JPEG_MARKER; + +OMX_IMAGE_CODINGTYPE COMXImage::GetCodingType() +{ + memset(&m_omx_image, 0x0, sizeof(OMX_IMAGE_PORTDEFINITIONTYPE)); + m_width = 0; + m_height = 0; + m_progressive = false; + m_orientation = 0; + + m_omx_image.eCompressionFormat = OMX_IMAGE_CodingMax; + + if(!m_image_size) + return OMX_IMAGE_CodingMax; + + bits_reader_t br; + CBitstreamConverter::bits_reader_set( &br, m_image_buffer, m_image_size ); + + /* JPEG Header */ + if(CBitstreamConverter::read_bits(&br, 16) == 0xFFD8) + { + m_omx_image.eCompressionFormat = OMX_IMAGE_CodingJPEG; + + CBitstreamConverter::read_bits(&br, 8); + unsigned char marker = CBitstreamConverter::read_bits(&br, 8); + unsigned short block_size = 0; + bool nMarker = false; + + while(!br.oflow) { + + switch(marker) + { + case M_TEM: + case M_DRI: + CBitstreamConverter::skip_bits(&br, 16); + continue; + case M_SOI: + case M_EOI: + continue; + + case M_SOS: + case M_DQT: + case M_DNL: + case M_DHP: + case M_EXP: + + case M_DHT: + + case M_SOF0: + case M_SOF1: + case M_SOF2: + case M_SOF3: + + case M_SOF5: + case M_SOF6: + case M_SOF7: + + case M_JPG: + case M_SOF9: + case M_SOF10: + case M_SOF11: + + case M_SOF13: + case M_SOF14: + case M_SOF15: + + case M_APP0: + case M_APP1: + case M_APP2: + case M_APP3: + case M_APP4: + case M_APP5: + case M_APP6: + case M_APP7: + case M_APP8: + case M_APP9: + case M_APP10: + case M_APP11: + case M_APP12: + case M_APP13: + case M_APP14: + case M_APP15: + block_size = CBitstreamConverter::read_bits(&br, 16); + nMarker = true; + break; + + default: + nMarker = false; + break; + } + + if(!nMarker) + { + break; + } + + if(marker >= M_SOF0 && marker <= M_SOF15) + { + if(marker == M_SOF2 || marker == M_SOF6 || marker == M_SOF10 || marker == M_SOF14) + { + m_progressive = true; + } + CBitstreamConverter::skip_bits(&br, 8); + m_omx_image.nFrameHeight = CBitstreamConverter::read_bits(&br, 16); + m_omx_image.nFrameWidth = CBitstreamConverter::read_bits(&br, 16); + + CBitstreamConverter::skip_bits(&br, 8 * (block_size - 9)); + } + else if(marker == M_APP1) + { + int readBits = 2; + bool bMotorolla = false; + bool bError = false; + + // Exif header + if(CBitstreamConverter::read_bits(&br, 32) == 0x45786966) + { + CBitstreamConverter::skip_bits(&br, 8 * 2); + readBits += 2; + + char o1 = CBitstreamConverter::read_bits(&br, 8); + char o2 = CBitstreamConverter::read_bits(&br, 8); + readBits += 2; + + /* Discover byte order */ + if(o1 == 'M' && o2 == 'M') + bMotorolla = true; + else if(o1 == 'I' && o2 == 'I') + bMotorolla = false; + else + bError = true; + + CBitstreamConverter::skip_bits(&br, 8 * 2); + readBits += 2; + + if(!bError) + { + unsigned int offset, a, b, numberOfTags, tagNumber; + + // Get first IFD offset (offset to IFD0) + if(bMotorolla) + { + CBitstreamConverter::skip_bits(&br, 8 * 2); + readBits += 2; + + a = CBitstreamConverter::read_bits(&br, 8); + b = CBitstreamConverter::read_bits(&br, 8); + readBits += 2; + offset = (a << 8) + b; + } + else + { + a = CBitstreamConverter::read_bits(&br, 8); + b = CBitstreamConverter::read_bits(&br, 8); + readBits += 2; + offset = (b << 8) + a; + + CBitstreamConverter::skip_bits(&br, 8 * 2); + readBits += 2; + } + + offset -= 8; + if(offset > 0) + { + CBitstreamConverter::skip_bits(&br, 8 * offset); + readBits += offset; + } + + // Get the number of directory entries contained in this IFD + if(bMotorolla) + { + a = CBitstreamConverter::read_bits(&br, 8); + b = CBitstreamConverter::read_bits(&br, 8); + numberOfTags = (a << 8) + b; + } + else + { + a = CBitstreamConverter::read_bits(&br, 8); + b = CBitstreamConverter::read_bits(&br, 8); + numberOfTags = (b << 8) + a; + } + readBits += 2; + + while(numberOfTags && !br.oflow) + { + // Get Tag number + if(bMotorolla) + { + a = CBitstreamConverter::read_bits(&br, 8); + b = CBitstreamConverter::read_bits(&br, 8); + tagNumber = (a << 8) + b; + readBits += 2; + } + else + { + a = CBitstreamConverter::read_bits(&br, 8); + b = CBitstreamConverter::read_bits(&br, 8); + tagNumber = (b << 8) + a; + readBits += 2; + } + + //found orientation tag + if(tagNumber == EXIF_TAG_ORIENTATION) + { + if(bMotorolla) + { + CBitstreamConverter::skip_bits(&br, 8 * 7); + readBits += 7; + m_orientation = CBitstreamConverter::read_bits(&br, 8); + readBits += 1; + CBitstreamConverter::skip_bits(&br, 8 * 2); + readBits += 2; + } + else + { + CBitstreamConverter::skip_bits(&br, 8 * 6); + readBits += 6; + m_orientation = CBitstreamConverter::read_bits(&br, 8); + readBits += 1; + CBitstreamConverter::skip_bits(&br, 8 * 3); + readBits += 3; + } + break; + } + else + { + CBitstreamConverter::skip_bits(&br, 8 * 10); + readBits += 10; + } + numberOfTags--; + } + } + } + readBits += 4; + CBitstreamConverter::skip_bits(&br, 8 * (block_size - readBits)); + } + else + { + CBitstreamConverter::skip_bits(&br, 8 * (block_size - 2)); + } + + CBitstreamConverter::read_bits(&br, 8); + marker = CBitstreamConverter::read_bits(&br, 8); + + } + + } + + CBitstreamConverter::bits_reader_set( &br, m_image_buffer, m_image_size ); + + /* PNG Header */ + if(CBitstreamConverter::read_bits(&br, 32) == 0x89504E47) + { + m_omx_image.eCompressionFormat = OMX_IMAGE_CodingPNG; + CBitstreamConverter::skip_bits(&br, 32 * 2); + if(CBitstreamConverter::read_bits(&br, 32) == 0x49484452) + { + m_omx_image.nFrameWidth = CBitstreamConverter::read_bits(&br, 32); + m_omx_image.nFrameHeight = CBitstreamConverter::read_bits(&br, 32); + (void)CBitstreamConverter::read_bits(&br, 8); // bit depth + unsigned int coding_type = CBitstreamConverter::read_bits(&br, 8); + m_alpha = coding_type==4 || coding_type==6; + } + } + + if(m_orientation > 8) + m_orientation = 0; + + m_width = m_omx_image.nFrameWidth; + m_height = m_omx_image.nFrameHeight; + + return m_omx_image.eCompressionFormat; +} + +bool COMXImage::ReadFile(const CStdString& inputFile) +{ + if(!m_pFile.Open(inputFile, 0)) + { + CLog::Log(LOGERROR, "%s::%s %s not found\n", CLASSNAME, __func__, inputFile.c_str()); + return false; + } + + if(m_image_buffer) + free(m_image_buffer); + m_image_buffer = NULL; + + m_image_size = m_pFile.GetLength(); + + if(!m_image_size) + return false; + + m_image_buffer = (uint8_t *)malloc(m_image_size); + if(!m_image_buffer) + return false; + + m_pFile.Read(m_image_buffer, m_image_size); + + GetCodingType(); + + if(m_width < 1 || m_height < 1) + return false; + + // ensure not too big for hardware + while (m_width > 2048 || m_height > 2048) + m_width >>= 1, m_height >>= 1; + // ensure not too small + while (m_width <= 32 || m_height <= 32) + m_width <<= 1, m_height <<= 1; + // surely not going to happen? + if (m_width > 2048 || m_height > 2048) + m_width = 256, m_height = 256; + + m_width = (m_width + 15) & ~15; + m_height = (m_height + 15) & ~15; + + m_is_open = true; + + return true; +} + +bool COMXImage::Decode(unsigned width, unsigned height) +{ + std::string componentName = ""; + bool m_firstFrame = true; + unsigned int demuxer_bytes = 0; + const uint8_t *demuxer_content = NULL; + OMX_ERRORTYPE omx_err = OMX_ErrorNone; + OMX_BUFFERHEADERTYPE *omx_buffer = NULL; + + OMX_INIT_STRUCTURE(m_decoded_format); + + CSingleLock lock(g_OMXSection); + + if(!m_image_buffer) + { + CLog::Log(LOGERROR, "%s::%s no input buffer\n", CLASSNAME, __func__); + return false; + } + + if(GetCompressionFormat() == OMX_IMAGE_CodingMax) + { + CLog::Log(LOGERROR, "%s::%s error unsupported image format\n", CLASSNAME, __func__); + return false; + } + + if(IsProgressive()) + { + CLog::Log(LOGWARNING, "%s::%s progressive images not supported by decoder\n", CLASSNAME, __func__); + return false; + } + + if(!m_is_open) + { + CLog::Log(LOGERROR, "%s::%s error not opened\n", CLASSNAME, __func__); + return false; + } + + componentName = "OMX.broadcom.image_decode"; + if(!m_omx_decoder.Initialize((const std::string)componentName, OMX_IndexParamImageInit)) + { + CLog::Log(LOGERROR, "%s::%s error m_omx_decoder.Initialize\n", CLASSNAME, __func__); + return false; + } + + componentName = "OMX.broadcom.resize"; + if(!m_omx_resize.Initialize((const std::string)componentName, OMX_IndexParamImageInit)) + { + CLog::Log(LOGERROR, "%s::%s error m_omx_resize.Initialize\n", CLASSNAME, __func__); + return false; + } + + m_decoder_open = true; + + m_omx_tunnel_decode.Initialize(&m_omx_decoder, m_omx_decoder.GetOutputPort(), &m_omx_resize, m_omx_resize.GetInputPort()); + + omx_err = m_omx_tunnel_decode.Establish(false); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_tunnel_decode.Establish\n", CLASSNAME, __func__); + return false; + } + + if(width == 0 || height == 0) + { +#ifndef STANDALONE + height = g_advancedSettings.m_imageRes; + if (g_advancedSettings.m_fanartRes > g_advancedSettings.m_imageRes) + { // a separate fanart resolution is specified - check if the image is exactly equal to this res + if (m_width == (unsigned int)g_advancedSettings.m_fanartRes * 16/9 && + m_height == (unsigned int)g_advancedSettings.m_fanartRes) + { // special case for fanart res + height = g_advancedSettings.m_fanartRes; + } + } + width = height * 16/9; + if(!width || !height) + { + //width = g_settings.m_ResInfo[g_guiSettings.m_LookAndFeelResolution].iScreenWidth; + //height = g_settings.m_ResInfo[g_guiSettings.m_LookAndFeelResolution].iScreenHeight; + width = g_settings.m_ResInfo[g_guiSettings.m_LookAndFeelResolution].iWidth; + height = g_settings.m_ResInfo[g_guiSettings.m_LookAndFeelResolution].iHeight; + } +#else + width = 2048; + height = 2048; +#endif + } + + OMX_PARAM_PORTDEFINITIONTYPE port_def; + OMX_INIT_STRUCTURE(port_def); + port_def.nPortIndex = m_omx_decoder.GetInputPort(); + + omx_err = m_omx_decoder.GetParameter(OMX_IndexParamPortDefinition, &port_def); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_decoder.GetParameter result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + + port_def.format.image.eCompressionFormat = GetCompressionFormat(); + port_def.format.image.eColorFormat = OMX_COLOR_FormatUnused; + port_def.format.image.nFrameWidth = 0; + port_def.format.image.nFrameHeight = 0; + port_def.format.image.nStride = 0; + port_def.format.image.nSliceHeight = 0; + port_def.format.image.bFlagErrorConcealment = OMX_FALSE; + + omx_err = m_omx_decoder.SetParameter(OMX_IndexParamPortDefinition, &port_def); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_decoder.SetParameter result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + + omx_err = m_omx_decoder.AllocInputBuffers(); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_decoder.AllocInputBuffers result(0x%x)", CLASSNAME, __func__, omx_err); + return false; + } + + omx_err = m_omx_decoder.SetStateForComponent(OMX_StateExecuting); + if (omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_decoder.SetStateForComponent result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + + OMX_INIT_STRUCTURE(port_def); + port_def.nPortIndex = m_omx_resize.GetOutputPort(); + + omx_err = m_omx_resize.GetParameter(OMX_IndexParamPortDefinition, &port_def); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_resize.GetParameter result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + + + port_def.format.image.eCompressionFormat = OMX_IMAGE_CodingUnused; + port_def.format.image.eColorFormat = OMX_COLOR_Format32bitABGR8888; + if((((width + 15)&~15) > width) || (((height + 15)&~15) > height)) + { + port_def.format.image.nFrameWidth = (width + 15)&~15; + port_def.format.image.nFrameHeight = (height + 15)&~15; + } + else + { + port_def.format.image.nFrameWidth = width; + port_def.format.image.nFrameHeight = height; + } + port_def.format.image.nStride = 0; + port_def.format.image.nSliceHeight = 0; + port_def.format.image.bFlagErrorConcealment = OMX_FALSE; + + omx_err = m_omx_resize.SetParameter(OMX_IndexParamPortDefinition, &port_def); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_resize.SetParameter result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + + omx_err = m_omx_resize.AllocOutputBuffers(); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_resize.AllocOutputBuffers result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + + omx_err = m_omx_resize.SetStateForComponent(OMX_StateExecuting); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_resize.SetStateForComponent result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + + demuxer_bytes = GetImageSize(); + demuxer_content = GetImageBuffer(); + if(!demuxer_bytes || !demuxer_content) + return false; + + m_firstFrame = true; + + while(demuxer_bytes > 0) + { + omx_buffer = m_omx_decoder.GetInputBuffer(1000); + if(omx_buffer == NULL) + return false; + + omx_buffer->nOffset = omx_buffer->nFlags = 0; + + omx_buffer->nFilledLen = (demuxer_bytes > omx_buffer->nAllocLen) ? omx_buffer->nAllocLen : demuxer_bytes; + memcpy(omx_buffer->pBuffer, demuxer_content, omx_buffer->nFilledLen); + + demuxer_content += omx_buffer->nFilledLen; + demuxer_bytes -= omx_buffer->nFilledLen; + + if(demuxer_bytes == 0) + omx_buffer->nFlags |= OMX_BUFFERFLAG_EOS; + + omx_err = m_omx_decoder.EmptyThisBuffer(omx_buffer); + if (omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s OMX_EmptyThisBuffer() failed with result(0x%x)\n", CLASSNAME, __func__, omx_err); + break; + } + if(m_firstFrame) + { + m_firstFrame = false; + + m_omx_decoder.DisablePort(m_omx_decoder.GetOutputPort(), NULL); + m_omx_resize.DisablePort(m_omx_resize.GetInputPort(), NULL); + //m_omx_resize.DisablePort(m_omx_resize.GetOutputPort(), NULL); + + OMX_PARAM_PORTDEFINITIONTYPE port_image; + OMX_INIT_STRUCTURE(port_image); + + port_image.nPortIndex = m_omx_decoder.GetOutputPort(); + m_omx_decoder.GetParameter(OMX_IndexParamPortDefinition, &port_image); + + port_image.nPortIndex = m_omx_resize.GetInputPort(); + m_omx_resize.SetParameter(OMX_IndexParamPortDefinition, &port_image); + + m_omx_decoder.EnablePort(m_omx_decoder.GetOutputPort(), NULL); + //m_omx_decoder.EnablePort(m_omx_decoder.GetInputPort(), NULL); + omx_err = m_omx_decoder.WaitForEvent(OMX_EventPortSettingsChanged); + if(omx_err == OMX_ErrorStreamCorrupt) + { + CLog::Log(LOGERROR, "%s::%s image not unsupported\n", CLASSNAME, __func__); + return false; + } + + m_omx_resize.EnablePort(m_omx_resize.GetInputPort(), NULL); + omx_err = m_omx_resize.WaitForEvent(OMX_EventPortSettingsChanged); + if(omx_err == OMX_ErrorStreamCorrupt) + { + CLog::Log(LOGERROR, "%s::%s image not unsupported\n", CLASSNAME, __func__); + return false; + } + } + } + + /* + omx_err = m_omx_resize.EnablePort(m_omx_resize.GetOutputPort(), NULL); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_resize.EnablePort result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + */ + + omx_err = m_omx_decoder.WaitForEvent(OMX_EventBufferFlag, 1000); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_decoder.WaitForEvent result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + + m_decoded_buffer = m_omx_resize.GetOutputBuffer(2000); + + if(!m_decoded_buffer) + { + CLog::Log(LOGERROR, "%s::%s no output buffer\n", CLASSNAME, __func__); + return false; + } + + omx_err = m_omx_resize.FillThisBuffer(m_decoded_buffer); + + omx_err = m_omx_resize.WaitForEvent(OMX_EventBufferFlag, 1000); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_resize WaitForEvent result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + + m_decoded_format.nPortIndex = m_omx_resize.GetOutputPort(); + omx_err = m_omx_resize.GetParameter(OMX_IndexParamPortDefinition, &m_decoded_format); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_resize.GetParameter result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + + m_omx_tunnel_decode.Deestablish(); + + SwapBlueRed(m_decoded_buffer->pBuffer, GetDecodedHeight(), GetDecodedWidth() * 4); + + return true; +} + +bool COMXImage::Encode(unsigned char *buffer, int size, unsigned width, unsigned height) +{ + std::string componentName = ""; + unsigned int demuxer_bytes = 0; + const uint8_t *demuxer_content = NULL; + uint8_t *internalBuffer = NULL; + OMX_ERRORTYPE omx_err = OMX_ErrorNone; + OMX_BUFFERHEADERTYPE *omx_buffer = NULL; + OMX_INIT_STRUCTURE(m_encoded_format); + + CSingleLock lock(g_OMXSection); + + if (!buffer || !size) + { + CLog::Log(LOGERROR, "%s::%s error no buffer\n", CLASSNAME, __func__); + return false; + } + + componentName = "OMX.broadcom.image_encode"; + if(!m_omx_encoder.Initialize((const std::string)componentName, OMX_IndexParamImageInit)) + { + CLog::Log(LOGERROR, "%s::%s error m_omx_encoder.Initialize\n", CLASSNAME, __func__); + return false; + } + + m_encoder_open = true; + + OMX_PARAM_PORTDEFINITIONTYPE port_def; + OMX_INIT_STRUCTURE(port_def); + port_def.nPortIndex = m_omx_encoder.GetInputPort(); + + omx_err = m_omx_encoder.GetParameter(OMX_IndexParamPortDefinition, &port_def); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_encoder.GetParameter result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + + port_def.format.image.eCompressionFormat = OMX_IMAGE_CodingUnused; + port_def.format.image.eColorFormat = OMX_COLOR_Format32bitABGR8888; + port_def.format.image.nFrameWidth = width; + port_def.format.image.nFrameHeight = height; + port_def.format.image.nStride = width * 4; + port_def.format.image.nSliceHeight = height; + port_def.format.image.bFlagErrorConcealment = OMX_FALSE; + + omx_err = m_omx_encoder.SetParameter(OMX_IndexParamPortDefinition, &port_def); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_encoder.SetParameter result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + + OMX_INIT_STRUCTURE(port_def); + port_def.nPortIndex = m_omx_encoder.GetOutputPort(); + + omx_err = m_omx_encoder.GetParameter(OMX_IndexParamPortDefinition, &port_def); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_encoder.GetParameter result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + + port_def.format.image.eCompressionFormat = OMX_IMAGE_CodingJPEG; + port_def.format.image.eColorFormat = OMX_COLOR_FormatUnused; + port_def.format.image.nFrameWidth = width; + port_def.format.image.nFrameHeight = height; + port_def.format.image.nStride = 0; + port_def.format.image.nSliceHeight = 0; + port_def.format.image.bFlagErrorConcealment = OMX_FALSE; + + omx_err = m_omx_encoder.SetParameter(OMX_IndexParamPortDefinition, &port_def); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_encoder.SetParameter result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + + OMX_IMAGE_PARAM_QFACTORTYPE qfactor; + OMX_INIT_STRUCTURE(qfactor); + qfactor.nPortIndex = m_omx_encoder.GetOutputPort(); + qfactor.nQFactor = 16; + + omx_err = m_omx_encoder.SetParameter(OMX_IndexParamQFactor, &qfactor); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_encoder.SetParameter OMX_IndexParamQFactor result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + + omx_err = m_omx_encoder.AllocInputBuffers(); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_encoder.AllocInputBuffers result(0x%x)", CLASSNAME, __func__, omx_err); + return false; + } + + omx_err = m_omx_encoder.AllocOutputBuffers(); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_encoder.AllocOutputBuffers result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + + omx_err = m_omx_encoder.SetStateForComponent(OMX_StateExecuting); + if (omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_encoder.SetStateForComponent result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + + internalBuffer = (uint8_t *)malloc(size); + memcpy(internalBuffer, buffer, size); + demuxer_bytes = size; + demuxer_content = internalBuffer; + SwapBlueRed(internalBuffer, height, width * 4); + + if(!demuxer_bytes || !demuxer_content) + return false; + + while(demuxer_bytes > 0) + { + omx_buffer = m_omx_encoder.GetInputBuffer(1000); + if(omx_buffer == NULL) + { + if(internalBuffer) free(internalBuffer); + return false; + } + + omx_buffer->nOffset = omx_buffer->nFlags = 0; + + omx_buffer->nFilledLen = (demuxer_bytes > omx_buffer->nAllocLen) ? omx_buffer->nAllocLen : demuxer_bytes; + memcpy(omx_buffer->pBuffer, demuxer_content, omx_buffer->nFilledLen); + + demuxer_content += omx_buffer->nFilledLen; + demuxer_bytes -= omx_buffer->nFilledLen; + + if(demuxer_bytes == 0) + omx_buffer->nFlags |= OMX_BUFFERFLAG_EOS; + + omx_err = m_omx_encoder.EmptyThisBuffer(omx_buffer); + if (omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s OMX_EmptyThisBuffer() failed with result(0x%x)\n", CLASSNAME, __func__, omx_err); + break; + } + } + + if(internalBuffer) free(internalBuffer); + + m_encoded_buffer = m_omx_encoder.GetOutputBuffer(2000); + + if(!m_encoded_buffer) + { + CLog::Log(LOGERROR, "%s::%s no output buffer\n", CLASSNAME, __func__); + return false; + } + + omx_err = m_omx_encoder.FillThisBuffer(m_encoded_buffer); + if(omx_err != OMX_ErrorNone) + return false; + + omx_err = m_omx_encoder.WaitForEvent(OMX_EventBufferFlag, 1000); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_encoder WaitForEvent result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + + m_encoded_format.nPortIndex = m_omx_encoder.GetOutputPort(); + omx_err = m_omx_encoder.GetParameter(OMX_IndexParamPortDefinition, &m_encoded_format); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s m_omx_encoder.GetParameter result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + + return true; +} + +unsigned char *COMXImage::GetDecodedData() +{ + if(!m_decoded_buffer) + return NULL; + + return (unsigned char *)m_decoded_buffer->pBuffer; +} + +unsigned int COMXImage::GetDecodedSize() +{ + if(!m_decoded_buffer) + return 0; + return (unsigned int)m_decoded_buffer->nFilledLen; +} + +unsigned char *COMXImage::GetEncodedData() +{ + if(!m_encoded_buffer) + return NULL; + + return (unsigned char *)m_encoded_buffer->pBuffer; +} + +unsigned int COMXImage::GetEncodedSize() +{ + if(!m_encoded_buffer) + return 0; + return (unsigned int)m_encoded_buffer->nFilledLen; +} + +bool COMXImage::SwapBlueRed(unsigned char *pixels, unsigned int height, unsigned int pitch, + unsigned int elements, unsigned int offset) +{ + if (!pixels) return false; + unsigned char *dst = pixels; + for (unsigned int y = 0; y < height; y++) + { + dst = pixels + (y * pitch); + for (unsigned int x = 0; x < pitch; x+=elements) + std::swap(dst[x+offset], dst[x+2+offset]); + } + return true; +} + +bool COMXImage::CreateThumbnail(const CStdString& sourceFile, const CStdString& destFile, + int minx, int miny, bool rotateExif) +{ + if (!ReadFile(sourceFile)) + return false; + + return CreateThumbnailFromMemory(m_image_buffer, m_image_size, destFile, minx, miny); +} + +bool COMXImage::CreateThumbnailFromMemory(unsigned char* buffer, unsigned int bufSize, const CStdString& destFile, + unsigned int minx, unsigned int miny) +{ + if(!bufSize || !buffer) + return false; + + if(!m_is_open) + { + m_image_size = bufSize; + m_image_buffer = (uint8_t *)malloc(m_image_size); + if(!m_image_buffer) + return false; + + memcpy(m_image_buffer, buffer, m_image_size); + + GetCodingType(); + + // ensure not too big for hardware + while (m_width > 2048 || m_height > 2048) + m_width >>= 1, m_height >>= 1; + // ensure not too small + while (m_width <= 32 || m_height <= 32) + m_width <<= 1, m_height <<= 1; + // surely not going to happen? + if (m_width > 2048 || m_height > 2048) + m_width = 256, m_height = 256; + + m_width = (m_width + 15) & ~15; + m_height = (m_height + 15) & ~15; + + m_is_open = true; + } + + if(!Decode(minx, miny)) + return false; + + return CreateThumbnailFromSurface(GetDecodedData(), GetDecodedWidth(), GetDecodedHeight(), + XB_FMT_A8R8G8B8, GetDecodedWidth() * 4, destFile); +} + +bool COMXImage::CreateThumbnailFromSurface(unsigned char* buffer, unsigned int width, unsigned int height, + unsigned int format, unsigned int pitch, const CStdString& destFile) +{ + if(format != XB_FMT_A8R8G8B8 || !buffer) + return false; + + // the omx encoder needs alligned sizes + if(width%16 || height%16) + { + unsigned int new_width = (width + 15)&~15; + unsigned int new_height = (height + 15)&~15; + unsigned int new_pitch = new_width * 4; + + unsigned int size = new_height * new_pitch; + unsigned char *dstBuffer = (unsigned char *)malloc(size); + unsigned char *dst = dstBuffer; + unsigned char *src = buffer; + + if(!dstBuffer) + return false; + + memset(dst, 0x0, size); + + for(unsigned int y = 0; y < height; y++) + { + memcpy(dst, src, pitch); + src += pitch; + dst += new_pitch; + } + if(!Encode(dstBuffer, size, new_width, new_height)) + { + free(dstBuffer); + return false; + } + free(dstBuffer); + } + else + { + if(!Encode(buffer, height * pitch, width, height)) + return false; + } + + XFILE::CFile file; + if (file.OpenForWrite(destFile, true)) + { + CLog::Log(LOGDEBUG, "%s::%s : %s width %d height %d\n", CLASSNAME, __func__, destFile.c_str(), width, height); + + file.Write(GetEncodedData(), GetEncodedSize()); + file.Close(); + return true; + } + + return false; +} diff --git a/xbmc/cores/omxplayer/OMXImage.h b/xbmc/cores/omxplayer/OMXImage.h new file mode 100644 index 0000000000..f1e4672ce3 --- /dev/null +++ b/xbmc/cores/omxplayer/OMXImage.h @@ -0,0 +1,107 @@ +#pragma once +/* + * Copyright (C) 2010 Team XBMC + * http://www.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, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#if defined(HAVE_OMXLIB) + +#include "OMXCore.h" + +#include <IL/OMX_Video.h> + +#include "OMXClock.h" +#if defined(STANDALONE) +#define XB_FMT_A8R8G8B8 1 +#include "File.h" +#else +#include "filesystem/File.h" +#include "guilib/XBTF.h" +#endif + +using namespace XFILE; +using namespace std; + +class COMXImage +{ +public: + COMXImage(); + virtual ~COMXImage(); + + // Required overrides + void Close(void); + bool ReadFile(const CStdString& inputFile); + bool IsProgressive() { return m_progressive; }; + bool IsAlpha() { return m_alpha; }; + int GetOrientation() { return m_orientation; }; + unsigned int GetOriginalWidth() { return m_omx_image.nFrameWidth; }; + unsigned int GetOriginalHeight() { return m_omx_image.nFrameHeight; }; + unsigned int GetWidth() { return m_width; }; + unsigned int GetHeight() { return m_height; }; + OMX_IMAGE_CODINGTYPE GetCodingType(); + const uint8_t *GetImageBuffer() { return (const uint8_t *)m_image_buffer; }; + unsigned long GetImageSize() { return m_image_size; }; + OMX_IMAGE_CODINGTYPE GetCompressionFormat() { return m_omx_image.eCompressionFormat; }; + bool Decode(unsigned width, unsigned height); + bool Encode(unsigned char *buffer, int size, unsigned width, unsigned height); + int GetDecodedWidth() { return (int)m_decoded_format.format.image.nFrameWidth; }; + int GetDecodedHeight() { return (int)m_decoded_format.format.image.nFrameHeight; }; + int GetDecodedStride() { return (int)m_decoded_format.format.image.nStride; }; + unsigned char *GetDecodedData(); + unsigned int GetDecodedSize(); + int GetEncodedWidth() { return (int)m_encoded_format.format.image.nFrameWidth; }; + int GetEncodedHeight() { return (int)m_encoded_format.format.image.nFrameHeight; }; + int GetEncodedStride() { return (int)m_encoded_format.format.image.nStride; }; + unsigned char *GetEncodedData(); + unsigned int GetEncodedSize(); + bool SwapBlueRed(unsigned char *pixels, unsigned int height, unsigned int pitch, + unsigned int elements = 4, unsigned int offset=0); + bool CreateThumbnail(const CStdString& sourceFile, const CStdString& destFile, + int minx, int miny, bool rotateExif); + bool CreateThumbnailFromMemory(unsigned char* buffer, unsigned int bufSize, + const CStdString& destFile, unsigned int minx, unsigned int miny); + bool CreateThumbnailFromSurface(unsigned char* buffer, unsigned int width, unsigned int height, + unsigned int format, unsigned int pitch, const CStdString& destFile); +protected: + uint8_t *m_image_buffer; + bool m_is_open; + unsigned long m_image_size; + unsigned int m_width; + unsigned int m_height; + bool m_progressive; + bool m_alpha; + int m_orientation; + XFILE::CFile m_pFile; + OMX_IMAGE_PORTDEFINITIONTYPE m_omx_image; + + // Components + COMXCoreComponent m_omx_decoder; + COMXCoreComponent m_omx_encoder; + COMXCoreComponent m_omx_resize; + COMXCoreTunel m_omx_tunnel_decode; + OMX_BUFFERHEADERTYPE *m_decoded_buffer; + OMX_BUFFERHEADERTYPE *m_encoded_buffer; + OMX_PARAM_PORTDEFINITIONTYPE m_decoded_format; + OMX_PARAM_PORTDEFINITIONTYPE m_encoded_format; + + bool m_decoder_open; + bool m_encoder_open; +}; + +#endif diff --git a/xbmc/cores/omxplayer/OMXPlayer.cpp b/xbmc/cores/omxplayer/OMXPlayer.cpp new file mode 100644 index 0000000000..f0dc764884 --- /dev/null +++ b/xbmc/cores/omxplayer/OMXPlayer.cpp @@ -0,0 +1,3816 @@ +/* + * Copyright (C) 2011 Team XBMC + * http://www.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, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "system.h" + +#if defined (HAS_OMXPLAYER) +#include "OMXPlayerAudio.h" +#include "OMXPlayerVideo.h" +#include "OMXPlayer.h" +#include "Application.h" +#include "FileItem.h" +#include "GUIInfoManager.h" +#include "filesystem/File.h" +#include "cores/VideoRenderers/RenderManager.h" +#include "cores/VideoRenderers/RenderFlags.h" +#include "filesystem/SpecialProtocol.h" +#include "guilib/GUIWindowManager.h" +#include "settings/AdvancedSettings.h" +#include "settings/GUISettings.h" +#include "settings/Settings.h" +#include "threads/SingleLock.h" +#include "windowing/WindowingFactory.h" +#include "utils/log.h" +#include "utils/MathUtils.h" +#include "utils/TimeUtils.h" +#include "utils/URIUtils.h" +#include "utils/XMLUtils.h" +#include "utils/Variant.h" +#include "xbmc/playlists/PlayListM3U.h" + +#include "FileItem.h" +#include "filesystem/File.h" +#include "utils/BitstreamStats.h" + +#include "utils/LangCodeExpander.h" +#include "guilib/LocalizeStrings.h" +#include "utils/StreamDetails.h" + +#include "DVDFileInfo.h" + +#include <sstream> +#include <iomanip> + +#include "BitstreamConverter.h" +#include "DVDInputStreams/DVDInputStreamNavigator.h" +#include "DVDInputStreams/DVDInputStreamTV.h" +#include "Util.h" +#include "storage/MediaManager.h" +#include "GUIUserMessages.h" +#include "utils/StreamUtils.h" + +#include "DVDDemuxers/DVDDemuxUtils.h" +#include "DVDDemuxers/DVDDemuxVobsub.h" +#include "DVDDemuxers/DVDFactoryDemuxer.h" +#include "DVDDemuxers/DVDDemuxFFmpeg.h" + +#include "LangInfo.h" + +#include "utils/JobManager.h" +#include "cores/AudioEngine/AEFactory.h" +#include "cores/AudioEngine/Utils/AEUtil.h" +#include "xbmc/ThumbLoader.h" + +using namespace XFILE; + +// **************************************************************** +void COMXSelectionStreams::Clear(StreamType type, StreamSource source) +{ + CSingleLock lock(m_section); + for(int i=m_Streams.size()-1;i>=0;i--) + { + if(type && m_Streams[i].type != type) + continue; + + if(source && m_Streams[i].source != source) + continue; + + m_Streams.erase(m_Streams.begin() + i); + } +} + +void COMXPlayer::GetAudioStreamLanguage(int iStream, CStdString &strLanguage) +{ + strLanguage = ""; + OMXSelectionStream& s = m_SelectionStreams.Get(STREAM_AUDIO, iStream); + if(s.language.length() > 0) + strLanguage = s.language; +} + +OMXSelectionStream& COMXSelectionStreams::Get(StreamType type, int index) +{ + CSingleLock lock(m_section); + int count = -1; + for(int i=0;i<(int)m_Streams.size();i++) + { + if(m_Streams[i].type != type) + continue; + count++; + if(count == index) + return m_Streams[i]; + } + CLog::Log(LOGERROR, "%s - failed to get stream", __FUNCTION__); + return m_invalid; +} + +bool COMXSelectionStreams::Get(StreamType type, CDemuxStream::EFlags flag, OMXSelectionStream& out) +{ + CSingleLock lock(m_section); + for(int i=0;i<(int)m_Streams.size();i++) + { + if(m_Streams[i].type != type) + continue; + if((m_Streams[i].flags & flag) != flag) + continue; + out = m_Streams[i]; + return true; + } + return false; +} + +std::vector<OMXSelectionStream> COMXSelectionStreams::Get(StreamType type) +{ + std::vector<OMXSelectionStream> streams; + int count = Count(type); + for(int index = 0; index < count; ++index){ + streams.push_back(Get(type, index)); + } + return streams; +} + +#define PREDICATE_RETURN(lh, rh) \ + do { \ + if((lh) != (rh)) \ + return (lh) > (rh); \ + } while(0) + +static bool PredicateAudioPriority(const OMXSelectionStream& lh, const OMXSelectionStream& rh) +{ + PREDICATE_RETURN(lh.type_index == g_settings.m_currentVideoSettings.m_AudioStream + , rh.type_index == g_settings.m_currentVideoSettings.m_AudioStream); + + if(!g_guiSettings.GetString("locale.audiolanguage").Equals("original")) + { + CStdString audio_language = g_langInfo.GetAudioLanguage(); + PREDICATE_RETURN(audio_language.Equals(lh.language.c_str()) + , audio_language.Equals(rh.language.c_str())); + } + + PREDICATE_RETURN(lh.flags & CDemuxStream::FLAG_DEFAULT + , rh.flags & CDemuxStream::FLAG_DEFAULT); + + PREDICATE_RETURN(lh.channels + , rh.channels); + + PREDICATE_RETURN(StreamUtils::GetCodecPriority(lh.codec) + , StreamUtils::GetCodecPriority(rh.codec)); + return false; +} + +static bool PredicateSubtitlePriority(const OMXSelectionStream& lh, const OMXSelectionStream& rh) +{ + if(!g_settings.m_currentVideoSettings.m_SubtitleOn) + { + PREDICATE_RETURN(lh.flags & CDemuxStream::FLAG_FORCED + , rh.flags & CDemuxStream::FLAG_FORCED); + } + + PREDICATE_RETURN(lh.type_index == g_settings.m_currentVideoSettings.m_SubtitleStream + , rh.type_index == g_settings.m_currentVideoSettings.m_SubtitleStream); + + CStdString subtitle_language = g_langInfo.GetSubtitleLanguage(); + if(!g_guiSettings.GetString("locale.subtitlelanguage").Equals("original")) + { + PREDICATE_RETURN((lh.source == STREAM_SOURCE_DEMUX_SUB || lh.source == STREAM_SOURCE_TEXT) && subtitle_language.Equals(lh.language.c_str()) + , (rh.source == STREAM_SOURCE_DEMUX_SUB || rh.source == STREAM_SOURCE_TEXT) && subtitle_language.Equals(rh.language.c_str())); + } + + PREDICATE_RETURN(lh.source == STREAM_SOURCE_DEMUX_SUB + , rh.source == STREAM_SOURCE_DEMUX_SUB); + + PREDICATE_RETURN(lh.source == STREAM_SOURCE_TEXT + , rh.source == STREAM_SOURCE_TEXT); + + if(!g_guiSettings.GetString("locale.subtitlelanguage").Equals("original")) + { + PREDICATE_RETURN(subtitle_language.Equals(lh.language.c_str()) + , subtitle_language.Equals(rh.language.c_str())); + } + + PREDICATE_RETURN(lh.flags & CDemuxStream::FLAG_DEFAULT + , rh.flags & CDemuxStream::FLAG_DEFAULT); + + return false; +} + +static bool PredicateVideoPriority(const OMXSelectionStream& lh, const OMXSelectionStream& rh) +{ + PREDICATE_RETURN(lh.flags & CDemuxStream::FLAG_DEFAULT + , rh.flags & CDemuxStream::FLAG_DEFAULT); + return false; +} + +int COMXSelectionStreams::IndexOf(StreamType type, int source, int id) const +{ + CSingleLock lock(m_section); + int count = -1; + for(int i=0;i<(int)m_Streams.size();i++) + { + if(type && m_Streams[i].type != type) + continue; + count++; + if(source && m_Streams[i].source != source) + continue; + if(id < 0) + continue; + if(m_Streams[i].id == id) + return count; + } + if(id < 0) + return count; + else + return -1; +} + +int COMXSelectionStreams::IndexOf(StreamType type, COMXPlayer& p) const +{ + if (p.m_pInputStream && p.m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD)) + { + int id = -1; + if(type == STREAM_AUDIO) + id = ((CDVDInputStreamNavigator*)p.m_pInputStream)->GetActiveAudioStream(); + else if(type == STREAM_VIDEO) + id = p.m_CurrentVideo.id; + else if(type == STREAM_SUBTITLE) + id = ((CDVDInputStreamNavigator*)p.m_pInputStream)->GetActiveSubtitleStream(); + + return IndexOf(type, STREAM_SOURCE_NAV, id); + } + + if(type == STREAM_AUDIO) + return IndexOf(type, p.m_CurrentAudio.source, p.m_CurrentAudio.id); + else if(type == STREAM_VIDEO) + return IndexOf(type, p.m_CurrentVideo.source, p.m_CurrentVideo.id); + else if(type == STREAM_SUBTITLE) + return IndexOf(type, p.m_CurrentSubtitle.source, p.m_CurrentSubtitle.id); + else if(type == STREAM_TELETEXT) + return IndexOf(type, p.m_CurrentTeletext.source, p.m_CurrentTeletext.id); + + return -1; +} + +int COMXSelectionStreams::Source(StreamSource source, std::string filename) +{ + CSingleLock lock(m_section); + int index = source - 1; + for(int i=0;i<(int)m_Streams.size();i++) + { + OMXSelectionStream &s = m_Streams[i]; + if(STREAM_SOURCE_MASK(s.source) != source) + continue; + // if it already exists, return same + if(s.filename == filename) + return s.source; + if(index < s.source) + index = s.source; + } + // return next index + return index + 1; +} + +void COMXSelectionStreams::Update(OMXSelectionStream& s) +{ + CSingleLock lock(m_section); + int index = IndexOf(s.type, s.source, s.id); + if(index >= 0) + { + OMXSelectionStream& o = Get(s.type, index); + s.type_index = o.type_index; + o = s; + } + else + { + s.type_index = Count(s.type); + m_Streams.push_back(s); + } +} + +void COMXSelectionStreams::Update(CDVDInputStream* input, CDVDDemux* demuxer) +{ + if(input && input->IsStreamType(DVDSTREAM_TYPE_DVD)) + { + CDVDInputStreamNavigator* nav = (CDVDInputStreamNavigator*)input; + string filename = nav->GetFileName(); + int source = Source(STREAM_SOURCE_NAV, filename); + + int count; + count = nav->GetAudioStreamCount(); + for(int i=0;i<count;i++) + { + OMXSelectionStream s; + s.source = source; + s.type = STREAM_AUDIO; + s.id = i; + s.name = nav->GetAudioStreamLanguage(i); + s.flags = CDemuxStream::FLAG_NONE; + s.filename = filename; + s.channels = 0; + Update(s); + } + + count = nav->GetSubTitleStreamCount(); + for(int i=0;i<count;i++) + { + OMXSelectionStream s; + s.source = source; + s.type = STREAM_SUBTITLE; + s.id = i; + s.name = nav->GetSubtitleStreamLanguage(i); + s.flags = CDemuxStream::FLAG_NONE; + s.filename = filename; + s.channels = 0; + Update(s); + } + } + else if(demuxer) + { + string filename = demuxer->GetFileName(); + int count = demuxer->GetNrOfStreams(); + int source; + if(input) /* hack to know this is sub decoder */ + source = Source(STREAM_SOURCE_DEMUX, filename); + else + source = Source(STREAM_SOURCE_DEMUX_SUB, filename); + + + for(int i=0;i<count;i++) + { + CDemuxStream* stream = demuxer->GetStream(i); + /* make sure stream is marked with right source */ + stream->source = source; + + OMXSelectionStream s; + s.source = source; + s.type = stream->type; + s.id = stream->iId; + s.language = stream->language; + s.flags = stream->flags; + s.filename = demuxer->GetFileName(); + stream->GetStreamName(s.name); + CStdString codec; + demuxer->GetStreamCodecName(stream->iId, codec); + s.codec = codec; + s.channels = 0; // Default to 0. Overwrite if STREAM_AUDIO below. + if(stream->type == STREAM_AUDIO) + { + std::string type; + ((CDemuxStreamAudio*)stream)->GetStreamType(type); + if(type.length() > 0) + { + if(s.name.length() > 0) + s.name += " - "; + s.name += type; + } + s.channels = ((CDemuxStreamAudio*)stream)->iChannels; + } + Update(s); + } + } +} + +// **************************************************************** +COMXPlayer::COMXPlayer(IPlayerCallback &callback) + : IPlayer(callback), + CThread("COMXPlayer"), + m_ready(true), + m_CurrentAudio(STREAM_AUDIO, DVDPLAYER_AUDIO), + m_CurrentVideo(STREAM_VIDEO, DVDPLAYER_VIDEO), + m_CurrentSubtitle(STREAM_SUBTITLE, DVDPLAYER_SUBTITLE), + m_CurrentTeletext(STREAM_TELETEXT, DVDPLAYER_TELETEXT), + m_player_video(&m_av_clock, &m_overlayContainer, m_messenger), + m_player_audio(&m_av_clock, m_messenger), + m_player_subtitle(&m_overlayContainer), + m_messenger("player") +{ + m_bAbortRequest = false; + m_pDemuxer = NULL; + m_pSubtitleDemuxer = NULL; + m_pInputStream = NULL; + m_UpdateApplication = 0; + m_caching = CACHESTATE_DONE; + m_playSpeed = DVD_PLAYSPEED_NORMAL; + + m_State.Clear(); + m_dvd.Clear(); +} + +COMXPlayer::~COMXPlayer() +{ + CloseFile(); +} + +bool COMXPlayer::OpenFile(const CFileItem &file, const CPlayerOptions &options) +{ + try + { + CLog::Log(LOGNOTICE, "COMXPlayer: Opening: %s", file.GetPath().c_str()); + // if playing a file close it first + // this has to be changed so we won't have to close it. + if(IsRunning()) + CloseFile(); + + m_playSpeed = DVD_PLAYSPEED_NORMAL; + SetPlaySpeed(DVD_PLAYSPEED_NORMAL); + + m_PlayerOptions = options; + m_bAbortRequest = false; + + m_UpdateApplication = 0; + m_offset_pts = 0; + m_current_volume = 0; + m_change_volume = true; + + m_item = file; + m_mimetype = file.GetMimeType(); + m_filename = file.GetPath(); + + m_State.Clear(); + + m_ready.Reset(); + + g_renderManager.PreInit(); + + Create(); + + if(!m_ready.WaitMSec(100)) + { + CGUIDialogBusy* dialog = (CGUIDialogBusy*)g_windowManager.GetWindow(WINDOW_DIALOG_BUSY); + dialog->Show(); + while(!m_ready.WaitMSec(1)) + g_windowManager.ProcessRenderLoop(false); + dialog->Close(); + } + + // Playback might have been stopped due to some error + if (m_bStop || m_bAbortRequest) + return false; + + return true; + } + catch(...) + { + CLog::Log(LOGERROR, "%s - Exception thrown on open", __FUNCTION__); + return false; + } +} + +bool COMXPlayer::CloseFile() +{ + CLog::Log(LOGDEBUG, "COMXPlayer::CloseFile"); + + // unpause the player + SetPlaySpeed(DVD_PLAYSPEED_NORMAL); + + // set the abort request so that other threads can finish up + m_bAbortRequest = true; + + // tell demuxer to abort + if(m_pDemuxer) + m_pDemuxer->Abort(); + + if(m_pSubtitleDemuxer) + m_pSubtitleDemuxer->Abort(); + + CLog::Log(LOGDEBUG, "COMXPlayer: waiting for threads to exit"); + // wait for the main thread to finish up + // since this main thread cleans up all other resources and threads + // we are done after the StopThread call + if(IsRunning()) + StopThread(); + + CLog::Log(LOGDEBUG, "COMXPlayer: finished waiting"); + + m_Edl.Clear(); + m_EdlAutoSkipMarkers.Clear(); + + g_renderManager.UnInit(); + return true; +} + +bool COMXPlayer::IsPlaying() const +{ + return !m_bStop; +} + +void COMXPlayer::OnStartup() +{ + m_CurrentVideo.Clear(); + m_CurrentAudio.Clear(); + m_CurrentSubtitle.Clear(); + + m_messenger.Init(); + + CUtil::ClearTempFonts(); +} + +bool COMXPlayer::OpenInputStream() +{ + if(m_pInputStream) + SAFE_DELETE(m_pInputStream); + + CLog::Log(LOGNOTICE, "Creating InputStream"); + + // correct the filename if needed + CStdString filename(m_filename); + if (filename.Find("dvd://") == 0 + || filename.CompareNoCase("iso9660://video_ts/video_ts.ifo") == 0) + { + m_filename = g_mediaManager.TranslateDevicePath(""); + } +retry: + // before creating the input stream, if this is an HLS playlist then get the + // most appropriate bitrate based on our network settings + if (filename.Left(7) == "http://" && filename.Right(5) == ".m3u8") + { + // get the available bandwidth (as per user settings) + int maxrate = g_guiSettings.GetInt("network.bandwidth"); + if(maxrate <= 0) + maxrate = INT_MAX; + + // determine the most appropriate stream + m_filename = PLAYLIST::CPlayListM3U::GetBestBandwidthStream(m_filename, (size_t)maxrate); + } + + m_pInputStream = CDVDFactoryInputStream::CreateInputStream(this, m_filename, m_mimetype); + if(m_pInputStream == NULL) + { + CLog::Log(LOGERROR, "COMXPlayer::OpenInputStream - unable to create input stream for [%s]", m_filename.c_str()); + return false; + } + else + m_pInputStream->SetFileItem(m_item); + + if (!m_pInputStream->Open(m_filename.c_str(), m_mimetype)) + { + if(m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD)) + { + CLog::Log(LOGERROR, "COMXPlayer::OpenInputStream - failed to open [%s] as DVD ISO, trying Bluray", m_filename.c_str()); + m_mimetype = "bluray/iso"; + filename = m_filename; + filename = filename + "/BDMV/index.bdmv"; + int title = (int)m_item.GetProperty("BlurayStartingTitle").asInteger(); + if( title ) + filename.AppendFormat("?title=%d",title); + + m_filename = filename; + goto retry; + } + CLog::Log(LOGERROR, "COMXPlayer::OpenInputStream - error opening [%s]", m_filename.c_str()); + return false; + } + + if (m_pInputStream && ( m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD) + || m_pInputStream->IsStreamType(DVDSTREAM_TYPE_BLURAY) ) ) + { + CLog::Log(LOGINFO, "COMXPlayer::OpenInputStream - DVD/BD not supported"); + return false; + } + + // find any available external subtitles for non dvd files + if (!m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD) + && !m_pInputStream->IsStreamType(DVDSTREAM_TYPE_TV) + && !m_pInputStream->IsStreamType(DVDSTREAM_TYPE_HTSP)) + { + // find any available external subtitles + std::vector<CStdString> filenames; + CUtil::ScanForExternalSubtitles( m_filename, filenames ); + + // find any upnp subtitles + CStdString key("upnp:subtitle:1"); + for(unsigned s = 1; m_item.HasProperty(key); key.Format("upnp:subtitle:%u", ++s)) + filenames.push_back(m_item.GetProperty(key).asString()); + + for(unsigned int i=0;i<filenames.size();i++) + { + // if vobsub subtitle: + if (URIUtils::GetExtension(filenames[i]) == ".idx") + { + CStdString strSubFile; + if ( CUtil::FindVobSubPair( filenames, filenames[i], strSubFile ) ) + AddSubtitleFile(filenames[i], strSubFile); + } + else + { + if ( !CUtil::IsVobSub(filenames, filenames[i] ) ) + { + AddSubtitleFile(filenames[i]); + } + } + } // end loop over all subtitle files + + g_settings.m_currentVideoSettings.m_SubtitleCached = true; + } + + SetAVDelay(g_settings.m_currentVideoSettings.m_AudioDelay); + SetSubTitleDelay(g_settings.m_currentVideoSettings.m_SubtitleDelay); + m_av_clock.Reset(); + m_av_clock.OMXReset(); + m_dvd.Clear(); + + return true; +} + +bool COMXPlayer::OpenDemuxStream() +{ + if(m_pDemuxer) + SAFE_DELETE(m_pDemuxer); + + CLog::Log(LOGNOTICE, "Creating Demuxer"); + + try + { + int attempts = 10; + while(!m_bStop && attempts-- > 0) + { + m_pDemuxer = CDVDFactoryDemuxer::CreateDemuxer(m_pInputStream); + if(!m_pDemuxer && m_pInputStream->NextStream() != CDVDInputStream::NEXTSTREAM_NONE) + { + CLog::Log(LOGDEBUG, "%s - New stream available from input, retry open", __FUNCTION__); + continue; + } + break; + } + + if(!m_pDemuxer) + { + CLog::Log(LOGERROR, "%s - Error creating demuxer", __FUNCTION__); + return false; + } + + } + catch(...) + { + CLog::Log(LOGERROR, "%s - Exception thrown when opening demuxer", __FUNCTION__); + return false; + } + + m_SelectionStreams.Clear(STREAM_NONE, STREAM_SOURCE_DEMUX); + m_SelectionStreams.Clear(STREAM_NONE, STREAM_SOURCE_NAV); + m_SelectionStreams.Update(m_pInputStream, m_pDemuxer); + + int64_t len = m_pInputStream->GetLength(); + int64_t tim = m_pDemuxer->GetStreamLength(); + if(len > 0 && tim > 0) + m_pInputStream->SetReadRate(len * 1000 / tim); + + return true; +} + +void COMXPlayer::OpenDefaultStreams() +{ + //int count; + OMXSelectionStreams streams; + bool valid; + + // open video stream + streams = m_SelectionStreams.Get(STREAM_VIDEO, PredicateVideoPriority); + valid = false; + for(OMXSelectionStreams::iterator it = streams.begin(); it != streams.end() && !valid; ++it) + { + if(OpenVideoStream(it->id, it->source)) + valid = true;; + } + if(!valid) + CloseVideoStream(true); + + // open audio stream + if(m_PlayerOptions.video_only) + streams.clear(); + else + streams = m_SelectionStreams.Get(STREAM_AUDIO, PredicateAudioPriority); + valid = false; + + for(OMXSelectionStreams::iterator it = streams.begin(); it != streams.end() && !valid; ++it) + { + if(OpenAudioStream(it->id, it->source)) + valid = true; + } + if(!valid) + CloseAudioStream(true); + + // enable subtitles + m_player_video.EnableSubtitle(g_settings.m_currentVideoSettings.m_SubtitleOn); + + // open subtitle stream + streams = m_SelectionStreams.Get(STREAM_SUBTITLE, PredicateSubtitlePriority); + valid = false; + for(OMXSelectionStreams::iterator it = streams.begin(); it != streams.end() && !valid; ++it) + { + if(OpenSubtitleStream(it->id, it->source)) + { + valid = true; + if(it->flags & CDemuxStream::FLAG_FORCED) + m_player_video.EnableSubtitle(true); + } + } + if(!valid) + CloseSubtitleStream(true); + + // open teletext stream + /* + streams = m_SelectionStreams.Get(STREAM_TELETEXT); + valid = false; + for(SelectionStreams::iterator it = streams.begin(); it != streams.end() && !valid; ++it) + { + if(OpenTeletextStream(it->id, it->source)) + valid = true; + } + if(!valid) + CloseTeletextStream(true); + */ + + m_av_clock.OMXStop(); + m_av_clock.OMXReset(); +} + +bool COMXPlayer::ReadPacket(DemuxPacket*& packet, CDemuxStream*& stream) +{ + + // check if we should read from subtitle demuxer + if(m_player_subtitle.AcceptsData() && m_pSubtitleDemuxer) + { + if(m_pSubtitleDemuxer) + packet = m_pSubtitleDemuxer->Read(); + + if(packet) + { + UpdateCorrection(packet, m_offset_pts); + if(packet->iStreamId < 0) + return true; + + stream = m_pSubtitleDemuxer->GetStream(packet->iStreamId); + if (!stream) + { + CLog::Log(LOGERROR, "%s - Error demux packet doesn't belong to a valid stream", __FUNCTION__); + return false; + } + if(stream->source == STREAM_SOURCE_NONE) + { + m_SelectionStreams.Clear(STREAM_NONE, STREAM_SOURCE_DEMUX_SUB); + m_SelectionStreams.Update(NULL, m_pSubtitleDemuxer); + } + return true; + } + } + + // read a data frame from stream. + if(m_pDemuxer) + packet = m_pDemuxer->Read(); + + if(packet) + { + UpdateCorrection(packet, m_offset_pts); + // this groupId stuff is getting a bit messy, need to find a better way + // currently it is used to determine if a menu overlay is associated with a picture + // for dvd's we use as a group id, the current cell and the current title + // to be a bit more precise we alse count the number of disc's in case of a pts wrap back in the same cell / title + packet->iGroupId = m_pInputStream->GetCurrentGroupId(); + + if(packet->iStreamId < 0) + return true; + + stream = m_pDemuxer->GetStream(packet->iStreamId); + if (!stream) + { + CLog::Log(LOGERROR, "%s - Error demux packet doesn't belong to a valid stream", __FUNCTION__); + return false; + } + if(stream->source == STREAM_SOURCE_NONE) + { + m_SelectionStreams.Clear(STREAM_NONE, STREAM_SOURCE_DEMUX); + m_SelectionStreams.Update(m_pInputStream, m_pDemuxer); + } + return true; + } + return false; +} + +bool COMXPlayer::IsValidStream(COMXCurrentStream& stream) +{ + if(stream.id<0) + return true; // we consider non selected as valid + + int source = STREAM_SOURCE_MASK(stream.source); + if(source == STREAM_SOURCE_TEXT) + return true; + if(source == STREAM_SOURCE_DEMUX_SUB) + { + CDemuxStream* st = m_pSubtitleDemuxer->GetStream(stream.id); + if(st == NULL || st->disabled) + return false; + if(st->type != stream.type) + return false; + return true; + } + if(source == STREAM_SOURCE_DEMUX) + { + CDemuxStream* st = m_pDemuxer->GetStream(stream.id); + if(st == NULL || st->disabled) + return false; + if(st->type != stream.type) + return false; + + if (m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD)) + { + if(stream.type == STREAM_AUDIO && st->iPhysicalId != m_dvd.iSelectedAudioStream) + return false; + if(stream.type == STREAM_SUBTITLE && st->iPhysicalId != m_dvd.iSelectedSPUStream) + return false; + } + + return true; + } + + return false; +} + +bool COMXPlayer::IsBetterStream(COMXCurrentStream& current, CDemuxStream* stream) +{ + // Do not reopen non-video streams if we're in video-only mode + //if(m_PlayerOptions.video_only && current.type != STREAM_VIDEO) + // return false; + + if (m_pInputStream && ( m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD) + || m_pInputStream->IsStreamType(DVDSTREAM_TYPE_BLURAY) ) ) + { + int source_type; + + source_type = STREAM_SOURCE_MASK(current.source); + if(source_type != STREAM_SOURCE_DEMUX + && source_type != STREAM_SOURCE_NONE) + return false; + + source_type = STREAM_SOURCE_MASK(stream->source); + if(source_type != STREAM_SOURCE_DEMUX + || stream->type != current.type + || stream->iId == current.id) + return false; + + if(current.type == STREAM_AUDIO && stream->iPhysicalId == m_dvd.iSelectedAudioStream) + return true; + if(current.type == STREAM_SUBTITLE && stream->iPhysicalId == m_dvd.iSelectedSPUStream) + return true; + if(current.type == STREAM_VIDEO && current.id < 0) + return true; + } + else + { + if(stream->source == current.source + && stream->iId == current.id) + return false; + + if(stream->disabled) + return false; + + if(stream->type != current.type) + return false; + + if(current.type == STREAM_SUBTITLE) + return false; + + if(current.type == STREAM_TELETEXT) + return false; + + if(current.id < 0) + return true; + } + return false; +} + +bool COMXPlayer::WaitForPausedThumbJobs(int timeout_ms) +{ + // use m_bStop and Sleep so we can get canceled. + while (!m_bStop && (timeout_ms > 0)) + { + if (CJobManager::GetInstance().IsProcessing(kJobTypeMediaFlags) > 0) + { + Sleep(100); + timeout_ms -= 100; + } + else + return true; + } + + return false; +} + +void COMXPlayer::Process() +{ + bool bAEStopped = false; + + if(!m_av_clock.OMXInitialize(false, false)) + { + m_bAbortRequest = true; + return; + } + if(g_guiSettings.GetBool("videoplayer.adjustrefreshrate")) + m_av_clock.HDMIClockSync(); + + m_av_clock.OMXStateExecute(); + m_av_clock.OMXStart(); + + //CLog::Log(LOGDEBUG, "COMXPlayer: Thread started"); + + try + { + m_stats = false; + + if(!OpenInputStream()) + { + m_bAbortRequest = true; + return; + } + + if(m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD)) + { + CLog::Log(LOGNOTICE, "OMXPlayer: playing a dvd with menu's"); + m_PlayerOptions.starttime = 0; + + if(m_PlayerOptions.state.size() > 0) + ((CDVDInputStreamNavigator*)m_pInputStream)->SetNavigatorState(m_PlayerOptions.state); + else + ((CDVDInputStreamNavigator*)m_pInputStream)->EnableSubtitleStream(g_settings.m_currentVideoSettings.m_SubtitleOn); + + g_settings.m_currentVideoSettings.m_SubtitleCached = true; + } + + if(!OpenDemuxStream()) + { + m_bAbortRequest = true; + return; + } + + m_player_video.EnableFullscreen(true); + + OpenDefaultStreams(); + + // look for any EDL files + m_Edl.Clear(); + m_EdlAutoSkipMarkers.Clear(); + float fFramesPerSecond; + if (m_CurrentVideo.id >= 0 && m_CurrentVideo.hint.fpsrate > 0 && m_CurrentVideo.hint.fpsscale > 0) + { + fFramesPerSecond = (float)m_CurrentVideo.hint.fpsrate / (float)m_CurrentVideo.hint.fpsscale; + m_Edl.ReadEditDecisionLists(m_filename, fFramesPerSecond, m_CurrentVideo.hint.height); + } + + /* + * Check to see if the demuxer should start at something other than time 0. This will be the case + * if there was a start time specified as part of the "Start from where last stopped" (aka + * auto-resume) feature or if there is an EDL cut or commercial break that starts at time 0. + */ + CEdl::Cut cut; + int starttime = 0; + if(m_PlayerOptions.starttime > 0 || m_PlayerOptions.startpercent > 0) + { + if (m_PlayerOptions.startpercent > 0 && m_pDemuxer) + { + int64_t playerStartTime = (int64_t) ( ( (float) m_pDemuxer->GetStreamLength() ) * ( m_PlayerOptions.startpercent/(float)100 ) ); + starttime = m_Edl.RestoreCutTime(playerStartTime); + } + else + { + starttime = m_Edl.RestoreCutTime((int64_t)m_PlayerOptions.starttime * 1000); // s to ms + } + CLog::Log(LOGDEBUG, "%s - Start position set to last stopped position: %d", __FUNCTION__, starttime); + } + else if(m_Edl.InCut(0, &cut) + && (cut.action == CEdl::CUT || cut.action == CEdl::COMM_BREAK)) + { + starttime = cut.end; + CLog::Log(LOGDEBUG, "%s - Start position set to end of first cut or commercial break: %d", __FUNCTION__, starttime); + if(cut.action == CEdl::COMM_BREAK) + { + /* + * Setup auto skip markers as if the commercial break had been skipped using standard + * detection. + */ + m_EdlAutoSkipMarkers.commbreak_start = cut.start; + m_EdlAutoSkipMarkers.commbreak_end = cut.end; + m_EdlAutoSkipMarkers.seek_to_start = true; + } + } + if(starttime > 0) + { + double startpts = DVD_NOPTS_VALUE; + if(m_pDemuxer) + { + //m_messenger.Put(new CDVDMsgPlayerSeek((int)starttime, true, true, true)); + if (m_pDemuxer->SeekTime(starttime, false, &startpts)) + CLog::Log(LOGDEBUG, "%s - starting demuxer from: %d", __FUNCTION__, starttime); + else + CLog::Log(LOGDEBUG, "%s - failed to start demuxing from: %d", __FUNCTION__, starttime); + } + if(m_pSubtitleDemuxer) + { + if(m_pSubtitleDemuxer->SeekTime(starttime, false, &startpts)) + CLog::Log(LOGDEBUG, "%s - starting subtitle demuxer from: %d", __FUNCTION__, starttime); + else + CLog::Log(LOGDEBUG, "%s - failed to start subtitle demuxing from: %d", __FUNCTION__, starttime); + } + } + + UpdateApplication(0); + UpdatePlayState(0); + + if (m_PlayerOptions.identify == false) + m_callback.OnPlayBackStarted(); + + // drop CGUIDialogBusy, and release the hold in OpenFile + m_ready.Set(); + + SetCaching(CACHESTATE_FLUSH); + + // shutdown AE + CAEFactory::Shutdown(); + bAEStopped = true; + + // stop thumb jobs + CJobManager::GetInstance().Pause(kJobTypeMediaFlags); + + if (CJobManager::GetInstance().IsProcessing(kJobTypeMediaFlags) > 0) + { + if (!WaitForPausedThumbJobs(20000)) + { + CJobManager::GetInstance().UnPause(kJobTypeMediaFlags); + CLog::Log(LOGINFO, "COMXPlayer::Process:thumbgen jobs still running !!!"); + } + } + + while (!m_bAbortRequest) + { + // handle messages send to this thread, like seek or demuxer reset requests + HandleMessages(); + + if(m_bAbortRequest) + break; + + // should we open a new input stream? + if(!m_pInputStream) + { + if (OpenInputStream() == false) + { + m_bAbortRequest = true; + break; + } + } + + // should we open a new demuxer? + if(!m_pDemuxer) + { + if (m_pInputStream->NextStream() == false) + break; + + if (m_pInputStream->IsEOF()) + break; + + if (OpenDemuxStream() == false) + { + m_bAbortRequest = true; + break; + } + + OpenDefaultStreams(); + UpdateApplication(0); + UpdatePlayState(0); + } + + // handle eventual seeks due to playspeed + HandlePlaySpeed(); + + // on seek back the omx clock is reset to 0 + //m_av_clock.OMXHandleBackward(); + + // update player state + UpdatePlayState(200); + + // update application with our state + UpdateApplication(1000); + + // if the queues are full, no need to read more + if ((!m_player_audio.AcceptsData() && m_CurrentAudio.id >= 0) + || (!m_player_video.AcceptsData() && m_CurrentVideo.id >= 0)) + { + Sleep(10); + continue; + } + + // always yield to players if they have data + if((m_player_audio.HasData() || m_CurrentAudio.id < 0) + && (m_player_video.HasData() || m_CurrentVideo.id < 0)) + Sleep(0); + + DemuxPacket* pPacket = NULL; + CDemuxStream *pStream = NULL; + ReadPacket(pPacket, pStream); + + if (pPacket && !pStream) + { + /* probably a empty packet, just free it and move on */ + CDVDDemuxUtils::FreeDemuxPacket(pPacket); + continue; + } + + if (!pPacket) + { + // when paused, demuxer could be be returning empty + if (m_playSpeed == DVD_PLAYSPEED_PAUSE) + continue; + + // check for a still frame state + if (m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD)) + { + CDVDInputStreamNavigator* pStream = static_cast<CDVDInputStreamNavigator*>(m_pInputStream); + + // stills will be skipped + if(m_dvd.state == DVDSTATE_STILL) + { + if (m_dvd.iDVDStillTime > 0) + { + if ((XbmcThreads::SystemClockMillis() - m_dvd.iDVDStillStartTime) >= m_dvd.iDVDStillTime) + { + m_dvd.iDVDStillTime = 0; + m_dvd.iDVDStillStartTime = 0; + m_dvd.state = DVDSTATE_NORMAL; + pStream->SkipStill(); + continue; + } + } + } + } + + // if there is another stream available, reopen demuxer + CDVDInputStream::ENextStream next = m_pInputStream->NextStream(); + if(next == CDVDInputStream::NEXTSTREAM_OPEN) + { + SAFE_DELETE(m_pDemuxer); + continue; + } + + // input stream asked us to just retry + if(next == CDVDInputStream::NEXTSTREAM_RETRY) + { + Sleep(100); + continue; + } + + // make sure we tell all players to finish it's data + if(m_CurrentAudio.inited) + m_player_audio.SendMessage (new CDVDMsg(CDVDMsg::GENERAL_EOF)); + if(m_CurrentVideo.inited) + m_player_video.SendMessage (new CDVDMsg(CDVDMsg::GENERAL_EOF)); + if(m_CurrentSubtitle.inited) + m_player_subtitle.SendMessage(new CDVDMsg(CDVDMsg::GENERAL_EOF)); + m_CurrentAudio.inited = false; + m_CurrentVideo.inited = false; + m_CurrentSubtitle.inited = false; + m_CurrentAudio.started = false; + m_CurrentVideo.started = false; + m_CurrentSubtitle.started = false; + + // if we are caching, start playing it again + SetCaching(CACHESTATE_DONE); + + if(m_player_video.HasData() + || m_player_audio.HasData()) + { + Sleep(100); + continue; + } + + if (!m_pInputStream->IsEOF()) + CLog::Log(LOGINFO, "%s - eof reading from demuxer", __FUNCTION__); + + break; + } + + // check so that none of our streams has become invalid + if (!IsValidStream(m_CurrentAudio) && m_player_audio.IsStalled()) CloseAudioStream(true); + if (!IsValidStream(m_CurrentVideo) && m_player_video.IsStalled()) CloseVideoStream(true); + if (!IsValidStream(m_CurrentSubtitle) && m_player_subtitle.IsStalled()) CloseSubtitleStream(true); + + // see if we can find something better to play + if (IsBetterStream(m_CurrentAudio, pStream)) OpenAudioStream (pStream->iId, pStream->source); + if (IsBetterStream(m_CurrentVideo, pStream)) OpenVideoStream (pStream->iId, pStream->source); + if (IsBetterStream(m_CurrentSubtitle, pStream)) OpenSubtitleStream(pStream->iId, pStream->source); + + if(m_change_volume) + { + m_player_audio.SetCurrentVolume(m_current_volume); + m_change_volume = false; + } + + ProcessPacket(pStream, pPacket); + + // check if in a cut or commercial break that should be automatically skipped + CheckAutoSceneSkip(); + + /* + printf("V : %8.02f %8d %8d A : %8.02f %8.02f \r", + m_player_video.GetCurrentPTS() / DVD_TIME_BASE, m_player_video.GetDecoderBufferSize(), + m_player_video.GetDecoderFreeSpace(), m_player_audio.GetCurrentPTS() / DVD_TIME_BASE, + m_player_audio.GetDelay()); + */ + } + } + catch(...) + { + CLog::Log(LOGERROR, "COMXPlayer::Process: Exception thrown"); + } + + if(bAEStopped) + { + // start AE + CAEFactory::LoadEngine(); + CAEFactory::StartEngine(); + + CAEFactory::SetMute (g_settings.m_bMute); + CAEFactory::SetSoundMode(g_guiSettings.GetInt("audiooutput.guisoundmode")); + } + + // let thumbgen jobs resume. + CJobManager::GetInstance().UnPause(kJobTypeMediaFlags); +} + +void COMXPlayer::ProcessPacket(CDemuxStream* pStream, DemuxPacket* pPacket) +{ + /* process packet if it belongs to selected stream. for dvd's don't allow automatic opening of streams*/ + OMXStreamLock lock(this); + + try + { + if (pPacket->iStreamId == m_CurrentAudio.id && pStream->source == m_CurrentAudio.source && pStream->type == STREAM_AUDIO) + ProcessAudioData(pStream, pPacket); + else if (pPacket->iStreamId == m_CurrentVideo.id && pStream->source == m_CurrentVideo.source && pStream->type == STREAM_VIDEO) + ProcessVideoData(pStream, pPacket); + else if (pPacket->iStreamId == m_CurrentSubtitle.id && pStream->source == m_CurrentSubtitle.source && pStream->type == STREAM_SUBTITLE) + ProcessSubData(pStream, pPacket); + else + { + pStream->SetDiscard(AVDISCARD_ALL); + CDVDDemuxUtils::FreeDemuxPacket(pPacket); // free it since we won't do anything with it + } + } + catch(...) + { + CLog::Log(LOGERROR, "%s - Exception thrown when processing demux packet", __FUNCTION__); + } + +} + +void COMXPlayer::ProcessAudioData(CDemuxStream* pStream, DemuxPacket* pPacket) +{ + if (m_CurrentAudio.stream != (void*)pStream) + { + /* check so that dmuxer hints or extra data hasn't changed */ + /* if they have, reopen stream */ + + if (m_CurrentAudio.hint != CDVDStreamInfo(*pStream, true)) + OpenAudioStream( pPacket->iStreamId, pStream->source ); + + m_CurrentAudio.stream = (void*)pStream; + } + + // check if we are too slow and need to recache + CheckStartCaching(m_CurrentAudio); + + CheckContinuity(m_CurrentAudio, pPacket); + UpdateTimestamps(m_CurrentAudio, pPacket); + bool drop = false; + if (CheckPlayerInit(m_CurrentAudio, DVDPLAYER_AUDIO)) + drop = true; + + /* + * If CheckSceneSkip() returns true then demux point is inside an EDL cut and the packets are dropped. + * If not inside a hard cut, but the demux point has reached an EDL mute section then trigger the + * AUDIO_SILENCE state. The AUDIO_SILENCE state is reverted as soon as the demux point is outside + * of any EDL section while EDL mute is still active. + */ + CEdl::Cut cut; + if (CheckSceneSkip(m_CurrentAudio)) + drop = true; + else if (m_Edl.InCut(DVD_TIME_TO_MSEC(m_CurrentAudio.dts + m_offset_pts), &cut) && cut.action == CEdl::MUTE // Inside EDL mute + && !m_EdlAutoSkipMarkers.mute) // Mute not already triggered + { + m_player_audio.SendMessage(new CDVDMsgBool(CDVDMsg::AUDIO_SILENCE, true)); + m_EdlAutoSkipMarkers.mute = true; + } + else if (!m_Edl.InCut(DVD_TIME_TO_MSEC(m_CurrentAudio.dts + m_offset_pts), &cut) // Outside of any EDL + && m_EdlAutoSkipMarkers.mute) // But the mute hasn't been removed yet + { + m_player_audio.SendMessage(new CDVDMsgBool(CDVDMsg::AUDIO_SILENCE, false)); + m_EdlAutoSkipMarkers.mute = false; + } + + m_player_audio.SendMessage(new CDVDMsgDemuxerPacket(pPacket, drop)); +} + +void COMXPlayer::ProcessVideoData(CDemuxStream* pStream, DemuxPacket* pPacket) +{ + if (m_CurrentVideo.stream != (void*)pStream) + { + /* check so that dmuxer hints or extra data hasn't changed */ + /* if they have reopen stream */ + + if (m_CurrentVideo.hint != CDVDStreamInfo(*pStream, true)) + OpenVideoStream(pPacket->iStreamId, pStream->source); + + m_CurrentVideo.stream = (void*)pStream; + } + + // check if we are too slow and need to recache + CheckStartCaching(m_CurrentVideo); + + if( pPacket->iSize != 4) //don't check the EOF_SEQUENCE of stillframes + { + CheckContinuity(m_CurrentVideo, pPacket); + UpdateTimestamps(m_CurrentVideo, pPacket); + } + + bool drop = false; + if (CheckPlayerInit(m_CurrentVideo, DVDPLAYER_VIDEO)) + drop = true; + + if (CheckSceneSkip(m_CurrentVideo)) + drop = true; + + m_player_video.SendMessage(new CDVDMsgDemuxerPacket(pPacket, drop)); +} + +void COMXPlayer::ProcessSubData(CDemuxStream* pStream, DemuxPacket* pPacket) +{ + if (m_CurrentSubtitle.stream != (void*)pStream) + { + /* check so that dmuxer hints or extra data hasn't changed */ + /* if they have reopen stream */ + + if (m_CurrentSubtitle.hint != CDVDStreamInfo(*pStream, true)) + OpenSubtitleStream(pPacket->iStreamId, pStream->source); + + m_CurrentSubtitle.stream = (void*)pStream; + } + + UpdateTimestamps(m_CurrentSubtitle, pPacket); + + bool drop = false; + if (CheckPlayerInit(m_CurrentSubtitle, DVDPLAYER_SUBTITLE)) + drop = true; + + if (CheckSceneSkip(m_CurrentSubtitle)) + drop = true; + + m_player_subtitle.SendMessage(new CDVDMsgDemuxerPacket(pPacket, drop)); + + if(m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD)) + m_player_subtitle.UpdateOverlayInfo((CDVDInputStreamNavigator*)m_pInputStream, LIBDVDNAV_BUTTON_NORMAL); +} + +bool COMXPlayer::GetCachingTimes(double& level, double& delay, double& offset) +{ + if(!m_pInputStream || !m_pDemuxer) + return false; + + XFILE::SCacheStatus status; + if (!m_pInputStream->GetCacheStatus(&status)) + return false; + + int64_t cached = status.forward; + unsigned currate = status.currate; + unsigned maxrate = status.maxrate; + bool full = status.full; + + int64_t length = m_pInputStream->GetLength(); + int64_t remain = length - m_pInputStream->Seek(0, SEEK_CUR); + + if(cached < 0 || length <= 0 || remain < 0) + return false; + + double play_sbp = DVD_MSEC_TO_TIME(m_pDemuxer->GetStreamLength()) / length; + double queued = 1000.0 * GetQueueTime() / play_sbp; + + delay = 0.0; + level = 0.0; + offset = (double)(cached + queued) / length; + + if (currate == 0) + return true; + + double cache_sbp = 1.1 * (double)DVD_TIME_BASE / currate; /* underestimate by 10 % */ + double play_left = play_sbp * (remain + queued); /* time to play out all remaining bytes */ + double cache_left = cache_sbp * (remain - cached); /* time to cache the remaining bytes */ + double cache_need = std::max(0.0, remain - play_left / cache_sbp); /* bytes needed until play_left == cache_left */ + + delay = cache_left - play_left; + + if (full && (currate < maxrate) ) + level = -1.0; /* buffer is full & our read rate is too low */ + else + level = (cached + queued) / (cache_need + queued); + + return true; +} + +void COMXPlayer::HandlePlaySpeed() +{ + ECacheState caching = m_caching; + + if(IsInMenu() && caching != CACHESTATE_DONE) + caching = CACHESTATE_DONE; + + if(caching == CACHESTATE_FULL) + { + double level, delay, offset; + if(GetCachingTimes(level, delay, offset)) + { + if(level < 0.0) + caching = CACHESTATE_INIT; + if(level >= 1.0) + caching = CACHESTATE_INIT; + } + else + { + if ((!m_player_audio.AcceptsData() && m_CurrentAudio.id >= 0) + || (!m_player_video.AcceptsData() && m_CurrentVideo.id >= 0)) + caching = CACHESTATE_INIT; + } + } + + if(caching == CACHESTATE_INIT) + { + // if all enabled streams have been inited we are done + if((m_CurrentVideo.id < 0 || m_CurrentVideo.started) + && (m_CurrentAudio.id < 0 || m_CurrentAudio.started)) + caching = CACHESTATE_PLAY; + + // handle situation that we get no data on one stream + if(m_CurrentAudio.id >= 0 && m_CurrentVideo.id >= 0) + { + if ((!m_player_audio.AcceptsData() && !m_CurrentVideo.started) + || (!m_player_video.AcceptsData() && !m_CurrentAudio.started)) + { + caching = CACHESTATE_DONE; + } + } + } + + if(caching == CACHESTATE_PLAY) + { + // if all enabled streams have started playing we are done + if((m_CurrentVideo.id < 0 || !m_player_video.IsStalled()) + && (m_CurrentAudio.id < 0 || !m_player_audio.IsStalled())) + caching = CACHESTATE_DONE; + } + + if(m_caching != caching) + SetCaching(caching); + + if(GetPlaySpeed() != DVD_PLAYSPEED_NORMAL && GetPlaySpeed() != DVD_PLAYSPEED_PAUSE) + { + if (IsInMenu()) + { + // this can't be done in menu + SetPlaySpeed(DVD_PLAYSPEED_NORMAL); + + } + else if (m_CurrentVideo.id >= 0 + && m_CurrentVideo.inited == true + && m_SpeedState.lastpts != m_player_video.GetCurrentPTS() + && m_SpeedState.lasttime != GetTime()) + { + m_SpeedState.lastpts = m_player_video.GetCurrentPTS(); + m_SpeedState.lasttime = GetTime(); + // check how much off clock video is when ff/rw:ing + // a problem here is that seeking isn't very accurate + // and since the clock will be resynced after seek + // we might actually not really be playing at the wanted + // speed. we'd need to have some way to not resync the clock + // after a seek to remember timing. still need to handle + // discontinuities somehow + + // when seeking, give the player a headstart to make sure + // the time it takes to seek doesn't make a difference. + double error; + error = m_av_clock.GetClock() - m_SpeedState.lastpts; + error *= m_playSpeed / abs(m_playSpeed); + + if(error > DVD_MSEC_TO_TIME(1000)) + { + CLog::Log(LOGDEBUG, "COMXPlayer::Process - Seeking to catch up"); + int64_t iTime = (int64_t)DVD_TIME_TO_MSEC(m_av_clock.GetClock() + m_State.time_offset + 500000.0 * m_playSpeed / DVD_PLAYSPEED_NORMAL); + m_messenger.Put(new CDVDMsgPlayerSeek(iTime, (GetPlaySpeed() < 0), true, false, false, true)); + } + } + } +} + +bool COMXPlayer::CheckStartCaching(COMXCurrentStream& current) +{ + if(m_caching != CACHESTATE_DONE + || m_playSpeed != DVD_PLAYSPEED_NORMAL) + return false; + + if(IsInMenu()) + return false; + + if((current.type == STREAM_AUDIO && m_player_audio.IsStalled()) + || (current.type == STREAM_VIDEO && m_player_video.IsStalled())) + { + // don't start caching if it's only a single stream that has run dry + if(m_player_audio.GetLevel() > 50 + || m_player_video.GetLevel() > 50) + return false; + + if(m_pInputStream->IsStreamType(DVDSTREAM_TYPE_HTSP) + || m_pInputStream->IsStreamType(DVDSTREAM_TYPE_TV)) + SetCaching(CACHESTATE_INIT); + else + { + if(current.inited) + SetCaching(CACHESTATE_FULL); + else + SetCaching(CACHESTATE_INIT); + } + return true; + } + return false; +} + +bool COMXPlayer::CheckPlayerInit(COMXCurrentStream& current, unsigned int source) +{ + if(current.inited) + return false; + + if(current.startpts != DVD_NOPTS_VALUE) + { + if(current.dts == DVD_NOPTS_VALUE) + { + CLog::Log(LOGDEBUG, "%s - dropping packet type:%d dts:%f to get to start point at %f", __FUNCTION__, source, current.dts, current.startpts); + return true; + } + + if((current.startpts - current.dts) > DVD_SEC_TO_TIME(20)) + { + CLog::Log(LOGDEBUG, "%s - too far to decode before finishing seek", __FUNCTION__); + if(m_CurrentAudio.startpts != DVD_NOPTS_VALUE) + m_CurrentAudio.startpts = current.dts; + if(m_CurrentVideo.startpts != DVD_NOPTS_VALUE) + m_CurrentVideo.startpts = current.dts; + if(m_CurrentSubtitle.startpts != DVD_NOPTS_VALUE) + m_CurrentSubtitle.startpts = current.dts; + if(m_CurrentTeletext.startpts != DVD_NOPTS_VALUE) + m_CurrentTeletext.startpts = current.dts; + } + + if(current.dts < current.startpts) + { + CLog::Log(LOGDEBUG, "%s - dropping packet type:%d dts:%f to get to start point at %f", __FUNCTION__, source, current.dts, current.startpts); + return true; + } + } + + //If this is the first packet after a discontinuity, send it as a resync + if (current.dts != DVD_NOPTS_VALUE) + { + current.inited = true; + current.startpts = current.dts; + + bool setclock = false; + if(m_playSpeed == DVD_PLAYSPEED_NORMAL) + { + if( source == DVDPLAYER_AUDIO) + setclock = !m_CurrentVideo.inited; + else if(source == DVDPLAYER_VIDEO) + setclock = !m_CurrentAudio.inited; + } + else + { + if(source == DVDPLAYER_VIDEO) + setclock = true; + } + + double starttime = current.startpts; + if(m_CurrentAudio.inited + && m_CurrentAudio.startpts != DVD_NOPTS_VALUE + && m_CurrentAudio.startpts < starttime) + starttime = m_CurrentAudio.startpts; + if(m_CurrentVideo.inited + && m_CurrentVideo.startpts != DVD_NOPTS_VALUE + && m_CurrentVideo.startpts < starttime) + starttime = m_CurrentVideo.startpts; + + starttime = current.startpts - starttime; + if(starttime > 0 && setclock) + { + if(starttime > DVD_SEC_TO_TIME(2)) + CLog::Log(LOGWARNING, "COMXPlayer::CheckPlayerInit(%d) - Ignoring too large delay of %f", source, starttime); + else + SendPlayerMessage(new CDVDMsgDouble(CDVDMsg::GENERAL_DELAY, starttime), source); + } + + SendPlayerMessage(new CDVDMsgGeneralResync(current.dts, setclock), source); + } + return false; +} + +void COMXPlayer::UpdateCorrection(DemuxPacket* pkt, double correction) +{ + if(pkt->dts != DVD_NOPTS_VALUE) pkt->dts -= correction; + if(pkt->pts != DVD_NOPTS_VALUE) pkt->pts -= correction; +} + +void COMXPlayer::UpdateTimestamps(COMXCurrentStream& current, DemuxPacket* pPacket) +{ + double dts = current.dts; + /* update stored values */ + if(pPacket->dts != DVD_NOPTS_VALUE) + dts = pPacket->dts; + else if(pPacket->pts != DVD_NOPTS_VALUE) + dts = pPacket->pts; + + /* calculate some average duration */ + if(pPacket->duration != DVD_NOPTS_VALUE) + current.dur = pPacket->duration; + else if(dts != DVD_NOPTS_VALUE && current.dts != DVD_NOPTS_VALUE) + current.dur = 0.1 * (current.dur * 9 + (dts - current.dts)); + + current.dts = dts; +} + +void COMXPlayer::UpdateLimits(double& minimum, double& maximum, double dts) +{ + if(dts == DVD_NOPTS_VALUE) + return; + if(minimum == DVD_NOPTS_VALUE || minimum > dts) minimum = dts; + if(maximum == DVD_NOPTS_VALUE || maximum < dts) maximum = dts; +} + +void COMXPlayer::CheckContinuity(COMXCurrentStream& current, DemuxPacket* pPacket) +{ + if (m_playSpeed < DVD_PLAYSPEED_PAUSE) + return; + + if( pPacket->dts == DVD_NOPTS_VALUE || current.dts == DVD_NOPTS_VALUE) + return; + + double mindts = DVD_NOPTS_VALUE, maxdts = DVD_NOPTS_VALUE; + UpdateLimits(mindts, maxdts, m_CurrentAudio.dts); + UpdateLimits(mindts, maxdts, m_CurrentVideo.dts); + UpdateLimits(mindts, maxdts, m_CurrentAudio.dts_end()); + UpdateLimits(mindts, maxdts, m_CurrentVideo.dts_end()); + + /* if we don't have max and min, we can't do anything more */ + if( mindts == DVD_NOPTS_VALUE || maxdts == DVD_NOPTS_VALUE ) + return; + + double correction = 0.0; + if( pPacket->dts > maxdts + DVD_MSEC_TO_TIME(1000)) + { + CLog::Log(LOGDEBUG, "COMXPlayer::CheckContinuity - resync forward :%d, prev:%f, curr:%f, diff:%f" + , current.type, current.dts, pPacket->dts, pPacket->dts - maxdts); + correction = pPacket->dts - maxdts; + } + + /* if it's large scale jump, correct for it */ + if(pPacket->dts + DVD_MSEC_TO_TIME(100) < current.dts_end()) + { + CLog::Log(LOGDEBUG, "COMXPlayer::CheckContinuity - resync backward :%d, prev:%f, curr:%f, diff:%f" + , current.type, current.dts, pPacket->dts, pPacket->dts - current.dts); + correction = pPacket->dts - current.dts_end(); + } + else if(pPacket->dts < current.dts) + { + CLog::Log(LOGDEBUG, "COMXPlayer::CheckContinuity - wrapback :%d, prev:%f, curr:%f, diff:%f" + , current.type, current.dts, pPacket->dts, pPacket->dts - current.dts); + } + + if(correction != 0.0) + { + /* disable detection on next packet on other stream to avoid ping pong-ing */ + if(m_CurrentAudio.player != current.player) m_CurrentAudio.dts = DVD_NOPTS_VALUE; + if(m_CurrentVideo.player != current.player) m_CurrentVideo.dts = DVD_NOPTS_VALUE; + + m_offset_pts += correction; + UpdateCorrection(pPacket, correction); + } +} + +bool COMXPlayer::CheckSceneSkip(COMXCurrentStream& current) +{ + if(!m_Edl.HasCut()) + return false; + + if(current.dts == DVD_NOPTS_VALUE) + return false; + + if(current.inited == false) + return false; + + CEdl::Cut cut; + return m_Edl.InCut(DVD_TIME_TO_MSEC(current.dts + m_offset_pts), &cut) && cut.action == CEdl::CUT; +} + +void COMXPlayer::CheckAutoSceneSkip() +{ + if(!m_Edl.HasCut()) + return; + + /* + * Check that there is an audio and video stream. + */ + if(m_CurrentAudio.id < 0 + || m_CurrentVideo.id < 0) + return; + + /* + * If there is a startpts defined for either the audio or video stream then dvdplayer is still + * still decoding frames to get to the previously requested seek point. + */ + if(m_CurrentAudio.inited == false + || m_CurrentVideo.inited == false) + return; + + if(m_CurrentAudio.dts == DVD_NOPTS_VALUE + || m_CurrentVideo.dts == DVD_NOPTS_VALUE) + return; + + const int64_t clock = DVD_TIME_TO_MSEC(min(m_CurrentAudio.dts, m_CurrentVideo.dts) + m_offset_pts); + + CEdl::Cut cut; + if(!m_Edl.InCut(clock, &cut)) + return; + + if(cut.action == CEdl::CUT + && !(cut.end == m_EdlAutoSkipMarkers.cut || cut.start == m_EdlAutoSkipMarkers.cut)) // To prevent looping if same cut again + { + CLog::Log(LOGDEBUG, "%s - Clock in EDL cut [%s - %s]: %s. Automatically skipping over.", + __FUNCTION__, CEdl::MillisecondsToTimeString(cut.start).c_str(), + CEdl::MillisecondsToTimeString(cut.end).c_str(), CEdl::MillisecondsToTimeString(clock).c_str()); + /* + * Seeking either goes to the start or the end of the cut depending on the play direction. + */ + int64_t seek = GetPlaySpeed() >= 0 ? cut.end : cut.start; + /* + * Seeking is NOT flushed so any content up to the demux point is retained when playing forwards. + */ + m_messenger.Put(new CDVDMsgPlayerSeek((int)seek, true, false, true, false, true)); + /* + * Seek doesn't always work reliably. Last physical seek time is recorded to prevent looping + * if there was an error with seeking and it landed somewhere unexpected, perhaps back in the + * cut. The cut automatic skip marker is reset every 500ms allowing another attempt at the seek. + */ + m_EdlAutoSkipMarkers.cut = GetPlaySpeed() >= 0 ? cut.end : cut.start; + } + else if(cut.action == CEdl::COMM_BREAK + && GetPlaySpeed() >= 0 + && cut.start > m_EdlAutoSkipMarkers.commbreak_end) + { + CLog::Log(LOGDEBUG, "%s - Clock in commercial break [%s - %s]: %s. Automatically skipping to end of commercial break (only done once per break)", + __FUNCTION__, CEdl::MillisecondsToTimeString(cut.start).c_str(), CEdl::MillisecondsToTimeString(cut.end).c_str(), + CEdl::MillisecondsToTimeString(clock).c_str()); + /* + * Seeking is NOT flushed so any content up to the demux point is retained when playing forwards. + */ + m_messenger.Put(new CDVDMsgPlayerSeek(cut.end + 1, true, false, true, false, true)); + /* + * Each commercial break is only skipped once so poorly detected commercial breaks can be + * manually re-entered. Start and end are recorded to prevent looping and to allow seeking back + * to the start of the commercial break if incorrectly flagged. + */ + m_EdlAutoSkipMarkers.commbreak_start = cut.start; + m_EdlAutoSkipMarkers.commbreak_end = cut.end; + m_EdlAutoSkipMarkers.seek_to_start = true; // Allow backwards Seek() to go directly to the start + } +} + +void COMXPlayer::SynchronizeDemuxer(unsigned int timeout) +{ + if(IsCurrentThread()) + return; + if(!m_messenger.IsInited()) + return; + + CDVDMsgGeneralSynchronize* message = new CDVDMsgGeneralSynchronize(timeout, 0); + m_messenger.Put(message->Acquire()); + message->Wait(&m_bStop, 0); + message->Release(); +} + +void COMXPlayer::SynchronizePlayers(unsigned int sources) +{ + /* we need a big timeout as audio queue is about 8seconds for 2ch ac3 */ + const int timeout = 10*1000; // in milliseconds + + CDVDMsgGeneralSynchronize* message = new CDVDMsgGeneralSynchronize(timeout, sources); + if (m_CurrentAudio.id >= 0) + m_player_audio.SendMessage(message->Acquire()); + + if (m_CurrentVideo.id >= 0) + m_player_video.SendMessage(message->Acquire()); +/* TODO - we have to rewrite the sync class, to not require + all other players waiting for subtitle, should only + be the oposite way + if (m_CurrentSubtitle.id >= 0) + m_player_subtitle.SendMessage(message->Acquire()); +*/ + message->Release(); +} + +void COMXPlayer::SendPlayerMessage(CDVDMsg* pMsg, unsigned int target) +{ + if(target == DVDPLAYER_AUDIO) + m_player_audio.SendMessage(pMsg); + if(target == DVDPLAYER_VIDEO) + m_player_video.SendMessage(pMsg); + if(target == DVDPLAYER_SUBTITLE) + m_player_subtitle.SendMessage(pMsg); +} + +void COMXPlayer::OnExit() +{ + try + { + CLog::Log(LOGNOTICE, "COMXPlayer::OnExit()"); + + m_av_clock.OMXStop(); + m_av_clock.OMXStateIdle(); + + // set event to inform openfile something went wrong in case openfile is still waiting for this event + SetCaching(CACHESTATE_DONE); + + // close each stream + if (!m_bAbortRequest) CLog::Log(LOGNOTICE, "OMXPlayer: eof, waiting for queues to empty"); + if (m_CurrentAudio.id >= 0) + { + CLog::Log(LOGNOTICE, "OMXPlayer: closing audio stream"); + CloseAudioStream(!m_bAbortRequest); + } + if (m_CurrentVideo.id >= 0) + { + CLog::Log(LOGNOTICE, "OMXPlayer: closing video stream"); + CloseVideoStream(!m_bAbortRequest); + } + if (m_CurrentSubtitle.id >= 0) + { + CLog::Log(LOGNOTICE, "OMXPlayer: closing subtitle stream"); + CloseSubtitleStream(!m_bAbortRequest); + } + /* + if (m_CurrentTeletext.id >= 0) + { + CLog::Log(LOGNOTICE, "OMXPlayer: closing teletext stream"); + CloseTeletextStream(!m_bAbortRequest); + } + */ + // destroy the demuxer + if (m_pDemuxer) + { + CLog::Log(LOGNOTICE, "COMXPlayer::OnExit() deleting demuxer"); + delete m_pDemuxer; + } + m_pDemuxer = NULL; + + if (m_pSubtitleDemuxer) + { + CLog::Log(LOGNOTICE, "COMXPlayer::OnExit() deleting subtitle demuxer"); + delete m_pSubtitleDemuxer; + } + m_pSubtitleDemuxer = NULL; + + // destroy the inputstream + if (m_pInputStream) + { + CLog::Log(LOGNOTICE, "COMXPlayer::OnExit() deleting input stream"); + delete m_pInputStream; + } + m_pInputStream = NULL; + + // clean up all selection streams + m_SelectionStreams.Clear(STREAM_NONE, STREAM_SOURCE_NONE); + + m_messenger.End(); + + m_av_clock.OMXDeinitialize(); + + } + catch (...) + { + CLog::Log(LOGERROR, "%s - Exception thrown when trying to close down player, memory leak will follow", __FUNCTION__); + m_pInputStream = NULL; + m_pDemuxer = NULL; + } + + m_bStop = true; + // if we didn't stop playing, advance to the next item in xbmc's playlist + if(m_PlayerOptions.identify == false) + { + if (m_bAbortRequest) + m_callback.OnPlayBackStopped(); + else + m_callback.OnPlayBackEnded(); + } + + // set event to inform openfile something went wrong in case openfile is still waiting for this event + m_ready.Set(); +} + +void COMXPlayer::HandleMessages() +{ + CDVDMsg* pMsg; + OMXStreamLock lock(this); + + while (m_messenger.Get(&pMsg, 0) == MSGQ_OK) + { + + try + { + if (pMsg->IsType(CDVDMsg::PLAYER_SEEK) && m_messenger.GetPacketCount(CDVDMsg::PLAYER_SEEK) == 0 + && m_messenger.GetPacketCount(CDVDMsg::PLAYER_SEEK_CHAPTER) == 0) + { + CDVDMsgPlayerSeek &msg(*((CDVDMsgPlayerSeek*)pMsg)); + + if(!msg.GetTrickPlay()) + { + g_infoManager.SetDisplayAfterSeek(100000); + if(msg.GetFlush()) + SetCaching(CACHESTATE_FLUSH); + } + + double start = DVD_NOPTS_VALUE; + + int time = msg.GetRestore() ? (int)m_Edl.RestoreCutTime(msg.GetTime()) : msg.GetTime(); + CLog::Log(LOGDEBUG, "demuxer seek to: %d", time); + if (m_pDemuxer && m_pDemuxer->SeekTime(time, msg.GetBackward(), &start)) + { + CLog::Log(LOGDEBUG, "demuxer seek to: %d, success", time); + if(m_pSubtitleDemuxer) + { + if(!m_pSubtitleDemuxer->SeekTime(time, msg.GetBackward())) + CLog::Log(LOGDEBUG, "failed to seek subtitle demuxer: %d, success", time); + } + FlushBuffers(!msg.GetFlush(), start, msg.GetAccurate()); + } + else + CLog::Log(LOGWARNING, "error while seeking"); + + // set flag to indicate we have finished a seeking request + if(!msg.GetTrickPlay()) + { + g_infoManager.m_performingSeek = false; + g_infoManager.SetDisplayAfterSeek(); + } + + // dvd's will issue a HOP_CHANNEL that we need to skip + if(m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD)) + m_dvd.state = DVDSTATE_SEEK; + } + else if (pMsg->IsType(CDVDMsg::PLAYER_SEEK_CHAPTER) && m_messenger.GetPacketCount(CDVDMsg::PLAYER_SEEK) == 0 + && m_messenger.GetPacketCount(CDVDMsg::PLAYER_SEEK_CHAPTER) == 0) + { + g_infoManager.SetDisplayAfterSeek(100000); + SetCaching(CACHESTATE_FLUSH); + + CDVDMsgPlayerSeekChapter &msg(*((CDVDMsgPlayerSeekChapter*)pMsg)); + double start = DVD_NOPTS_VALUE; + + // This should always be the case. + if(m_pDemuxer && m_pDemuxer->SeekChapter(msg.GetChapter(), &start)) + { + FlushBuffers(false, start, true); + m_callback.OnPlayBackSeekChapter(msg.GetChapter()); + } + + g_infoManager.SetDisplayAfterSeek(); + } + else if (pMsg->IsType(CDVDMsg::DEMUXER_RESET)) + { + m_CurrentAudio.stream = NULL; + m_CurrentVideo.stream = NULL; + m_CurrentSubtitle.stream = NULL; + + // we need to reset the demuxer, probably because the streams have changed + if(m_pDemuxer) + m_pDemuxer->Reset(); + if(m_pSubtitleDemuxer) + m_pSubtitleDemuxer->Reset(); + } + else if (pMsg->IsType(CDVDMsg::PLAYER_SET_AUDIOSTREAM)) + { + CDVDMsgPlayerSetAudioStream* pMsg2 = (CDVDMsgPlayerSetAudioStream*)pMsg; + + OMXSelectionStream& st = m_SelectionStreams.Get(STREAM_AUDIO, pMsg2->GetStreamId()); + if(st.source != STREAM_SOURCE_NONE) + { + if(st.source == STREAM_SOURCE_NAV && m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD)) + { + CDVDInputStreamNavigator* pStream = (CDVDInputStreamNavigator*)m_pInputStream; + if(pStream->SetActiveAudioStream(st.id)) + { + m_dvd.iSelectedAudioStream = -1; + CloseAudioStream(false); + CloseVideoStream(false); + m_messenger.Put(new CDVDMsgPlayerSeek(GetTime(), true, true, true)); + } + } + else + { + CloseAudioStream(false); + OpenAudioStream(st.id, st.source); + m_messenger.Put(new CDVDMsgPlayerSeek(GetTime(), true, true, true)); + } + } + } + else if (pMsg->IsType(CDVDMsg::PLAYER_SET_SUBTITLESTREAM)) + { + CDVDMsgPlayerSetSubtitleStream* pMsg2 = (CDVDMsgPlayerSetSubtitleStream*)pMsg; + + OMXSelectionStream& st = m_SelectionStreams.Get(STREAM_SUBTITLE, pMsg2->GetStreamId()); + if(st.source != STREAM_SOURCE_NONE) + { + if(st.source == STREAM_SOURCE_NAV && m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD)) + { + CDVDInputStreamNavigator* pStream = (CDVDInputStreamNavigator*)m_pInputStream; + if(pStream->SetActiveSubtitleStream(st.id)) + { + m_dvd.iSelectedSPUStream = -1; + CloseSubtitleStream(false); + } + } + else + { + CloseSubtitleStream(false); + OpenSubtitleStream(st.id, st.source); + } + } + } + else if (pMsg->IsType(CDVDMsg::PLAYER_SET_SUBTITLESTREAM_VISIBLE)) + { + CDVDMsgBool* pValue = (CDVDMsgBool*)pMsg; + + m_player_video.EnableSubtitle(pValue->m_value); + + if (m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD)) + static_cast<CDVDInputStreamNavigator*>(m_pInputStream)->EnableSubtitleStream(pValue->m_value); + } + else if (pMsg->IsType(CDVDMsg::PLAYER_SET_STATE)) + { + g_infoManager.SetDisplayAfterSeek(100000); + SetCaching(CACHESTATE_FLUSH); + + CDVDMsgPlayerSetState* pMsgPlayerSetState = (CDVDMsgPlayerSetState*)pMsg; + + if (m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD)) + { + std::string s = pMsgPlayerSetState->GetState(); + ((CDVDInputStreamNavigator*)m_pInputStream)->SetNavigatorState(s); + m_dvd.state = DVDSTATE_NORMAL; + m_dvd.iDVDStillStartTime = 0; + m_dvd.iDVDStillTime = 0; + } + + g_infoManager.SetDisplayAfterSeek(); + } + else if (pMsg->IsType(CDVDMsg::PLAYER_SET_RECORD)) + { + if (m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_TV)) + static_cast<CDVDInputStreamTV*>(m_pInputStream)->Record(*(CDVDMsgBool*)pMsg); + } + else if (pMsg->IsType(CDVDMsg::GENERAL_FLUSH)) + { + FlushBuffers(false); + } + else if (pMsg->IsType(CDVDMsg::PLAYER_SETSPEED)) + { + int speed = static_cast<CDVDMsgInt*>(pMsg)->m_value; + + // correct our current clock, as it would start going wrong otherwise + if(m_State.timestamp > 0) + { + double offset; + offset = m_av_clock.GetAbsoluteClock() - m_State.timestamp; + offset *= m_playSpeed / DVD_PLAYSPEED_NORMAL; + if(offset > 1000) offset = 1000; + if(offset < -1000) offset = -1000; + m_State.time += DVD_TIME_TO_MSEC(offset); + m_State.timestamp = m_av_clock.GetAbsoluteClock(); + } + + if (speed != DVD_PLAYSPEED_PAUSE && m_playSpeed != DVD_PLAYSPEED_PAUSE && speed != m_playSpeed) + m_callback.OnPlayBackSpeedChanged(speed / DVD_PLAYSPEED_NORMAL); + + // if playspeed is different then DVD_PLAYSPEED_NORMAL or DVD_PLAYSPEED_PAUSE + // audioplayer, stops outputing audio to audiorendere, but still tries to + // sleep an correct amount for each packet + // videoplayer just plays faster after the clock speed has been increased + // 1. disable audio + // 2. skip frames and adjust their pts or the clock + m_playSpeed = speed; + m_caching = CACHESTATE_DONE; + m_av_clock.SetSpeed(speed); + m_player_audio.SetSpeed(speed); + m_player_video.SetSpeed(speed); + m_av_clock.OMXSetSpeed(m_playSpeed); + + // TODO - we really shouldn't pause demuxer + // until our buffers are somewhat filled + if(m_pDemuxer) + m_pDemuxer->SetSpeed(speed); + } + else if (pMsg->IsType(CDVDMsg::PLAYER_CHANNEL_NEXT) || + pMsg->IsType(CDVDMsg::PLAYER_CHANNEL_PREV) || + (pMsg->IsType(CDVDMsg::PLAYER_CHANNEL_SELECT) && m_messenger.GetPacketCount(CDVDMsg::PLAYER_CHANNEL_SELECT) == 0)) + { + CDVDInputStream::IChannel* input = dynamic_cast<CDVDInputStream::IChannel*>(m_pInputStream); + if(input) + { + g_infoManager.SetDisplayAfterSeek(100000); + + bool result; + if (pMsg->IsType(CDVDMsg::PLAYER_CHANNEL_SELECT)) + result = input->SelectChannel(static_cast<CDVDMsgInt*>(pMsg)->m_value); + else if(pMsg->IsType(CDVDMsg::PLAYER_CHANNEL_NEXT)) + result = input->NextChannel(); + else + result = input->PrevChannel(); + + if(result) + { + FlushBuffers(false); + SAFE_DELETE(m_pDemuxer); + } + + g_infoManager.SetDisplayAfterSeek(); + } + } + else if (pMsg->IsType(CDVDMsg::GENERAL_GUI_ACTION)) + OnAction(((CDVDMsgType<CAction>*)pMsg)->m_value); + else if (pMsg->IsType(CDVDMsg::PLAYER_STARTED)) + { + int player = ((CDVDMsgInt*)pMsg)->m_value; + if(player == DVDPLAYER_AUDIO) + m_CurrentAudio.started = true; + if(player == DVDPLAYER_VIDEO) + m_CurrentVideo.started = true; + CLog::Log(LOGDEBUG, "COMXPlayer::HandleMessages - player started %d", player); + } + } + catch (...) + { + CLog::Log(LOGERROR, "%s - Exception thrown when handling message", __FUNCTION__); + } + + pMsg->Release(); + } +} + +void COMXPlayer::SetCaching(ECacheState state) +{ + if(state == CACHESTATE_FLUSH) + { + double level, delay, offset; + if(GetCachingTimes(level, delay, offset)) + state = CACHESTATE_FULL; + else + state = CACHESTATE_INIT; + } + + if(m_caching == state) + return; + + CLog::Log(LOGDEBUG, "COMXPlayer::SetCaching - caching state %d", state); + if(state == CACHESTATE_FULL + || state == CACHESTATE_INIT) + { + m_av_clock.SetSpeed(DVD_PLAYSPEED_PAUSE); + m_av_clock.OMXSetSpeed(DVD_PLAYSPEED_PAUSE); + m_player_audio.SetSpeed(DVD_PLAYSPEED_PAUSE); + m_player_audio.SendMessage(new CDVDMsg(CDVDMsg::PLAYER_STARTED), 1); + m_player_video.SetSpeed(DVD_PLAYSPEED_PAUSE); + m_player_video.SendMessage(new CDVDMsg(CDVDMsg::PLAYER_STARTED), 1); + } + + if(state == CACHESTATE_PLAY + ||(state == CACHESTATE_DONE && m_caching != CACHESTATE_PLAY)) + { + m_av_clock.SetSpeed(m_playSpeed); + m_av_clock.OMXSetSpeed(m_playSpeed); + m_player_audio.SetSpeed(m_playSpeed); + m_player_video.SetSpeed(m_playSpeed); + } + m_caching = state; +} + +void COMXPlayer::SetPlaySpeed(int speed) +{ + /* only pause and normal playspeeds are allowed */ + if(speed < 0 || speed > DVD_PLAYSPEED_NORMAL) + return; + + m_messenger.Put(new CDVDMsgInt(CDVDMsg::PLAYER_SETSPEED, speed)); + m_player_audio.SetSpeed(speed); + m_player_video.SetSpeed(speed); + SynchronizeDemuxer(100); +} + +void COMXPlayer::Pause() +{ + if(m_playSpeed != DVD_PLAYSPEED_PAUSE && m_caching == CACHESTATE_FULL) + { + SetCaching(CACHESTATE_DONE); + return; + } + + if (m_playSpeed == DVD_PLAYSPEED_PAUSE) + { + SetPlaySpeed(DVD_PLAYSPEED_NORMAL); + m_callback.OnPlayBackResumed(); + } + else + { + SetPlaySpeed(DVD_PLAYSPEED_PAUSE); + m_callback.OnPlayBackPaused(); + } +} + +bool COMXPlayer::IsPaused() const +{ + return (m_playSpeed == DVD_PLAYSPEED_PAUSE) || m_caching == CACHESTATE_FULL; +} + +bool COMXPlayer::HasVideo() const +{ + if (m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD)) return true; + + return m_SelectionStreams.Count(STREAM_VIDEO) > 0 ? true : false; +} + +bool COMXPlayer::HasAudio() const +{ + return m_SelectionStreams.Count(STREAM_AUDIO) > 0 ? true : false; +} + +bool COMXPlayer::IsPassthrough() const +{ + return m_player_audio.Passthrough(); +} + +bool COMXPlayer::CanSeek() +{ + return GetTotalTime() > 0; +} + +void COMXPlayer::Seek(bool bPlus, bool bLargeStep) +{ + if(((bPlus && GetChapter() < GetChapterCount()) + || (!bPlus && GetChapter() > 1)) && bLargeStep) + { + if(bPlus) + SeekChapter(GetChapter() + 1); + else + SeekChapter(GetChapter() - 1); + return; + } + + int64_t seek; + if (g_advancedSettings.m_videoUseTimeSeeking && GetTotalTime() > 2*g_advancedSettings.m_videoTimeSeekForwardBig) + { + if (bLargeStep) + seek = bPlus ? g_advancedSettings.m_videoTimeSeekForwardBig : g_advancedSettings.m_videoTimeSeekBackwardBig; + else + seek = bPlus ? g_advancedSettings.m_videoTimeSeekForward : g_advancedSettings.m_videoTimeSeekBackward; + seek *= 1000; + seek += GetTime(); + } + else + { + float percent; + if (bLargeStep) + percent = bPlus ? g_advancedSettings.m_videoPercentSeekForwardBig : g_advancedSettings.m_videoPercentSeekBackwardBig; + else + percent = bPlus ? g_advancedSettings.m_videoPercentSeekForward : g_advancedSettings.m_videoPercentSeekBackward; + seek = (int64_t)(GetTotalTimeInMsec()*(GetPercentage()+percent)/100); + } + + bool restore = true; + + if (m_Edl.HasCut()) + { + /* + * Alter the standard seek position based on whether any commercial breaks have been + * automatically skipped. + */ + const int clock = DVD_TIME_TO_MSEC(m_av_clock.GetClock()); + /* + * If a large backwards seek occurs within 10 seconds of the end of the last automated + * commercial skip, then seek back to the start of the commercial break under the assumption + * it was flagged incorrectly. 10 seconds grace period is allowed in case the watcher has to + * fumble around finding the remote. Only happens once per commercial break. + * + * Small skip does not trigger this in case the start of the commercial break was in fact fine + * but it skipped too far into the program. In that case small skip backwards behaves as normal. + */ + if (!bPlus && bLargeStep + && m_EdlAutoSkipMarkers.seek_to_start + && clock >= m_EdlAutoSkipMarkers.commbreak_end + && clock <= m_EdlAutoSkipMarkers.commbreak_end + 10*1000) // Only if within 10 seconds of the end (in msec) + { + CLog::Log(LOGDEBUG, "%s - Seeking back to start of commercial break [%s - %s] as large backwards skip activated within 10 seconds of the automatic commercial skip (only done once per break).", + __FUNCTION__, CEdl::MillisecondsToTimeString(m_EdlAutoSkipMarkers.commbreak_start).c_str(), + CEdl::MillisecondsToTimeString(m_EdlAutoSkipMarkers.commbreak_end).c_str()); + seek = m_EdlAutoSkipMarkers.commbreak_start; + restore = false; + m_EdlAutoSkipMarkers.seek_to_start = false; // So this will only happen within the 10 second grace period once. + } + /* + * If big skip forward within the last "reverted" commercial break, seek to the end of the + * commercial break under the assumption that the break was incorrectly flagged and playback has + * now reached the actual start of the commercial break. Assume that the end is flagged more + * correctly than the landing point for a standard big skip (ends seem to be flagged more + * accurately than the start). + */ + else if (bPlus && bLargeStep + && clock >= m_EdlAutoSkipMarkers.commbreak_start + && clock <= m_EdlAutoSkipMarkers.commbreak_end) + { + CLog::Log(LOGDEBUG, "%s - Seeking to end of previously skipped commercial break [%s - %s] as big forwards skip activated within the break.", + __FUNCTION__, CEdl::MillisecondsToTimeString(m_EdlAutoSkipMarkers.commbreak_start).c_str(), + CEdl::MillisecondsToTimeString(m_EdlAutoSkipMarkers.commbreak_end).c_str()); + seek = m_EdlAutoSkipMarkers.commbreak_end; + restore = false; + } + } + + int64_t time = GetTime(); + if(g_application.CurrentFileItem().IsStack() + && (seek > GetTotalTimeInMsec() || seek < 0)) + { + g_application.SeekTime((seek - time) * 0.001 + g_application.GetTime()); + // warning, don't access any dvdplayer variables here as + // the dvdplayer object may have been destroyed + return; + } + + m_messenger.Put(new CDVDMsgPlayerSeek((int)seek, !bPlus, true, false, restore)); + SynchronizeDemuxer(100); + if (seek < 0) seek = 0; + m_callback.OnPlayBackSeek((int)seek, (int)(seek - time)); +} + +bool COMXPlayer::SeekScene(bool bPlus) +{ + if (!m_Edl.HasSceneMarker()) + return false; + + /* + * There is a 5 second grace period applied when seeking for scenes backwards. If there is no + * grace period applied it is impossible to go backwards past a scene marker. + */ + int64_t clock = GetTime(); + if (!bPlus && clock > 5 * 1000) // 5 seconds + clock -= 5 * 1000; + + int64_t iScenemarker; + if (m_Edl.GetNextSceneMarker(bPlus, clock, &iScenemarker)) + { + /* + * Seeking is flushed and inaccurate, just like Seek() + */ + m_messenger.Put(new CDVDMsgPlayerSeek((int)iScenemarker, !bPlus, true, false, false)); + SynchronizeDemuxer(100); + return true; + } + return false; +} + +void COMXPlayer::GetAudioInfo(CStdString &strAudioInfo) +{ + { CSingleLock lock(m_StateSection); + strAudioInfo.Format("D(%s)", m_State.demux_audio.c_str()); + } + strAudioInfo.AppendFormat(" P(%s)", m_player_audio.GetPlayerInfo().c_str()); +} + +void COMXPlayer::GetVideoInfo(CStdString &strVideoInfo) +{ + { CSingleLock lock(m_StateSection); + strVideoInfo.Format("D(%s)", m_State.demux_video.c_str()); + } + strVideoInfo.AppendFormat(" P(%s)", m_player_video.GetPlayerInfo().c_str()); +} + +void COMXPlayer::GetGeneralInfo(CStdString& strGeneralInfo) +{ + if (!m_bStop) + { + double dDelay = 0; + + double apts = m_player_audio.GetCurrentPTS(); + double vpts = m_player_video.GetCurrentPTS(); + double dDiff = 0; + + if( apts != DVD_NOPTS_VALUE && vpts != DVD_NOPTS_VALUE ) + dDiff = (apts - vpts) / DVD_TIME_BASE; + + CStdString strEDL; + strEDL.AppendFormat(", edl:%s", m_Edl.GetInfo().c_str()); + + CStdString strBuf; + CSingleLock lock(m_StateSection); + if(m_State.cache_bytes >= 0) + { + strBuf.AppendFormat(" cache:%s %2.0f%%" + , StringUtils::SizeToString(m_State.cache_bytes).c_str() + , m_State.cache_level * 100); + if(m_playSpeed == 0 || m_caching == CACHESTATE_FULL) + strBuf.AppendFormat(" %d sec", DVD_TIME_TO_SEC(m_State.cache_delay)); + } + + strGeneralInfo.Format("C( ad:% 6.3f, a/v:% 6.3f%s, dcpu:%2i%% acpu:%2i%% vcpu:%2i%%%s, omx vb:%8d ad:% 6.3f )" + , dDelay + , dDiff + , strEDL.c_str() + , (int)(CThread::GetRelativeUsage()*100) + , (int)(m_player_audio.GetRelativeUsage()*100) + , (int)(m_player_video.GetRelativeUsage()*100) + , strBuf.c_str() + , m_player_video.GetFreeSpace() + , m_player_audio.GetDelay()); + + } +} + +void COMXPlayer::SeekPercentage(float fPercent) +{ + int64_t iTotalTime = GetTotalTimeInMsec(); + + if (!iTotalTime) + return; + + SeekTime((int64_t)(iTotalTime * fPercent / 100)); +} + +float COMXPlayer::GetPercentage() +{ + int64_t iTotalTime = GetTotalTimeInMsec(); + + if (!iTotalTime) + return 0.0f; + + return GetTime() * 100 / (float)iTotalTime; +} + +float COMXPlayer::GetCachePercentage() +{ + CSingleLock lock(m_StateSection); + return m_State.cache_offset * 100; // NOTE: Percentage returned is relative +} + +void COMXPlayer::SetAVDelay(float fValue) +{ + m_player_video.SetDelay(fValue * DVD_TIME_BASE); +} + +float COMXPlayer::GetAVDelay() +{ + return m_player_video.GetDelay() / (float)DVD_TIME_BASE; +} + +void COMXPlayer::SetSubTitleDelay(float fValue) +{ + m_player_video.SetSubtitleDelay(-fValue * DVD_TIME_BASE); +} + +float COMXPlayer::GetSubTitleDelay() +{ + return -m_player_video.GetSubtitleDelay() / DVD_TIME_BASE; +} + +int COMXPlayer::GetSubtitleCount() +{ + OMXStreamLock lock(this); + m_SelectionStreams.Update(m_pInputStream, m_pDemuxer); + return m_SelectionStreams.Count(STREAM_SUBTITLE); +} + +int COMXPlayer::GetSubtitle() +{ + return m_SelectionStreams.IndexOf(STREAM_SUBTITLE, *this); +} + +void COMXPlayer::GetSubtitleName(int iStream, CStdString &strStreamName) +{ + strStreamName = ""; + OMXSelectionStream& s = m_SelectionStreams.Get(STREAM_SUBTITLE, iStream); + if(s.name.length() > 0) + strStreamName = s.name; + else + strStreamName = g_localizeStrings.Get(13205); // Unknown + + if(s.type == STREAM_NONE) + strStreamName += "(Invalid)"; +} + +void COMXPlayer::GetSubtitleLanguage(int iStream, CStdString &strStreamLang) +{ + OMXSelectionStream& s = m_SelectionStreams.Get(STREAM_SUBTITLE, iStream); + if (!g_LangCodeExpander.Lookup(strStreamLang, s.language)) + strStreamLang = g_localizeStrings.Get(13205); // Unknown +} + +void COMXPlayer::SetSubtitle(int iStream) +{ + m_messenger.Put(new CDVDMsgPlayerSetSubtitleStream(iStream)); +} + +bool COMXPlayer::GetSubtitleVisible() +{ + if (m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD)) + { + CDVDInputStreamNavigator* pStream = (CDVDInputStreamNavigator*)m_pInputStream; + if(pStream->IsInMenu()) + return g_settings.m_currentVideoSettings.m_SubtitleOn; + else + return pStream->IsSubtitleStreamEnabled(); + } + + return m_player_video.IsSubtitleEnabled(); +} + +void COMXPlayer::SetSubtitleVisible(bool bVisible) +{ + g_settings.m_currentVideoSettings.m_SubtitleOn = bVisible; + m_messenger.Put(new CDVDMsgBool(CDVDMsg::PLAYER_SET_SUBTITLESTREAM_VISIBLE, bVisible)); +} + +int COMXPlayer::GetAudioStreamCount() +{ + OMXStreamLock lock(this); + m_SelectionStreams.Update(m_pInputStream, m_pDemuxer); + return m_SelectionStreams.Count(STREAM_AUDIO); +} + +int COMXPlayer::GetAudioStream() +{ + return m_SelectionStreams.IndexOf(STREAM_AUDIO, *this); +} + +void COMXPlayer::GetAudioStreamName(int iStream, CStdString &strStreamName) +{ + strStreamName = ""; + OMXSelectionStream& s = m_SelectionStreams.Get(STREAM_AUDIO, iStream); + if(s.name.length() > 0) + strStreamName += s.name; + else + strStreamName += "Unknown"; + + if(s.type == STREAM_NONE) + strStreamName += " (Invalid)"; +} + +void COMXPlayer::SetAudioStream(int iStream) +{ + m_messenger.Put(new CDVDMsgPlayerSetAudioStream(iStream)); + SynchronizeDemuxer(100); +} + +void COMXPlayer::SeekTime(int64_t iTime) +{ + int seekOffset = (int)(iTime - GetTime()); + m_messenger.Put(new CDVDMsgPlayerSeek((int)iTime, true, true, true)); + SynchronizeDemuxer(100); + m_callback.OnPlayBackSeek((int)iTime, seekOffset); +} + +// return the time in milliseconds +int64_t COMXPlayer::GetTime() +{ + CSingleLock lock(m_StateSection); + double offset = 0; + if(m_State.timestamp > 0) + { + offset = m_av_clock.GetAbsoluteClock() - m_State.timestamp; + offset *= m_playSpeed / DVD_PLAYSPEED_NORMAL; + if(offset > 1000) offset = 1000; + if(offset < -1000) offset = -1000; + } + //printf("COMXPlayer::GetTime %Lf offset %Lf %Lf\n", m_State.time, offset, m_av_clock.GetClock()); + return llrint(m_State.time + DVD_TIME_TO_MSEC(offset)); +} + +// return length in msec +int64_t COMXPlayer::GetTotalTimeInMsec() +{ + CSingleLock lock(m_StateSection); + return llrint(m_State.time_total); +} + +// return length in seconds.. this should be changed to return in milleseconds throughout xbmc +int64_t COMXPlayer::GetTotalTime() +{ + return GetTotalTimeInMsec(); +} + +void COMXPlayer::ToFFRW(int iSpeed) +{ + // can't rewind in menu as seeking isn't possible + // forward is fine + if (iSpeed < 0 && IsInMenu()) return; + + /* only pause and normal playspeeds are allowed */ + if(iSpeed > 1 || iSpeed < 0) + return; + + SetPlaySpeed(iSpeed * DVD_PLAYSPEED_NORMAL); +} + +bool COMXPlayer::OpenAudioStream(int iStream, int source) +{ + CLog::Log(LOGNOTICE, "Opening audio stream: %i source: %i", iStream, source); + + if (!m_pDemuxer) + return false; + + CDemuxStream* pStream = m_pDemuxer->GetStream(iStream); + if (!pStream || pStream->disabled) + return false; + + if( m_CurrentAudio.id < 0 && m_CurrentVideo.id >= 0 ) + { + // up until now we wheren't playing audio, but we did play video + // this will change what is used to sync the dvdclock. + // since the new audio data doesn't have to have any relation + // to the current video data in the packet que, we have to + // wait for it to empty + + // this happens if a new cell has audio data, but previous didn't + // and both have video data + + SynchronizePlayers(SYNCSOURCE_AUDIO); + } + + CDVDStreamInfo hint(*pStream, true); + + if(m_CurrentAudio.id < 0 + || m_CurrentAudio.hint != hint) + { + if(!m_player_audio.OpenStream(hint)) + { + /* mark stream as disabled, to disallaw further attempts*/ + CLog::Log(LOGWARNING, "%s - Unsupported stream %d. Stream disabled.", __FUNCTION__, iStream); + pStream->disabled = true; + pStream->SetDiscard(AVDISCARD_ALL); + return false; + } + m_av_clock.SetSpeed(DVD_PLAYSPEED_NORMAL); + m_av_clock.OMXSetSpeed(DVD_PLAYSPEED_NORMAL); + } + else + m_player_audio.SendMessage(new CDVDMsg(CDVDMsg::GENERAL_RESET)); + + /* store information about stream */ + m_CurrentAudio.id = iStream; + m_CurrentAudio.source = source; + m_CurrentAudio.hint = hint; + m_CurrentAudio.stream = (void*)pStream; + m_CurrentAudio.started = false; + + /* we are potentially going to be waiting on this */ + m_player_audio.SendMessage(new CDVDMsg(CDVDMsg::PLAYER_STARTED), 1); + + /* software decoding normaly consumes full cpu time so prio it */ + m_player_audio.SetPriority(GetPriority()+1); + + return true; +} + +bool COMXPlayer::OpenVideoStream(int iStream, int source) +{ + CLog::Log(LOGNOTICE, "Opening video stream: %i source: %i", iStream, source); + + if (!m_pDemuxer) + return false; + + CDemuxStream* pStream = m_pDemuxer->GetStream(iStream); + if(!pStream || pStream->disabled) + return false; + pStream->SetDiscard(AVDISCARD_NONE); + + CDVDStreamInfo hint(*pStream, true); + + if( m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD) ) + { + /* set aspect ratio as requested by navigator for dvd's */ + float aspect = static_cast<CDVDInputStreamNavigator*>(m_pInputStream)->GetVideoAspectRatio(); + if(aspect != 0.0) + { + hint.aspect = aspect; + hint.forced_aspect = true; + } + hint.software = true; + } + + CDVDInputStream::IMenus* pMenus = dynamic_cast<CDVDInputStream::IMenus*>(m_pInputStream); + if(pMenus && pMenus->IsInMenu()) + hint.stills = true; + + if(m_CurrentVideo.id < 0 + || m_CurrentVideo.hint != hint) + { + if(!m_player_video.OpenStream(hint)) + { + /* mark stream as disabled, to disallaw further attempts */ + CLog::Log(LOGWARNING, "%s - Unsupported stream %d. Stream disabled.", __FUNCTION__, iStream); + pStream->disabled = true; + pStream->SetDiscard(AVDISCARD_ALL); + return false; + } + m_av_clock.SetSpeed(DVD_PLAYSPEED_NORMAL); + m_av_clock.OMXSetSpeed(DVD_PLAYSPEED_NORMAL); + } + else + m_player_video.SendMessage(new CDVDMsg(CDVDMsg::GENERAL_RESET)); + + unsigned flags = 0; + if(m_filename.find("3DSBS") != string::npos) + flags = CONF_FLAGS_FORMAT_SBS; + m_player_video.SetFlags(flags); + + /* store information about stream */ + m_CurrentVideo.id = iStream; + m_CurrentVideo.source = source; + m_CurrentVideo.hint = hint; + m_CurrentVideo.stream = (void*)pStream; + m_CurrentVideo.started = false; + + /* we are potentially going to be waiting on this */ + m_player_video.SendMessage(new CDVDMsg(CDVDMsg::PLAYER_STARTED), 1); + + /* use same priority for video thread as demuxing thread, as */ + /* otherwise demuxer will starve if video consumes the full cpu */ + m_player_video.SetPriority(GetPriority()); + + return true; +} + +bool COMXPlayer::OpenSubtitleStream(int iStream, int source) +{ + CLog::Log(LOGNOTICE, "Opening Subtitle stream: %i source: %i", iStream, source); + + CDemuxStream* pStream = NULL; + std::string filename; + CDVDStreamInfo hint; + + if(STREAM_SOURCE_MASK(source) == STREAM_SOURCE_DEMUX_SUB) + { + int index = m_SelectionStreams.IndexOf(STREAM_SUBTITLE, source, iStream); + if(index < 0) + return false; + OMXSelectionStream st = m_SelectionStreams.Get(STREAM_SUBTITLE, index); + + if(!m_pSubtitleDemuxer || m_pSubtitleDemuxer->GetFileName() != st.filename) + { + CLog::Log(LOGNOTICE, "Opening Subtitle file: %s", st.filename.c_str()); + auto_ptr<CDVDDemuxVobsub> demux(new CDVDDemuxVobsub()); + if(!demux->Open(st.filename, st.filename2)) + return false; + m_pSubtitleDemuxer = demux.release(); + } + + pStream = m_pSubtitleDemuxer->GetStream(iStream); + if(!pStream || pStream->disabled) + return false; + pStream->SetDiscard(AVDISCARD_NONE); + double pts = m_player_video.GetCurrentPTS(); + if(pts == DVD_NOPTS_VALUE) + pts = m_CurrentVideo.dts; + if(pts == DVD_NOPTS_VALUE) + pts = 0; + pts += m_offset_pts; + m_pSubtitleDemuxer->SeekTime((int)(1000.0 * pts / (double)DVD_TIME_BASE)); + + hint.Assign(*pStream, true); + } + else if(STREAM_SOURCE_MASK(source) == STREAM_SOURCE_TEXT) + { + int index = m_SelectionStreams.IndexOf(STREAM_SUBTITLE, source, iStream); + if(index < 0) + return false; + filename = m_SelectionStreams.Get(STREAM_SUBTITLE, index).filename; + + hint.Clear(); + hint.fpsscale = m_CurrentVideo.hint.fpsscale; + hint.fpsrate = m_CurrentVideo.hint.fpsrate; + } + else + { + if(!m_pDemuxer) + return false; + pStream = m_pDemuxer->GetStream(iStream); + if(!pStream || pStream->disabled) + return false; + pStream->SetDiscard(AVDISCARD_NONE); + + hint.Assign(*pStream, true); + + if(m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD)) + filename = "dvd"; + } + + if(m_CurrentSubtitle.id < 0 + || m_CurrentSubtitle.hint != hint) + { + if(m_CurrentSubtitle.id >= 0) + { + CLog::Log(LOGDEBUG, " - codecs hints have changed, must close previous stream"); + CloseSubtitleStream(false); + } + + if(!m_player_subtitle.OpenStream(hint, filename)) + { + CLog::Log(LOGWARNING, "%s - Unsupported stream %d. Stream disabled.", __FUNCTION__, iStream); + if(pStream) + { + pStream->disabled = true; + pStream->SetDiscard(AVDISCARD_ALL); + } + return false; + } + } + else + m_player_subtitle.SendMessage(new CDVDMsg(CDVDMsg::GENERAL_RESET)); + + m_CurrentSubtitle.id = iStream; + m_CurrentSubtitle.source = source; + m_CurrentSubtitle.hint = hint; + m_CurrentSubtitle.stream = (void*)pStream; + m_CurrentSubtitle.started = false; + + return true; +} + +bool COMXPlayer::CloseAudioStream(bool bWaitForBuffers) +{ + if (m_CurrentAudio.id < 0) + return false; + + CLog::Log(LOGNOTICE, "Closing audio stream"); + + if(bWaitForBuffers) + SetCaching(CACHESTATE_DONE); + + m_player_audio.CloseStream(bWaitForBuffers); + + m_CurrentAudio.Clear(); + return true; +} + +bool COMXPlayer::CloseVideoStream(bool bWaitForBuffers) +{ + if (m_CurrentVideo.id < 0) + return false; + + CLog::Log(LOGNOTICE, "Closing video stream"); + + if(bWaitForBuffers) + SetCaching(CACHESTATE_DONE); + + m_player_video.CloseStream(bWaitForBuffers); + + m_CurrentVideo.Clear(); + return true; +} + +bool COMXPlayer::CloseSubtitleStream(bool bKeepOverlays) +{ + if (m_CurrentSubtitle.id < 0) + return false; + + CLog::Log(LOGNOTICE, "Closing subtitle stream"); + + m_player_subtitle.CloseStream(!bKeepOverlays); + + m_CurrentSubtitle.Clear(); + return true; +} + +void COMXPlayer::FlushBuffers(bool queued, double pts, bool accurate) +{ + double startpts; + if(accurate) + startpts = pts; + else + startpts = DVD_NOPTS_VALUE; + + /* call with demuxer pts */ + if(startpts != DVD_NOPTS_VALUE) + startpts -= m_offset_pts; + + m_CurrentAudio.inited = false; + m_CurrentAudio.dts = DVD_NOPTS_VALUE; + m_CurrentAudio.startpts = startpts; + + m_CurrentVideo.inited = false; + m_CurrentVideo.dts = DVD_NOPTS_VALUE; + m_CurrentVideo.startpts = startpts; + + m_CurrentSubtitle.inited = false; + m_CurrentSubtitle.dts = DVD_NOPTS_VALUE; + m_CurrentSubtitle.startpts = startpts; + + m_CurrentTeletext.inited = false; + m_CurrentTeletext.dts = DVD_NOPTS_VALUE; + m_CurrentTeletext.startpts = startpts; + + if(queued) + { + m_player_audio.SendMessage(new CDVDMsg(CDVDMsg::GENERAL_RESET)); + m_player_video.SendMessage(new CDVDMsg(CDVDMsg::GENERAL_RESET)); + m_player_video.SendMessage(new CDVDMsg(CDVDMsg::VIDEO_NOSKIP)); + m_player_subtitle.SendMessage(new CDVDMsg(CDVDMsg::GENERAL_RESET)); + SynchronizePlayers(SYNCSOURCE_ALL); + } + else + { + m_player_video.Flush(); + m_player_audio.Flush(); + m_player_subtitle.Flush(); + + // clear subtitle and menu overlays + m_overlayContainer.Clear(); + + if(m_playSpeed == DVD_PLAYSPEED_NORMAL + || m_playSpeed == DVD_PLAYSPEED_PAUSE) + { + // make sure players are properly flushed, should put them in stalled state + CDVDMsgGeneralSynchronize* msg = new CDVDMsgGeneralSynchronize(1000, 0); + m_player_video.SendMessage(msg->Acquire(), 1); + m_player_audio.SendMessage(msg->Acquire(), 1); + msg->Wait(&m_bStop, 0); + msg->Release(); + + // purge any pending PLAYER_STARTED messages + m_messenger.Flush(CDVDMsg::PLAYER_STARTED); + + // we should now wait for init cache + SetCaching(CACHESTATE_FLUSH); + m_CurrentAudio.started = false; + m_CurrentVideo.started = false; + m_CurrentSubtitle.started = false; + m_CurrentTeletext.started = false; + } + + if(pts != DVD_NOPTS_VALUE) + m_av_clock.Discontinuity(pts); + UpdatePlayState(0); + + /* + CloseVideoStream(false); + if(m_CurrentVideo.id >= 0) + OpenVideoStream(m_CurrentVideo.id, m_CurrentVideo.source); + */ + } +} + +// since we call ffmpeg functions to decode, this is being called in the same thread as ::Process() is +int COMXPlayer::OnDVDNavResult(void* pData, int iMessage) +{ + if (m_pInputStream->IsStreamType(DVDSTREAM_TYPE_BLURAY)) + { + if(iMessage == 0) + m_overlayContainer.Add((CDVDOverlay*)pData); + else if(iMessage == 1) + m_messenger.Put(new CDVDMsg(CDVDMsg::GENERAL_FLUSH)); + else if(iMessage == 2) + m_dvd.iSelectedAudioStream = *(int*)pData; + else if(iMessage == 3) + m_dvd.iSelectedSPUStream = *(int*)pData; + else if(iMessage == 4) + m_player_video.EnableSubtitle(*(int*)pData ? true: false); + + return 0; + } + + if (m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD)) + { + CDVDInputStreamNavigator* pStream = (CDVDInputStreamNavigator*)m_pInputStream; + + switch (iMessage) + { + case DVDNAV_STILL_FRAME: + { + //CLog::Log(LOGDEBUG, "DVDNAV_STILL_FRAME"); + + dvdnav_still_event_t *still_event = (dvdnav_still_event_t *)pData; + // should wait the specified time here while we let the player running + // after that call dvdnav_still_skip(m_dvdnav); + + if (m_dvd.state != DVDSTATE_STILL) + { + // else notify the player we have received a still frame + + if(still_event->length < 0xff) + m_dvd.iDVDStillTime = still_event->length * 1000; + else + m_dvd.iDVDStillTime = 0; + + m_dvd.iDVDStillStartTime = XbmcThreads::SystemClockMillis(); + + /* adjust for the output delay in the video queue */ + DWORD time = 0; + if( m_CurrentVideo.stream && m_dvd.iDVDStillTime > 0 ) + { + time = (DWORD)(m_player_video.GetOutputDelay() / ( DVD_TIME_BASE / 1000 )); + if( time < 10000 && time > 0 ) + m_dvd.iDVDStillTime += time; + } + m_dvd.state = DVDSTATE_STILL; + CLog::Log(LOGDEBUG, + "DVDNAV_STILL_FRAME - waiting %i sec, with delay of %d sec", + still_event->length, time / 1000); + } + return NAVRESULT_HOLD; + } + break; + case DVDNAV_SPU_CLUT_CHANGE: + { + m_player_subtitle.SendMessage(new CDVDMsgSubtitleClutChange((BYTE*)pData)); + } + break; + case DVDNAV_SPU_STREAM_CHANGE: + { + dvdnav_spu_stream_change_event_t* event = (dvdnav_spu_stream_change_event_t*)pData; + + int iStream = event->physical_wide; + bool visible = !(iStream & 0x80); + + m_player_video.EnableSubtitle(visible); + + if (iStream >= 0) + m_dvd.iSelectedSPUStream = (iStream & ~0x80); + else + m_dvd.iSelectedSPUStream = -1; + + m_CurrentSubtitle.stream = NULL; + } + break; + case DVDNAV_AUDIO_STREAM_CHANGE: + { + // This should be the correct way i think, however we don't have any streams right now + // since the demuxer hasn't started so it doesn't change. not sure how to do this. + dvdnav_audio_stream_change_event_t* event = (dvdnav_audio_stream_change_event_t*)pData; + + // Tell system what audiostream should be opened by default + if (event->logical >= 0) + m_dvd.iSelectedAudioStream = event->physical; + else + m_dvd.iSelectedAudioStream = -1; + + m_CurrentAudio.stream = NULL; + } + break; + case DVDNAV_HIGHLIGHT: + { + //dvdnav_highlight_event_t* pInfo = (dvdnav_highlight_event_t*)pData; + int iButton = pStream->GetCurrentButton(); + CLog::Log(LOGDEBUG, "DVDNAV_HIGHLIGHT: Highlight button %d\n", iButton); + m_player_subtitle.UpdateOverlayInfo((CDVDInputStreamNavigator*)m_pInputStream, LIBDVDNAV_BUTTON_NORMAL); + } + break; + case DVDNAV_VTS_CHANGE: + { + //dvdnav_vts_change_event_t* vts_change_event = (dvdnav_vts_change_event_t*)pData; + CLog::Log(LOGDEBUG, "DVDNAV_VTS_CHANGE"); + + //Make sure we clear all the old overlays here, or else old forced items are left. + m_overlayContainer.Clear(); + + //Force an aspect ratio that is set in the dvdheaders if available + m_CurrentVideo.hint.aspect = pStream->GetVideoAspectRatio(); + if( m_player_audio.IsInited() ) + m_player_video.SendMessage(new CDVDMsgDouble(CDVDMsg::VIDEO_SET_ASPECT, m_CurrentVideo.hint.aspect)); + + m_SelectionStreams.Clear(STREAM_NONE, STREAM_SOURCE_NAV); + m_SelectionStreams.Update(m_pInputStream, m_pDemuxer); + } + break; + case DVDNAV_CELL_CHANGE: + { + //dvdnav_cell_change_event_t* cell_change_event = (dvdnav_cell_change_event_t*)pData; + CLog::Log(LOGDEBUG, "DVDNAV_CELL_CHANGE"); + + m_dvd.state = DVDSTATE_NORMAL; + + if( m_player_video.IsInited() ) + m_player_video.SendMessage(new CDVDMsg(CDVDMsg::VIDEO_NOSKIP)); + } + break; + case DVDNAV_NAV_PACKET: + { + //pci_t* pci = (pci_t*)pData; + + // this should be possible to use to make sure we get + // seamless transitions over these boundaries + // if we remember the old vobunits boundaries + // when a packet comes out of demuxer that has + // pts values outside that boundary, it belongs + // to the new vobunit, wich has new timestamps + UpdatePlayState(0); + } + break; + case DVDNAV_HOP_CHANNEL: + { + // This event is issued whenever a non-seamless operation has been executed. + // Applications with fifos should drop the fifos content to speed up responsiveness. + CLog::Log(LOGDEBUG, "DVDNAV_HOP_CHANNEL"); + if(m_dvd.state == DVDSTATE_SEEK) + m_dvd.state = DVDSTATE_NORMAL; + else + m_messenger.Put(new CDVDMsg(CDVDMsg::GENERAL_FLUSH)); + + return NAVRESULT_ERROR; + } + break; + case DVDNAV_STOP: + { + CLog::Log(LOGDEBUG, "DVDNAV_STOP"); + m_dvd.state = DVDSTATE_NORMAL; + } + break; + default: + {} + break; + } + } + return NAVRESULT_NOP; +} + +bool COMXPlayer::OnAction(const CAction &action) +{ +#define THREAD_ACTION(action) \ + do { \ + if (!IsCurrentThread()) { \ + m_messenger.Put(new CDVDMsgType<CAction>(CDVDMsg::GENERAL_GUI_ACTION, action)); \ + return true; \ + } \ + } while(false) + + CDVDInputStream::IMenus* pMenus = dynamic_cast<CDVDInputStream::IMenus*>(m_pInputStream); + if (pMenus) + { + if( m_dvd.state == DVDSTATE_STILL && m_dvd.iDVDStillTime != 0 && pMenus->GetTotalButtons() == 0 ) + { + switch(action.GetID()) + { + case ACTION_NEXT_ITEM: + case ACTION_MOVE_RIGHT: + case ACTION_MOVE_UP: + case ACTION_SELECT_ITEM: + { + THREAD_ACTION(action); + /* this will force us out of the stillframe */ + CLog::Log(LOGDEBUG, "%s - User asked to exit stillframe", __FUNCTION__); + m_dvd.iDVDStillStartTime = 0; + m_dvd.iDVDStillTime = 1; + } + return true; + } + } + + + switch (action.GetID()) + { +/* this code is disabled to allow switching playlist items (dvdimage "stacks") */ +#if 0 + case ACTION_PREV_ITEM: // SKIP-: + { + THREAD_ACTION(action); + CLog::Log(LOGDEBUG, " - pushed prev"); + pMenus->OnPrevious(); + g_infoManager.SetDisplayAfterSeek(); + return true; + } + break; + case ACTION_NEXT_ITEM: // SKIP+: + { + THREAD_ACTION(action); + CLog::Log(LOGDEBUG, " - pushed next"); + pMenus->OnNext(); + g_infoManager.SetDisplayAfterSeek(); + return true; + } + break; +#endif + case ACTION_SHOW_VIDEOMENU: // start button + { + THREAD_ACTION(action); + CLog::Log(LOGDEBUG, " - go to menu"); + pMenus->OnMenu(); + // send a message to everyone that we've gone to the menu + CGUIMessage msg(GUI_MSG_VIDEO_MENU_STARTED, 0, 0); + g_windowManager.SendMessage(msg); + return true; + } + break; + } + if (pMenus->IsInMenu()) + { + switch (action.GetID()) + { + case ACTION_NEXT_ITEM: + THREAD_ACTION(action); + CLog::Log(LOGDEBUG, " - pushed next in menu, stream will decide"); + pMenus->OnNext(); + g_infoManager.SetDisplayAfterSeek(); + return true; + case ACTION_PREV_ITEM: + THREAD_ACTION(action); + CLog::Log(LOGDEBUG, " - pushed prev in menu, stream will decide"); + pMenus->OnPrevious(); + g_infoManager.SetDisplayAfterSeek(); + return true; + case ACTION_PREVIOUS_MENU: + case ACTION_NAV_BACK: + { + THREAD_ACTION(action); + CLog::Log(LOGDEBUG, " - menu back"); + pMenus->OnBack(); + } + break; + case ACTION_MOVE_LEFT: + { + THREAD_ACTION(action); + CLog::Log(LOGDEBUG, " - move left"); + pMenus->OnLeft(); + } + break; + case ACTION_MOVE_RIGHT: + { + THREAD_ACTION(action); + CLog::Log(LOGDEBUG, " - move right"); + pMenus->OnRight(); + } + break; + case ACTION_MOVE_UP: + { + THREAD_ACTION(action); + CLog::Log(LOGDEBUG, " - move up"); + pMenus->OnUp(); + } + break; + case ACTION_MOVE_DOWN: + { + THREAD_ACTION(action); + CLog::Log(LOGDEBUG, " - move down"); + pMenus->OnDown(); + } + break; + + case ACTION_MOUSE_MOVE: + case ACTION_MOUSE_LEFT_CLICK: + { + CRect rs, rd; + GetVideoRect(rs, rd); + CPoint pt(action.GetAmount(), action.GetAmount(1)); + if (!rd.PtInRect(pt)) + return false; // out of bounds + THREAD_ACTION(action); + // convert to video coords... + pt -= CPoint(rd.x1, rd.y1); + pt.x *= rs.Width() / rd.Width(); + pt.y *= rs.Height() / rd.Height(); + pt += CPoint(rs.x1, rs.y1); + if (action.GetID() == ACTION_MOUSE_LEFT_CLICK) + return pMenus->OnMouseClick(pt); + return pMenus->OnMouseMove(pt); + } + break; + case ACTION_SELECT_ITEM: + { + THREAD_ACTION(action); + CLog::Log(LOGDEBUG, " - button select"); + // show button pushed overlay + if(m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD)) + m_player_subtitle.UpdateOverlayInfo((CDVDInputStreamNavigator*)m_pInputStream, LIBDVDNAV_BUTTON_CLICKED); + + pMenus->ActivateButton(); + } + break; + case REMOTE_0: + case REMOTE_1: + case REMOTE_2: + case REMOTE_3: + case REMOTE_4: + case REMOTE_5: + case REMOTE_6: + case REMOTE_7: + case REMOTE_8: + case REMOTE_9: + { + THREAD_ACTION(action); + // Offset from key codes back to button number + int button = action.GetID() - REMOTE_0; + CLog::Log(LOGDEBUG, " - button pressed %d", button); + pMenus->SelectButton(button); + } + break; + default: + return false; + break; + } + return true; // message is handled + } + } + if (dynamic_cast<CDVDInputStream::IChannel*>(m_pInputStream)) + { + switch (action.GetID()) + { + case ACTION_NEXT_ITEM: + m_messenger.Put(new CDVDMsg(CDVDMsg::PLAYER_CHANNEL_NEXT)); + g_infoManager.SetDisplayAfterSeek(); + return true; + break; + + case ACTION_PREV_ITEM: + m_messenger.Put(new CDVDMsg(CDVDMsg::PLAYER_CHANNEL_PREV)); + g_infoManager.SetDisplayAfterSeek(); + return true; + break; + + case ACTION_CHANNEL_SWITCH: + { + // Offset from key codes back to button number + int channel = action.GetAmount(); + m_messenger.Put(new CDVDMsgInt(CDVDMsg::PLAYER_CHANNEL_SELECT, channel)); + g_infoManager.SetDisplayAfterSeek(); + return true; + } + break; + } + } + + switch (action.GetID()) + { + case ACTION_NEXT_ITEM: + if(GetChapterCount() > 0) + { + m_messenger.Put(new CDVDMsgPlayerSeekChapter(GetChapter()+1)); + g_infoManager.SetDisplayAfterSeek(); + return true; + } + else + break; + case ACTION_PREV_ITEM: + if(GetChapterCount() > 0) + { + m_messenger.Put(new CDVDMsgPlayerSeekChapter(GetChapter()-1)); + g_infoManager.SetDisplayAfterSeek(); + return true; + } + else + break; + } + + // return false to inform the caller we didn't handle the message + return false; +} + +bool COMXPlayer::IsInMenu() const +{ + CDVDInputStream::IMenus* pStream = dynamic_cast<CDVDInputStream::IMenus*>(m_pInputStream); + if (pStream) + { + if( m_dvd.state == DVDSTATE_STILL ) + return true; + else + return pStream->IsInMenu(); + } + return false; +} + +bool COMXPlayer::HasMenu() +{ + CDVDInputStream::IMenus* pStream = dynamic_cast<CDVDInputStream::IMenus*>(m_pInputStream); + if (pStream) + return true; + else + return false; +} + +bool COMXPlayer::GetCurrentSubtitle(CStdString& strSubtitle) +{ + if (m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD)) + return false; + + double pts = m_av_clock.OMXMediaTime(); + + m_player_subtitle.GetCurrentSubtitle(strSubtitle, pts - m_player_video.GetSubtitleDelay()); + + // In case we stalled, don't output any subs + if ((m_player_video.IsStalled() && HasVideo()) || (m_player_audio.IsStalled() && HasAudio())) + strSubtitle = m_lastSub; + else + m_lastSub = strSubtitle; + + return !strSubtitle.IsEmpty(); +} + +CStdString COMXPlayer::GetPlayerState() +{ + CSingleLock lock(m_StateSection); + return m_State.player_state; +} + +bool COMXPlayer::SetPlayerState(CStdString state) +{ + m_messenger.Put(new CDVDMsgPlayerSetState(state)); + return true; +} + +int COMXPlayer::GetChapterCount() +{ + CSingleLock lock(m_StateSection); + return m_State.chapter_count; +} + +int COMXPlayer::GetChapter() +{ + CSingleLock lock(m_StateSection); + return m_State.chapter; +} + +void COMXPlayer::GetChapterName(CStdString& strChapterName) +{ + CSingleLock lock(m_StateSection); + strChapterName = m_State.chapter_name; +} + +int COMXPlayer::SeekChapter(int iChapter) +{ + if (GetChapterCount() > 0) + { + if (iChapter < 0) + iChapter = 0; + if (iChapter > GetChapterCount()) + return 0; + + // Seek to the chapter. + m_messenger.Put(new CDVDMsgPlayerSeekChapter(iChapter)); + SynchronizeDemuxer(100); + } + else + { + // Do a regular big jump. + if (GetChapter() > 0 && iChapter > GetChapter()) + Seek(true, true); + else + Seek(false, true); + } + return 0; +} + +int COMXPlayer::AddSubtitle(const CStdString& strSubPath) +{ + return AddSubtitleFile(strSubPath); +} + +int COMXPlayer::GetCacheLevel() const +{ + CSingleLock lock(m_StateSection); + return (int)(m_State.cache_level * 100); +} + +double COMXPlayer::GetQueueTime() +{ + int a = m_player_video.GetLevel(); + int v = m_player_audio.GetLevel(); + return max(a, v) * 8000.0 / 100; +} + +int COMXPlayer::GetAudioBitrate() +{ + return m_player_audio.GetAudioBitrate(); +} + +int COMXPlayer::GetVideoBitrate() +{ + return m_player_video.GetVideoBitrate(); +} + +int COMXPlayer::GetSourceBitrate() +{ + if (m_pInputStream) + return (int)m_pInputStream->GetBitstreamStats().GetBitrate(); + + return 0; +} + +int COMXPlayer::AddSubtitleFile(const std::string& filename, const std::string& subfilename, CDemuxStream::EFlags flags) +{ + std::string ext = URIUtils::GetExtension(filename); + std::string vobsubfile = subfilename; + if(ext == ".idx") + { + if (vobsubfile.empty()) + vobsubfile = URIUtils::ReplaceExtension(filename, ".sub"); + + CDVDDemuxVobsub v; + if(!v.Open(filename, vobsubfile)) + return -1; + m_SelectionStreams.Update(NULL, &v); + int index = m_SelectionStreams.IndexOf(STREAM_SUBTITLE, m_SelectionStreams.Source(STREAM_SOURCE_DEMUX_SUB, filename), 0); + m_SelectionStreams.Get(STREAM_SUBTITLE, index).flags = flags; + m_SelectionStreams.Get(STREAM_SUBTITLE, index).filename2 = vobsubfile; + return index; + } + if(ext == ".sub") + { + CStdString strReplace(URIUtils::ReplaceExtension(filename,".idx")); + if (XFILE::CFile::Exists(strReplace)) + return -1; + } + OMXSelectionStream s; + s.source = m_SelectionStreams.Source(STREAM_SOURCE_TEXT, filename); + s.type = STREAM_SUBTITLE; + s.id = 0; + s.filename = filename; + s.name = URIUtils::GetFileName(filename); + s.flags = flags; + m_SelectionStreams.Update(s); + return m_SelectionStreams.IndexOf(STREAM_SUBTITLE, s.source, s.id); +} + +void COMXPlayer::UpdatePlayState(double timeout) +{ + if(m_State.timestamp != 0 + && m_State.timestamp + DVD_MSEC_TO_TIME(timeout) > m_av_clock.GetAbsoluteClock()) + return; + + SPlayerState state(m_State); + + if (m_CurrentVideo.dts != DVD_NOPTS_VALUE) + state.dts = m_CurrentVideo.dts; + else if(m_CurrentAudio.dts != DVD_NOPTS_VALUE) + state.dts = m_CurrentAudio.dts; + else + state.dts = m_av_clock.GetClock(); + + if(m_pDemuxer) + { + state.chapter = m_pDemuxer->GetChapter(); + state.chapter_count = m_pDemuxer->GetChapterCount(); + m_pDemuxer->GetChapterName(state.chapter_name); + + state.time = DVD_TIME_TO_MSEC(m_av_clock.GetClock() + m_offset_pts); + state.time_total = m_pDemuxer->GetStreamLength(); + } + + if(m_pInputStream) + { + // override from input stream if needed + + if (m_pInputStream->IsStreamType(DVDSTREAM_TYPE_TV)) + { + state.canrecord = static_cast<CDVDInputStreamTV*>(m_pInputStream)->CanRecord(); + state.recording = static_cast<CDVDInputStreamTV*>(m_pInputStream)->IsRecording(); + } + + CDVDInputStream::IDisplayTime* pDisplayTime = dynamic_cast<CDVDInputStream::IDisplayTime*>(m_pInputStream); + if (pDisplayTime) + { + state.time = pDisplayTime->GetTime(); + state.time_total = pDisplayTime->GetTotalTime(); + } + + if (dynamic_cast<CDVDInputStream::IMenus*>(m_pInputStream)) + { + if(m_dvd.state == DVDSTATE_STILL) + { + state.time = XbmcThreads::SystemClockMillis() - m_dvd.iDVDStillStartTime; + state.time_total = m_dvd.iDVDStillTime; + } + } + + if (m_pInputStream->IsStreamType(DVDSTREAM_TYPE_TV)) + { + if(((CDVDInputStreamTV*)m_pInputStream)->GetTotalTime() > 0) + { + state.time -= ((CDVDInputStreamTV*)m_pInputStream)->GetStartTime(); + state.time_total = ((CDVDInputStreamTV*)m_pInputStream)->GetTotalTime(); + } + } + } + + if (m_Edl.HasCut()) + { + state.time = m_Edl.RemoveCutTime(llrint(state.time)); + state.time_total = m_Edl.RemoveCutTime(llrint(state.time_total)); + } + + state.player_state = ""; + if (m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD)) + { + state.time_offset = DVD_MSEC_TO_TIME(state.time) - state.dts; + if(!((CDVDInputStreamNavigator*)m_pInputStream)->GetNavigatorState(state.player_state)) + state.player_state = ""; + } + else + state.time_offset = 0; + + if (m_CurrentAudio.id >= 0 && m_pDemuxer) + { + CDemuxStream* pStream = m_pDemuxer->GetStream(m_CurrentAudio.id); + if (pStream && pStream->type == STREAM_AUDIO) + ((CDemuxStreamAudio*)pStream)->GetStreamInfo(state.demux_audio); + } + else + state.demux_audio = ""; + + if (m_CurrentVideo.id >= 0 && m_pDemuxer) + { + CDemuxStream* pStream = m_pDemuxer->GetStream(m_CurrentVideo.id); + if (pStream && pStream->type == STREAM_VIDEO) + ((CDemuxStreamVideo*)pStream)->GetStreamInfo(state.demux_video); + } + else + state.demux_video = ""; + + double level, delay, offset; + if(GetCachingTimes(level, delay, offset)) + { + state.cache_delay = max(0.0, delay); + state.cache_level = max(0.0, min(1.0, level)); + state.cache_offset = offset; + } + else + { + state.cache_delay = 0.0; + state.cache_level = min(1.0, GetQueueTime() / 8000.0); + state.cache_offset = GetQueueTime() / state.time_total; + } + + XFILE::SCacheStatus status; + if(m_pInputStream && m_pInputStream->GetCacheStatus(&status) && status.forward >=0) + { + state.cache_bytes = status.forward; + if(state.time_total) + state.cache_bytes += m_pInputStream->GetLength() * GetQueueTime() / state.time_total; + } + else + state.cache_bytes = 0; + + state.timestamp = m_av_clock.GetAbsoluteClock(); + + CSingleLock lock(m_StateSection); + m_State = state; +} + +void COMXPlayer::UpdateApplication(double timeout) +{ + if(m_UpdateApplication != 0 + && m_UpdateApplication + DVD_MSEC_TO_TIME(timeout) > m_av_clock.GetAbsoluteClock()) + return; + + CDVDInputStream::IChannel* pStream = dynamic_cast<CDVDInputStream::IChannel*>(m_pInputStream); + if(pStream) + { + CFileItem item(g_application.CurrentFileItem()); + if(pStream->UpdateItem(item)) + { + g_application.CurrentFileItem() = item; + g_infoManager.SetCurrentItem(item); + } + } + m_UpdateApplication = m_av_clock.GetAbsoluteClock(); +} + +bool COMXPlayer::CanRecord() +{ + CSingleLock lock(m_StateSection); + return m_State.canrecord; +} + +bool COMXPlayer::IsRecording() +{ + CSingleLock lock(m_StateSection); + return m_State.recording; +} + +bool COMXPlayer::Record(bool bOnOff) +{ + if (m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_TV)) + { + m_messenger.Put(new CDVDMsgBool(CDVDMsg::PLAYER_SET_RECORD, bOnOff)); + return true; + } + return false; +} + +int COMXPlayer::GetChannels() +{ + if (m_pDemuxer && (m_CurrentAudio.id != -1)) + { + CDemuxStreamAudio* stream = static_cast<CDemuxStreamAudio*>(m_pDemuxer->GetStream(m_CurrentAudio.id)); + if (stream) + return stream->iChannels; + } + return -1; +} + +CStdString COMXPlayer::GetAudioCodecName() +{ + CStdString retVal; + if (m_pDemuxer && (m_CurrentAudio.id != -1)) + m_pDemuxer->GetStreamCodecName(m_CurrentAudio.id, retVal); + return retVal; +} + +CStdString COMXPlayer::GetVideoCodecName() +{ + CStdString retVal; + if (m_pDemuxer && (m_CurrentVideo.id != -1)) + m_pDemuxer->GetStreamCodecName(m_CurrentVideo.id, retVal); + return retVal; +} + +int COMXPlayer::GetPictureWidth() +{ + if (m_pDemuxer && (m_CurrentVideo.id != -1)) + { + CDemuxStreamVideo* stream = static_cast<CDemuxStreamVideo*>(m_pDemuxer->GetStream(m_CurrentVideo.id)); + if (stream) + return stream->iWidth; + } + return 0; +} + +int COMXPlayer::GetPictureHeight() +{ + if (m_pDemuxer && (m_CurrentVideo.id != -1)) + { + CDemuxStreamVideo* stream = static_cast<CDemuxStreamVideo*>(m_pDemuxer->GetStream(m_CurrentVideo.id)); + if (stream) + return stream->iHeight; + } + return 0; +} + +bool COMXPlayer::GetStreamDetails(CStreamDetails &details) +{ + if (m_pDemuxer) + { + bool result=CDVDFileInfo::DemuxerToStreamDetails(m_pInputStream, m_pDemuxer, details); + if (result && details.GetStreamCount(CStreamDetail::VIDEO) > 0) // this is more correct (dvds in particular) + { + ((CStreamDetailVideo*)details.GetNthStream(CStreamDetail::VIDEO,0))->m_fAspect = m_CurrentVideo.hint.aspect; + ((CStreamDetailVideo*)details.GetNthStream(CStreamDetail::VIDEO,0))->m_iDuration = GetTotalTime() / 1000; + } + return result; + } + else + return false; +} + +CStdString COMXPlayer::GetPlayingTitle() +{ + return ""; +} + +void COMXPlayer::GetVideoRect(CRect& SrcRect, CRect& DestRect) +{ + g_renderManager.GetVideoRect(SrcRect, DestRect); +} + +void COMXPlayer::SetVolume(float fVolume) +{ + m_current_volume = fVolume; + m_change_volume = true; +} + +void COMXPlayer::Update(bool bPauseDrawing) +{ + g_renderManager.Update(bPauseDrawing); +} + +void COMXPlayer::GetVideoAspectRatio(float &fAR) +{ + fAR = g_renderManager.GetAspectRatio(); +} + +#endif diff --git a/xbmc/cores/omxplayer/OMXPlayer.h b/xbmc/cores/omxplayer/OMXPlayer.h new file mode 100644 index 0000000000..ca2a0eb873 --- /dev/null +++ b/xbmc/cores/omxplayer/OMXPlayer.h @@ -0,0 +1,469 @@ +#pragma once +/* + * Copyright (C) 2011 Team XBMC + * http://www.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, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#if defined(HAVE_CONFIG_H) && !defined(TARGET_WINDOWS) +#include "config.h" +#define DECLARE_UNUSED(a,b) a __attribute__((unused)) b; +#endif + +#include "FileItem.h" +#include "cores/IPlayer.h" +#include "cores/dvdplayer/IDVDPlayer.h" +#include "dialogs/GUIDialogBusy.h" +#include "threads/Thread.h" +#include <semaphore.h> + +#include "threads/SingleLock.h" + +#include "OMXCore.h" +#include "OMXClock.h" +#include "OMXPlayerAudio.h" +#include "OMXPlayerVideo.h" +//#include "OMXPlayerSubtitle.h" +#include "DVDPlayerSubtitle.h" + +#include "utils/BitstreamStats.h" + +#include "linux/DllBCM.h" + +#include "DVDStreamInfo.h" +#include "DVDInputStreams/DVDInputStream.h" +#include "DVDInputStreams/DVDFactoryInputStream.h" +#include "DVDDemuxers/DVDDemuxFFmpeg.h" +#include "DVDDemuxers/DVDDemuxUtils.h" +#include "DVDDemuxers/DVDFactoryDemuxer.h" +#include "DVDMessageQueue.h" +#include "DVDSubtitles/DVDFactorySubtitle.h" +#include "Edl.h" + +#include <deque> + +#define MAX_CHAPTERS 64 + +#define DVDPLAYER_AUDIO 1 +#define DVDPLAYER_VIDEO 2 +#define DVDPLAYER_SUBTITLE 3 +#define DVDPLAYER_TELETEXT 4 + +#define DVDSTATE_NORMAL 0x00000001 // normal dvd state +#define DVDSTATE_STILL 0x00000002 // currently displaying a still frame +#define DVDSTATE_WAIT 0x00000003 // waiting for demuxer read error +#define DVDSTATE_SEEK 0x00000004 // we are finishing a seek request + +class COMXPlayer; +class OMXPlayerVideo; +class OMXPlayerAudio; + +class COMXCurrentStream +{ +public: + int id; // demuxerid of current playing stream + int source; + double dts; // last dts from demuxer, used to find disncontinuities + double dur; // last frame expected duration + CDVDStreamInfo hint; // stream hints, used to notice stream changes + void* stream; // pointer or integer, identifying stream playing. if it changes stream changed + bool inited; + bool started; // has the player started + const StreamType type; + const int player; + // stuff to handle starting after seek + double startpts; + + COMXCurrentStream(StreamType t, int i) + : type(t) + , player(i) + { + Clear(); + } + + void Clear() + { + id = -1; + source = STREAM_SOURCE_NONE; + dts = DVD_NOPTS_VALUE; + dur = DVD_NOPTS_VALUE; + hint.Clear(); + stream = NULL; + inited = false; + started = false; + startpts = DVD_NOPTS_VALUE; + } + double dts_end() + { + if(dts == DVD_NOPTS_VALUE) + return DVD_NOPTS_VALUE; + if(dur == DVD_NOPTS_VALUE) + return dts; + return dts + dur; + } +}; + +typedef struct +{ + StreamType type; + int type_index; + std::string filename; + std::string filename2; // for vobsub subtitles, 2 files are necessary (idx/sub) + std::string language; + std::string name; + CDemuxStream::EFlags flags; + int source; + int id; + std::string codec; + int channels; +} OMXSelectionStream; + +typedef std::vector<OMXSelectionStream> OMXSelectionStreams; + +class COMXSelectionStreams +{ + CCriticalSection m_section; + OMXSelectionStream m_invalid; +public: + COMXSelectionStreams() + { + m_invalid.id = -1; + m_invalid.source = STREAM_SOURCE_NONE; + m_invalid.type = STREAM_NONE; + } + std::vector<OMXSelectionStream> m_Streams; + + int IndexOf (StreamType type, int source, int id) const; + int IndexOf (StreamType type, COMXPlayer& p) const; + int Count (StreamType type) const { return IndexOf(type, STREAM_SOURCE_NONE, -1) + 1; } + OMXSelectionStream& Get (StreamType type, int index); + bool Get (StreamType type, CDemuxStream::EFlags flag, OMXSelectionStream& out); + + OMXSelectionStreams Get(StreamType type); + template<typename Compare> OMXSelectionStreams Get(StreamType type, Compare compare) + { + OMXSelectionStreams streams = Get(type); + std::stable_sort(streams.begin(), streams.end(), compare); + return streams; + } + + void Clear (StreamType type, StreamSource source); + int Source (StreamSource source, std::string filename); + + void Update (OMXSelectionStream& s); + void Update (CDVDInputStream* input, CDVDDemux* demuxer); +}; + + +class COMXPlayer : public IPlayer, public CThread, public IDVDPlayer +{ +public: + + COMXPlayer(IPlayerCallback &callback); + virtual ~COMXPlayer(); + + virtual void RegisterAudioCallback(IAudioCallback* pCallback) { m_player_audio.RegisterAudioCallback(pCallback); }; + virtual void UnRegisterAudioCallback() { m_player_audio.UnRegisterAudioCallback(); }; + + virtual bool IsValidStream(COMXCurrentStream& stream); + virtual bool IsBetterStream(COMXCurrentStream& current, CDemuxStream* stream); + virtual bool ReadPacket(DemuxPacket*& packet, CDemuxStream*& stream); + virtual bool CloseAudioStream(bool bWaitForBuffers); + virtual bool CloseVideoStream(bool bWaitForBuffers); + virtual bool CloseSubtitleStream(bool bKeepOverlays); + virtual bool OpenAudioStream(int iStream, int source); + virtual bool OpenVideoStream(int iStream, int source); + virtual bool OpenSubtitleStream(int iStream, int source); + virtual void OpenDefaultStreams(); + virtual bool OpenDemuxStream(); + virtual bool OpenInputStream(); + virtual bool CheckPlayerInit(COMXCurrentStream& current, unsigned int source); + virtual void UpdateCorrection(DemuxPacket* pkt, double correction); + virtual void UpdateTimestamps(COMXCurrentStream& current, DemuxPacket* pPacket); + virtual void UpdateLimits(double& minimum, double& maximum, double dts); + virtual bool CheckSceneSkip(COMXCurrentStream& current); + virtual void CheckAutoSceneSkip(); + virtual void CheckContinuity(COMXCurrentStream& current, DemuxPacket* pPacket); + virtual void ProcessAudioData(CDemuxStream* pStream, DemuxPacket* pPacket); + virtual void ProcessVideoData(CDemuxStream* pStream, DemuxPacket* pPacket); + virtual void ProcessSubData(CDemuxStream* pStream, DemuxPacket* pPacket); + virtual void ProcessPacket(CDemuxStream* pStream, DemuxPacket* pPacket); + virtual void SynchronizeDemuxer(unsigned int timeout); + virtual void SynchronizePlayers(unsigned int sources); + virtual void SendPlayerMessage(CDVDMsg* pMsg, unsigned int target); + virtual void HandleMessages(); + + virtual bool OpenFile(const CFileItem &file, const CPlayerOptions &options); + virtual bool QueueNextFile(const CFileItem &file) {return false;} + virtual void OnNothingToQueueNotify() {} + virtual bool CloseFile(); + virtual bool IsPlaying() const; + virtual void SetPlaySpeed(int speed); + int GetPlaySpeed() { return m_playSpeed; } + virtual void Pause(); + virtual bool IsPaused() const; + virtual bool HasVideo() const; + virtual bool HasAudio() const; + virtual bool IsPassthrough() const; + virtual bool CanSeek(); + virtual void Seek(bool bPlus = true, bool bLargeStep = false); + virtual bool SeekScene(bool bPlus = true); + virtual void SeekPercentage(float fPercent = 0.0f); + virtual float GetPercentage(); + virtual float GetCachePercentage(); + + virtual void SetVolume(float fVolume); + virtual void SetDynamicRangeCompression(long drc) {} + virtual void GetAudioInfo(CStdString &strAudioInfo); + virtual void GetVideoInfo(CStdString &strVideoInfo); + virtual void GetGeneralInfo(CStdString &strVideoInfo); + virtual void Update(bool bPauseDrawing); + virtual void GetVideoRect(CRect& SrcRect, CRect& DestRect); + virtual void GetVideoAspectRatio(float &fAR); + virtual void UpdateApplication(double timeout); + virtual bool CanRecord(); + virtual bool IsRecording(); + virtual bool Record(bool bOnOff); + virtual void SetAVDelay(float fValue = 0.0f); + virtual float GetAVDelay(); + + virtual void SetSubTitleDelay(float fValue = 0.0f); + virtual float GetSubTitleDelay(); + virtual int GetSubtitleCount(); + virtual int GetSubtitle(); + virtual void GetSubtitleName(int iStream, CStdString &strStreamName); + virtual void GetSubtitleLanguage(int iStream, CStdString &strStreamLang); + virtual void SetSubtitle(int iStream); + virtual bool GetSubtitleVisible(); + virtual void SetSubtitleVisible(bool bVisible); + virtual bool GetSubtitleExtension(CStdString &strSubtitleExtension) { return false; } + virtual int AddSubtitle(const CStdString& strSubPath); + + virtual int GetAudioStreamCount(); + virtual int GetAudioStream(); + virtual void GetAudioStreamName(int iStream, CStdString &strStreamName); + virtual void SetAudioStream(int iStream); + virtual void GetAudioStreamLanguage(int iStream, CStdString &strLanguage); + + virtual TextCacheStruct_t* GetTeletextCache() {return NULL;}; + virtual void LoadPage(int p, int sp, unsigned char* buffer) {}; + + virtual int GetChapterCount(); + virtual int GetChapter(); + virtual void GetChapterName(CStdString& strChapterName); + virtual int SeekChapter(int iChapter); + + virtual void SeekTime(int64_t iTime = 0); + virtual int64_t GetTotalTimeInMsec(); + virtual int64_t GetTime(); + virtual int64_t GetTotalTime(); + virtual void ToFFRW(int iSpeed = 0); + virtual int GetAudioBitrate(); + virtual int GetVideoBitrate(); + virtual int GetSourceBitrate(); + virtual int GetChannels(); + virtual CStdString GetAudioCodecName(); + virtual CStdString GetVideoCodecName(); + virtual int GetPictureWidth(); + virtual int GetPictureHeight(); + virtual bool GetStreamDetails(CStreamDetails &details); + + virtual bool IsInMenu() const; + virtual bool HasMenu(); + + virtual bool GetCurrentSubtitle(CStdString& strSubtitle); + //returns a state that is needed for resuming from a specific time + virtual CStdString GetPlayerState(); + virtual bool SetPlayerState(CStdString state); + + virtual CStdString GetPlayingTitle(); + + enum ECacheState + { CACHESTATE_DONE = 0 + , CACHESTATE_FULL // player is filling up the demux queue + , CACHESTATE_INIT // player is waiting for first packet of each stream + , CACHESTATE_PLAY // player is waiting for players to not be stalled + , CACHESTATE_FLUSH // temporary state player will choose startup between init or full + }; + + int m_playSpeed; + struct SSpeedState + { + double lastpts; // holds last display pts during ff/rw operations + double lasttime; + } m_SpeedState; + + void HandlePlaySpeed(); + bool GetCachingTimes(double& play_left, double& cache_left, double& file_offset); + bool CheckStartCaching(COMXCurrentStream& current); + void SetCaching(ECacheState state); + double GetQueueTime(); + virtual bool IsCaching() const { return m_caching == CACHESTATE_FULL; } + virtual int GetCacheLevel() const; + + virtual int OnDVDNavResult(void* pData, int iMessage); + virtual bool OnAction(const CAction &action); +protected: + friend class COMXSelectionStreams; + + class OMXStreamLock : public CSingleLock + { + public: + inline OMXStreamLock(COMXPlayer* comxplayer) : CSingleLock(comxplayer->m_critStreamSection) {} + }; + + virtual void OnStartup(); + virtual void OnExit(); + bool WaitForPausedThumbJobs(int timeout_ms); + virtual void Process(); + + CEvent m_ready; + std::string m_filename; // holds the actual filename + CDVDInputStream *m_pInputStream; + CDVDDemux *m_pDemuxer; + CDVDDemux* m_pSubtitleDemuxer; + COMXSelectionStreams m_SelectionStreams; + std::string m_mimetype; + COMXCurrentStream m_CurrentAudio; + COMXCurrentStream m_CurrentVideo; + COMXCurrentStream m_CurrentSubtitle; + COMXCurrentStream m_CurrentTeletext; + + struct SDVDInfo + { + void Clear() + { + state = DVDSTATE_NORMAL; + iSelectedSPUStream = -1; + iSelectedAudioStream = -1; + iDVDStillTime = 0; + iDVDStillStartTime = 0; + } + + int state; // current dvdstate + unsigned int iDVDStillTime; // total time in ticks we should display the still before continuing + unsigned int iDVDStillStartTime; // time in ticks when we started the still + int iSelectedSPUStream; // mpeg stream id, or -1 if disabled + int iSelectedAudioStream; // mpeg stream id, or -1 if disabled + } m_dvd; + + struct SPlayerState + { + SPlayerState() { Clear(); } + void Clear() + { + timestamp = 0; + time = 0; + time_total = 0; + time_offset = 0; + dts = DVD_NOPTS_VALUE; + player_state = ""; + chapter = 0; + chapter_name = ""; + chapter_count = 0; + canrecord = false; + recording = false; + demux_video = ""; + demux_audio = ""; + cache_bytes = 0; + cache_level = 0.0; + cache_delay = 0.0; + cache_offset = 0.0; + } + + double timestamp; // last time of update + double time_offset; // difference between time and pts + + double time; // current playback time + double time_total; // total playback time + double dts; // last known dts + + std::string player_state; // full player state + + int chapter; // current chapter + std::string chapter_name; // name of current chapter + int chapter_count;// number of chapter + + bool canrecord; // can input stream record + bool recording; // are we currently recording + + std::string demux_video; + std::string demux_audio; + + int64_t cache_bytes; // number of bytes current's cached + double cache_level; // current estimated required cache level + double cache_delay; // time until cache is expected to reach estimated level + double cache_offset; // percentage of file ahead of current position + } m_State; + CCriticalSection m_StateSection; + + CEdl m_Edl; + + struct SEdlAutoSkipMarkers { + + void Clear() + { + cut = -1; + commbreak_start = -1; + commbreak_end = -1; + seek_to_start = false; + mute = false; + } + + int cut; // last automatically skipped EDL cut seek position + int commbreak_start; // start time of the last commercial break automatically skipped + int commbreak_end; // end time of the last commercial break automatically skipped + bool seek_to_start; // whether seeking can go back to the start of a previously skipped break + bool mute; // whether EDL mute is on + + } m_EdlAutoSkipMarkers; + + int AddSubtitleFile(const std::string& filename, const std::string& subfilename = "", CDemuxStream::EFlags flags = CDemuxStream::FLAG_NONE); + virtual void UpdatePlayState(double timeout); + + double m_UpdateApplication; + + void RenderUpdateCallBack(const void *ctx, const CRect &SrcRect, const CRect &DestRect); + +private: + void FlushBuffers(bool queued, double pts = DVD_NOPTS_VALUE, bool accurate = true); + + CCriticalSection m_critStreamSection; + + bool m_paused; + bool m_bAbortRequest; + CFileItem m_item; + CPlayerOptions m_PlayerOptions; + + std::string m_lastSub; + + double m_offset_pts; + + OMXClock m_av_clock; + OMXPlayerVideo m_player_video; + OMXPlayerAudio m_player_audio; + CDVDPlayerSubtitle m_player_subtitle; + + CDVDMessageQueue m_messenger; + + float m_current_volume; + bool m_change_volume; + bool m_stats; + CDVDOverlayContainer m_overlayContainer; + ECacheState m_caching; +}; diff --git a/xbmc/cores/omxplayer/OMXPlayerAudio.cpp b/xbmc/cores/omxplayer/OMXPlayerAudio.cpp new file mode 100644 index 0000000000..511ead7e4c --- /dev/null +++ b/xbmc/cores/omxplayer/OMXPlayerAudio.cpp @@ -0,0 +1,847 @@ +/* + * Copyright (C) 2005-2008 Team XBMC + * http://www.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, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#if (defined HAVE_CONFIG_H) && (!defined WIN32) + #include "config.h" +#elif defined(_WIN32) +#include "system.h" +#endif + +#include "OMXPlayerAudio.h" + +#include <stdio.h> +#include <unistd.h> +#include <iomanip> + +#include "FileItem.h" +#include "linux/XMemUtils.h" +#include "utils/BitstreamStats.h" +#include "settings/GUISettings.h" +#include "settings/Settings.h" + +#include "DVDDemuxers/DVDDemuxUtils.h" +#include "utils/MathUtils.h" +#include "settings/AdvancedSettings.h" +#include "settings/GUISettings.h" +#include "settings/Settings.h" +#include "video/VideoReferenceClock.h" +#include "utils/TimeUtils.h" + +#include "OMXPlayer.h" + +#include <iostream> +#include <sstream> + +class COMXMsgAudioCodecChange : public CDVDMsg +{ +public: + COMXMsgAudioCodecChange(const CDVDStreamInfo &hints, COMXAudioCodecOMX* codec) + : CDVDMsg(GENERAL_STREAMCHANGE) + , m_codec(codec) + , m_hints(hints) + {} + ~COMXMsgAudioCodecChange() + { + delete m_codec; + } + COMXAudioCodecOMX *m_codec; + CDVDStreamInfo m_hints; +}; + +OMXPlayerAudio::OMXPlayerAudio(OMXClock *av_clock, + CDVDMessageQueue& parent) +: CThread("COMXPlayerAudio") +, m_messageQueue("audio") +, m_messageParent(parent) +{ + m_av_clock = av_clock; + m_pAudioCodec = NULL; + m_speed = DVD_PLAYSPEED_NORMAL; + m_started = false; + m_stalled = false; + m_audioClock = 0; + m_buffer_empty = false; + m_nChannels = 0; + m_DecoderOpen = false; + m_freq = CurrentHostFrequency(); + m_hints_current.Clear(); + + m_av_clock->SetMasterClock(false); + + m_messageQueue.SetMaxDataSize(3 * 1024 * 1024); + m_messageQueue.SetMaxTimeSize(8.0); +} + + +OMXPlayerAudio::~OMXPlayerAudio() +{ + CloseStream(false); + + m_DllBcmHost.Unload(); +} + +bool OMXPlayerAudio::OpenStream(CDVDStreamInfo &hints) +{ + /* + if(IsRunning()) + CloseStream(false); + */ + + if(!m_DllBcmHost.Load()) + return false; + + COMXAudioCodecOMX *codec = new COMXAudioCodecOMX(); + + if(!codec || !codec->Open(hints)) + { + CLog::Log(LOGERROR, "Unsupported audio codec"); + delete codec; codec = NULL; + return false; + } + + if(m_messageQueue.IsInited()) + m_messageQueue.Put(new COMXMsgAudioCodecChange(hints, codec), 0); + else + { + if(!OpenStream(hints, codec)) + return false; + CLog::Log(LOGNOTICE, "Creating audio thread"); + m_messageQueue.Init(); + Create(); + } + + /* + if(!OpenStream(hints, codec)) + return false; + + CLog::Log(LOGNOTICE, "Creating audio thread"); + m_messageQueue.Init(); + Create(); + */ + + return true; +} + +bool OMXPlayerAudio::OpenStream(CDVDStreamInfo &hints, COMXAudioCodecOMX *codec) +{ + SAFE_DELETE(m_pAudioCodec); + + m_hints = hints; + m_pAudioCodec = codec; + + if(m_hints.bitspersample == 0) + m_hints.bitspersample = 16; + + m_speed = DVD_PLAYSPEED_NORMAL; + m_audioClock = 0; + m_error = 0; + m_errorbuff = 0; + m_errorcount = 0; + m_integral = 0; + m_skipdupcount = 0; + m_prevskipped = false; + m_syncclock = true; + m_hw_decode = false; + m_errortime = CurrentHostCounter(); + m_silence = false; + m_started = false; + m_flush = false; + m_nChannels = 0; + m_synctype = SYNC_DISCON; + m_stalled = m_messageQueue.GetPacketCount(CDVDMsg::DEMUXER_PACKET) == 0; + m_use_passthrough = (g_guiSettings.GetInt("audiooutput.mode") == AUDIO_HDMI) ? true : false ; + m_use_hw_decode = g_advancedSettings.m_omxHWAudioDecode; + + return true /*OpenDecoder()*/; +} + +bool OMXPlayerAudio::CloseStream(bool bWaitForBuffers) +{ + // wait until buffers are empty + if (bWaitForBuffers && m_speed > 0) m_messageQueue.WaitUntilEmpty(); + + m_messageQueue.Abort(); + + if(IsRunning()) + StopThread(); + + m_messageQueue.End(); + + if (m_pAudioCodec) + { + m_pAudioCodec->Dispose(); + delete m_pAudioCodec; + m_pAudioCodec = NULL; + } + + CloseDecoder(); + + m_speed = DVD_PLAYSPEED_NORMAL; + m_started = false; + + return true; +} + +void OMXPlayerAudio::OnStartup() +{ +} + +void OMXPlayerAudio::OnExit() +{ + CLog::Log(LOGNOTICE, "thread end: OMXPlayerAudio::OnExit()"); +} + + + +void OMXPlayerAudio::HandleSyncError(double duration) +{ + double clock = m_av_clock->GetClock(); + double error = m_audioClock - clock; + int64_t now; + + if( fabs(error) > DVD_MSEC_TO_TIME(100) || m_syncclock ) + { + m_av_clock->Discontinuity(clock+error); + /* + if(m_speed == DVD_PLAYSPEED_NORMAL) + CLog::Log(LOGDEBUG, "OMXPlayerAudio:: Discontinuity - was:%f, should be:%f, error:%f\n", clock, clock+error, error); + */ + + m_errorbuff = 0; + m_errorcount = 0; + m_skipdupcount = 0; + m_error = 0; + m_syncclock = false; + m_errortime = m_av_clock->CurrentHostCounter(); + + return; + } + + if (m_speed != DVD_PLAYSPEED_NORMAL) + { + m_errorbuff = 0; + m_errorcount = 0; + m_integral = 0; + m_skipdupcount = 0; + m_error = 0; + m_errortime = m_av_clock->CurrentHostCounter(); + return; + } + + //check if measured error for 1 second + now = m_av_clock->CurrentHostCounter(); + if ((now - m_errortime) >= m_freq) + { + m_errortime = now; + m_error = m_errorbuff / m_errorcount; + + m_errorbuff = 0; + m_errorcount = 0; + + if (m_synctype == SYNC_DISCON) + { + double limit, error; + + if (m_av_clock->GetRefreshRate(&limit) > 0) + { + //when the videoreferenceclock is running, the discontinuity limit is one vblank period + limit *= DVD_TIME_BASE; + + //make error a multiple of limit, rounded towards zero, + //so it won't interfere with the sync methods in CXBMCRenderManager::WaitPresentTime + if (m_error > 0.0) + error = limit * floor(m_error / limit); + else + error = limit * ceil(m_error / limit); + } + else + { + limit = DVD_MSEC_TO_TIME(10); + error = m_error; + } + + /* + limit = DVD_MSEC_TO_TIME(10); + error = m_error; + */ + + if (fabs(error) > limit - 0.001) + { + m_av_clock->Discontinuity(clock+error); + /* + if(m_speed == DVD_PLAYSPEED_NORMAL) + CLog::Log(LOGDEBUG, "COMXPlayerAudio:: Discontinuity - was:%f, should be:%f, error:%f", clock, clock+error, error); + */ + } + } + /* + else if (m_synctype == SYNC_SKIPDUP && m_skipdupcount == 0 && fabs(m_error) > DVD_MSEC_TO_TIME(10)) + if (m_skipdupcount == 0 && fabs(m_error) > DVD_MSEC_TO_TIME(10)) + { + //check how many packets to skip/duplicate + m_skipdupcount = (int)(m_error / duration); + //if less than one frame off, see if it's more than two thirds of a frame, so we can get better in sync + if (m_skipdupcount == 0 && fabs(m_error) > duration / 3 * 2) + m_skipdupcount = (int)(m_error / (duration / 3 * 2)); + + if (m_skipdupcount > 0) + CLog::Log(LOGDEBUG, "OMXPlayerAudio:: Duplicating %i packet(s) of %.2f ms duration", + m_skipdupcount, duration / DVD_TIME_BASE * 1000.0); + else if (m_skipdupcount < 0) + CLog::Log(LOGDEBUG, "OMXPlayerAudio:: Skipping %i packet(s) of %.2f ms duration ", + m_skipdupcount * -1, duration / DVD_TIME_BASE * 1000.0); + } + */ + } +} + +bool OMXPlayerAudio::CodecChange() +{ + unsigned int old_bitrate = m_hints.bitrate; + unsigned int new_bitrate = m_hints_current.bitrate; + + if(m_pAudioCodec) + { + m_hints.channels = m_pAudioCodec->GetChannels(); + m_hints.samplerate = m_pAudioCodec->GetSampleRate(); + } + + /* only check bitrate changes on CODEC_ID_DTS, CODEC_ID_AC3, CODEC_ID_EAC3 */ + if(m_hints.codec != CODEC_ID_DTS && m_hints.codec != CODEC_ID_AC3 && m_hints.codec != CODEC_ID_EAC3) + new_bitrate = old_bitrate = 0; + + if(m_hints_current.codec != m_hints.codec || + m_hints_current.channels != m_hints.channels || + m_hints_current.samplerate != m_hints.samplerate || + m_hints_current.bitspersample != m_hints.bitspersample || + old_bitrate != new_bitrate || + !m_DecoderOpen) + { + m_hints_current = m_hints; + return true; + } + + return false; +} + +bool OMXPlayerAudio::Decode(DemuxPacket *pkt, bool bDropPacket) +{ + if(!pkt) + return false; + + /* last decoder reinit went wrong */ + if(!m_pAudioCodec) + return true; + + if(pkt->dts != DVD_NOPTS_VALUE) + m_audioClock = pkt->dts; + + const uint8_t *data_dec = pkt->pData; + int data_len = pkt->iSize; + + if(!OMX_IS_RAW(m_format.m_dataFormat)) + { + while(!m_bStop && data_len > 0) + { + int len = m_pAudioCodec->Decode((BYTE *)data_dec, data_len); + if( (len < 0) || (len > data_len) ) + { + m_pAudioCodec->Reset(); + break; + } + + data_dec+= len; + data_len -= len; + + uint8_t *decoded; + int decoded_size = m_pAudioCodec->GetData(&decoded); + + if(decoded_size <=0) + continue; + + int ret = 0; + + m_audioStats.AddSampleBytes(decoded_size); + + if(CodecChange()) + { + CloseDecoder(); + + m_DecoderOpen = OpenDecoder(); + if(!m_DecoderOpen) + return false; + } + + while(!m_bStop) + { + if(m_flush) + { + m_flush = false; + break; + } + + if(m_omxAudio.GetSpace() < (unsigned int)pkt->iSize) + { + Sleep(10); + continue; + } + + if(!bDropPacket) + { + // Zero out the frame data if we are supposed to silence the audio + if(m_silence) + memset(decoded, 0x0, decoded_size); + + ret = m_omxAudio.AddPackets(decoded, decoded_size, m_audioClock, m_audioClock); + + if(ret != decoded_size) + { + CLog::Log(LOGERROR, "error ret %d decoded_size %d\n", ret, decoded_size); + } + } + + int n = (m_nChannels * m_hints.bitspersample * m_hints.samplerate)>>3; + if (n > 0) + m_audioClock += ((double)decoded_size * DVD_TIME_BASE) / n; + + if(m_speed == DVD_PLAYSPEED_NORMAL) + HandleSyncError((((double)decoded_size * DVD_TIME_BASE) / n)); + break; + + } + } + } + else + { + if(CodecChange()) + { + CloseDecoder(); + + m_DecoderOpen = OpenDecoder(); + if(!m_DecoderOpen) + return false; + } + + while(!m_bStop) + { + if(m_flush) + { + m_flush = false; + break; + } + + if(m_omxAudio.GetSpace() < (unsigned int)pkt->iSize) + { + Sleep(10); + continue; + } + + if(!bDropPacket) + { + if(m_silence) + memset(pkt->pData, 0x0, pkt->iSize); + + m_omxAudio.AddPackets(pkt->pData, pkt->iSize, m_audioClock, m_audioClock); + } + + if(m_speed == DVD_PLAYSPEED_NORMAL) + HandleSyncError(0); + + m_audioStats.AddSampleBytes(pkt->iSize); + + break; + } + } + + if(bDropPacket) + m_stalled = false; + + if(m_omxAudio.GetDelay() < 0.1) + m_stalled = true; + + // signal to our parent that we have initialized + if(m_started == false) + { + m_started = true; + m_messageParent.Put(new CDVDMsgInt(CDVDMsg::PLAYER_STARTED, DVDPLAYER_AUDIO)); + } + + if(!bDropPacket && m_speed == DVD_PLAYSPEED_NORMAL) + { + if(GetDelay() < 0.1f && !m_av_clock->OMXAudioBuffer()) + { + clock_gettime(CLOCK_REALTIME, &m_starttime); + m_av_clock->OMXAudioBufferStart(); + } + else if(GetDelay() > (AUDIO_BUFFER_SECONDS * 0.75f) && m_av_clock->OMXAudioBuffer()) + { + m_av_clock->OMXAudioBufferStop(); + } + else if(m_av_clock->OMXAudioBuffer()) + { + clock_gettime(CLOCK_REALTIME, &m_endtime); + if((m_endtime.tv_sec - m_starttime.tv_sec) > 1) + { + m_av_clock->OMXAudioBufferStop(); + } + } + } + + return true; +} + +void OMXPlayerAudio::Process() +{ + m_audioStats.Start(); + + while(!m_bStop) + { + CDVDMsg* pMsg; + int priority = (m_speed == DVD_PLAYSPEED_PAUSE && m_started) ? 1 : 0; + int timeout = 1000; + + MsgQueueReturnCode ret = m_messageQueue.Get(&pMsg, timeout, priority); + + if (ret == MSGQ_TIMEOUT) + { + Sleep(10); + continue; + } + + if (MSGQ_IS_ERROR(ret) || ret == MSGQ_ABORT) + { + Sleep(10); + continue; + } + + if (pMsg->IsType(CDVDMsg::DEMUXER_PACKET)) + { + DemuxPacket* pPacket = ((CDVDMsgDemuxerPacket*)pMsg)->GetPacket(); + bool bPacketDrop = ((CDVDMsgDemuxerPacket*)pMsg)->GetPacketDrop(); + + if(Decode(pPacket, m_speed > DVD_PLAYSPEED_NORMAL || m_speed < 0 || bPacketDrop)) + { + if (m_stalled && (m_omxAudio.GetDelay() > (AUDIO_BUFFER_SECONDS * 0.75f))) + { + CLog::Log(LOGINFO, "COMXPlayerAudio - Switching to normal playback"); + m_stalled = false; + } + } + } + else if (pMsg->IsType(CDVDMsg::GENERAL_SYNCHRONIZE)) + { + if(((CDVDMsgGeneralSynchronize*)pMsg)->Wait( 100, SYNCSOURCE_AUDIO )) + CLog::Log(LOGDEBUG, "COMXPlayerAudio - CDVDMsg::GENERAL_SYNCHRONIZE"); + else + m_messageQueue.Put(pMsg->Acquire(), 1); /* push back as prio message, to process other prio messages */ + } + else if (pMsg->IsType(CDVDMsg::GENERAL_RESYNC)) + { //player asked us to set internal clock + CDVDMsgGeneralResync* pMsgGeneralResync = (CDVDMsgGeneralResync*)pMsg; + + if (pMsgGeneralResync->m_timestamp != DVD_NOPTS_VALUE) + m_audioClock = pMsgGeneralResync->m_timestamp; + + if (pMsgGeneralResync->m_clock) + { + CLog::Log(LOGDEBUG, "COMXPlayerAudio - CDVDMsg::GENERAL_RESYNC(%f, 1)", m_audioClock); + m_av_clock->Discontinuity(m_audioClock); + //m_av_clock->OMXUpdateClock(m_audioClock); + } + else + CLog::Log(LOGDEBUG, "COMXPlayerAudio - CDVDMsg::GENERAL_RESYNC(%f, 0)", m_audioClock); + } + else if (pMsg->IsType(CDVDMsg::GENERAL_RESET)) + { + if (m_pAudioCodec) + m_pAudioCodec->Reset(); + m_started = false; + } + else if (pMsg->IsType(CDVDMsg::GENERAL_FLUSH)) + { + CLog::Log(LOGDEBUG, "COMXPlayerAudio - CDVDMsg::GENERAL_FLUSH"); + m_av_clock->Lock(); + m_av_clock->OMXStop(false); + m_omxAudio.Flush(); + m_av_clock->OMXReset(false); + m_av_clock->UnLock(); + m_syncclock = true; + m_stalled = true; + m_started = false; + + if (m_pAudioCodec) + m_pAudioCodec->Reset(); + } + else if (pMsg->IsType(CDVDMsg::PLAYER_STARTED)) + { + if(m_started) + m_messageParent.Put(new CDVDMsgInt(CDVDMsg::PLAYER_STARTED, DVDPLAYER_AUDIO)); + } + else if (pMsg->IsType(CDVDMsg::GENERAL_EOF)) + { + CLog::Log(LOGDEBUG, "COMXPlayerAudio - CDVDMsg::GENERAL_EOF"); + WaitCompletion(); + } + else if (pMsg->IsType(CDVDMsg::GENERAL_DELAY)) + { + if (m_speed != DVD_PLAYSPEED_PAUSE) + { + double timeout = static_cast<CDVDMsgDouble*>(pMsg)->m_value; + + CLog::Log(LOGDEBUG, "COMXPlayerAudio - CDVDMsg::GENERAL_DELAY(%f)", timeout); + + timeout *= (double)DVD_PLAYSPEED_NORMAL / abs(m_speed); + timeout += m_av_clock->GetAbsoluteClock(); + + while(!m_bStop && m_av_clock->GetAbsoluteClock() < timeout) + Sleep(1); + } + } + else if (pMsg->IsType(CDVDMsg::PLAYER_SETSPEED)) + { + CLog::Log(LOGDEBUG, "COMXPlayerAudio - CDVDMsg::PLAYER_SETSPEED"); + m_speed = static_cast<CDVDMsgInt*>(pMsg)->m_value; + if (m_speed != DVD_PLAYSPEED_NORMAL) + { + m_syncclock = true; + } + } + else if (pMsg->IsType(CDVDMsg::AUDIO_SILENCE)) + { + m_silence = static_cast<CDVDMsgBool*>(pMsg)->m_value; + if (m_silence) + CLog::Log(LOGDEBUG, "COMXPlayerAudio - CDVDMsg::AUDIO_SILENCE(%f, 1)", m_audioClock); + else + CLog::Log(LOGDEBUG, "COMXPlayerAudio - CDVDMsg::AUDIO_SILENCE(%f, 0)", m_audioClock); + } + else if (pMsg->IsType(CDVDMsg::GENERAL_STREAMCHANGE)) + { + COMXMsgAudioCodecChange* msg(static_cast<COMXMsgAudioCodecChange*>(pMsg)); + OpenStream(msg->m_hints, msg->m_codec); + msg->m_codec = NULL; + } + + pMsg->Release(); + } +} + +void OMXPlayerAudio::Flush() +{ + m_flush = true; + m_messageQueue.Flush(); + m_messageQueue.Put( new CDVDMsg(CDVDMsg::GENERAL_FLUSH), 1); +} + +void OMXPlayerAudio::WaitForBuffers() +{ + // make sure there are no more packets available + m_messageQueue.WaitUntilEmpty(); + + // make sure almost all has been rendered + // leave 500ms to avound buffer underruns + double delay = GetCacheTime(); + if(delay > 0.5) + Sleep((int)(1000 * (delay - 0.5))); +} + +bool OMXPlayerAudio::Passthrough() const +{ + return m_passthrough; +} + +AEDataFormat OMXPlayerAudio::GetDataFormat(CDVDStreamInfo hints) +{ + AEDataFormat dataFormat = AE_FMT_S16NE; + bool hdmi_passthrough_dts = false; + bool hdmi_passthrough_ac3 = false; + + if (m_DllBcmHost.vc_tv_hdmi_audio_supported(EDID_AudioFormat_eAC3, 2, EDID_AudioSampleRate_e44KHz, EDID_AudioSampleSize_16bit ) == 0) + hdmi_passthrough_ac3 = true; + if (m_DllBcmHost.vc_tv_hdmi_audio_supported(EDID_AudioFormat_eDTS, 2, EDID_AudioSampleRate_e44KHz, EDID_AudioSampleSize_16bit ) == 0) + hdmi_passthrough_dts = true; + //printf("Audio support AC3=%d, DTS=%d\n", hdmi_passthrough_ac3, hdmi_passthrough_dts); + + m_passthrough = false; + m_hw_decode = false; + + /* check our audio capabilties */ + + /* pathrought is overriding hw decode*/ + if(AUDIO_IS_BITSTREAM(g_guiSettings.GetInt("audiooutput.mode")) && m_use_passthrough) + { + if(hints.codec == CODEC_ID_AC3 && g_guiSettings.GetBool("audiooutput.ac3passthrough") && hdmi_passthrough_ac3) + { + dataFormat = AE_FMT_AC3; + m_passthrough = true; + } + if(hints.codec == CODEC_ID_DTS && g_guiSettings.GetBool("audiooutput.dtspassthrough") && hdmi_passthrough_dts) + { + dataFormat = AE_FMT_DTS; + m_passthrough = true; + } + } + + /* hw decode */ + if(m_use_hw_decode && !m_passthrough) + { + if(hints.codec == CODEC_ID_AC3 && COMXAudio::CanHWDecode(m_hints.codec)) + { + dataFormat = AE_FMT_AC3; + m_hw_decode = true; + } + if(hints.codec == CODEC_ID_DTS && COMXAudio::CanHWDecode(m_hints.codec)) + { + dataFormat = AE_FMT_DTS; + m_hw_decode = true; + } + } + + /* software path */ + if(!m_passthrough && !m_hw_decode) + { + /* 6 channel have to be mapped to 8 for PCM */ + if(m_nChannels > 4) + m_nChannels = 8; + dataFormat = AE_FMT_S16NE; + } + + return dataFormat; +} + +bool OMXPlayerAudio::OpenDecoder() +{ + bool bAudioRenderOpen = false; + + m_nChannels = m_hints.channels; + m_passthrough = false; + m_hw_decode = false; + + m_omxAudio.SetClock(m_av_clock); + + m_av_clock->Lock(); + m_av_clock->OMXStop(false); + m_av_clock->HasAudio(false); + + /* setup audi format for audio render */ + m_format.m_sampleRate = m_hints.samplerate; + m_format.m_channelLayout = m_pAudioCodec->GetChannelMap(); + /* GetDataFormat is setting up evrything */ + m_format.m_dataFormat = GetDataFormat(m_hints); + + std::string device = ""; + + if(g_guiSettings.GetInt("audiooutput.mode") == AUDIO_HDMI) + device = "hdmi"; + else + device = "local"; + + bAudioRenderOpen = m_omxAudio.Initialize(m_format, device, m_av_clock, m_hints, m_passthrough, m_hw_decode); + + m_codec_name = ""; + + if(!bAudioRenderOpen) + { + CLog::Log(LOGERROR, "OMXPlayerAudio : Error open audio output"); + m_av_clock->HasAudio(false); + m_av_clock->OMXReset(false); + m_av_clock->UnLock(); + return false; + } + else + { + CLog::Log(LOGINFO, "Audio codec %s channels %d samplerate %d bitspersample %d\n", + m_codec_name.c_str(), m_nChannels, m_hints.samplerate, m_hints.bitspersample); + } + + m_av_clock->HasAudio(true); + m_av_clock->OMXReset(false); + m_av_clock->UnLock(); + return true; +} + +void OMXPlayerAudio::CloseDecoder() +{ + m_av_clock->Lock(); + m_av_clock->OMXStop(false); + m_omxAudio.Deinitialize(); + m_av_clock->HasAudio(false); + m_av_clock->OMXReset(false); + m_av_clock->UnLock(); + + m_DecoderOpen = false; +} + +double OMXPlayerAudio::GetDelay() +{ + return m_omxAudio.GetDelay(); +} + +double OMXPlayerAudio::GetCacheTime() +{ + return m_omxAudio.GetCacheTime(); +} + +void OMXPlayerAudio::WaitCompletion() +{ + m_omxAudio.WaitCompletion(); +} + +void OMXPlayerAudio::RegisterAudioCallback(IAudioCallback *pCallback) +{ + m_omxAudio.RegisterAudioCallback(pCallback); +} + +void OMXPlayerAudio::UnRegisterAudioCallback() +{ + m_omxAudio.UnRegisterAudioCallback(); +} + +void OMXPlayerAudio::DoAudioWork() +{ + m_omxAudio.DoAudioWork(); +} + +void OMXPlayerAudio::SetCurrentVolume(float fVolume) +{ + m_omxAudio.SetCurrentVolume(fVolume); +} + +void OMXPlayerAudio::SetSpeed(int speed) +{ + if(m_messageQueue.IsInited()) + m_messageQueue.Put( new CDVDMsgInt(CDVDMsg::PLAYER_SETSPEED, speed), 1 ); + else + m_speed = speed; +} + +int OMXPlayerAudio::GetAudioBitrate() +{ + return (int)m_audioStats.GetBitrate(); +} + +std::string OMXPlayerAudio::GetPlayerInfo() +{ + std::ostringstream s; + s << "aq:" << setw(2) << min(99,m_messageQueue.GetLevel() + MathUtils::round_int(100.0/8.0*GetCacheTime())) << "%"; + s << ", kB/s:" << fixed << setprecision(2) << (double)GetAudioBitrate() / 1024.0; + + return s.str(); +} diff --git a/xbmc/cores/omxplayer/OMXPlayerAudio.h b/xbmc/cores/omxplayer/OMXPlayerAudio.h new file mode 100644 index 0000000000..c2dec91f33 --- /dev/null +++ b/xbmc/cores/omxplayer/OMXPlayerAudio.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2005-2008 Team XBMC + * http://www.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, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef _OMX_PLAYERAUDIO_H_ +#define _OMX_PLAYERAUDIO_H_ + +#include "utils/StdString.h" + +#include "OMXClock.h" +#include "DVDStreamInfo.h" +#include "OMXAudio.h" +#include "OMXAudioCodecOMX.h" +#include "threads/Thread.h" + +#include <deque> +#include <sys/types.h> + +#include "DVDDemuxers/DVDDemux.h" +#include "DVDMessageQueue.h" +#include "utils/BitstreamStats.h" +#include "xbmc/linux/DllBCM.h" + +using namespace std; + +class OMXPlayerAudio : public CThread +{ +protected: + CDVDMessageQueue m_messageQueue; + CDVDMessageQueue &m_messageParent; + + CDVDStreamInfo m_hints_current; + CDVDStreamInfo m_hints; + OMXClock *m_av_clock; + COMXAudio m_omxAudio; + std::string m_codec_name; + bool m_use_passthrough; + bool m_passthrough; + bool m_use_hw_decode; + bool m_hw_decode; + AEAudioFormat m_format; + CAEChannelInfo m_channelLayout; + COMXAudioCodecOMX *m_pAudioCodec; + unsigned int m_speed; + bool m_silence; + double m_audioClock; + double m_error; //last average error + + int64_t m_errortime; //timestamp of last time we measured + int64_t m_freq; + + void HandleSyncError(double duration); + double m_errorbuff; //place to store average errors + int m_errorcount;//number of errors stored + bool m_syncclock; + + double m_integral; //integral correction for resampler + int m_skipdupcount; //counter for skip/duplicate synctype + bool m_prevskipped; + + bool m_stalled; + bool m_started; + + BitstreamStats m_audioStats; + + struct timespec m_starttime, m_endtime; + bool m_buffer_empty; + bool m_flush; + //SYNC_DISCON, SYNC_SKIPDUP, SYNC_RESAMPLE + int m_synctype; + int m_nChannels; + bool m_DecoderOpen; + + DllBcmHost m_DllBcmHost; + + virtual void OnStartup(); + virtual void OnExit(); + virtual void Process(); +private: +public: + OMXPlayerAudio(OMXClock *av_clock, CDVDMessageQueue& parent); + ~OMXPlayerAudio(); + bool OpenStream(CDVDStreamInfo &hints); + bool OpenStream(CDVDStreamInfo &hints, COMXAudioCodecOMX *codec); + void SendMessage(CDVDMsg* pMsg, int priority = 0) { m_messageQueue.Put(pMsg, priority); } + bool AcceptsData() const { return !m_messageQueue.IsFull(); } + bool HasData() const { return m_messageQueue.GetDataSize() > 0; } + bool IsInited() const { return m_messageQueue.IsInited(); } + int GetLevel() const { return m_messageQueue.GetLevel(); } + bool IsStalled() { return m_stalled; } + void WaitForBuffers(); + bool CloseStream(bool bWaitForBuffers); + bool CodecChange(); + bool Decode(DemuxPacket *pkt, bool bDropPacket); + void Flush(); + bool AddPacket(DemuxPacket *pkt); + AEDataFormat GetDataFormat(CDVDStreamInfo hints); + bool Passthrough() const; + bool OpenDecoder(); + void CloseDecoder(); + double GetDelay(); + double GetCacheTime(); + double GetCurrentPTS() { return m_audioClock; }; + void WaitCompletion(); + void RegisterAudioCallback(IAudioCallback* pCallback); + void UnRegisterAudioCallback(); + void DoAudioWork(); + void SetCurrentVolume(float fVolume); + void SetSpeed(int iSpeed); + int GetAudioBitrate(); + std::string GetPlayerInfo(); +}; +#endif diff --git a/xbmc/cores/omxplayer/OMXPlayerVideo.cpp b/xbmc/cores/omxplayer/OMXPlayerVideo.cpp new file mode 100644 index 0000000000..b0bc571dc6 --- /dev/null +++ b/xbmc/cores/omxplayer/OMXPlayerVideo.cpp @@ -0,0 +1,816 @@ +/* + * Copyright (C) 2005-2008 Team XBMC + * http://www.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, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#if (defined HAVE_CONFIG_H) && (!defined WIN32) + #include "config.h" +#elif defined(_WIN32) +#include "system.h" +#endif + +#include "OMXPlayerVideo.h" + +#include <stdio.h> +#include <unistd.h> +#include <sys/time.h> +#include <iomanip> + +#include "FileItem.h" +#include "linux/XMemUtils.h" +#include "utils/BitstreamStats.h" + +#include "DVDDemuxers/DVDDemuxUtils.h" +#include "DVDCodecs/DVDCodecUtils.h" +#include "windowing/WindowingFactory.h" +#include "DVDOverlayRenderer.h" +#include "settings/GUISettings.h" +#include "settings/Settings.h" +#include "cores/VideoRenderers/RenderFormats.h" +#include "cores/VideoRenderers/RenderFlags.h" + +#include "OMXPlayer.h" + +#include <iostream> +#include <sstream> + +class COMXMsgAudioCodecChange : public CDVDMsg +{ +public: + COMXMsgAudioCodecChange(const CDVDStreamInfo &hints, COMXVideo *codec) + : CDVDMsg(GENERAL_STREAMCHANGE) + , m_codec(codec) + , m_hints(hints) + {} + ~COMXMsgAudioCodecChange() + { + delete m_codec; + } + COMXVideo *m_codec; + CDVDStreamInfo m_hints; +}; + +OMXPlayerVideo::OMXPlayerVideo(OMXClock *av_clock, + CDVDOverlayContainer* pOverlayContainer, + CDVDMessageQueue& parent) +: CThread("COMXPlayerVideo") +, m_messageQueue("video") +, m_messageParent(parent) +{ + m_av_clock = av_clock; + m_pOverlayContainer = pOverlayContainer; + m_pTempOverlayPicture = NULL; + m_open = false; + m_stream_id = -1; + m_fFrameRate = 25.0f; + m_flush = false; + m_hdmi_clock_sync = false; + m_iVideoDelay = 0; + m_speed = DVD_PLAYSPEED_NORMAL; + m_stalled = false; + m_codecname = ""; + m_iSubtitleDelay = 0; + m_bRenderSubs = false; + m_width = 0; + m_height = 0; + m_fps = 0.0f; + m_flags = 0; + m_bAllowFullscreen = false; + m_iCurrentPts = DVD_NOPTS_VALUE; + m_fFrameRate = 25.0f; + m_iVideoDelay = 0; + m_messageQueue.SetMaxDataSize(10 * 1024 * 1024); + m_messageQueue.SetMaxTimeSize(8.0); + + RESOLUTION res = g_graphicsContext.GetVideoResolution(); + m_video_width = g_settings.m_ResInfo[res].iWidth; + m_video_height = g_settings.m_ResInfo[res].iHeight; + + m_dst_rect.SetRect(0, 0, 0, 0); + +} + +OMXPlayerVideo::~OMXPlayerVideo() +{ + CloseStream(false); +} + +bool OMXPlayerVideo::OpenStream(CDVDStreamInfo &hints) +{ + /* + if(IsRunning()) + CloseStream(false); + */ + + m_hints = hints; + m_Deinterlace = ( g_settings.m_currentVideoSettings.m_DeinterlaceMode == VS_DEINTERLACEMODE_OFF ) ? false : true; + m_flush = false; + m_hdmi_clock_sync = g_guiSettings.GetBool("videoplayer.adjustrefreshrate"); + m_started = false; + m_stalled = m_messageQueue.GetPacketCount(CDVDMsg::DEMUXER_PACKET) == 0; + m_autosync = 1; + + m_audio_count = m_av_clock->HasAudio(); + + if (!m_DllBcmHost.Load()) + return false; + + if(!OpenDecoder()) + { + return false; + } + + if(m_messageQueue.IsInited()) + m_messageQueue.Put(new COMXMsgAudioCodecChange(hints, NULL), 0); + else + { + if(!OpenStream(hints, NULL)) + return false; + CLog::Log(LOGNOTICE, "Creating video thread"); + m_messageQueue.Init(); + Create(); + } + + /* + if(!OpenStream(hints, NULL)) + return false; + + CLog::Log(LOGNOTICE, "Creating video thread"); + m_messageQueue.Init(); + Create(); + */ + + m_open = true; + + return true; +} + +bool OMXPlayerVideo::OpenStream(CDVDStreamInfo &hints, COMXVideo *codec) +{ + return true; +} + +bool OMXPlayerVideo::CloseStream(bool bWaitForBuffers) +{ + m_flush = true; + + // wait until buffers are empty + if (bWaitForBuffers && m_speed > 0) m_messageQueue.WaitUntilEmpty(); + + m_messageQueue.Abort(); + + if(IsRunning()) + StopThread(); + + m_messageQueue.End(); + + m_open = false; + m_stream_id = -1; + m_speed = DVD_PLAYSPEED_NORMAL; + m_started = false; + + if (m_pTempOverlayPicture) + { + CDVDCodecUtils::FreePicture(m_pTempOverlayPicture); + m_pTempOverlayPicture = NULL; + } + + m_av_clock->Lock(); + m_av_clock->OMXStop(false); + m_omxVideo.Close(); + m_av_clock->HasVideo(false); + m_av_clock->OMXReset(false); + m_av_clock->UnLock(); + + if(m_DllBcmHost.IsLoaded()) + m_DllBcmHost.Unload(); + + return true; +} + +void OMXPlayerVideo::OnStartup() +{ + m_iCurrentPts = DVD_NOPTS_VALUE; + m_FlipTimeStamp = m_av_clock->GetAbsoluteClock(); +} + +void OMXPlayerVideo::OnExit() +{ + CLog::Log(LOGNOTICE, "thread end: video_thread"); +} + +void OMXPlayerVideo::ProcessOverlays(int iGroupId, double pts) +{ + // remove any overlays that are out of time + if (m_started) + m_pOverlayContainer->CleanUp(pts - m_iSubtitleDelay); + + enum EOverlay + { OVERLAY_AUTO // select mode auto + , OVERLAY_GPU // render osd using gpu + , OVERLAY_BUF // render osd on buffer + } render = OVERLAY_AUTO; + + /* + if(m_pOverlayContainer->ContainsOverlayType(DVDOVERLAY_TYPE_SPU) + || m_pOverlayContainer->ContainsOverlayType(DVDOVERLAY_TYPE_IMAGE) + || m_pOverlayContainer->ContainsOverlayType(DVDOVERLAY_TYPE_SSA) ) + render = OVERLAY_BUF; + */ + + if(render == OVERLAY_BUF) + { + // rendering spu overlay types directly on video memory costs a lot of processing power. + // thus we allocate a temp picture, copy the original to it (needed because the same picture can be used more than once). + // then do all the rendering on that temp picture and finaly copy it to video memory. + // In almost all cases this is 5 or more times faster!. + + if(m_pTempOverlayPicture && ( m_pTempOverlayPicture->iWidth != m_width + || m_pTempOverlayPicture->iHeight != m_height)) + { + CDVDCodecUtils::FreePicture(m_pTempOverlayPicture); + m_pTempOverlayPicture = NULL; + } + + if(!m_pTempOverlayPicture) + m_pTempOverlayPicture = CDVDCodecUtils::AllocatePicture(m_width, m_height); + if(!m_pTempOverlayPicture) + return; + m_pTempOverlayPicture->format = RENDER_FMT_YUV420P; + } + + if(render == OVERLAY_AUTO) + render = OVERLAY_GPU; + + VecOverlays overlays; + + { + CSingleLock lock(*m_pOverlayContainer); + + VecOverlays* pVecOverlays = m_pOverlayContainer->GetOverlays(); + VecOverlaysIter it = pVecOverlays->begin(); + + //Check all overlays and render those that should be rendered, based on time and forced + //Both forced and subs should check timeing, pts == 0 in the stillframe case + while (it != pVecOverlays->end()) + { + CDVDOverlay* pOverlay = *it++; + if(!pOverlay->bForced && !m_bRenderSubs) + continue; + + if(pOverlay->iGroupId != iGroupId) + continue; + + double pts2 = pOverlay->bForced ? pts : pts - m_iSubtitleDelay; + + if((pOverlay->iPTSStartTime <= pts2 && (pOverlay->iPTSStopTime > pts2 || pOverlay->iPTSStopTime == 0LL)) || pts == 0) + { + if(pOverlay->IsOverlayType(DVDOVERLAY_TYPE_GROUP)) + overlays.insert(overlays.end(), static_cast<CDVDOverlayGroup*>(pOverlay)->m_overlays.begin() + , static_cast<CDVDOverlayGroup*>(pOverlay)->m_overlays.end()); + else + overlays.push_back(pOverlay); + + } + } + + for(it = overlays.begin(); it != overlays.end(); ++it) + { + double pts2 = (*it)->bForced ? pts : pts - m_iSubtitleDelay; + + if (render == OVERLAY_GPU) + g_renderManager.AddOverlay(*it, pts2); + + /* + printf("subtitle : DVDOVERLAY_TYPE_SPU %d DVDOVERLAY_TYPE_IMAGE %d DVDOVERLAY_TYPE_SSA %d\n", + m_pOverlayContainer->ContainsOverlayType(DVDOVERLAY_TYPE_SPU), + m_pOverlayContainer->ContainsOverlayType(DVDOVERLAY_TYPE_IMAGE), + m_pOverlayContainer->ContainsOverlayType(DVDOVERLAY_TYPE_SSA) ); + */ + + if (render == OVERLAY_BUF) + CDVDOverlayRenderer::Render(m_pTempOverlayPicture, *it, pts2); + } + } +} + +void OMXPlayerVideo::Output(int iGroupId, double pts, bool bDropPacket) +{ + + if (!g_renderManager.IsConfigured() + || m_video_width != m_width + || m_video_height != m_height + || m_fps != m_fFrameRate) + { + m_width = m_video_width; + m_height = m_video_height; + m_fps = m_fFrameRate; + + unsigned flags = 0; + ERenderFormat format = RENDER_FMT_BYPASS; + + if(m_bAllowFullscreen) + { + flags |= CONF_FLAGS_FULLSCREEN; + m_bAllowFullscreen = false; // only allow on first configure + } + + if(m_flags & CONF_FLAGS_FORMAT_SBS) + { + if(g_Windowing.Support3D(m_video_width, m_video_height, D3DPRESENTFLAG_MODE3DSBS)) + { + CLog::Log(LOGNOTICE, "3DSBS movie found"); + flags |= CONF_FLAGS_FORMAT_SBS; + } + } + + CLog::Log(LOGDEBUG,"%s - change configuration. %dx%d. framerate: %4.2f. format: BYPASS", + __FUNCTION__, m_width, m_height, m_fps); + + if(!g_renderManager.Configure(m_video_width, m_video_height, + m_video_width, m_video_height, m_fps, flags, format, 0, + m_hints.orientation)) + { + CLog::Log(LOGERROR, "%s - failed to configure renderer", __FUNCTION__); + return; + } + + g_renderManager.RegisterRenderUpdateCallBack((const void*)this, RenderUpdateCallBack); + } + + if (!g_renderManager.IsStarted()) { + CLog::Log(LOGERROR, "%s - renderer not started", __FUNCTION__); + return; + } + + // calculate the time we need to delay this picture before displaying + double iSleepTime, iClockSleep, iFrameSleep, iPlayingClock, iCurrentClock, iFrameDuration; + + iPlayingClock = m_av_clock->GetClock(iCurrentClock, false); // snapshot current clock + iClockSleep = pts - iPlayingClock; //sleep calculated by pts to clock comparison + iFrameSleep = m_FlipTimeStamp - iCurrentClock; // sleep calculated by duration of frame + iFrameDuration = (double)DVD_TIME_BASE / m_fFrameRate; //pPacket->duration; + + // correct sleep times based on speed + if(m_speed) + { + iClockSleep = iClockSleep * DVD_PLAYSPEED_NORMAL / m_speed; + iFrameSleep = iFrameSleep * DVD_PLAYSPEED_NORMAL / abs(m_speed); + iFrameDuration = iFrameDuration * DVD_PLAYSPEED_NORMAL / abs(m_speed); + } + else + { + iClockSleep = 0; + iFrameSleep = 0; + } + + // dropping to a very low framerate is not correct (it should not happen at all) + iClockSleep = min(iClockSleep, DVD_MSEC_TO_TIME(500)); + iFrameSleep = min(iFrameSleep, DVD_MSEC_TO_TIME(500)); + + if( m_stalled ) + iSleepTime = iFrameSleep; + else + iSleepTime = iFrameSleep + (iClockSleep - iFrameSleep) / m_autosync; + + // present the current pts of this frame to user, and include the actual + // presentation delay, to allow him to adjust for it + if( m_stalled ) + m_iCurrentPts = DVD_NOPTS_VALUE; + else + m_iCurrentPts = pts - max(0.0, iSleepTime); + + // timestamp when we think next picture should be displayed based on current duration + m_FlipTimeStamp = iCurrentClock; + m_FlipTimeStamp += max(0.0, iSleepTime); + m_FlipTimeStamp += iFrameDuration; + + if( m_speed < 0 ) + { + if( iClockSleep < -DVD_MSEC_TO_TIME(200)) + return; + } + + if(bDropPacket) + return; + +#if 0 + if( m_speed != DVD_PLAYSPEED_NORMAL) + { + // calculate frame dropping pattern to render at this speed + // we do that by deciding if this or next frame is closest + // to the flip timestamp + double current = fabs(m_dropbase - m_droptime); + double next = fabs(m_dropbase - (m_droptime + iFrameDuration)); + double frametime = (double)DVD_TIME_BASE / m_fFrameRate; + + m_droptime += iFrameDuration; +#ifndef PROFILE + if( next < current /*&& !(pPicture->iFlags & DVP_FLAG_NOSKIP) */) + return /*result | EOS_DROPPED*/; +#endif + + while(!m_bStop && m_dropbase < m_droptime) m_dropbase += frametime; + while(!m_bStop && m_dropbase - frametime > m_droptime) m_dropbase -= frametime; + } + else + { + m_droptime = 0.0f; + m_dropbase = 0.0f; + } +#else + m_droptime = 0.0f; + m_dropbase = 0.0f; +#endif + + double pts_media = m_av_clock->OMXMediaTime(); + ProcessOverlays(iGroupId, pts_media); + + while(!CThread::m_bStop && m_av_clock->GetAbsoluteClock(false) < (iCurrentClock + iSleepTime + DVD_MSEC_TO_TIME(500)) ) + Sleep(1); + + g_renderManager.FlipPage(CThread::m_bStop, (iCurrentClock + iSleepTime) / DVD_TIME_BASE, -1, FS_NONE); + + //m_av_clock->WaitAbsoluteClock((iCurrentClock + iSleepTime)); +} + +void OMXPlayerVideo::Process() +{ + double pts = 0; + double frametime = (double)DVD_TIME_BASE / m_fFrameRate; + bool bRequestDrop = false; + + m_videoStats.Start(); + + while(!m_bStop) + { + CDVDMsg* pMsg; + int iQueueTimeOut = (int)(m_stalled ? frametime / 4 : frametime * 10) / 1000; + int iPriority = (m_speed == DVD_PLAYSPEED_PAUSE && m_started) ? 1 : 0; + MsgQueueReturnCode ret = m_messageQueue.Get(&pMsg, iQueueTimeOut, iPriority); + + if (MSGQ_IS_ERROR(ret) || ret == MSGQ_ABORT) + { + CLog::Log(LOGERROR, "Got MSGQ_ABORT or MSGO_IS_ERROR return true"); + break; + } + else if (ret == MSGQ_TIMEOUT) + { + // if we only wanted priority messages, this isn't a stall + if( iPriority ) + continue; + + //Okey, start rendering at stream fps now instead, we are likely in a stillframe + if( !m_stalled ) + { + if(m_started) + CLog::Log(LOGINFO, "COMXPlayerVideo - Stillframe detected, switching to forced %f fps", m_fFrameRate); + m_stalled = true; + pts += frametime*4; + } + + pts += frametime; + + continue; + } + + if (pMsg->IsType(CDVDMsg::GENERAL_SYNCHRONIZE)) + { + if(((CDVDMsgGeneralSynchronize*)pMsg)->Wait(100, SYNCSOURCE_VIDEO)) + { + CLog::Log(LOGDEBUG, "COMXPlayerVideo - CDVDMsg::GENERAL_SYNCHRONIZE"); + + } + else + m_messageQueue.Put(pMsg->Acquire(), 1); /* push back as prio message, to process other prio messages */ + + pMsg->Release(); + + continue; + } + else if (pMsg->IsType(CDVDMsg::GENERAL_RESYNC)) + { + CDVDMsgGeneralResync* pMsgGeneralResync = (CDVDMsgGeneralResync*)pMsg; + + if(pMsgGeneralResync->m_timestamp != DVD_NOPTS_VALUE) + pts = pMsgGeneralResync->m_timestamp; + + double delay = m_FlipTimeStamp - m_av_clock->GetAbsoluteClock(); + if( delay > frametime ) delay = frametime; + else if( delay < 0 ) delay = 0; + + if(pMsgGeneralResync->m_clock) + { + CLog::Log(LOGDEBUG, "COMXPlayerVideo - CDVDMsg::GENERAL_RESYNC(%f, 1)", pts); + m_av_clock->Discontinuity(pts - delay); + //m_av_clock->OMXUpdateClock(pts - delay); + } + else + CLog::Log(LOGDEBUG, "COMXPlayerVideo - CDVDMsg::GENERAL_RESYNC(%f, 0)", pts); + + pMsgGeneralResync->Release(); + continue; + } + else if (pMsg->IsType(CDVDMsg::GENERAL_DELAY)) + { + if (m_speed != DVD_PLAYSPEED_PAUSE) + { + double timeout = static_cast<CDVDMsgDouble*>(pMsg)->m_value; + + CLog::Log(LOGDEBUG, "COMXPlayerVideo - CDVDMsg::GENERAL_DELAY(%f)", timeout); + + timeout *= (double)DVD_PLAYSPEED_NORMAL / abs(m_speed); + timeout += m_av_clock->GetAbsoluteClock(); + + while(!m_bStop && m_av_clock->GetAbsoluteClock() < timeout) + Sleep(1); + } + } + else if (pMsg->IsType(CDVDMsg::GENERAL_RESET)) + { + CLog::Log(LOGDEBUG, "COMXPlayerVideo - CDVDMsg::GENERAL_RESET"); + m_av_clock->Lock(); + m_av_clock->OMXStop(false); + m_omxVideo.Reset(); + m_av_clock->OMXReset(false); + m_av_clock->UnLock(); + m_started = false; + } + else if (pMsg->IsType(CDVDMsg::GENERAL_FLUSH)) // private message sent by (COMXPlayerVideo::Flush()) + { + CLog::Log(LOGDEBUG, "COMXPlayerVideo - CDVDMsg::GENERAL_FLUSH"); + m_stalled = true; + m_started = false; + m_av_clock->Lock(); + m_av_clock->OMXStop(false); + m_omxVideo.Reset(); + m_av_clock->OMXReset(false); + m_av_clock->UnLock(); + } + else if (pMsg->IsType(CDVDMsg::PLAYER_SETSPEED)) + { + m_speed = static_cast<CDVDMsgInt*>(pMsg)->m_value; + } + else if (pMsg->IsType(CDVDMsg::PLAYER_STARTED)) + { + if(m_started) + m_messageParent.Put(new CDVDMsgInt(CDVDMsg::PLAYER_STARTED, DVDPLAYER_VIDEO)); + } + else if (pMsg->IsType(CDVDMsg::GENERAL_STREAMCHANGE)) + { + COMXMsgAudioCodecChange* msg(static_cast<COMXMsgAudioCodecChange*>(pMsg)); + OpenStream(msg->m_hints, msg->m_codec); + msg->m_codec = NULL; + } + else if (pMsg->IsType(CDVDMsg::GENERAL_EOF) && !m_audio_count) + { + CLog::Log(LOGDEBUG, "COMXPlayerVideo - CDVDMsg::GENERAL_EOF"); + WaitCompletion(); + } + else if (pMsg->IsType(CDVDMsg::DEMUXER_PACKET)) + { + DemuxPacket* pPacket = ((CDVDMsgDemuxerPacket*)pMsg)->GetPacket(); + bool bPacketDrop = ((CDVDMsgDemuxerPacket*)pMsg)->GetPacketDrop(); + + if (m_messageQueue.GetDataSize() == 0 + || m_speed < 0) + { + bRequestDrop = false; + } + + // if player want's us to drop this packet, do so nomatter what + if(bPacketDrop) + bRequestDrop = true; + + m_omxVideo.SetDropState(bRequestDrop); + + while (!m_bStop) + { + if(m_flush) + { + m_flush = false; + break; + } + + if((unsigned long)m_omxVideo.GetFreeSpace() < pPacket->iSize) + { + Sleep(10); + continue; + } + + if (m_stalled) + { + CLog::Log(LOGINFO, "COMXPlayerVideo - Stillframe left, switching to normal playback"); + m_stalled = false; + } + + // validate picture timing, + // if both dts/pts invalid, use pts calulated from picture.iDuration + // if pts invalid use dts, else use picture.pts as passed + if (pPacket->dts == DVD_NOPTS_VALUE && pPacket->pts == DVD_NOPTS_VALUE) + pPacket->pts = pts; + else if (pPacket->pts == DVD_NOPTS_VALUE) + pPacket->pts = pPacket->dts; + + if(pPacket->pts != DVD_NOPTS_VALUE) + pPacket->pts += m_iVideoDelay; + + if(pPacket->duration == 0) + pPacket->duration = frametime; + + m_omxVideo.Decode(pPacket->pData, pPacket->iSize, pPacket->pts, pPacket->pts); + Output(pPacket->iGroupId, pPacket->pts, bRequestDrop); + + if(m_started == false) + { + m_codecname = m_omxVideo.GetDecoderName(); + m_started = true; + m_messageParent.Put(new CDVDMsgInt(CDVDMsg::PLAYER_STARTED, DVDPLAYER_VIDEO)); + } + + // guess next frame pts. iDuration is always valid + if (m_speed != 0) + pts += pPacket->duration * m_speed / abs(m_speed); + + break; + } + + bRequestDrop = false; + + m_videoStats.AddSampleBytes(pPacket->iSize); + } + pMsg->Release(); + + } +} + +void OMXPlayerVideo::Flush() +{ + m_flush = true; + m_messageQueue.Flush(); + m_messageQueue.Put(new CDVDMsg(CDVDMsg::GENERAL_FLUSH), 1); +} + +bool OMXPlayerVideo::OpenDecoder() +{ + if(!m_av_clock) + return false; + + if (m_hints.fpsrate && m_hints.fpsscale) + m_fFrameRate = DVD_TIME_BASE / OMXClock::NormalizeFrameduration((double)DVD_TIME_BASE * m_hints.fpsscale / m_hints.fpsrate); + else + m_fFrameRate = 25; + + if( m_fFrameRate > 100 || m_fFrameRate < 5 ) + { + CLog::Log(LOGINFO, "OMXPlayerVideo::OpenDecoder : Invalid framerate %d, using forced 25fps and just trust timestamps\n", (int)m_fFrameRate); + m_fFrameRate = 25; + } + + m_av_clock->Lock(); + m_av_clock->OMXStop(false); + if(!m_omxVideo.Open(m_hints, m_av_clock, m_Deinterlace, m_hdmi_clock_sync)) + { + CLog::Log(LOGERROR, "OMXPlayerAudio : Error open video output"); + m_av_clock->HasVideo(false); + m_av_clock->OMXReset(false); + m_av_clock->UnLock(); + return false; + } + else + CLog::Log(LOGINFO, "OMXPlayerVideo::OpenDecoder : Video codec %s width %d height %d profile %d fps %f\n", + m_omxVideo.GetDecoderName().c_str() , m_hints.width, m_hints.height, m_hints.profile, m_fFrameRate); + + m_codecname = m_omxVideo.GetDecoderName(); + + // if we are closer to ntsc version of framerate, let gpu know + int iFrameRate = (int)(m_fFrameRate + 0.5f); + bool bNtscFreq = fabs(m_fFrameRate * 1001.0f / 1000.0f - iFrameRate) < fabs(m_fFrameRate - iFrameRate); + char response[80], command[80]; + sprintf(command, "hdmi_ntsc_freqs %d", bNtscFreq); + CLog::Log(LOGINFO, "OMXPlayerVideo::OpenDecoder fps: %f %s\n", m_fFrameRate, command); + m_DllBcmHost.vc_gencmd(response, sizeof response, command); + + if(m_av_clock) + m_av_clock->SetRefreshRate(m_fFrameRate); + + m_av_clock->HasVideo(true); + m_av_clock->OMXReset(false); + m_av_clock->UnLock(); + return true; +} + +int OMXPlayerVideo::GetDecoderBufferSize() +{ + return m_omxVideo.GetInputBufferSize(); +} + +int OMXPlayerVideo::GetDecoderFreeSpace() +{ + return m_omxVideo.GetFreeSpace(); +} + +void OMXPlayerVideo::WaitCompletion() +{ + m_omxVideo.WaitCompletion(); +} + +void OMXPlayerVideo::SetSpeed(int speed) +{ + if(m_messageQueue.IsInited()) + m_messageQueue.Put( new CDVDMsgInt(CDVDMsg::PLAYER_SETSPEED, speed), 1 ); + else + m_speed = speed; +} + +std::string OMXPlayerVideo::GetPlayerInfo() +{ + std::ostringstream s; + s << "fr:" << fixed << setprecision(3) << m_fFrameRate; + s << ", vq:" << setw(2) << min(99,GetLevel()) << "%"; + s << ", dc:" << m_codecname; + s << ", Mb/s:" << fixed << setprecision(2) << (double)GetVideoBitrate() / (1024.0*1024.0); + + return s.str(); +} + +int OMXPlayerVideo::GetVideoBitrate() +{ + return (int)m_videoStats.GetBitrate(); +} + +double OMXPlayerVideo::GetOutputDelay() +{ + double time = m_messageQueue.GetPacketCount(CDVDMsg::DEMUXER_PACKET); + if( m_fFrameRate ) + time = (time * DVD_TIME_BASE) / m_fFrameRate; + else + time = 0.0; + + if( m_speed != 0 ) + time = time * DVD_PLAYSPEED_NORMAL / abs(m_speed); + + return time; +} + +int OMXPlayerVideo::GetFreeSpace() +{ + return m_omxVideo.GetFreeSpace(); +} + +void OMXPlayerVideo::SetVideoRect(const CRect &SrcRect, const CRect &DestRect) +{ + // check if destination rect or video view mode has changed + if ((m_dst_rect != DestRect) || (m_view_mode != g_settings.m_currentVideoSettings.m_ViewMode)) + { + m_dst_rect = DestRect; + m_view_mode = g_settings.m_currentVideoSettings.m_ViewMode; + } + else + { + return; + } + + // might need to scale up m_dst_rect to display size as video decodes + // to separate video plane that is at display size. + CRect gui, display, dst_rect; + RESOLUTION res = g_graphicsContext.GetVideoResolution(); + gui.SetRect(0, 0, g_settings.m_ResInfo[res].iWidth, g_settings.m_ResInfo[res].iHeight); + display.SetRect(0, 0, g_settings.m_ResInfo[res].iWidth, g_settings.m_ResInfo[res].iHeight); + + dst_rect = m_dst_rect; + if (gui != display) + { + float xscale = display.Width() / gui.Width(); + float yscale = display.Height() / gui.Height(); + dst_rect.x1 *= xscale; + dst_rect.x2 *= xscale; + dst_rect.y1 *= yscale; + dst_rect.y2 *= yscale; + } + + m_omxVideo.SetVideoRect(SrcRect, m_dst_rect); +} + +void OMXPlayerVideo::RenderUpdateCallBack(const void *ctx, const CRect &SrcRect, const CRect &DestRect) +{ + OMXPlayerVideo *player = (OMXPlayerVideo*)ctx; + player->SetVideoRect(SrcRect, DestRect); +} + diff --git a/xbmc/cores/omxplayer/OMXPlayerVideo.h b/xbmc/cores/omxplayer/OMXPlayerVideo.h new file mode 100644 index 0000000000..449400584f --- /dev/null +++ b/xbmc/cores/omxplayer/OMXPlayerVideo.h @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2005-2008 Team XBMC + * http://www.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, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef _OMX_PLAYERVIDEO_H_ +#define _OMX_PLAYERVIDEO_H_ + +#include "utils/StdString.h" + +#include "OMXClock.h" +#include "DVDStreamInfo.h" +#include "OMXVideo.h" +#include "threads/Thread.h" + +#include <deque> +#include <sys/types.h> + +#include "DVDDemuxers/DVDDemux.h" +#include "DVDStreamInfo.h" +#include "DVDCodecs/Video/DVDVideoCodec.h" +#include "DVDOverlayContainer.h" +#include "DVDMessageQueue.h" +#include "utils/BitstreamStats.h" +#include "linux/DllBCM.h" + +using namespace std; + +class OMXPlayerVideo : public CThread +{ +protected: + CDVDMessageQueue m_messageQueue; + int m_stream_id; + bool m_open; + CDVDStreamInfo m_hints; + double m_iCurrentPts; + OMXClock *m_av_clock; + COMXVideo m_omxVideo; + float m_fFrameRate; + bool m_Deinterlace; + bool m_flush; + bool m_hdmi_clock_sync; + double m_iVideoDelay; + int m_speed; + double m_FlipTimeStamp; // time stamp of last flippage. used to play at a forced framerate + int m_audio_count; + bool m_stalled; + bool m_started; + std::string m_codecname; + double m_droptime; + double m_dropbase; + unsigned int m_autosync; + double m_iSubtitleDelay; + bool m_bRenderSubs; + bool m_bAllowFullscreen; + + unsigned int m_width; + unsigned int m_height; + unsigned int m_video_width; + unsigned int m_video_height; + unsigned m_flags; + float m_fps; + + CRect m_dst_rect; + int m_view_mode; + + DllBcmHost m_DllBcmHost; + + CDVDOverlayContainer *m_pOverlayContainer; + CDVDMessageQueue &m_messageParent; + + BitstreamStats m_videoStats; + + DVDVideoPicture* m_pTempOverlayPicture; + + void ProcessOverlays(int iGroupId, double pts); + + virtual void OnStartup(); + virtual void OnExit(); + virtual void Process(); +private: +public: + OMXPlayerVideo(OMXClock *av_clock, CDVDOverlayContainer* pOverlayContainer, CDVDMessageQueue& parent); + ~OMXPlayerVideo(); + bool OpenStream(CDVDStreamInfo &hints); + bool OpenStream(CDVDStreamInfo &hints, COMXVideo *codec); + void SendMessage(CDVDMsg* pMsg, int priority = 0) { m_messageQueue.Put(pMsg, priority); } + bool AcceptsData() const { return !m_messageQueue.IsFull(); } + bool HasData() const { return m_messageQueue.GetDataSize() > 0; } + bool IsInited() const { return m_messageQueue.IsInited(); } + void WaitForBuffers() { m_messageQueue.WaitUntilEmpty(); } + int GetLevel() const { return m_messageQueue.GetLevel(); } + bool IsStalled() { return m_stalled; } + bool CloseStream(bool bWaitForBuffers); + void Output(int iGroupId, double pts, bool bDropPacket); + void Flush(); + bool OpenDecoder(); + int GetDecoderBufferSize(); + int GetDecoderFreeSpace(); + double GetCurrentPTS() { return m_iCurrentPts; }; + double GetFPS() { return m_fFrameRate; }; + void WaitCompletion(); + void SetDelay(double delay) { m_iVideoDelay = delay; } + double GetDelay() { return m_iVideoDelay; } + void SetSpeed(int iSpeed); + std::string GetPlayerInfo(); + int GetVideoBitrate(); + double GetOutputDelay(); + double GetSubtitleDelay() { return m_iSubtitleDelay; } + void SetSubtitleDelay(double delay) { m_iSubtitleDelay = delay; } + void EnableSubtitle(bool bEnable) { m_bRenderSubs = bEnable; } + bool IsSubtitleEnabled() { return m_bRenderSubs; } + void EnableFullscreen(bool bEnable) { m_bAllowFullscreen = bEnable; } + void SetFlags(unsigned flags) { m_flags = flags; }; + int GetFreeSpace(); + void SetVideoRect(const CRect &SrcRect, const CRect &DestRect); + static void RenderUpdateCallBack(const void *ctx, const CRect &SrcRect, const CRect &DestRect); +}; +#endif diff --git a/xbmc/cores/omxplayer/OMXVideo.cpp b/xbmc/cores/omxplayer/OMXVideo.cpp new file mode 100644 index 0000000000..9b6bd6bf97 --- /dev/null +++ b/xbmc/cores/omxplayer/OMXVideo.cpp @@ -0,0 +1,966 @@ +/* + * Copyright (C) 2010 Team XBMC + * http://www.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, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#if (defined HAVE_CONFIG_H) && (!defined WIN32) + #include "config.h" +#elif defined(_WIN32) +#include "system.h" +#endif + +#include "OMXVideo.h" + +#include "utils/log.h" +#include "linux/XMemUtils.h" +#include "DVDDemuxers/DVDDemuxUtils.h" +#include "settings/AdvancedSettings.h" + +#include <sys/time.h> +#include <inttypes.h> + +#ifdef CLASSNAME +#undef CLASSNAME +#endif +#define CLASSNAME "COMXVideo" + +#if 0 +// TODO: These are Nvidia Tegra2 dependent, need to dynamiclly find the +// right codec matched to video format. +#define OMX_H264BASE_DECODER "OMX.Nvidia.h264.decode" +// OMX.Nvidia.h264ext.decode segfaults, not sure why. +//#define OMX_H264MAIN_DECODER "OMX.Nvidia.h264ext.decode" +#define OMX_H264MAIN_DECODER "OMX.Nvidia.h264.decode" +#define OMX_H264HIGH_DECODER "OMX.Nvidia.h264ext.decode" +#define OMX_MPEG4_DECODER "OMX.Nvidia.mp4.decode" +#define OMX_MPEG4EXT_DECODER "OMX.Nvidia.mp4ext.decode" +#define OMX_MPEG2V_DECODER "OMX.Nvidia.mpeg2v.decode" +#define OMX_VC1_DECODER "OMX.Nvidia.vc1.decode" +#endif + +#define OMX_VIDEO_DECODER "OMX.broadcom.video_decode" +#define OMX_H264BASE_DECODER OMX_VIDEO_DECODER +#define OMX_H264MAIN_DECODER OMX_VIDEO_DECODER +#define OMX_H264HIGH_DECODER OMX_VIDEO_DECODER +#define OMX_MPEG4_DECODER OMX_VIDEO_DECODER +#define OMX_MSMPEG4V1_DECODER OMX_VIDEO_DECODER +#define OMX_MSMPEG4V2_DECODER OMX_VIDEO_DECODER +#define OMX_MSMPEG4V3_DECODER OMX_VIDEO_DECODER +#define OMX_MPEG4EXT_DECODER OMX_VIDEO_DECODER +#define OMX_MPEG2V_DECODER OMX_VIDEO_DECODER +#define OMX_VC1_DECODER OMX_VIDEO_DECODER +#define OMX_WMV3_DECODER OMX_VIDEO_DECODER +#define OMX_VP8_DECODER OMX_VIDEO_DECODER + +#define MAX_TEXT_LENGTH 1024 + +COMXVideo::COMXVideo() +{ + m_is_open = false; + m_Pause = false; + m_extradata = NULL; + m_extrasize = 0; + m_converter = NULL; + m_video_convert = false; + m_video_codec_name = ""; + m_deinterlace = false; + m_hdmi_clock_sync = false; + m_first_frame = true; +} + +COMXVideo::~COMXVideo() +{ + if (m_is_open) + Close(); +} + +bool COMXVideo::SendDecoderConfig() +{ + OMX_ERRORTYPE omx_err = OMX_ErrorNone; + + /* send decoder config */ + if(m_extrasize > 0 && m_extradata != NULL) + { + OMX_BUFFERHEADERTYPE *omx_buffer = m_omx_decoder.GetInputBuffer(); + + if(omx_buffer == NULL) + { + CLog::Log(LOGERROR, "%s::%s - buffer error 0x%08x", CLASSNAME, __func__, omx_err); + return false; + } + + omx_buffer->nOffset = 0; + omx_buffer->nFilledLen = m_extrasize; + if(omx_buffer->nFilledLen > omx_buffer->nAllocLen) + { + CLog::Log(LOGERROR, "%s::%s - omx_buffer->nFilledLen > omx_buffer->nAllocLen", CLASSNAME, __func__); + return false; + } + + memset((unsigned char *)omx_buffer->pBuffer, 0x0, omx_buffer->nAllocLen); + memcpy((unsigned char *)omx_buffer->pBuffer, m_extradata, omx_buffer->nFilledLen); + omx_buffer->nFlags = OMX_BUFFERFLAG_CODECCONFIG | OMX_BUFFERFLAG_ENDOFFRAME; + + omx_err = m_omx_decoder.EmptyThisBuffer(omx_buffer); + if (omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s - OMX_EmptyThisBuffer() failed with result(0x%x)\n", CLASSNAME, __func__, omx_err); + return false; + } + } + return true; +} + +bool COMXVideo::Open(CDVDStreamInfo &hints, OMXClock *clock, bool deinterlace, bool hdmi_clock_sync) +{ + if(m_is_open) + Close(); + + OMX_ERRORTYPE omx_err = OMX_ErrorNone; + std::string decoder_name; + + m_video_codec_name = ""; + m_codingType = OMX_VIDEO_CodingUnused; + + m_decoded_width = hints.width; + m_decoded_height = hints.height; + + m_hdmi_clock_sync = hdmi_clock_sync; + + if(!m_decoded_width || !m_decoded_height) + return false; + + m_converter = new CBitstreamConverter(); + m_video_convert = m_converter->Open(hints.codec, (uint8_t *)hints.extradata, hints.extrasize, false); + + if(m_video_convert) + { + if(m_converter->GetExtraData() != NULL && m_converter->GetExtraSize() > 0) + { + m_extrasize = m_converter->GetExtraSize(); + m_extradata = (uint8_t *)malloc(m_extrasize); + memcpy(m_extradata, m_converter->GetExtraData(), m_converter->GetExtraSize()); + } + } + else + { + if(hints.extrasize > 0 && hints.extradata != NULL) + { + m_extrasize = hints.extrasize; + m_extradata = (uint8_t *)malloc(m_extrasize); + memcpy(m_extradata, hints.extradata, hints.extrasize); + } + } + + switch (hints.codec) + { + case CODEC_ID_H264: + { + switch(hints.profile) + { + case FF_PROFILE_H264_BASELINE: + // (role name) video_decoder.avc + // H.264 Baseline profile + decoder_name = OMX_H264BASE_DECODER; + m_codingType = OMX_VIDEO_CodingAVC; + m_video_codec_name = "omx-h264"; + break; + case FF_PROFILE_H264_MAIN: + // (role name) video_decoder.avc + // H.264 Main profile + decoder_name = OMX_H264MAIN_DECODER; + m_codingType = OMX_VIDEO_CodingAVC; + m_video_codec_name = "omx-h264"; + break; + case FF_PROFILE_H264_HIGH: + // (role name) video_decoder.avc + // H.264 Main profile + decoder_name = OMX_H264HIGH_DECODER; + m_codingType = OMX_VIDEO_CodingAVC; + m_video_codec_name = "omx-h264"; + break; + case FF_PROFILE_UNKNOWN: + decoder_name = OMX_H264HIGH_DECODER; + m_codingType = OMX_VIDEO_CodingAVC; + m_video_codec_name = "omx-h264"; + break; + default: + decoder_name = OMX_H264HIGH_DECODER; + m_codingType = OMX_VIDEO_CodingAVC; + m_video_codec_name = "omx-h264"; + break; + } + } + break; + case CODEC_ID_MPEG4: + // (role name) video_decoder.mpeg4 + // MPEG-4, DivX 4/5 and Xvid compatible + decoder_name = OMX_MPEG4_DECODER; + m_codingType = OMX_VIDEO_CodingMPEG4; + m_video_codec_name = "omx-mpeg4"; + break; + case CODEC_ID_MPEG1VIDEO: + case CODEC_ID_MPEG2VIDEO: + // (role name) video_decoder.mpeg2 + // MPEG-2 + decoder_name = OMX_MPEG2V_DECODER; + m_codingType = OMX_VIDEO_CodingMPEG2; + m_video_codec_name = "omx-mpeg2"; + break; + case CODEC_ID_H263: + // (role name) video_decoder.mpeg4 + // MPEG-4, DivX 4/5 and Xvid compatible + decoder_name = OMX_MPEG4_DECODER; + m_codingType = OMX_VIDEO_CodingMPEG4; + m_video_codec_name = "omx-h263"; + break; + case CODEC_ID_VP8: + // (role name) video_decoder.vp8 + // VP8 + decoder_name = OMX_VP8_DECODER; + m_codingType = OMX_VIDEO_CodingVP8; + m_video_codec_name = "omx-vp8"; + break; + case CODEC_ID_VC1: + // (role name) video_decoder.vc1 + // VC-1, WMV9 + decoder_name = OMX_VC1_DECODER; + m_codingType = OMX_VIDEO_CodingWMV; + m_video_codec_name = "omx-vc1"; + break; + /* + case CODEC_ID_WMV3: + // (role name) video_decoder.wmv3 + //WMV3 + decoder_name = OMX_WMV3_DECODER; + m_codingType = OMX_VIDEO_CodingWMV; + m_video_codec_name = "omx-wmv3"; + break; + */ + default: + return false; + break; + } + + if(m_decoded_width <= 720 && m_decoded_height <=576 && deinterlace) + { + CLog::Log(LOGDEBUG, "COMXVideo::Open : enable deinterlace\n"); + m_deinterlace = true; + } + else + { + m_deinterlace = false; + } + + std::string componentName = ""; + + componentName = decoder_name; + if(!m_omx_decoder.Initialize((const std::string)componentName, OMX_IndexParamVideoInit)) + return false; + + componentName = "OMX.broadcom.video_render"; + if(!m_omx_render.Initialize((const std::string)componentName, OMX_IndexParamVideoInit)) + return false; + + componentName = "OMX.broadcom.video_scheduler"; + if(!m_omx_sched.Initialize((const std::string)componentName, OMX_IndexParamVideoInit)) + return false; + + if(m_deinterlace) + { + componentName = "OMX.broadcom.image_fx"; + if(!m_omx_image_fx.Initialize((const std::string)componentName, OMX_IndexParamImageInit)) + return false; + } + + OMX_VIDEO_PARAM_PORTFORMATTYPE formatType; + /* + OMX_INIT_STRUCTURE(formatType); + formatType.nPortIndex = m_omx_decoder.GetInputPort(); + OMX_U32 nIndex = 1; + bool bFound = false; + + omx_err = OMX_ErrorNone; + do + { + formatType.nIndex = nIndex; + omx_err = m_omx_decoder.GetParameter(OMX_IndexParamVideoPortFormat, &formatType); + if(formatType.eCompressionFormat == m_codingType) + { + bFound = true; + break; + } + nIndex++; + } + while(omx_err == OMX_ErrorNone); + + if(!bFound) + { + CLog::Log(LOGINFO, "COMXVideo::Open coding : %s not supported\n", m_video_codec_name.c_str()); + return false; + } + */ + + if(clock == NULL) + return false; + + m_av_clock = clock; + m_omx_clock = m_av_clock->GetOMXClock(); + + if(m_omx_clock->GetComponent() == NULL) + { + m_av_clock = NULL; + m_omx_clock = NULL; + return false; + } + + if(m_deinterlace) + { + m_omx_tunnel_decoder.Initialize(&m_omx_decoder, m_omx_decoder.GetOutputPort(), &m_omx_image_fx, m_omx_image_fx.GetInputPort()); + m_omx_tunnel_image_fx.Initialize(&m_omx_image_fx, m_omx_image_fx.GetOutputPort(), &m_omx_sched, m_omx_sched.GetInputPort()); + } + else + { + m_omx_tunnel_decoder.Initialize(&m_omx_decoder, m_omx_decoder.GetOutputPort(), &m_omx_sched, m_omx_sched.GetInputPort()); + } + m_omx_tunnel_sched.Initialize(&m_omx_sched, m_omx_sched.GetOutputPort(), &m_omx_render, m_omx_render.GetInputPort()); + + m_omx_tunnel_clock.Initialize(m_omx_clock, m_omx_clock->GetInputPort() + 1, &m_omx_sched, m_omx_sched.GetOutputPort() + 1); + + omx_err = m_omx_tunnel_clock.Establish(false); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXVideo::Open m_omx_tunnel_clock.Establish\n"); + return false; + } + + omx_err = m_omx_decoder.SetStateForComponent(OMX_StateIdle); + if (omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXVideo::Open m_omx_decoder.SetStateForComponent\n"); + return false; + } + + OMX_INIT_STRUCTURE(formatType); + formatType.nPortIndex = m_omx_decoder.GetInputPort(); + formatType.eCompressionFormat = m_codingType; + + if (hints.fpsscale > 0 && hints.fpsrate > 0) + { + formatType.xFramerate = (long long)(1<<16)*hints.fpsrate / hints.fpsscale; + } + else + { + formatType.xFramerate = 25 * (1<<16); + } + + omx_err = m_omx_decoder.SetParameter(OMX_IndexParamVideoPortFormat, &formatType); + if(omx_err != OMX_ErrorNone) + return false; + + OMX_PARAM_PORTDEFINITIONTYPE portParam; + OMX_INIT_STRUCTURE(portParam); + portParam.nPortIndex = m_omx_decoder.GetInputPort(); + + omx_err = m_omx_decoder.GetParameter(OMX_IndexParamPortDefinition, &portParam); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXVideo::Open error OMX_IndexParamPortDefinition omx_err(0x%08x)\n", omx_err); + return false; + } + + portParam.nPortIndex = m_omx_decoder.GetInputPort(); + portParam.nBufferCountActual = VIDEO_BUFFERS; + + omx_err = m_omx_decoder.SetParameter(OMX_IndexParamPortDefinition, &portParam); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXVideo::Open error OMX_IndexParamPortDefinition omx_err(0x%08x)\n", omx_err); + return false; + } + + OMX_PARAM_BRCMVIDEODECODEERRORCONCEALMENTTYPE concanParam; + OMX_INIT_STRUCTURE(concanParam); + if(g_advancedSettings.m_omxDecodeStartWithValidFrame) + concanParam.bStartWithValidFrame = OMX_TRUE; + else + concanParam.bStartWithValidFrame = OMX_FALSE; + + omx_err = m_omx_decoder.SetParameter(OMX_IndexParamBrcmVideoDecodeErrorConcealment, &concanParam); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXVideo::Open error OMX_IndexParamBrcmVideoDecodeErrorConcealment omx_err(0x%08x)\n", omx_err); + return false; + } + + if(m_hdmi_clock_sync) + { + OMX_CONFIG_LATENCYTARGETTYPE latencyTarget; + OMX_INIT_STRUCTURE(latencyTarget); + latencyTarget.nPortIndex = m_omx_render.GetInputPort(); + latencyTarget.bEnabled = OMX_TRUE; + latencyTarget.nFilter = 2; + latencyTarget.nTarget = 4000; + latencyTarget.nShift = 3; + latencyTarget.nSpeedFactor = -135; + latencyTarget.nInterFactor = 500; + latencyTarget.nAdjCap = 20; + + omx_err = m_omx_render.SetConfig(OMX_IndexConfigLatencyTarget, &latencyTarget); + if (omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXVideo::Open OMX_IndexConfigLatencyTarget error (0%08x)\n", omx_err); + return false; + } + } + + // Alloc buffers for the omx intput port. + omx_err = m_omx_decoder.AllocInputBuffers(); + if (omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXVideo::Open AllocOMXInputBuffers error (0%08x)\n", omx_err); + return false; + } + + omx_err = m_omx_tunnel_decoder.Establish(false); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXVideo::Open m_omx_tunnel_decoder.Establish\n"); + return false; + } + + omx_err = m_omx_decoder.SetStateForComponent(OMX_StateExecuting); + if (omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXVideo::Open error m_omx_decoder.SetStateForComponent\n"); + return false; + } + + if(m_deinterlace) + { + OMX_CONFIG_IMAGEFILTERPARAMSTYPE image_filter; + OMX_INIT_STRUCTURE(image_filter); + + image_filter.nPortIndex = m_omx_image_fx.GetOutputPort(); + image_filter.nNumParams = 1; + image_filter.nParams[0] = 3; + image_filter.eImageFilter = OMX_ImageFilterDeInterlaceAdvanced; + + omx_err = m_omx_image_fx.SetConfig(OMX_IndexConfigCommonImageFilterParameters, &image_filter); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXVideo::Open error OMX_IndexConfigCommonImageFilterParameters omx_err(0x%08x)\n", omx_err); + return false; + } + + omx_err = m_omx_tunnel_image_fx.Establish(false); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXVideo::Open m_omx_tunnel_image_fx.Establish\n"); + return false; + } + + omx_err = m_omx_image_fx.SetStateForComponent(OMX_StateExecuting); + if (omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXVideo::Open error m_omx_image_fx.SetStateForComponent\n"); + return false; + } + + m_omx_image_fx.DisablePort(m_omx_image_fx.GetInputPort(), false); + m_omx_image_fx.DisablePort(m_omx_image_fx.GetOutputPort(), false); + } + + omx_err = m_omx_tunnel_sched.Establish(false); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXVideo::Open m_omx_tunnel_sched.Establish\n"); + return false; + } + + omx_err = m_omx_sched.SetStateForComponent(OMX_StateExecuting); + if (omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXVideo::Open error m_omx_sched.SetStateForComponent\n"); + return false; + } + + omx_err = m_omx_render.SetStateForComponent(OMX_StateExecuting); + if (omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "COMXVideo::Open error m_omx_render.SetStateForComponent\n"); + return false; + } + + if(!SendDecoderConfig()) + return false; + + m_is_open = true; + m_drop_state = false; + + OMX_CONFIG_DISPLAYREGIONTYPE configDisplay; + OMX_INIT_STRUCTURE(configDisplay); + configDisplay.nPortIndex = m_omx_render.GetInputPort(); + + configDisplay.set = OMX_DISPLAY_SET_TRANSFORM; + + switch(hints.orientation) + { + case 90: + configDisplay.transform = OMX_DISPLAY_ROT90; + break; + case 180: + configDisplay.transform = OMX_DISPLAY_ROT180; + break; + case 270: + configDisplay.transform = OMX_DISPLAY_ROT270; + break; + default: + configDisplay.transform = OMX_DISPLAY_ROT0; + break; + } + + omx_err = m_omx_render.SetConfig(OMX_IndexConfigDisplayRegion, &configDisplay); + if(omx_err != OMX_ErrorNone) + { + CLog::Log(LOGWARNING, "COMXVideo::Open could not set orientation : %d\n", hints.orientation); + } + + /* + configDisplay.set = OMX_DISPLAY_SET_LAYER; + configDisplay.layer = 2; + + omx_err = m_omx_render.SetConfig(OMX_IndexConfigDisplayRegion, &configDisplay); + if(omx_err != OMX_ErrorNone) + return false; + + configDisplay.set = OMX_DISPLAY_SET_DEST_RECT; + configDisplay.dest_rect.x_offset = 100; + configDisplay.dest_rect.y_offset = 100; + configDisplay.dest_rect.width = 640; + configDisplay.dest_rect.height = 480; + + omx_err = m_omx_render.SetConfig(OMX_IndexConfigDisplayRegion, &configDisplay); + if(omx_err != OMX_ErrorNone) + return false; + + configDisplay.set = OMX_DISPLAY_SET_TRANSFORM; + configDisplay.transform = OMX_DISPLAY_ROT180; + + omx_err = m_omx_render.SetConfig(OMX_IndexConfigDisplayRegion, &configDisplay); + if(omx_err != OMX_ErrorNone) + return false; + + configDisplay.set = OMX_DISPLAY_SET_FULLSCREEN; + configDisplay.fullscreen = OMX_FALSE; + + omx_err = m_omx_render.SetConfig(OMX_IndexConfigDisplayRegion, &configDisplay); + if(omx_err != OMX_ErrorNone) + return false; + + configDisplay.set = OMX_DISPLAY_SET_MODE; + configDisplay.mode = OMX_DISPLAY_MODE_FILL; //OMX_DISPLAY_MODE_LETTERBOX; + + omx_err = m_omx_render.SetConfig(OMX_IndexConfigDisplayRegion, &configDisplay); + if(omx_err != OMX_ErrorNone) + return false; + + configDisplay.set = OMX_DISPLAY_SET_LAYER; + configDisplay.layer = 1; + + omx_err = m_omx_render.SetConfig(OMX_IndexConfigDisplayRegion, &configDisplay); + if(omx_err != OMX_ErrorNone) + return false; + + configDisplay.set = OMX_DISPLAY_SET_ALPHA; + configDisplay.alpha = OMX_FALSE; + + omx_err = m_omx_render.SetConfig(OMX_IndexConfigDisplayRegion, &configDisplay); + if(omx_err != OMX_ErrorNone) + return false; + + */ + + CLog::Log(LOGDEBUG, + "%s::%s - decoder_component(0x%p), input_port(0x%x), output_port(0x%x) deinterlace %d hdmiclocksync %d\n", + CLASSNAME, __func__, m_omx_decoder.GetComponent(), m_omx_decoder.GetInputPort(), m_omx_decoder.GetOutputPort(), + m_deinterlace, m_hdmi_clock_sync); + + m_av_clock->OMXStateExecute(false); + + m_first_frame = true; + return true; +} + +void COMXVideo::Close() +{ + if(!m_is_open) + return; + + /* + if(m_av_clock) + { + m_av_clock->Lock(); + m_av_clock->OMXStop(false); + } + */ + + m_omx_tunnel_decoder.Flush(); + if(m_deinterlace) + m_omx_tunnel_image_fx.Flush(); + m_omx_tunnel_clock.Flush(); + m_omx_tunnel_sched.Flush(); + + m_omx_tunnel_clock.Deestablish(); + m_omx_tunnel_decoder.Deestablish(); + if(m_deinterlace) + m_omx_tunnel_image_fx.Deestablish(); + m_omx_tunnel_sched.Deestablish(); + + m_omx_decoder.FlushInput(); + + m_omx_sched.Deinitialize(); + if(m_deinterlace) + m_omx_image_fx.Deinitialize(); + m_omx_decoder.Deinitialize(); + m_omx_render.Deinitialize(); + + /* + if(m_av_clock) + { + m_av_clock->OMXReset(false); + m_av_clock->UnLock(); + } + */ + + m_is_open = false; + + if(m_extradata) + free(m_extradata); + m_extradata = NULL; + m_extrasize = 0; + + if(m_converter) + delete m_converter; + m_converter = NULL; + m_video_convert = false; + m_video_codec_name = ""; + m_deinterlace = false; + m_first_frame = true; +} + +void COMXVideo::SetDropState(bool bDrop) +{ + m_drop_state = bDrop; +} + +unsigned int COMXVideo::GetFreeSpace() +{ + return m_omx_decoder.GetInputBufferSpace(); +} + +unsigned int COMXVideo::GetSize() +{ + return m_omx_decoder.GetInputBufferSize(); +} + +int COMXVideo::Decode(uint8_t *pData, int iSize, double dts, double pts) +{ + OMX_ERRORTYPE omx_err; + + if( m_drop_state ) + return true; + + if (pData || iSize > 0) + { + unsigned int demuxer_bytes = (unsigned int)iSize; + uint8_t *demuxer_content = pData; + + if(m_video_convert) + { + m_converter->Convert(pData, iSize); + demuxer_bytes = m_converter->GetConvertSize(); + demuxer_content = m_converter->GetConvertBuffer(); + if(!demuxer_bytes && demuxer_bytes < 1) + { + return false; + } + } + + while(demuxer_bytes) + { + // 500ms timeout + OMX_BUFFERHEADERTYPE *omx_buffer = m_omx_decoder.GetInputBuffer(500); + if(omx_buffer == NULL) + { + CLog::Log(LOGERROR, "OMXVideo::Decode timeout\n"); + return false; + } + + /* + CLog::Log(DEBUG, "COMXVideo::Video VDec : pts %lld omx_buffer 0x%08x buffer 0x%08x number %d\n", + pts, omx_buffer, omx_buffer->pBuffer, (int)omx_buffer->pAppPrivate); + if(pts == DVD_NOPTS_VALUE) + { + CLog::Log(LOGDEBUG, "VDec : pts %f omx_buffer 0x%08x buffer 0x%08x number %d\n", + (float)pts / AV_TIME_BASE, (int)omx_buffer, (int)omx_buffer->pBuffer, (int)omx_buffer->pAppPrivate); + } + */ + + omx_buffer->nFlags = 0; + omx_buffer->nOffset = 0; + + uint64_t val = (uint64_t)(pts == DVD_NOPTS_VALUE) ? 0 : pts; + + if(m_av_clock->VideoStart()) + { + omx_buffer->nFlags = OMX_BUFFERFLAG_STARTTIME; + CLog::Log(LOGDEBUG, "VDec : setStartTime %f\n", (float)val / DVD_TIME_BASE); + m_av_clock->VideoStart(false); + } + else + { + if(pts == DVD_NOPTS_VALUE) + omx_buffer->nFlags = OMX_BUFFERFLAG_TIME_UNKNOWN; + } + + omx_buffer->nTimeStamp = ToOMXTime(val); + + omx_buffer->nFilledLen = (demuxer_bytes > omx_buffer->nAllocLen) ? omx_buffer->nAllocLen : demuxer_bytes; + memcpy(omx_buffer->pBuffer, demuxer_content, omx_buffer->nFilledLen); + + demuxer_bytes -= omx_buffer->nFilledLen; + demuxer_content += omx_buffer->nFilledLen; + + if(demuxer_bytes == 0) + omx_buffer->nFlags |= OMX_BUFFERFLAG_ENDOFFRAME; + + int nRetry = 0; + while(true) + { + omx_err = m_omx_decoder.EmptyThisBuffer(omx_buffer); + if (omx_err == OMX_ErrorNone) + { + break; + } + else + { + CLog::Log(LOGERROR, "%s::%s - OMX_EmptyThisBuffer() failed with result(0x%x)\n", CLASSNAME, __func__, omx_err); + nRetry++; + } + if(nRetry == 5) + { + CLog::Log(LOGERROR, "%s::%s - OMX_EmptyThisBuffer() finaly failed\n", CLASSNAME, __func__); + return false; + } + } + + if(m_first_frame && m_deinterlace) + { + OMX_PARAM_PORTDEFINITIONTYPE port_image; + OMX_INIT_STRUCTURE(port_image); + port_image.nPortIndex = m_omx_decoder.GetOutputPort(); + + omx_err = m_omx_decoder.GetParameter(OMX_IndexParamPortDefinition, &port_image); + if(omx_err != OMX_ErrorNone) + CLog::Log(LOGERROR, "%s::%s - error OMX_IndexParamPortDefinition 1 omx_err(0x%08x)\n", CLASSNAME, __func__, omx_err); + + /* we assume when the sizes equal we have the first decoded frame */ + if(port_image.format.video.nFrameWidth == m_decoded_width && port_image.format.video.nFrameHeight == m_decoded_height) + { + m_first_frame = false; + + omx_err = m_omx_decoder.WaitForEvent(OMX_EventPortSettingsChanged); + if(omx_err == OMX_ErrorStreamCorrupt) + { + CLog::Log(LOGERROR, "%s::%s - image not unsupported\n", CLASSNAME, __func__); + return false; + } + + m_omx_decoder.DisablePort(m_omx_decoder.GetOutputPort(), false); + m_omx_sched.DisablePort(m_omx_sched.GetInputPort(), false); + + if(m_deinterlace) + { + m_omx_image_fx.DisablePort(m_omx_image_fx.GetOutputPort(), false); + m_omx_image_fx.DisablePort(m_omx_image_fx.GetInputPort(), false); + + port_image.nPortIndex = m_omx_image_fx.GetInputPort(); + omx_err = m_omx_image_fx.SetParameter(OMX_IndexParamPortDefinition, &port_image); + if(omx_err != OMX_ErrorNone) + CLog::Log(LOGERROR, "%s::%s - error OMX_IndexParamPortDefinition 2 omx_err(0x%08x)\n", CLASSNAME, __func__, omx_err); + + port_image.nPortIndex = m_omx_image_fx.GetOutputPort(); + omx_err = m_omx_image_fx.SetParameter(OMX_IndexParamPortDefinition, &port_image); + if(omx_err != OMX_ErrorNone) + CLog::Log(LOGERROR, "%s::%s - error OMX_IndexParamPortDefinition 3 omx_err(0x%08x)\n", CLASSNAME, __func__, omx_err); + } + + m_omx_decoder.EnablePort(m_omx_decoder.GetOutputPort(), false); + + if(m_deinterlace) + { + m_omx_image_fx.EnablePort(m_omx_image_fx.GetOutputPort(), false); + m_omx_image_fx.EnablePort(m_omx_image_fx.GetInputPort(), false); + } + + m_omx_sched.EnablePort(m_omx_sched.GetInputPort(), false); + } + } + } + + return true; + + } + + return false; +} + +void COMXVideo::Reset(void) +{ + if(!m_is_open) + return; + + m_omx_decoder.FlushInput(); + m_omx_tunnel_decoder.Flush(); + + /* + OMX_ERRORTYPE omx_err; + OMX_CONFIG_BOOLEANTYPE configBool; + OMX_INIT_STRUCTURE(configBool); + configBool.bEnabled = OMX_TRUE; + + omx_err = m_omx_decoder.SetConfig(OMX_IndexConfigRefreshCodec, &configBool); + if (omx_err != OMX_ErrorNone) + CLog::Log(LOGERROR, "%s::%s - error reopen codec omx_err(0x%08x)\n", CLASSNAME, __func__, omx_err); + + SendDecoderConfig(); + + m_first_frame = true; + */ +} + +/////////////////////////////////////////////////////////////////////////////////////////// +bool COMXVideo::Pause() +{ + if(m_omx_render.GetComponent() == NULL) + return false; + + if(m_Pause) return true; + m_Pause = true; + + m_omx_sched.SetStateForComponent(OMX_StatePause); + m_omx_render.SetStateForComponent(OMX_StatePause); + + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////// +bool COMXVideo::Resume() +{ + if(m_omx_render.GetComponent() == NULL) + return false; + + if(!m_Pause) return true; + m_Pause = false; + + m_omx_sched.SetStateForComponent(OMX_StateExecuting); + m_omx_render.SetStateForComponent(OMX_StateExecuting); + + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////// +void COMXVideo::SetVideoRect(const CRect& SrcRect, const CRect& DestRect) +{ + if(!m_is_open) + return; + + OMX_CONFIG_DISPLAYREGIONTYPE configDisplay; + OMX_INIT_STRUCTURE(configDisplay); + configDisplay.nPortIndex = m_omx_render.GetInputPort(); + + configDisplay.set = OMX_DISPLAY_SET_FULLSCREEN; + configDisplay.fullscreen = OMX_FALSE; + + m_omx_render.SetConfig(OMX_IndexConfigDisplayRegion, &configDisplay); + + configDisplay.set = OMX_DISPLAY_SET_DEST_RECT; + configDisplay.dest_rect.x_offset = DestRect.x1; + configDisplay.dest_rect.y_offset = DestRect.y1; + configDisplay.dest_rect.width = DestRect.Width(); + configDisplay.dest_rect.height = DestRect.Height(); + + m_omx_render.SetConfig(OMX_IndexConfigDisplayRegion, &configDisplay); + + CLog::Log(LOGDEBUG, "dest_rect.x_offset %d dest_rect.y_offset %d dest_rect.width %d dest_rect.height %d\n", + configDisplay.dest_rect.x_offset, configDisplay.dest_rect.y_offset, + configDisplay.dest_rect.width, configDisplay.dest_rect.height); +} + +int COMXVideo::GetInputBufferSize() +{ + return m_omx_decoder.GetInputBufferSize(); +} + +void COMXVideo::WaitCompletion() +{ + if(!m_is_open) + return; + + OMX_ERRORTYPE omx_err = OMX_ErrorNone; + OMX_BUFFERHEADERTYPE *omx_buffer = m_omx_decoder.GetInputBuffer(); + struct timespec starttime, endtime; + + if(omx_buffer == NULL) + { + CLog::Log(LOGERROR, "%s::%s - buffer error 0x%08x", CLASSNAME, __func__, omx_err); + return; + } + + omx_buffer->nOffset = 0; + omx_buffer->nFilledLen = 0; + omx_buffer->nTimeStamp = ToOMXTime(0LL); + + omx_buffer->nFlags = OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_EOS | OMX_BUFFERFLAG_TIME_UNKNOWN; + + omx_err = m_omx_decoder.EmptyThisBuffer(omx_buffer); + if (omx_err != OMX_ErrorNone) + { + CLog::Log(LOGERROR, "%s::%s - OMX_EmptyThisBuffer() failed with result(0x%x)\n", CLASSNAME, __func__, omx_err); + return; + } + + clock_gettime(CLOCK_REALTIME, &starttime); + + while(true) + { + if(m_omx_render.IsEOS()) + break; + clock_gettime(CLOCK_REALTIME, &endtime); + if((endtime.tv_sec - starttime.tv_sec) > 5) + { + CLog::Log(LOGERROR, "%s::%s - wait for eos timed out\n", CLASSNAME, __func__); + break; + } + Sleep(50); + } + + return; +} diff --git a/xbmc/cores/omxplayer/OMXVideo.h b/xbmc/cores/omxplayer/OMXVideo.h new file mode 100644 index 0000000000..7bfca9f8ef --- /dev/null +++ b/xbmc/cores/omxplayer/OMXVideo.h @@ -0,0 +1,98 @@ +#pragma once +/* + * Copyright (C) 2010 Team XBMC + * http://www.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, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#if defined(HAVE_OMXLIB) + +#include "OMXCore.h" +#include "DVDStreamInfo.h" + +#include <IL/OMX_Video.h> + +#include "BitstreamConverter.h" + +#include "OMXClock.h" + +#include "guilib/Geometry.h" +#include "DVDDemuxers/DVDDemux.h" +#include <string> + +#define VIDEO_BUFFERS 60 + +#define CLASSNAME "COMXVideo" + +class COMXVideo +{ +public: + COMXVideo(); + ~COMXVideo(); + + // Required overrides + bool SendDecoderConfig(); + bool Open(CDVDStreamInfo &hints, OMXClock *clock, bool deinterlace = false, bool hdmi_clock_sync = false); + void Close(void); + unsigned int GetFreeSpace(); + unsigned int GetSize(); + int Decode(uint8_t *pData, int iSize, double dts, double pts); + void Reset(void); + void SetDropState(bool bDrop); + bool Pause(); + bool Resume(); + std::string GetDecoderName() { return m_video_codec_name; }; + void SetVideoRect(const CRect& SrcRect, const CRect& DestRect); + int GetInputBufferSize(); + void WaitCompletion(); +protected: + // Video format + bool m_drop_state; + unsigned int m_decoded_width; + unsigned int m_decoded_height; + + OMX_VIDEO_CODINGTYPE m_codingType; + + COMXCoreComponent m_omx_decoder; + COMXCoreComponent m_omx_render; + COMXCoreComponent m_omx_sched; + COMXCoreComponent m_omx_image_fx; + COMXCoreComponent *m_omx_clock; + OMXClock *m_av_clock; + + COMXCoreTunel m_omx_tunnel_decoder; + COMXCoreTunel m_omx_tunnel_clock; + COMXCoreTunel m_omx_tunnel_sched; + COMXCoreTunel m_omx_tunnel_image_fx; + bool m_is_open; + + bool m_Pause; + + uint8_t *m_extradata; + int m_extrasize; + + CBitstreamConverter *m_converter; + bool m_video_convert; + std::string m_video_codec_name; + + bool m_deinterlace; + bool m_hdmi_clock_sync; + bool m_first_frame; +}; + +#endif diff --git a/xbmc/cores/omxplayer/OMXVideoCodec.h b/xbmc/cores/omxplayer/OMXVideoCodec.h new file mode 100644 index 0000000000..6990f31df2 --- /dev/null +++ b/xbmc/cores/omxplayer/OMXVideoCodec.h @@ -0,0 +1,240 @@ +#pragma once + +/* + * Copyright (C) 2005-2008 Team XBMC + * http://www.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, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "system.h" + +#include <vector> + +// when modifying these structures, make sure you update all codecs accordingly +#define FRAME_TYPE_UNDEF 0 +#define FRAME_TYPE_I 1 +#define FRAME_TYPE_P 2 +#define FRAME_TYPE_B 3 +#define FRAME_TYPE_D 4 + +namespace DXVA { class CSurfaceContext; } +namespace VAAPI { struct CHolder; } +class CVDPAU; +class COMXCore; +class COMXCoreVideo; +struct OMXCoreVideoBuffer; +#ifdef HAVE_VIDEOTOOLBOXDECODER + class COMXVideoCodecVideoToolBox; + struct __CVBuffer; +#endif + +// should be entirely filled by all codecs +struct DVDVideoPicture +{ + double pts; // timestamp in seconds, used in the CDVDPlayer class to keep track of pts + double dts; + + union + { + struct { + BYTE* data[4]; // [4] = alpha channel, currently not used + int iLineSize[4]; // [4] = alpha channel, currently not used + }; + struct { + DXVA::CSurfaceContext* context; + }; + struct { + CVDPAU* vdpau; + }; + struct { + VAAPI::CHolder* vaapi; + }; + + struct { + COMXCore *openMax; + OMXCoreVideoBuffer *openMaxBuffer; + }; +#ifdef HAVE_VIDEOTOOLBOXDECODER + struct { + COMXVideoCodecVideoToolBox *vtb; + struct __CVBuffer *cvBufferRef; + }; +#endif + }; + + unsigned int iFlags; + + double iRepeatPicture; + double iDuration; + unsigned int iFrameType : 4; // see defines above // 1->I, 2->P, 3->B, 0->Undef + unsigned int color_matrix : 4; + unsigned int color_range : 1; // 1 indicate if we have a full range of color + unsigned int chroma_position; + unsigned int color_primaries; + unsigned int color_transfer; + unsigned int extended_format; + int iGroupId; + + int8_t* qscale_table; // Quantization parameters, primarily used by filters + int qscale_stride; + int qscale_type; + + unsigned int iWidth; + unsigned int iHeight; + unsigned int iDisplayWidth; // width of the picture without black bars + unsigned int iDisplayHeight; // height of the picture without black bars + + enum EFormat { + FMT_YUV420P = 0, + FMT_VDPAU, + FMT_NV12, + FMT_UYVY, + FMT_YUY2, + FMT_DXVA, + FMT_VAAPI, + FMT_OMXEGL, + FMT_CVBREF, + } format; +}; + +struct DVDVideoUserData +{ + BYTE* data; + int size; +}; + +#define DVP_FLAG_TOP_FIELD_FIRST 0x00000001 +#define DVP_FLAG_REPEAT_TOP_FIELD 0x00000002 //Set to indicate that the top field should be repeated +#define DVP_FLAG_ALLOCATED 0x00000004 //Set to indicate that this has allocated data +#define DVP_FLAG_INTERLACED 0x00000008 //Set to indicate that this frame is interlaced + +#define DVP_FLAG_NOSKIP 0x00000010 // indicate this picture should never be dropped +#define DVP_FLAG_DROPPED 0x00000020 // indicate that this picture has been dropped in decoder stage, will have no data + +// DVP_FLAG 0x00000100 - 0x00000f00 is in use by libmpeg2! + +#define DVP_QSCALE_UNKNOWN 0 +#define DVP_QSCALE_MPEG1 1 +#define DVP_QSCALE_MPEG2 2 +#define DVP_QSCALE_H264 3 + +class COMXStreamInfo; +class CDVDCodecOption; +class CDVDCodecOptions; + +// VC_ messages, messages can be combined +#define VC_ERROR 0x00000001 // an error occured, no other messages will be returned +#define VC_BUFFER 0x00000002 // the decoder needs more data +#define VC_PICTURE 0x00000004 // the decoder got a picture, call Decode(NULL, 0) again to parse the rest of the data +#define VC_USERDATA 0x00000008 // the decoder found some userdata, call Decode(NULL, 0) again to parse the rest of the data +#define VC_FLUSHED 0x00000010 // the decoder lost it's state, we need to restart decoding again +class COMXVideoCodec +{ +public: + + COMXVideoCodec() {} + virtual ~COMXVideoCodec() {} + + /* + * Open the decoder, returns true on success + */ + virtual bool Open(COMXStreamInfo &hints, CDVDCodecOptions &options) = 0; + + /* + * Dispose, Free all resources + */ + virtual void Dispose() = 0; + + /* + * returns one or a combination of VC_ messages + * pData and iSize can be NULL, this means we should flush the rest of the data. + */ + virtual int Decode(BYTE* pData, int iSize, double dts, double pts) = 0; + + /* + * Reset the decoder. + * Should be the same as calling Dispose and Open after each other + */ + virtual void Reset() = 0; + + /* + * returns true if successfull + * the data is valid until the next Decode call + */ + virtual bool GetPicture(DVDVideoPicture* pDvdVideoPicture) = 0; + + + /* + * returns true if successfull + * the data is cleared to zero + */ + virtual bool ClearPicture(DVDVideoPicture* pDvdVideoPicture) + { + memset(pDvdVideoPicture, 0, sizeof(DVDVideoPicture)); + return true; + } + + /* + * returns true if successfull + * the data is valid until the next Decode call + * userdata can be anything, for now we use it for closed captioning + */ + virtual bool GetUserData(DVDVideoUserData* pDvdVideoUserData) + { + pDvdVideoUserData->data = NULL; + pDvdVideoUserData->size = 0; + return false; + } + + /* + * will be called by video player indicating if a frame will eventually be dropped + * codec can then skip actually decoding the data, just consume the data set picture headers + */ + virtual void SetDropState(bool bDrop) = 0; + + + enum EFilterFlags { + FILTER_NONE = 0x0, + FILTER_DEINTERLACE_YADIF = 0x1, /* use first deinterlace mode */ + FILTER_DEINTERLACE_ANY = 0xf, /* use any deinterlace mode */ + FILTER_DEINTERLACE_FLAGGED = 0x10, /* only deinterlace flagged frames */ + FILTER_DEINTERLACE_HALFED = 0x20, /* do half rate deinterlacing */ + }; + + /* + * set the type of filters that should be applied at decoding stage if possible + */ + virtual unsigned int SetFilters(unsigned int filters) { return 0u; } + + /* + * + * should return codecs name + */ + virtual const char* GetName() = 0; + + /* + * + * How many packets should player remember, so codec + * can recover should something cause it to flush + * outside of players control + */ + virtual unsigned GetConvergeCount() + { + return 0; + } +}; diff --git a/xbmc/cores/omxplayer/omxplayer_advancedsettings.xml b/xbmc/cores/omxplayer/omxplayer_advancedsettings.xml new file mode 100644 index 0000000000..d2100397b6 --- /dev/null +++ b/xbmc/cores/omxplayer/omxplayer_advancedsettings.xml @@ -0,0 +1,6 @@ +<advancedsettings> + <video> + <defaultplayer>omxplayer</defaultplayer> + <defaultdvdplayer>omxplayer</defaultdvdplayer> + </video> +</advancedsettings> diff --git a/xbmc/cores/playercorefactory/PlayerCoreConfig.h b/xbmc/cores/playercorefactory/PlayerCoreConfig.h index 486c09f0e1..255633d02b 100644 --- a/xbmc/cores/playercorefactory/PlayerCoreConfig.h +++ b/xbmc/cores/playercorefactory/PlayerCoreConfig.h @@ -28,6 +28,9 @@ #if defined(HAS_AMLPLAYER) #include "cores/amlplayer/AMLPlayer.h" #endif +#if defined(HAS_OMXPLAYER) +#include "cores/omxplayer/OMXPlayer.h" +#endif #include "cores/ExternalPlayer/ExternalPlayer.h" #include "utils/log.h" @@ -80,6 +83,9 @@ public: #if defined(HAS_AMLPLAYER) case EPC_AMLPLAYER: pPlayer = new CAMLPlayer(callback); break; #endif +#if defined(HAS_OMXPLAYER) + case EPC_OMXPLAYER: pPlayer = new COMXPlayer(callback); break; +#endif default: return NULL; } diff --git a/xbmc/cores/playercorefactory/PlayerCoreFactory.cpp b/xbmc/cores/playercorefactory/PlayerCoreFactory.cpp index 9a4506bbf9..96f2973823 100644 --- a/xbmc/cores/playercorefactory/PlayerCoreFactory.cpp +++ b/xbmc/cores/playercorefactory/PlayerCoreFactory.cpp @@ -287,6 +287,13 @@ bool CPlayerCoreFactory::LoadConfiguration(TiXmlElement* pConfig, bool clear) s_vecCoreConfigs.push_back(amlplayer); #endif +#if defined(HAS_OMXPLAYER) + CPlayerCoreConfig* omxplayer = new CPlayerCoreConfig("OMXPlayer", EPC_OMXPLAYER, NULL); + omxplayer->m_bPlaysAudio = true; + omxplayer->m_bPlaysVideo = true; + s_vecCoreConfigs.push_back(omxplayer); +#endif + for(std::vector<CPlayerSelectionRule *>::iterator it = s_vecCoreSelectionRules.begin(); it != s_vecCoreSelectionRules.end(); it++) delete *it; s_vecCoreSelectionRules.clear(); diff --git a/xbmc/cores/playercorefactory/PlayerCoreFactory.h b/xbmc/cores/playercorefactory/PlayerCoreFactory.h index 69070e0908..012a27cac1 100644 --- a/xbmc/cores/playercorefactory/PlayerCoreFactory.h +++ b/xbmc/cores/playercorefactory/PlayerCoreFactory.h @@ -37,9 +37,12 @@ enum EPLAYERCORES EPC_DVDPLAYER, EPC_MPLAYER, EPC_PAPLAYER, - EPC_EXTPLAYER, + EPC_EXTPLAYER #if defined(HAS_AMLPLAYER) - EPC_AMLPLAYER + , EPC_AMLPLAYER +#endif +#if defined(HAS_OMXPLAYER) + , EPC_OMXPLAYER #endif }; @@ -52,6 +55,9 @@ const PLAYERCOREID PCID_PAPLAYER = EPC_PAPLAYER; #if defined(HAS_AMLPLAYER) const PLAYERCOREID PCID_AMLPLAYER = EPC_AMLPLAYER; #endif +#if defined(HAS_OMXPLAYER) +const PLAYERCOREID PCID_OMXPLAYER = EPC_OMXPLAYER; +#endif class CPlayerCoreFactory { diff --git a/xbmc/video/windows/GUIWindowFullScreen.cpp b/xbmc/video/windows/GUIWindowFullScreen.cpp index 1f0fc6c5c1..15fae284bd 100644 --- a/xbmc/video/windows/GUIWindowFullScreen.cpp +++ b/xbmc/video/windows/GUIWindowFullScreen.cpp @@ -979,6 +979,9 @@ void CGUIWindowFullScreen::RenderTTFSubtitles() #if defined(HAS_AMLPLAYER) g_application.GetCurrentPlayer() == EPC_AMLPLAYER || #endif +#if defined(HAS_OMXPLAYER) + g_application.GetCurrentPlayer() == EPC_OMXPLAYER || +#endif g_application.GetCurrentPlayer() == EPC_DVDPLAYER) && CUtil::IsUsingTTFSubtitles() && (g_application.m_pPlayer->GetSubtitleVisible())) { |