Commit 9f69c8d1 authored by Steve Lhomme's avatar Steve Lhomme

video.c: don't display pre-rolled pictures

es_out.c: handle the pre-roll earlier
parent 77e19efd
/*****************************************************************************
* video.c: video decoder using the ffmpeg library
*****************************************************************************
* Copyright (C) 1999-2001 VideoLAN
* $Id$
*
* Authors: Laurent Aimar <fenrir@via.ecp.fr>
* Gildas Bazin <gbazin@videolan.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 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#include <vlc/vlc.h>
#include <vlc/decoder.h>
/* ffmpeg header */
#ifdef HAVE_FFMPEG_AVCODEC_H
# include <ffmpeg/avcodec.h>
#else
# include <avcodec.h>
#endif
#include "ffmpeg.h"
/*****************************************************************************
* decoder_sys_t : decoder descriptor
*****************************************************************************/
struct decoder_sys_t
{
/* Common part between video and audio decoder */
int i_cat;
int i_codec_id;
char *psz_namecodec;
AVCodecContext *p_context;
AVCodec *p_codec;
/* Video decoder specific part */
mtime_t input_pts;
mtime_t input_dts;
mtime_t i_pts;
AVFrame *p_ff_pic;
BITMAPINFOHEADER *p_format;
/* for frame skipping algo */
int b_hurry_up;
int i_frame_skip;
/* how many decoded frames are late */
int i_late_frames;
mtime_t i_late_frames_start;
/* for direct rendering */
int b_direct_rendering;
vlc_bool_t b_has_b_frames;
/* Hack to force display of still pictures */
vlc_bool_t b_first_frame;
int i_buffer_orig, i_buffer;
char *p_buffer_orig, *p_buffer;
/* Postprocessing handle */
void *p_pp;
vlc_bool_t b_pp;
vlc_bool_t b_pp_async;
vlc_bool_t b_pp_init;
};
/* FIXME (dummy palette for now) */
static AVPaletteControl palette_control;
/*****************************************************************************
* Local prototypes
*****************************************************************************/
static void ffmpeg_CopyPicture ( decoder_t *, picture_t *, AVFrame * );
static int ffmpeg_GetFrameBuf ( struct AVCodecContext *, AVFrame * );
static void ffmpeg_ReleaseFrameBuf( struct AVCodecContext *, AVFrame * );
static uint32_t ffmpeg_CodecTag( vlc_fourcc_t fcc )
{
uint8_t *p = (uint8_t*)&fcc;
return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
}
/*****************************************************************************
* Local Functions
*****************************************************************************/
static uint32_t ffmpeg_PixFmtToChroma( int i_ff_chroma )
{
switch( i_ff_chroma )
{
case PIX_FMT_YUV420P:
return VLC_FOURCC('I','4','2','0');
case PIX_FMT_YUV422P:
return VLC_FOURCC('I','4','2','2');
case PIX_FMT_YUV444P:
return VLC_FOURCC('I','4','4','4');
case PIX_FMT_YUV422:
return VLC_FOURCC('Y','U','Y','2');
case PIX_FMT_RGB555:
return VLC_FOURCC('R','V','1','5');
case PIX_FMT_RGB565:
return VLC_FOURCC('R','V','1','6');
case PIX_FMT_RGB24:
return VLC_FOURCC('R','V','2','4');
case PIX_FMT_RGBA32:
return VLC_FOURCC('R','V','3','2');
case PIX_FMT_GRAY8:
return VLC_FOURCC('G','R','E','Y');
case PIX_FMT_YUV410P:
case PIX_FMT_YUV411P:
case PIX_FMT_BGR24:
default:
return 0;
}
}
/* Returns a new picture buffer */
static inline picture_t *ffmpeg_NewPictBuf( decoder_t *p_dec,
AVCodecContext *p_context )
{
decoder_sys_t *p_sys = p_dec->p_sys;
picture_t *p_pic;
p_dec->fmt_out.video.i_width = p_context->width;
p_dec->fmt_out.video.i_height = p_context->height;
p_dec->fmt_out.i_codec = ffmpeg_PixFmtToChroma( p_context->pix_fmt );
if( !p_context->width || !p_context->height )
{
return NULL; /* invalid display size */
}
if( !p_dec->fmt_out.i_codec )
{
/* we make conversion if possible*/
p_dec->fmt_out.i_codec = VLC_FOURCC('I','4','2','0');
}
/* If an aspect-ratio was specified in the input format then force it */
if( p_dec->fmt_in.video.i_aspect )
{
p_dec->fmt_out.video.i_aspect = p_dec->fmt_in.video.i_aspect;
}
else
{
#if LIBAVCODEC_BUILD >= 4687
p_dec->fmt_out.video.i_aspect =
VOUT_ASPECT_FACTOR * ( av_q2d(p_context->sample_aspect_ratio) *
p_context->width / p_context->height );
#else
p_dec->fmt_out.video.i_aspect =
VOUT_ASPECT_FACTOR * p_context->aspect_ratio;
#endif
if( p_dec->fmt_out.video.i_aspect == 0 )
{
p_dec->fmt_out.video.i_aspect =
VOUT_ASPECT_FACTOR * p_context->width / p_context->height;
}
}
if( p_context->frame_rate > 0 && p_context->frame_rate_base > 0 )
{
p_dec->fmt_out.video.i_frame_rate = p_context->frame_rate;
p_dec->fmt_out.video.i_frame_rate_base = p_context->frame_rate_base;
}
p_pic = p_dec->pf_vout_buffer_new( p_dec );
#ifdef LIBAVCODEC_PP
if( p_sys->p_pp && p_sys->b_pp && !p_sys->b_pp_init )
{
E_(InitPostproc)( p_dec, p_sys->p_pp, p_context->width,
p_context->height, p_context->pix_fmt );
p_sys->b_pp_init = VLC_TRUE;
}
#endif
return p_pic;
}
/*****************************************************************************
* InitVideo: initialize the video decoder
*****************************************************************************
* the ffmpeg codec will be opened, some memory allocated. The vout is not yet
* opened (done after the first decoded frame).
*****************************************************************************/
int E_(InitVideoDec)( decoder_t *p_dec, AVCodecContext *p_context,
AVCodec *p_codec, int i_codec_id, char *psz_namecodec )
{
decoder_sys_t *p_sys;
vlc_value_t lockval;
vlc_value_t val;
var_Get( p_dec->p_libvlc, "avcodec", &lockval );
/* Allocate the memory needed to store the decoder's structure */
if( ( p_dec->p_sys = p_sys =
(decoder_sys_t *)malloc(sizeof(decoder_sys_t)) ) == NULL )
{
msg_Err( p_dec, "out of memory" );
return VLC_EGENERIC;
}
p_dec->p_sys->p_context = p_context;
p_dec->p_sys->p_codec = p_codec;
p_dec->p_sys->i_codec_id = i_codec_id;
p_dec->p_sys->psz_namecodec = psz_namecodec;
p_sys->p_ff_pic = avcodec_alloc_frame();
/* ***** Fill p_context with init values ***** */
/* FIXME: remove when ffmpeg deals properly with avc1 */
if( p_dec->fmt_in.i_codec != VLC_FOURCC('a','v','c','1') )
/* End FIXME */
p_sys->p_context->codec_tag = ffmpeg_CodecTag( p_dec->fmt_in.i_codec );
p_sys->p_context->width = p_dec->fmt_in.video.i_width;
p_sys->p_context->height = p_dec->fmt_in.video.i_height;
p_sys->p_context->bits_per_sample = p_dec->fmt_in.video.i_bits_per_pixel;
/* ***** Get configuration of ffmpeg plugin ***** */
p_sys->p_context->workaround_bugs =
config_GetInt( p_dec, "ffmpeg-workaround-bugs" );
p_sys->p_context->error_resilience =
config_GetInt( p_dec, "ffmpeg-error-resilience" );
var_Create( p_dec, "grayscale", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
var_Get( p_dec, "grayscale", &val );
if( val.b_bool ) p_sys->p_context->flags |= CODEC_FLAG_GRAY;
var_Create( p_dec, "ffmpeg-vismv", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
var_Get( p_dec, "ffmpeg-vismv", &val );
#if LIBAVCODEC_BUILD >= 4698
if( val.i_int ) p_sys->p_context->debug_mv = val.i_int;
#endif
var_Create( p_dec, "ffmpeg-lowres", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
var_Get( p_dec, "ffmpeg-lowres", &val );
#if LIBAVCODEC_BUILD >= 4723
if( val.i_int > 0 && val.i_int <= 2 ) p_sys->p_context->lowres = val.i_int;
#endif
/* ***** ffmpeg frame skipping ***** */
var_Create( p_dec, "ffmpeg-hurry-up", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
var_Get( p_dec, "ffmpeg-hurry-up", &val );
p_sys->b_hurry_up = val.b_bool;
/* ***** ffmpeg direct rendering ***** */
p_sys->b_direct_rendering = 0;
var_Create( p_dec, "ffmpeg-dr", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
var_Get( p_dec, "ffmpeg-dr", &val );
if( val.b_bool && (p_sys->p_codec->capabilities & CODEC_CAP_DR1) &&
ffmpeg_PixFmtToChroma( p_sys->p_context->pix_fmt ) &&
/* Apparently direct rendering doesn't work with YUV422P */
p_sys->p_context->pix_fmt != PIX_FMT_YUV422P &&
/* H264 uses too many reference frames */
p_sys->i_codec_id != CODEC_ID_H264 &&
!(p_sys->p_context->width % 16) && !(p_sys->p_context->height % 16) &&
#if LIBAVCODEC_BUILD >= 4698
!p_sys->p_context->debug_mv )
#else
1 )
#endif
{
/* Some codecs set pix_fmt only after the 1st frame has been decoded,
* so we need to do another check in ffmpeg_GetFrameBuf() */
p_sys->b_direct_rendering = 1;
}
#ifdef LIBAVCODEC_PP
p_sys->p_pp = NULL;
p_sys->b_pp = p_sys->b_pp_async = p_sys->b_pp_init = VLC_FALSE;
p_sys->p_pp = E_(OpenPostproc)( p_dec, &p_sys->b_pp_async );
#endif
/* ffmpeg doesn't properly release old pictures when frames are skipped */
//if( p_sys->b_hurry_up ) p_sys->b_direct_rendering = 0;
if( p_sys->b_direct_rendering )
{
msg_Dbg( p_dec, "using direct rendering" );
p_sys->p_context->flags |= CODEC_FLAG_EMU_EDGE;
}
/* Always use our get_buffer wrapper so we can calculate the
* PTS correctly */
p_sys->p_context->get_buffer = ffmpeg_GetFrameBuf;
p_sys->p_context->release_buffer = ffmpeg_ReleaseFrameBuf;
p_sys->p_context->opaque = p_dec;
/* ***** init this codec with special data ***** */
if( p_dec->fmt_in.i_extra )
{
int i_size = p_dec->fmt_in.i_extra;
if( p_sys->i_codec_id == CODEC_ID_SVQ3 )
{
uint8_t *p;
p_sys->p_context->extradata_size = i_size + 12;
p = p_sys->p_context->extradata =
malloc( p_sys->p_context->extradata_size );
memcpy( &p[0], "SVQ3", 4 );
memset( &p[4], 0, 8 );
memcpy( &p[12], p_dec->fmt_in.p_extra, i_size );
/* Now remove all atoms before the SMI one */
if( p_sys->p_context->extradata_size > 0x5a &&
strncmp( &p[0x56], "SMI ", 4 ) )
{
uint8_t *psz = &p[0x52];
while( psz < &p[p_sys->p_context->extradata_size - 8] )
{
int i_size = GetDWBE( psz );
if( i_size <= 1 )
{
/* FIXME handle 1 as long size */
break;
}
if( !strncmp( &psz[4], "SMI ", 4 ) )
{
memmove( &p[0x52], psz,
&p[p_sys->p_context->extradata_size] - psz );
break;
}
psz += i_size;
}
}
}
else if( p_dec->fmt_in.i_codec == VLC_FOURCC( 'R', 'V', '1', '0' ) ||
p_dec->fmt_in.i_codec == VLC_FOURCC( 'R', 'V', '1', '3' ) ||
p_dec->fmt_in.i_codec == VLC_FOURCC( 'R', 'V', '2', '0' ) )
{
if( p_dec->fmt_in.i_extra == 8 )
{
p_sys->p_context->extradata_size = 8;
p_sys->p_context->extradata = malloc( 8 );
memcpy( p_sys->p_context->extradata,
p_dec->fmt_in.p_extra,
p_dec->fmt_in.i_extra );
p_sys->p_context->sub_id= ((uint32_t*)p_dec->fmt_in.p_extra)[1];
msg_Warn( p_dec, "using extra data for RV codec sub_id=%08x",
p_sys->p_context->sub_id );
}
}
/* FIXME: remove when ffmpeg deals properly with avc1 */
else if( p_dec->fmt_in.i_codec == VLC_FOURCC('a','v','c','1') )
{
;
}
/* End FIXME */
else
{
p_sys->p_context->extradata_size = i_size;
p_sys->p_context->extradata =
malloc( i_size + FF_INPUT_BUFFER_PADDING_SIZE );
memcpy( p_sys->p_context->extradata,
p_dec->fmt_in.p_extra, i_size );
memset( &((uint8_t*)p_sys->p_context->extradata)[i_size],
0, FF_INPUT_BUFFER_PADDING_SIZE );
}
}
/* ***** misc init ***** */
p_sys->input_pts = p_sys->input_dts = 0;
p_sys->i_pts = 0;
p_sys->b_has_b_frames = VLC_FALSE;
p_sys->b_first_frame = VLC_TRUE;
p_sys->i_late_frames = 0;
p_sys->i_buffer = 0;
p_sys->i_buffer_orig = 1;
p_sys->p_buffer_orig = p_sys->p_buffer = malloc( p_sys->i_buffer_orig );
/* Set output properties */
p_dec->fmt_out.i_cat = VIDEO_ES;
p_dec->fmt_out.i_codec = ffmpeg_PixFmtToChroma( p_context->pix_fmt );
/* Setup palette */
#if LIBAVCODEC_BUILD >= 4688
if( p_dec->fmt_in.video.p_palette )
p_sys->p_context->palctrl =
(AVPaletteControl *)p_dec->fmt_in.video.p_palette;
else
p_sys->p_context->palctrl = &palette_control;
#endif
/* ***** Open the codec ***** */
vlc_mutex_lock( lockval.p_address );
if( avcodec_open( p_sys->p_context, p_sys->p_codec ) < 0 )
{
vlc_mutex_unlock( lockval.p_address );
msg_Err( p_dec, "cannot open codec (%s)", p_sys->psz_namecodec );
free( p_sys );
return VLC_EGENERIC;
}
vlc_mutex_unlock( lockval.p_address );
msg_Dbg( p_dec, "ffmpeg codec (%s) started", p_sys->psz_namecodec );
return VLC_SUCCESS;
}
/*****************************************************************************
* DecodeVideo: Called to decode one or more frames
*****************************************************************************/
picture_t *E_(DecodeVideo)( decoder_t *p_dec, block_t **pp_block )
{
decoder_sys_t *p_sys = p_dec->p_sys;
int b_drawpicture;
int b_null_size = VLC_FALSE;
block_t *p_block;
if( !pp_block || !*pp_block ) return NULL;
p_block = *pp_block;
if( p_block->i_flags & (BLOCK_FLAG_DISCONTINUITY|BLOCK_FLAG_CORRUPTED) )
{
p_sys->i_buffer = 0;
p_sys->i_pts = 0; /* To make sure we recover properly */
p_sys->input_pts = p_sys->input_dts = 0;
p_sys->i_late_frames = 0;
block_Release( p_block );
return NULL;
}
if( p_block->i_flags & BLOCK_FLAG_PREROLL )
{
/* Do not care about late frames when prerolling
* TODO avoid decoding of non reference frame
* (ie all B except for H264 where it depends only on nal_ref_idc) */
p_sys->i_late_frames = 0;
}
if( !p_dec->b_pace_control && p_sys->i_late_frames > 0 &&
mdate() - p_sys->i_late_frames_start > I64C(5000000) )
{
if( p_sys->i_pts )
{
msg_Err( p_dec, "more than 5 seconds of late video -> "
"dropping frame (computer too slow ?)" );
p_sys->i_pts = 0; /* To make sure we recover properly */
}
block_Release( p_block );
p_sys->i_late_frames--;
return NULL;
}
if( p_block->i_pts > 0 || p_block->i_dts > 0 )
{
p_sys->input_pts = p_block->i_pts;
p_sys->input_dts = p_block->i_dts;
/* Make sure we don't reuse the same timestamps twice */
p_block->i_pts = p_block->i_dts = 0;
}
/* TODO implement it in a better way */
/* A good idea could be to decode all I pictures and see for the other */
if( !p_dec->b_pace_control &&
p_sys->b_hurry_up && p_sys->i_late_frames > 4 )
{
b_drawpicture = 0;
if( p_sys->i_late_frames < 8 )
{
p_sys->p_context->hurry_up = 2;
}
else
{
/* picture too late, won't decode
* but break picture until a new I, and for mpeg4 ...*/
p_sys->i_late_frames--; /* needed else it will never be decrease */
block_Release( p_block );
p_sys->i_buffer = 0;
return NULL;
}
}
else
{
b_drawpicture = 1;
p_sys->p_context->hurry_up = 0;
}
if( p_sys->p_context->width <= 0 || p_sys->p_context->height <= 0 )
{
p_sys->p_context->hurry_up = 5;
b_null_size = VLC_TRUE;
}
/*
* Do the actual decoding now
*/
/* Check if post-processing was enabled */
p_sys->b_pp = p_sys->b_pp_async;
/* Don't forget that ffmpeg requires a little more bytes
* that the real frame size */
if( p_block->i_buffer > 0 )
{
p_sys->i_buffer = p_block->i_buffer;
if( p_sys->i_buffer + FF_INPUT_BUFFER_PADDING_SIZE >
p_sys->i_buffer_orig )
{
free( p_sys->p_buffer_orig );
p_sys->i_buffer_orig =
p_block->i_buffer + FF_INPUT_BUFFER_PADDING_SIZE;
p_sys->p_buffer_orig = malloc( p_sys->i_buffer_orig );
}
p_sys->p_buffer = p_sys->p_buffer_orig;
p_sys->i_buffer = p_block->i_buffer;
p_dec->p_vlc->pf_memcpy( p_sys->p_buffer, p_block->p_buffer,
p_block->i_buffer );
memset( p_sys->p_buffer + p_block->i_buffer, 0,
FF_INPUT_BUFFER_PADDING_SIZE );
p_block->i_buffer = 0;
}
while( p_sys->i_buffer > 0 )
{
int i_used, b_gotpicture;
picture_t *p_pic;
i_used = avcodec_decode_video( p_sys->p_context, p_sys->p_ff_pic,
&b_gotpicture,
p_sys->p_buffer, p_sys->i_buffer );
if( b_null_size && p_sys->p_context->width > 0 &&
p_sys->p_context->height > 0 )
{
/* Reparse it to not drop the I frame */
b_null_size = VLC_FALSE;
p_sys->p_context->hurry_up = 0;
i_used = avcodec_decode_video( p_sys->p_context, p_sys->p_ff_pic,
&b_gotpicture,
p_sys->p_buffer, p_sys->i_buffer );
}
if( i_used < 0 )
{
msg_Warn( p_dec, "cannot decode one frame (%d bytes)",
p_sys->i_buffer );
block_Release( p_block );
return NULL;
}
else if( i_used > p_sys->i_buffer )
{
i_used = p_sys->i_buffer;
}
/* Consumed bytes */
p_sys->i_buffer -= i_used;
p_sys->p_buffer += i_used;
/* Nothing to display */
if( !b_gotpicture )
{
if( i_used == 0 ) break;
continue;
}
/* Update frame late count (except when doing preroll) */
if( p_sys->i_pts && p_sys->i_pts <= mdate() &&
!(p_block->i_flags & BLOCK_FLAG_PREROLL) )
{
p_sys->i_late_frames++;
if( p_sys->i_late_frames == 1 )
p_sys->i_late_frames_start = mdate();
}
else
{
p_sys->i_late_frames = 0;
}
if( !b_drawpicture || !p_sys->p_ff_pic->linesize[0] )
{
/* Do not display the picture */
continue;
}
if( !p_sys->p_ff_pic->opaque )
{
/* Get a new picture */
p_pic = ffmpeg_NewPictBuf( p_dec, p_sys->p_context );
if( !p_pic )
{
block_Release( p_block );
return NULL;
}
/* Fill p_picture_t from AVVideoFrame and do chroma conversion
* if needed */
ffmpeg_CopyPicture( p_dec, p_pic, p_sys->p_ff_pic );
}
else
{
p_pic = (picture_t *)p_sys->p_ff_pic->opaque;
}
/* Set the PTS */
if( p_sys->p_ff_pic->pts ) p_sys->i_pts = p_sys->p_ff_pic->pts;
/* Sanity check (seems to be needed for some streams ) */
if( p_sys->p_ff_pic->pict_type == FF_B_TYPE )
{
p_sys->b_has_b_frames = VLC_TRUE;
}
/* Send decoded frame to vout */
if( p_sys->i_pts )
{
p_pic->date = p_sys->i_pts;
/* interpolate the next PTS */
if( p_sys->p_context->frame_rate > 0 )
{
p_sys->i_pts += I64C(1000000) *
(2 + p_sys->p_ff_pic->repeat_pict) *
p_sys->p_context->frame_rate_base /
(2 * p_sys->p_context->frame_rate);
}
if( p_sys->b_first_frame )
{
/* Hack to force display of still pictures */
p_sys->b_first_frame = VLC_FALSE;
p_pic->b_force = VLC_TRUE;
}
p_pic->i_nb_fields = 2 + p_sys->p_ff_pic->repeat_pict;
#if LIBAVCODEC_BUILD >= 4685
p_pic->b_progressive = !p_sys->p_ff_pic->interlaced_frame;
p_pic->b_top_field_first = p_sys->p_ff_pic->top_field_first;
#endif
return p_pic;
}
else
{
p_dec->pf_vout_buffer_del( p_dec, p_pic );
}
}
block_Release( p_block );
return NULL;
}
/*****************************************************************************
* EndVideo: decoder destruction
*****************************************************************************
* This function is called when the thread ends after a sucessful
* initialization.
*****************************************************************************/
void E_(EndVideoDec)( decoder_t *p_dec )
{
decoder_sys_t *p_sys = p_dec->p_sys;
if( p_sys->p_ff_pic ) av_free( p_sys->p_ff_pic );
#ifdef LIBAVCODEC_PP
E_(ClosePostproc)( p_dec, p_sys->p_pp );
#endif
free( p_sys->p_buffer_orig );
}
/*****************************************************************************
* ffmpeg_CopyPicture: copy a picture from ffmpeg internal buffers to a
* picture_t structure (when not in direct rendering mode).
*****************************************************************************/
static void ffmpeg_CopyPicture( decoder_t *p_dec,
picture_t *p_pic, AVFrame *p_ff_pic )
{
decoder_sys_t *p_sys = p_dec->p_sys;
if( ffmpeg_PixFmtToChroma( p_sys->p_context->pix_fmt ) )
{
int i_plane, i_size, i_line;
uint8_t *p_dst, *p_src;
int i_src_stride, i_dst_stride;
#ifdef LIBAVCODEC_PP
if( p_sys->p_pp && p_sys->b_pp )
E_(PostprocPict)( p_dec, p_sys->p_pp, p_pic, p_ff_pic );
else
#endif
for( i_plane = 0; i_plane < p_pic->i_planes; i_plane++ )
{
p_src = p_ff_pic->data[i_plane];
p_dst = p_pic->p[i_plane].p_pixels;
i_src_stride = p_ff_pic->linesize[i_plane];
i_dst_stride = p_pic->p[i_plane].i_pitch;
i_size = __MIN( i_src_stride, i_dst_stride );
for( i_line = 0; i_line < p_pic->p[i_plane].i_visible_lines;
i_line++ )
{
p_dec->p_vlc->pf_memcpy( p_dst, p_src, i_size );
p_src += i_src_stride;
p_dst += i_dst_stride;
}
}
}
else
{
AVPicture dest_pic;
int i;
/* we need to convert to I420 */
switch( p_sys->p_context->pix_fmt )
{
case PIX_FMT_YUV410P:
case PIX_FMT_YUV411P:
case PIX_FMT_PAL8:
for( i = 0; i < p_pic->i_planes; i++ )
{
dest_pic.data[i] = p_pic->p[i].p_pixels;
dest_pic.linesize[i] = p_pic->p[i].i_pitch;
}
img_convert( &dest_pic, PIX_FMT_YUV420P,
(AVPicture *)p_ff_pic,
p_sys->p_context->pix_fmt,
p_sys->p_context->width,
p_sys->p_context->height );
break;
default:
msg_Err( p_dec, "don't know how to convert chroma %i",
p_sys->p_context->pix_fmt );
p_dec->b_error = 1;
break;
}
}
}
/*****************************************************************************
* ffmpeg_GetFrameBuf: callback used by ffmpeg to get a frame buffer.
*****************************************************************************
* It is used for direct rendering as well as to get the right PTS for each
* decoded picture (even in indirect rendering mode).
*****************************************************************************/
static int ffmpeg_GetFrameBuf( struct AVCodecContext *p_context,
AVFrame *p_ff_pic )
{
decoder_t *p_dec = (decoder_t *)p_context->opaque;
decoder_sys_t *p_sys = p_dec->p_sys;
picture_t *p_pic;
/* Set picture PTS */
if( p_sys->input_pts )
{
p_ff_pic->pts = p_sys->input_pts;
}
else if( p_sys->input_dts )
{
/* Some demuxers only set the dts so let's try to find a useful
* timestamp from this */
if( !p_context->has_b_frames || !p_sys->b_has_b_frames ||
!p_ff_pic->reference || !p_sys->i_pts )
{
p_ff_pic->pts = p_sys->input_dts;
}
else p_ff_pic->pts = 0;
}
else p_ff_pic->pts = 0;
if( p_sys->i_pts ) /* make sure 1st frame has a pts > 0 */
{
p_sys->input_pts = p_sys->input_dts = 0;
}
p_ff_pic->opaque = 0;
/* Not much to do in indirect rendering mode */
if( !p_sys->b_direct_rendering || p_sys->b_pp )
{
return avcodec_default_get_buffer( p_context, p_ff_pic );
}
/* Some codecs set pix_fmt only after the 1st frame has been decoded,
* so this check is necessary. */
if( !ffmpeg_PixFmtToChroma( p_context->pix_fmt ) ||
p_sys->p_context->width % 16 || p_sys->p_context->height % 16 )
{
msg_Dbg( p_dec, "disabling direct rendering" );
p_sys->b_direct_rendering = 0;
return avcodec_default_get_buffer( p_context, p_ff_pic );
}
/* Get a new picture */
//p_sys->p_vout->render.b_allow_modify_pics = 0;
p_pic = ffmpeg_NewPictBuf( p_dec, p_sys->p_context );
if( !p_pic )
{
p_sys->b_direct_rendering = 0;
return avcodec_default_get_buffer( p_context, p_ff_pic );
}
p_sys->p_context->draw_horiz_band = NULL;
p_ff_pic->opaque = (void*)p_pic;
p_ff_pic->type = FF_BUFFER_TYPE_USER;
p_ff_pic->data[0] = p_pic->p[0].p_pixels;
p_ff_pic->data[1] = p_pic->p[1].p_pixels;
p_ff_pic->data[2] = p_pic->p[2].p_pixels;
p_ff_pic->data[3] = NULL; /* alpha channel but I'm not sure */
p_ff_pic->linesize[0] = p_pic->p[0].i_pitch;
p_ff_pic->linesize[1] = p_pic->p[1].i_pitch;
p_ff_pic->linesize[2] = p_pic->p[2].i_pitch;
p_ff_pic->linesize[3] = 0;
if( p_ff_pic->reference != 0 )
{
p_dec->pf_picture_link( p_dec, p_pic );
}
/* FIXME what is that, should give good value */
p_ff_pic->age = 256*256*256*64; // FIXME FIXME from ffmpeg
return 0;
}
static void ffmpeg_ReleaseFrameBuf( struct AVCodecContext *p_context,
AVFrame *p_ff_pic )
{
decoder_t *p_dec = (decoder_t *)p_context->opaque;
picture_t *p_pic;
if( !p_ff_pic->opaque )
{
avcodec_default_release_buffer( p_context, p_ff_pic );
return;
}
p_pic = (picture_t*)p_ff_pic->opaque;
p_ff_pic->data[0] = NULL;
p_ff_pic->data[1] = NULL;
p_ff_pic->data[2] = NULL;
p_ff_pic->data[3] = NULL;
if( p_ff_pic->reference != 0 )
{
p_dec->pf_picture_unlink( p_dec, p_pic );
}
}
/*****************************************************************************
* video.c: video decoder using the ffmpeg library
*****************************************************************************
* Copyright (C) 1999-2001 VideoLAN
* $Id$
*
* Authors: Laurent Aimar <fenrir@via.ecp.fr>
* Gildas Bazin <gbazin@videolan.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 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#include <vlc/vlc.h>
#include <vlc/decoder.h>
/* ffmpeg header */
#ifdef HAVE_FFMPEG_AVCODEC_H
# include <ffmpeg/avcodec.h>
#else
# include <avcodec.h>
#endif
#include "ffmpeg.h"
/*****************************************************************************
* decoder_sys_t : decoder descriptor
*****************************************************************************/
struct decoder_sys_t
{
/* Common part between video and audio decoder */
int i_cat;
int i_codec_id;
char *psz_namecodec;
AVCodecContext *p_context;
AVCodec *p_codec;
/* Video decoder specific part */
mtime_t input_pts;
mtime_t input_dts;
mtime_t i_pts;
AVFrame *p_ff_pic;
BITMAPINFOHEADER *p_format;
/* for frame skipping algo */
int b_hurry_up;
int i_frame_skip;
/* how many decoded frames are late */
int i_late_frames;
mtime_t i_late_frames_start;
/* for direct rendering */
int b_direct_rendering;
vlc_bool_t b_has_b_frames;
/* Hack to force display of still pictures */
vlc_bool_t b_first_frame;
int i_buffer_orig, i_buffer;
char *p_buffer_orig, *p_buffer;
/* Postprocessing handle */
void *p_pp;
vlc_bool_t b_pp;
vlc_bool_t b_pp_async;
vlc_bool_t b_pp_init;
};
/* FIXME (dummy palette for now) */
static AVPaletteControl palette_control;
/*****************************************************************************
* Local prototypes
*****************************************************************************/
static void ffmpeg_CopyPicture ( decoder_t *, picture_t *, AVFrame * );
static int ffmpeg_GetFrameBuf ( struct AVCodecContext *, AVFrame * );
static void ffmpeg_ReleaseFrameBuf( struct AVCodecContext *, AVFrame * );
static uint32_t ffmpeg_CodecTag( vlc_fourcc_t fcc )
{
uint8_t *p = (uint8_t*)&fcc;
return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
}
/*****************************************************************************
* Local Functions
*****************************************************************************/
static uint32_t ffmpeg_PixFmtToChroma( int i_ff_chroma )
{
switch( i_ff_chroma )
{
case PIX_FMT_YUV420P:
return VLC_FOURCC('I','4','2','0');
case PIX_FMT_YUV422P:
return VLC_FOURCC('I','4','2','2');
case PIX_FMT_YUV444P:
return VLC_FOURCC('I','4','4','4');
case PIX_FMT_YUV422:
return VLC_FOURCC('Y','U','Y','2');
case PIX_FMT_RGB555:
return VLC_FOURCC('R','V','1','5');
case PIX_FMT_RGB565:
return VLC_FOURCC('R','V','1','6');
case PIX_FMT_RGB24:
return VLC_FOURCC('R','V','2','4');
case PIX_FMT_RGBA32:
return VLC_FOURCC('R','V','3','2');
case PIX_FMT_GRAY8:
return VLC_FOURCC('G','R','E','Y');
case PIX_FMT_YUV410P:
case PIX_FMT_YUV411P:
case PIX_FMT_BGR24:
default:
return 0;
}
}
/* Returns a new picture buffer */
static inline picture_t *ffmpeg_NewPictBuf( decoder_t *p_dec,
AVCodecContext *p_context )
{
decoder_sys_t *p_sys = p_dec->p_sys;
picture_t *p_pic;
p_dec->fmt_out.video.i_width = p_context->width;
p_dec->fmt_out.video.i_height = p_context->height;
p_dec->fmt_out.i_codec = ffmpeg_PixFmtToChroma( p_context->pix_fmt );
if( !p_context->width || !p_context->height )
{
return NULL; /* invalid display size */
}
if( !p_dec->fmt_out.i_codec )
{
/* we make conversion if possible*/
p_dec->fmt_out.i_codec = VLC_FOURCC('I','4','2','0');
}
/* If an aspect-ratio was specified in the input format then force it */
if( p_dec->fmt_in.video.i_aspect )
{
p_dec->fmt_out.video.i_aspect = p_dec->fmt_in.video.i_aspect;
}
else
{
#if LIBAVCODEC_BUILD >= 4687
p_dec->fmt_out.video.i_aspect =
VOUT_ASPECT_FACTOR * ( av_q2d(p_context->sample_aspect_ratio) *
p_context->width / p_context->height );
#else
p_dec->fmt_out.video.i_aspect =
VOUT_ASPECT_FACTOR * p_context->aspect_ratio;
#endif
if( p_dec->fmt_out.video.i_aspect == 0 )
{
p_dec->fmt_out.video.i_aspect =
VOUT_ASPECT_FACTOR * p_context->width / p_context->height;
}
}
if( p_context->frame_rate > 0 && p_context->frame_rate_base > 0 )
{
p_dec->fmt_out.video.i_frame_rate = p_context->frame_rate;
p_dec->fmt_out.video.i_frame_rate_base = p_context->frame_rate_base;
}
p_pic = p_dec->pf_vout_buffer_new( p_dec );
#ifdef LIBAVCODEC_PP
if( p_sys->p_pp && p_sys->b_pp && !p_sys->b_pp_init )
{
E_(InitPostproc)( p_dec, p_sys->p_pp, p_context->width,
p_context->height, p_context->pix_fmt );
p_sys->b_pp_init = VLC_TRUE;
}
#endif
return p_pic;
}
/*****************************************************************************
* InitVideo: initialize the video decoder
*****************************************************************************
* the ffmpeg codec will be opened, some memory allocated. The vout is not yet
* opened (done after the first decoded frame).
*****************************************************************************/
int E_(InitVideoDec)( decoder_t *p_dec, AVCodecContext *p_context,
AVCodec *p_codec, int i_codec_id, char *psz_namecodec )
{
decoder_sys_t *p_sys;
vlc_value_t lockval;
vlc_value_t val;
var_Get( p_dec->p_libvlc, "avcodec", &lockval );
/* Allocate the memory needed to store the decoder's structure */
if( ( p_dec->p_sys = p_sys =
(decoder_sys_t *)malloc(sizeof(decoder_sys_t)) ) == NULL )
{
msg_Err( p_dec, "out of memory" );
return VLC_EGENERIC;
}
p_dec->p_sys->p_context = p_context;
p_dec->p_sys->p_codec = p_codec;
p_dec->p_sys->i_codec_id = i_codec_id;
p_dec->p_sys->psz_namecodec = psz_namecodec;
p_sys->p_ff_pic = avcodec_alloc_frame();
/* ***** Fill p_context with init values ***** */
/* FIXME: remove when ffmpeg deals properly with avc1 */
if( p_dec->fmt_in.i_codec != VLC_FOURCC('a','v','c','1') )
/* End FIXME */
p_sys->p_context->codec_tag = ffmpeg_CodecTag( p_dec->fmt_in.i_codec );
p_sys->p_context->width = p_dec->fmt_in.video.i_width;
p_sys->p_context->height = p_dec->fmt_in.video.i_height;
p_sys->p_context->bits_per_sample = p_dec->fmt_in.video.i_bits_per_pixel;
/* ***** Get configuration of ffmpeg plugin ***** */
p_sys->p_context->workaround_bugs =
config_GetInt( p_dec, "ffmpeg-workaround-bugs" );
p_sys->p_context->error_resilience =
config_GetInt( p_dec, "ffmpeg-error-resilience" );
var_Create( p_dec, "grayscale", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
var_Get( p_dec, "grayscale", &val );
if( val.b_bool ) p_sys->p_context->flags |= CODEC_FLAG_GRAY;
var_Create( p_dec, "ffmpeg-vismv", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
var_Get( p_dec, "ffmpeg-vismv", &val );
#if LIBAVCODEC_BUILD >= 4698
if( val.i_int ) p_sys->p_context->debug_mv = val.i_int;
#endif
var_Create( p_dec, "ffmpeg-lowres", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
var_Get( p_dec, "ffmpeg-lowres", &val );
#if LIBAVCODEC_BUILD >= 4723
if( val.i_int > 0 && val.i_int <= 2 ) p_sys->p_context->lowres = val.i_int;
#endif
/* ***** ffmpeg frame skipping ***** */
var_Create( p_dec, "ffmpeg-hurry-up", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
var_Get( p_dec, "ffmpeg-hurry-up", &val );
p_sys->b_hurry_up = val.b_bool;
/* ***** ffmpeg direct rendering ***** */
p_sys->b_direct_rendering = 0;
var_Create( p_dec, "ffmpeg-dr", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
var_Get( p_dec, "ffmpeg-dr", &val );
if( val.b_bool && (p_sys->p_codec->capabilities & CODEC_CAP_DR1) &&
ffmpeg_PixFmtToChroma( p_sys->p_context->pix_fmt ) &&
/* Apparently direct rendering doesn't work with YUV422P */
p_sys->p_context->pix_fmt != PIX_FMT_YUV422P &&
/* H264 uses too many reference frames */
p_sys->i_codec_id != CODEC_ID_H264 &&
!(p_sys->p_context->width % 16) && !(p_sys->p_context->height % 16) &&
#if LIBAVCODEC_BUILD >= 4698
!p_sys->p_context->debug_mv )
#else
1 )
#endif
{
/* Some codecs set pix_fmt only after the 1st frame has been decoded,
* so we need to do another check in ffmpeg_GetFrameBuf() */
p_sys->b_direct_rendering = 1;
}
#ifdef LIBAVCODEC_PP
p_sys->p_pp = NULL;
p_sys->b_pp = p_sys->b_pp_async = p_sys->b_pp_init = VLC_FALSE;
p_sys->p_pp = E_(OpenPostproc)( p_dec, &p_sys->b_pp_async );
#endif
/* ffmpeg doesn't properly release old pictures when frames are skipped */
//if( p_sys->b_hurry_up ) p_sys->b_direct_rendering = 0;
if( p_sys->b_direct_rendering )
{
msg_Dbg( p_dec, "using direct rendering" );
p_sys->p_context->flags |= CODEC_FLAG_EMU_EDGE;
}
/* Always use our get_buffer wrapper so we can calculate the
* PTS correctly */
p_sys->p_context->get_buffer = ffmpeg_GetFrameBuf;
p_sys->p_context->release_buffer = ffmpeg_ReleaseFrameBuf;
p_sys->p_context->opaque = p_dec;
/* ***** init this codec with special data ***** */
if( p_dec->fmt_in.i_extra )
{
int i_size = p_dec->fmt_in.i_extra;
if( p_sys->i_codec_id == CODEC_ID_SVQ3 )
{
uint8_t *p;
p_sys->p_context->extradata_size = i_size + 12;
p = p_sys->p_context->extradata =
malloc( p_sys->p_context->extradata_size );
memcpy( &p[0], "SVQ3", 4 );
memset( &p[4], 0, 8 );
memcpy( &p[12], p_dec->fmt_in.p_extra, i_size );
/* Now remove all atoms before the SMI one */
if( p_sys->p_context->extradata_size > 0x5a &&
strncmp( &p[0x56], "SMI ", 4 ) )
{
uint8_t *psz = &p[0x52];
while( psz < &p[p_sys->p_context->extradata_size - 8] )
{
int i_size = GetDWBE( psz );
if( i_size <= 1 )
{
/* FIXME handle 1 as long size */
break;
}
if( !strncmp( &psz[4], "SMI ", 4 ) )
{
memmove( &p[0x52], psz,
&p[p_sys->p_context->extradata_size] - psz );
break;
}
psz += i_size;
}
}
}
else if( p_dec->fmt_in.i_codec == VLC_FOURCC( 'R', 'V', '1', '0' ) ||
p_dec->fmt_in.i_codec == VLC_FOURCC( 'R', 'V', '1', '3' ) ||
p_dec->fmt_in.i_codec == VLC_FOURCC( 'R', 'V', '2', '0' ) )
{
if( p_dec->fmt_in.i_extra == 8 )
{
p_sys->p_context->extradata_size = 8;
p_sys->p_context->extradata = malloc( 8 );
memcpy( p_sys->p_context->extradata,
p_dec->fmt_in.p_extra,
p_dec->fmt_in.i_extra );
p_sys->p_context->sub_id= ((uint32_t*)p_dec->fmt_in.p_extra)[1];
msg_Warn( p_dec, "using extra data for RV codec sub_id=%08x",
p_sys->p_context->sub_id );
}
}
/* FIXME: remove when ffmpeg deals properly with avc1 */
else if( p_dec->fmt_in.i_codec == VLC_FOURCC('a','v','c','1') )
{
;
}
/* End FIXME */
else
{
p_sys->p_context->extradata_size = i_size;
p_sys->p_context->extradata =
malloc( i_size + FF_INPUT_BUFFER_PADDING_SIZE );
memcpy( p_sys->p_context->extradata,
p_dec->fmt_in.p_extra, i_size );
memset( &((uint8_t*)p_sys->p_context->extradata)[i_size],
0, FF_INPUT_BUFFER_PADDING_SIZE );
}
}
/* ***** misc init ***** */
p_sys->input_pts = p_sys->input_dts = 0;
p_sys->i_pts = 0;
p_sys->b_has_b_frames = VLC_FALSE;
p_sys->b_first_frame = VLC_TRUE;
p_sys->i_late_frames = 0;
p_sys->i_buffer = 0;
p_sys->i_buffer_orig = 1;
p_sys->p_buffer_orig = p_sys->p_buffer = malloc( p_sys->i_buffer_orig );
/* Set output properties */
p_dec->fmt_out.i_cat = VIDEO_ES;
p_dec->fmt_out.i_codec = ffmpeg_PixFmtToChroma( p_context->pix_fmt );
/* Setup palette */
#if LIBAVCODEC_BUILD >= 4688
if( p_dec->fmt_in.video.p_palette )
p_sys->p_context->palctrl =
(AVPaletteControl *)p_dec->fmt_in.video.p_palette;
else
p_sys->p_context->palctrl = &palette_control;
#endif
/* ***** Open the codec ***** */
vlc_mutex_lock( lockval.p_address );
if( avcodec_open( p_sys->p_context, p_sys->p_codec ) < 0 )
{
vlc_mutex_unlock( lockval.p_address );
msg_Err( p_dec, "cannot open codec (%s)", p_sys->psz_namecodec );
free( p_sys );
return VLC_EGENERIC;
}
vlc_mutex_unlock( lockval.p_address );
msg_Dbg( p_dec, "ffmpeg codec (%s) started", p_sys->psz_namecodec );
return VLC_SUCCESS;
}
/*****************************************************************************
* DecodeVideo: Called to decode one or more frames
*****************************************************************************/
picture_t *E_(DecodeVideo)( decoder_t *p_dec, block_t **pp_block )
{
decoder_sys_t *p_sys = p_dec->p_sys;
int b_drawpicture;
int b_null_size = VLC_FALSE;
block_t *p_block;
if( !pp_block || !*pp_block ) return NULL;
p_block = *pp_block;
if( p_block->i_flags & (BLOCK_FLAG_DISCONTINUITY|BLOCK_FLAG_CORRUPTED) )
{
p_sys->i_buffer = 0;
p_sys->i_pts = 0; /* To make sure we recover properly */
p_sys->input_pts = p_sys->input_dts = 0;
p_sys->i_late_frames = 0;
block_Release( p_block );
return NULL;
}
if( p_block->i_flags & BLOCK_FLAG_PREROLL )
{
/* Do not care about late frames when prerolling
* TODO avoid decoding of non reference frame
* (ie all B except for H264 where it depends only on nal_ref_idc) */
p_sys->i_late_frames = 0;
}
if( !p_dec->b_pace_control && p_sys->i_late_frames > 0 &&
mdate() - p_sys->i_late_frames_start > I64C(5000000) )
{
if( p_sys->i_pts )
{
msg_Err( p_dec, "more than 5 seconds of late video -> "
"dropping frame (computer too slow ?)" );
p_sys->i_pts = 0; /* To make sure we recover properly */
}
block_Release( p_block );
p_sys->i_late_frames--;
return NULL;
}
if( p_block->i_pts > 0 || p_block->i_dts > 0 )
{
p_sys->input_pts = p_block->i_pts;
p_sys->input_dts = p_block->i_dts;
/* Make sure we don't reuse the same timestamps twice */
p_block->i_pts = p_block->i_dts = 0;
}
/* TODO implement it in a better way */
/* A good idea could be to decode all I pictures and see for the other */
if( !p_dec->b_pace_control &&
p_sys->b_hurry_up && p_sys->i_late_frames > 4 )
{
b_drawpicture = 0;
if( p_sys->i_late_frames < 8 )
{
p_sys->p_context->hurry_up = 2;
}
else
{
/* picture too late, won't decode
* but break picture until a new I, and for mpeg4 ...*/
p_sys->i_late_frames--; /* needed else it will never be decrease */
block_Release( p_block );
p_sys->i_buffer = 0;
return NULL;
}
}
else
{
if (!(p_block->i_flags & BLOCK_FLAG_PREROLL))
{
b_drawpicture = 1;
p_sys->p_context->hurry_up = 0;
}
else
{
b_drawpicture = 0;
p_sys->p_context->hurry_up = 1;
}
}
if( p_sys->p_context->width <= 0 || p_sys->p_context->height <= 0 )
{
p_sys->p_context->hurry_up = 5;
b_null_size = VLC_TRUE;
}
/*
* Do the actual decoding now
*/
/* Check if post-processing was enabled */
p_sys->b_pp = p_sys->b_pp_async;
/* Don't forget that ffmpeg requires a little more bytes
* that the real frame size */
if( p_block->i_buffer > 0 )
{
p_sys->i_buffer = p_block->i_buffer;
if( p_sys->i_buffer + FF_INPUT_BUFFER_PADDING_SIZE >
p_sys->i_buffer_orig )
{
free( p_sys->p_buffer_orig );
p_sys->i_buffer_orig =
p_block->i_buffer + FF_INPUT_BUFFER_PADDING_SIZE;
p_sys->p_buffer_orig = malloc( p_sys->i_buffer_orig );
}
p_sys->p_buffer = p_sys->p_buffer_orig;
p_sys->i_buffer = p_block->i_buffer;
p_dec->p_vlc->pf_memcpy( p_sys->p_buffer, p_block->p_buffer,
p_block->i_buffer );
memset( p_sys->p_buffer + p_block->i_buffer, 0,
FF_INPUT_BUFFER_PADDING_SIZE );
p_block->i_buffer = 0;
}
while( p_sys->i_buffer > 0 )
{
int i_used, b_gotpicture;
picture_t *p_pic;
i_used = avcodec_decode_video( p_sys->p_context, p_sys->p_ff_pic,
&b_gotpicture,
p_sys->p_buffer, p_sys->i_buffer );
if( b_null_size && p_sys->p_context->width > 0 &&
p_sys->p_context->height > 0 )
{
/* Reparse it to not drop the I frame */
b_null_size = VLC_FALSE;
p_sys->p_context->hurry_up = 0;
i_used = avcodec_decode_video( p_sys->p_context, p_sys->p_ff_pic,
&b_gotpicture,
p_sys->p_buffer, p_sys->i_buffer );
}
if( i_used < 0 )
{
msg_Warn( p_dec, "cannot decode one frame (%d bytes)",
p_sys->i_buffer );
block_Release( p_block );
return NULL;
}
else if( i_used > p_sys->i_buffer )
{
i_used = p_sys->i_buffer;
}
/* Consumed bytes */
p_sys->i_buffer -= i_used;
p_sys->p_buffer += i_used;
/* Nothing to display */
if( !b_gotpicture )
{
if( i_used == 0 ) break;
continue;
}
/* Update frame late count (except when doing preroll) */
if( p_sys->i_pts && p_sys->i_pts <= mdate() &&
!(p_block->i_flags & BLOCK_FLAG_PREROLL) )
{
p_sys->i_late_frames++;
if( p_sys->i_late_frames == 1 )
p_sys->i_late_frames_start = mdate();
}
else
{
p_sys->i_late_frames = 0;
}
if( !b_drawpicture || !p_sys->p_ff_pic->linesize[0] )
{
/* Do not display the picture */
continue;
}
if( !p_sys->p_ff_pic->opaque )
{
/* Get a new picture */
p_pic = ffmpeg_NewPictBuf( p_dec, p_sys->p_context );
if( !p_pic )
{
block_Release( p_block );
return NULL;
}
/* Fill p_picture_t from AVVideoFrame and do chroma conversion
* if needed */
ffmpeg_CopyPicture( p_dec, p_pic, p_sys->p_ff_pic );
}
else
{
p_pic = (picture_t *)p_sys->p_ff_pic->opaque;
}
/* Set the PTS */
if( p_sys->p_ff_pic->pts ) p_sys->i_pts = p_sys->p_ff_pic->pts;
/* Sanity check (seems to be needed for some streams ) */
if( p_sys->p_ff_pic->pict_type == FF_B_TYPE )
{
p_sys->b_has_b_frames = VLC_TRUE;
}
/* Send decoded frame to vout */
if( p_sys->i_pts )
{
p_pic->date = p_sys->i_pts;
/* interpolate the next PTS */
if( p_sys->p_context->frame_rate > 0 )
{
p_sys->i_pts += I64C(1000000) *
(2 + p_sys->p_ff_pic->repeat_pict) *
p_sys->p_context->frame_rate_base /
(2 * p_sys->p_context->frame_rate);
}
if( p_sys->b_first_frame )
{
/* Hack to force display of still pictures */
p_sys->b_first_frame = VLC_FALSE;
p_pic->b_force = VLC_TRUE;
}
p_pic->i_nb_fields = 2 + p_sys->p_ff_pic->repeat_pict;
#if LIBAVCODEC_BUILD >= 4685
p_pic->b_progressive = !p_sys->p_ff_pic->interlaced_frame;
p_pic->b_top_field_first = p_sys->p_ff_pic->top_field_first;
#endif
return p_pic;
}
else
{
p_dec->pf_vout_buffer_del( p_dec, p_pic );
}
}
block_Release( p_block );
return NULL;
}
/*****************************************************************************
* EndVideo: decoder destruction
*****************************************************************************
* This function is called when the thread ends after a sucessful
* initialization.
*****************************************************************************/
void E_(EndVideoDec)( decoder_t *p_dec )
{
decoder_sys_t *p_sys = p_dec->p_sys;
if( p_sys->p_ff_pic ) av_free( p_sys->p_ff_pic );
#ifdef LIBAVCODEC_PP
E_(ClosePostproc)( p_dec, p_sys->p_pp );
#endif
free( p_sys->p_buffer_orig );
}
/*****************************************************************************
* ffmpeg_CopyPicture: copy a picture from ffmpeg internal buffers to a
* picture_t structure (when not in direct rendering mode).
*****************************************************************************/
static void ffmpeg_CopyPicture( decoder_t *p_dec,
picture_t *p_pic, AVFrame *p_ff_pic )
{
decoder_sys_t *p_sys = p_dec->p_sys;
if( ffmpeg_PixFmtToChroma( p_sys->p_context->pix_fmt ) )
{
int i_plane, i_size, i_line;
uint8_t *p_dst, *p_src;
int i_src_stride, i_dst_stride;
#ifdef LIBAVCODEC_PP
if( p_sys->p_pp && p_sys->b_pp )
E_(PostprocPict)( p_dec, p_sys->p_pp, p_pic, p_ff_pic );
else
#endif
for( i_plane = 0; i_plane < p_pic->i_planes; i_plane++ )
{
p_src = p_ff_pic->data[i_plane];
p_dst = p_pic->p[i_plane].p_pixels;
i_src_stride = p_ff_pic->linesize[i_plane];
i_dst_stride = p_pic->p[i_plane].i_pitch;
i_size = __MIN( i_src_stride, i_dst_stride );
for( i_line = 0; i_line < p_pic->p[i_plane].i_visible_lines;
i_line++ )
{
p_dec->p_vlc->pf_memcpy( p_dst, p_src, i_size );
p_src += i_src_stride;
p_dst += i_dst_stride;
}
}
}
else
{
AVPicture dest_pic;
int i;
/* we need to convert to I420 */
switch( p_sys->p_context->pix_fmt )
{
case PIX_FMT_YUV410P:
case PIX_FMT_YUV411P:
case PIX_FMT_PAL8:
for( i = 0; i < p_pic->i_planes; i++ )
{
dest_pic.data[i] = p_pic->p[i].p_pixels;
dest_pic.linesize[i] = p_pic->p[i].i_pitch;
}
img_convert( &dest_pic, PIX_FMT_YUV420P,
(AVPicture *)p_ff_pic,
p_sys->p_context->pix_fmt,
p_sys->p_context->width,
p_sys->p_context->height );
break;
default:
msg_Err( p_dec, "don't know how to convert chroma %i",
p_sys->p_context->pix_fmt );
p_dec->b_error = 1;
break;
}
}
}
/*****************************************************************************
* ffmpeg_GetFrameBuf: callback used by ffmpeg to get a frame buffer.
*****************************************************************************
* It is used for direct rendering as well as to get the right PTS for each
* decoded picture (even in indirect rendering mode).
*****************************************************************************/
static int ffmpeg_GetFrameBuf( struct AVCodecContext *p_context,
AVFrame *p_ff_pic )
{
decoder_t *p_dec = (decoder_t *)p_context->opaque;
decoder_sys_t *p_sys = p_dec->p_sys;
picture_t *p_pic;
/* Set picture PTS */
if( p_sys->input_pts )
{
p_ff_pic->pts = p_sys->input_pts;
}
else if( p_sys->input_dts )
{
/* Some demuxers only set the dts so let's try to find a useful
* timestamp from this */
if( !p_context->has_b_frames || !p_sys->b_has_b_frames ||
!p_ff_pic->reference || !p_sys->i_pts )
{
p_ff_pic->pts = p_sys->input_dts;
}
else p_ff_pic->pts = 0;
}
else p_ff_pic->pts = 0;
if( p_sys->i_pts ) /* make sure 1st frame has a pts > 0 */
{
p_sys->input_pts = p_sys->input_dts = 0;
}
p_ff_pic->opaque = 0;
/* Not much to do in indirect rendering mode */
if( !p_sys->b_direct_rendering || p_sys->b_pp )
{
return avcodec_default_get_buffer( p_context, p_ff_pic );
}
/* Some codecs set pix_fmt only after the 1st frame has been decoded,
* so this check is necessary. */
if( !ffmpeg_PixFmtToChroma( p_context->pix_fmt ) ||
p_sys->p_context->width % 16 || p_sys->p_context->height % 16 )
{
msg_Dbg( p_dec, "disabling direct rendering" );
p_sys->b_direct_rendering = 0;
return avcodec_default_get_buffer( p_context, p_ff_pic );
}
/* Get a new picture */
//p_sys->p_vout->render.b_allow_modify_pics = 0;
p_pic = ffmpeg_NewPictBuf( p_dec, p_sys->p_context );
if( !p_pic )
{
p_sys->b_direct_rendering = 0;
return avcodec_default_get_buffer( p_context, p_ff_pic );
}
p_sys->p_context->draw_horiz_band = NULL;
p_ff_pic->opaque = (void*)p_pic;
p_ff_pic->type = FF_BUFFER_TYPE_USER;
p_ff_pic->data[0] = p_pic->p[0].p_pixels;
p_ff_pic->data[1] = p_pic->p[1].p_pixels;
p_ff_pic->data[2] = p_pic->p[2].p_pixels;
p_ff_pic->data[3] = NULL; /* alpha channel but I'm not sure */
p_ff_pic->linesize[0] = p_pic->p[0].i_pitch;
p_ff_pic->linesize[1] = p_pic->p[1].i_pitch;
p_ff_pic->linesize[2] = p_pic->p[2].i_pitch;
p_ff_pic->linesize[3] = 0;
if( p_ff_pic->reference != 0 )
{
p_dec->pf_picture_link( p_dec, p_pic );
}
/* FIXME what is that, should give good value */
p_ff_pic->age = 256*256*256*64; // FIXME FIXME from ffmpeg
return 0;
}
static void ffmpeg_ReleaseFrameBuf( struct AVCodecContext *p_context,
AVFrame *p_ff_pic )
{
decoder_t *p_dec = (decoder_t *)p_context->opaque;
picture_t *p_pic;
if( !p_ff_pic->opaque )
{
avcodec_default_release_buffer( p_context, p_ff_pic );
return;
}
p_pic = (picture_t*)p_ff_pic->opaque;
p_ff_pic->data[0] = NULL;
p_ff_pic->data[1] = NULL;
p_ff_pic->data[2] = NULL;
p_ff_pic->data[3] = NULL;
if( p_ff_pic->reference != 0 )
{
p_dec->pf_picture_unlink( p_dec, p_pic );
}
}
/*****************************************************************************
* es_out.c: Es Out handler for input.
*****************************************************************************
* Copyright (C) 2003-2004 VideoLAN
* $Id$
*
* Authors: Laurent Aimar <fenrir@via.ecp.fr>
*
* 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#include <stdlib.h>
#include <vlc/vlc.h>
#include <vlc/input.h>
#include <vlc/decoder.h>
#include "input_internal.h"
#include "vlc_playlist.h"
#include "iso_lang.h"
/* FIXME we should find a better way than including that */
#include "../misc/iso-639_def.h"
/*****************************************************************************
* Local prototypes
*****************************************************************************/
typedef struct
{
/* Program ID */
int i_id;
/* Number of es for this pgrm */
int i_es;
vlc_bool_t b_selected;
/* Clock for this program */
input_clock_t clock;
} es_out_pgrm_t;
struct es_out_id_t
{
/* ES ID */
int i_id;
es_out_pgrm_t *p_pgrm;
/* */
int64_t i_preroll_end;
/* Channel in the track type */
int i_channel;
es_format_t fmt;
char *psz_language;
char *psz_language_code;
decoder_t *p_dec;
};
struct es_out_sys_t
{
input_thread_t *p_input;
/* all programs */
int i_pgrm;
es_out_pgrm_t **pgrm;
es_out_pgrm_t **pp_selected_pgrm; /* --programs */
es_out_pgrm_t *p_pgrm; /* Master program */
/* all es */
int i_id;
int i_es;
es_out_id_t **es;
/* mode gestion */
vlc_bool_t b_active;
int i_mode;
/* es count */
int i_audio;
int i_video;
int i_sub;
/* es to select */
int i_audio_last;
int i_sub_last;
char **ppsz_audio_language;
char **ppsz_sub_language;
/* current main es */
es_out_id_t *p_es_audio;
es_out_id_t *p_es_video;
es_out_id_t *p_es_sub;
/* delay */
int64_t i_audio_delay;
int64_t i_spu_delay;
};
static es_out_id_t *EsOutAdd ( es_out_t *, es_format_t * );
static int EsOutSend ( es_out_t *, es_out_id_t *, block_t * );
static void EsOutDel ( es_out_t *, es_out_id_t * );
static void EsOutSelect( es_out_t *out, es_out_id_t *es, vlc_bool_t b_force );
static int EsOutControl( es_out_t *, int i_query, va_list );
static void EsOutAddInfo( es_out_t *, es_out_id_t *es );
static void EsSelect( es_out_t *out, es_out_id_t *es );
static void EsUnselect( es_out_t *out, es_out_id_t *es, vlc_bool_t b_update );
static char *LanguageGetName( const char *psz_code );
static char *LanguageGetCode( const char *psz_lang );
static char **LanguageSplit( const char *psz_langs );
static int LanguageArrayIndex( char **ppsz_langs, char *psz_lang );
/*****************************************************************************
* input_EsOutNew:
*****************************************************************************/
es_out_t *input_EsOutNew( input_thread_t *p_input )
{
es_out_t *out = malloc( sizeof( es_out_t ) );
es_out_sys_t *p_sys = malloc( sizeof( es_out_sys_t ) );
vlc_value_t val;
int i;
out->pf_add = EsOutAdd;
out->pf_send = EsOutSend;
out->pf_del = EsOutDel;
out->pf_control = EsOutControl;
out->p_sys = p_sys;
p_sys->p_input = p_input;
p_sys->b_active = VLC_FALSE;
p_sys->i_mode = ES_OUT_MODE_AUTO;
p_sys->i_pgrm = 0;
p_sys->pgrm = NULL;
p_sys->p_pgrm = NULL;
p_sys->i_id = 0;
p_sys->i_es = 0;
p_sys->es = NULL;
p_sys->i_audio = 0;
p_sys->i_video = 0;
p_sys->i_sub = 0;
/* */
var_Get( p_input, "audio-track", &val );
p_sys->i_audio_last = val.i_int;
var_Get( p_input, "sub-track", &val );
p_sys->i_sub_last = val.i_int;
var_Get( p_input, "audio-language", &val );
p_sys->ppsz_audio_language = LanguageSplit(val.psz_string);
if( p_sys->ppsz_audio_language )
{
for( i = 0; p_sys->ppsz_audio_language[i]; i++ )
msg_Dbg( p_input, "Select audio in language[%d] %s",
i, p_sys->ppsz_audio_language[i] );
}
var_Get( p_input, "sub-language", &val );
p_sys->ppsz_sub_language = LanguageSplit(val.psz_string);
if( p_sys->ppsz_sub_language )
{
for( i = 0; p_sys->ppsz_sub_language[i]; i++ )
msg_Dbg( p_input, "Select subtitle in language[%d] %s",
i, p_sys->ppsz_sub_language[i] );
}
/* */
p_sys->p_es_audio = NULL;
p_sys->p_es_video = NULL;
p_sys->p_es_sub = NULL;
p_sys->i_audio_delay= 0;
p_sys->i_spu_delay = 0;
return out;
}
/*****************************************************************************
* input_EsOutDelete:
*****************************************************************************/
void input_EsOutDelete( es_out_t *out )
{
es_out_sys_t *p_sys = out->p_sys;
int i;
for( i = 0; i < p_sys->i_es; i++ )
{
if( p_sys->es[i]->p_dec )
{
input_DecoderDelete( p_sys->es[i]->p_dec );
}
if( p_sys->es[i]->psz_language )
free( p_sys->es[i]->psz_language );
if( p_sys->es[i]->psz_language_code )
free( p_sys->es[i]->psz_language_code );
es_format_Clean( &p_sys->es[i]->fmt );
free( p_sys->es[i] );
}
if( p_sys->ppsz_audio_language )
{
for( i = 0; p_sys->ppsz_audio_language[i]; i++ )
free( p_sys->ppsz_audio_language[i] );
free( p_sys->ppsz_audio_language );
}
if( p_sys->ppsz_sub_language )
{
for( i = 0; p_sys->ppsz_sub_language[i]; i++ )
free( p_sys->ppsz_sub_language[i] );
free( p_sys->ppsz_sub_language );
}
if( p_sys->es )
free( p_sys->es );
for( i = 0; i < p_sys->i_pgrm; i++ )
{
free( p_sys->pgrm[i] );
}
if( p_sys->pgrm )
free( p_sys->pgrm );
free( p_sys );
free( out );
}
es_out_id_t *input_EsOutGetFromID( es_out_t *out, int i_id )
{
int i;
if( i_id < 0 )
{
/* Special HACK, -i_id is tha cat of the stream */
return (es_out_id_t*)((uint8_t*)NULL-i_id);
}
for( i = 0; i < out->p_sys->i_es; i++ )
{
if( out->p_sys->es[i]->i_id == i_id )
return out->p_sys->es[i];
}
return NULL;
}
void input_EsOutDiscontinuity( es_out_t *out, vlc_bool_t b_audio )
{
es_out_sys_t *p_sys = out->p_sys;
int i;
for( i = 0; i < p_sys->i_es; i++ )
{
es_out_id_t *es = p_sys->es[i];
/* Send a dummy block to let decoder know that
* there is a discontinuity */
if( es->p_dec && ( !b_audio || es->fmt.i_cat == AUDIO_ES ) )
{
input_DecoderDiscontinuity( es->p_dec );
}
}
}
void input_EsOutSetDelay( es_out_t *out, int i_cat, int64_t i_delay )
{
es_out_sys_t *p_sys = out->p_sys;
if( i_cat == AUDIO_ES )
p_sys->i_audio_delay = i_delay;
else if( i_cat == SPU_ES )
p_sys->i_spu_delay = i_delay;
}
vlc_bool_t input_EsOutDecodersEmpty( es_out_t *out )
{
es_out_sys_t *p_sys = out->p_sys;
int i;
for( i = 0; i < p_sys->i_es; i++ )
{
es_out_id_t *es = p_sys->es[i];
if( es->p_dec && !input_DecoderEmpty( es->p_dec ) )
return VLC_FALSE;
}
return VLC_TRUE;
}
/*****************************************************************************
*
*****************************************************************************/
static void EsOutESVarUpdate( es_out_t *out, es_out_id_t *es,
vlc_bool_t b_delete )
{
es_out_sys_t *p_sys = out->p_sys;
input_thread_t *p_input = p_sys->p_input;
vlc_value_t val, text;
char *psz_var;
if( es->fmt.i_cat == AUDIO_ES )
psz_var = "audio-es";
else if( es->fmt.i_cat == VIDEO_ES )
psz_var = "video-es";
else if( es->fmt.i_cat == SPU_ES )
psz_var = "spu-es";
else
return;
if( b_delete )
{
val.i_int = es->i_id;
var_Change( p_input, psz_var, VLC_VAR_DELCHOICE, &val, NULL );
var_SetBool( p_sys->p_input, "intf-change", VLC_TRUE );
return;
}
/* Get the number of ES already added */
var_Change( p_input, psz_var, VLC_VAR_CHOICESCOUNT, &val, NULL );
if( val.i_int == 0 )
{
vlc_value_t val2;
/* First one, we need to add the "Disable" choice */
val2.i_int = -1; text.psz_string = _("Disable");
var_Change( p_input, psz_var, VLC_VAR_ADDCHOICE, &val2, &text );
val.i_int++;
}
/* Take care of the ES description */
if( es->fmt.psz_description && *es->fmt.psz_description )
{
if( es->psz_language && *es->psz_language )
{
text.psz_string = malloc( strlen( es->fmt.psz_description) + strlen( es->psz_language ) + 10 );
sprintf( text.psz_string, "%s - [%s]", es->fmt.psz_description, es->psz_language );
}
else text.psz_string = strdup( es->fmt.psz_description );
}
else
{
if( es->psz_language && *es->psz_language )
{
char *temp;
text.psz_string = malloc( strlen( _("Track %i") )+ strlen( es->psz_language ) + 30 );
asprintf( &temp, _("Track %i"), val.i_int );
sprintf( text.psz_string, "%s - [%s]", temp, es->psz_language );
free( temp );
}
else
{
text.psz_string = malloc( strlen( _("Track %i") ) + 20 );
sprintf( text.psz_string, _("Track %i"), val.i_int );
}
}
val.i_int = es->i_id;
var_Change( p_input, psz_var, VLC_VAR_ADDCHOICE, &val, &text );
free( text.psz_string );
var_SetBool( p_sys->p_input, "intf-change", VLC_TRUE );
}
/* EsOutProgramSelect:
* Select a program and update the object variable
*/
static void EsOutProgramSelect( es_out_t *out, es_out_pgrm_t *p_pgrm )
{
es_out_sys_t *p_sys = out->p_sys;
input_thread_t *p_input = p_sys->p_input;
vlc_value_t val;
int i;
if( p_sys->p_pgrm == p_pgrm )
return; /* Nothing to do */
if( p_sys->p_pgrm )
{
es_out_pgrm_t *old = p_sys->p_pgrm;
msg_Dbg( p_input, "Unselecting program id=%d", old->i_id );
for( i = 0; i < p_sys->i_es; i++ )
{
if( p_sys->es[i]->p_pgrm == old && p_sys->es[i]->p_dec &&
p_sys->i_mode != ES_OUT_MODE_ALL )
EsUnselect( out, p_sys->es[i], VLC_TRUE );
}
p_sys->p_es_audio = NULL;
p_sys->p_es_sub = NULL;
p_sys->p_es_video = NULL;
}
msg_Dbg( p_input, "Selecting program id=%d", p_pgrm->i_id );
/* Mark it selected */
p_pgrm->b_selected = VLC_TRUE;
/* Switch master stream */
if( p_sys->p_pgrm && p_sys->p_pgrm->clock.b_master )
{
p_sys->p_pgrm->clock.b_master = VLC_FALSE;
}
p_pgrm->clock.b_master = VLC_TRUE;
p_sys->p_pgrm = p_pgrm;
/* Update "program" */
val.i_int = p_pgrm->i_id;
var_Change( p_input, "program", VLC_VAR_SETVALUE, &val, NULL );
/* Update "es-*" */
var_Change( p_input, "audio-es", VLC_VAR_CLEARCHOICES, NULL, NULL );
var_Change( p_input, "video-es", VLC_VAR_CLEARCHOICES, NULL, NULL );
var_Change( p_input, "spu-es", VLC_VAR_CLEARCHOICES, NULL, NULL );
for( i = 0; i < p_sys->i_es; i++ )
{
if( p_sys->es[i]->p_pgrm == p_sys->p_pgrm )
EsOutESVarUpdate( out, p_sys->es[i], VLC_FALSE );
EsOutSelect( out, p_sys->es[i], VLC_FALSE );
}
var_SetBool( p_sys->p_input, "intf-change", VLC_TRUE );
}
/* EsOutAddProgram:
* Add a program
*/
static es_out_pgrm_t *EsOutProgramAdd( es_out_t *out, int i_group )
{
es_out_sys_t *p_sys = out->p_sys;
input_thread_t *p_input = p_sys->p_input;
vlc_value_t val;
es_out_pgrm_t *p_pgrm = malloc( sizeof( es_out_pgrm_t ) );
/* Init */
p_pgrm->i_id = i_group;
p_pgrm->i_es = 0;
p_pgrm->b_selected = VLC_FALSE;
input_ClockInit( &p_pgrm->clock, VLC_FALSE, p_input->input.i_cr_average );
/* Append it */
TAB_APPEND( p_sys->i_pgrm, p_sys->pgrm, p_pgrm );
/* Update "program" variable */
val.i_int = i_group;
var_Change( p_input, "program", VLC_VAR_ADDCHOICE, &val, NULL );
if( i_group == var_GetInteger( p_input, "program" ) )
{
EsOutProgramSelect( out, p_pgrm );
}
else
{
var_SetBool( p_sys->p_input, "intf-change", VLC_TRUE );
}
return p_pgrm;
}
/* EsOutAdd:
* Add an es_out
*/
static es_out_id_t *EsOutAdd( es_out_t *out, es_format_t *fmt )
{
es_out_sys_t *p_sys = out->p_sys;
input_thread_t *p_input = p_sys->p_input;
es_out_id_t *es = malloc( sizeof( es_out_id_t ) );
es_out_pgrm_t *p_pgrm = NULL;
int i;
if( fmt->i_group < 0 )
{
msg_Err( p_input, "invalid group number" );
return NULL;
}
/* Search the program */
for( i = 0; i < p_sys->i_pgrm; i++ )
{
if( fmt->i_group == p_sys->pgrm[i]->i_id )
{
p_pgrm = p_sys->pgrm[i];
break;
}
}
if( p_pgrm == NULL )
{
/* Create a new one */
p_pgrm = EsOutProgramAdd( out, fmt->i_group );
}
/* Increase ref count for program */
p_pgrm->i_es++;
/* Set up ES */
if( fmt->i_id < 0 )
fmt->i_id = out->p_sys->i_id;
es->i_id = fmt->i_id;
es->p_pgrm = p_pgrm;
es_format_Copy( &es->fmt, fmt );
es->i_preroll_end = -1;
switch( fmt->i_cat )
{
case AUDIO_ES:
es->i_channel = p_sys->i_audio;
break;
case VIDEO_ES:
es->i_channel = p_sys->i_video;
break;
case SPU_ES:
es->i_channel = p_sys->i_sub;
break;
default:
es->i_channel = 0;
break;
}
es->psz_language = LanguageGetName( fmt->psz_language ); /* remember so we only need to do it once */
es->psz_language_code = LanguageGetCode( fmt->psz_language );
es->p_dec = NULL;
if( es->p_pgrm == p_sys->p_pgrm )
EsOutESVarUpdate( out, es, VLC_FALSE );
/* Select it if needed */
EsOutSelect( out, es, VLC_FALSE );
TAB_APPEND( out->p_sys->i_es, out->p_sys->es, es );
p_sys->i_id++; /* always incremented */
switch( fmt->i_cat )
{
case AUDIO_ES:
p_sys->i_audio++;
break;
case SPU_ES:
p_sys->i_sub++;
break;
case VIDEO_ES:
p_sys->i_video++;
break;
}
EsOutAddInfo( out, es );
return es;
}
static void EsSelect( es_out_t *out, es_out_id_t *es )
{
es_out_sys_t *p_sys = out->p_sys;
input_thread_t *p_input = p_sys->p_input;
vlc_value_t val;
char *psz_var;
if( es->p_dec )
{
msg_Warn( p_input, "ES 0x%x is already selected", es->i_id );
return;
}
if( es->fmt.i_cat == VIDEO_ES || es->fmt.i_cat == SPU_ES )
{
if( !var_GetBool( p_input, "video" ) ||
( p_input->p_sout && !var_GetBool( p_input, "sout-video" ) ) )
{
msg_Dbg( p_input, "video is disabled, not selecting ES 0x%x",
es->i_id );
return;
}
}
else if( es->fmt.i_cat == AUDIO_ES )
{
var_Get( p_input, "audio", &val );
if( !var_GetBool( p_input, "audio" ) ||
( p_input->p_sout && !var_GetBool( p_input, "sout-audio" ) ) )
{
msg_Dbg( p_input, "audio is disabled, not selecting ES 0x%x",
es->i_id );
return;
}
}
es->i_preroll_end = -1;
es->p_dec = input_DecoderNew( p_input, &es->fmt, VLC_FALSE );
if( es->p_dec == NULL || es->p_pgrm != p_sys->p_pgrm )
return;
if( es->fmt.i_cat == VIDEO_ES )
psz_var = "video-es";
else if( es->fmt.i_cat == AUDIO_ES )
psz_var = "audio-es";
else if( es->fmt.i_cat == SPU_ES )
psz_var = "spu-es";
else
return;
/* Mark it as selected */
val.i_int = es->i_id;
var_Change( p_input, psz_var, VLC_VAR_SETVALUE, &val, NULL );
var_SetBool( p_sys->p_input, "intf-change", VLC_TRUE );
}
static void EsUnselect( es_out_t *out, es_out_id_t *es, vlc_bool_t b_update )
{
es_out_sys_t *p_sys = out->p_sys;
input_thread_t *p_input = p_sys->p_input;
vlc_value_t val;
char *psz_var;
if( es->p_dec == NULL )
{
msg_Warn( p_input, "ES 0x%x is already unselected", es->i_id );
return;
}
input_DecoderDelete( es->p_dec );
es->p_dec = NULL;
if( !b_update )
return;
/* Update var */
if( es->p_dec == NULL )
return;
if( es->fmt.i_cat == VIDEO_ES )
psz_var = "video-es";
else if( es->fmt.i_cat == AUDIO_ES )
psz_var = "audio-es";
else if( es->fmt.i_cat == SPU_ES )
psz_var = "spu-es";
else
return;
/* Mark it as selected */
val.i_int = -1;
var_Change( p_input, psz_var, VLC_VAR_SETVALUE, &val, NULL );
var_SetBool( p_sys->p_input, "intf-change", VLC_TRUE );
}
/**
* Select an ES given the current mode
* XXX: you need to take a the lock before (stream.stream_lock)
*
* \param out The es_out structure
* \param es es_out_id structure
* \param b_force ...
* \return nothing
*/
static void EsOutSelect( es_out_t *out, es_out_id_t *es, vlc_bool_t b_force )
{
es_out_sys_t *p_sys = out->p_sys;
int i_cat = es->fmt.i_cat;
if( !p_sys->b_active ||
( !b_force && es->fmt.i_priority < 0 ) )
{
return;
}
if( p_sys->i_mode == ES_OUT_MODE_ALL || b_force )
{
if( !es->p_dec )
EsSelect( out, es );
}
else if( p_sys->i_mode == ES_OUT_MODE_PARTIAL )
{
vlc_value_t val;
int i;
var_Get( p_sys->p_input, "programs", &val );
for ( i = 0; i < val.p_list->i_count; i++ )
{
if ( val.p_list->p_values[i].i_int == es->p_pgrm->i_id || b_force )
{
if( !es->p_dec )
EsSelect( out, es );
break;
}
}
var_Change( p_sys->p_input, "programs", VLC_VAR_FREELIST, &val, NULL );
}
else if( p_sys->i_mode == ES_OUT_MODE_AUTO )
{
int i_wanted = -1;
if( es->p_pgrm != p_sys->p_pgrm )
return;
if( i_cat == AUDIO_ES )
{
int idx1 = LanguageArrayIndex( p_sys->ppsz_audio_language,
es->psz_language_code );
if( p_sys->p_es_audio &&
p_sys->p_es_audio->fmt.i_priority >= es->fmt.i_priority )
{
int idx2 = LanguageArrayIndex( p_sys->ppsz_audio_language,
p_sys->p_es_audio->psz_language_code );
if( idx1 < 0 || ( idx2 >= 0 && idx2 <= idx1 ) )
return;
i_wanted = es->i_channel;
}
else
{
/* Select audio if (no audio selected yet)
* - no audio-language
* - no audio code for the ES
* - audio code in the requested list */
if( idx1 >= 0 ||
!strcmp( es->psz_language_code, "??" ) ||
!p_sys->ppsz_audio_language )
i_wanted = es->i_channel;
}
if( p_sys->i_audio_last >= 0 )
i_wanted = p_sys->i_audio_last;
}
else if( i_cat == SPU_ES )
{
int idx1 = LanguageArrayIndex( p_sys->ppsz_sub_language,
es->psz_language_code );
if( p_sys->p_es_sub &&
p_sys->p_es_sub->fmt.i_priority >= es->fmt.i_priority )
{
int idx2 = LanguageArrayIndex( p_sys->ppsz_sub_language,
p_sys->p_es_sub->psz_language_code );
msg_Dbg( p_sys->p_input, "idx1=%d(%s) idx2=%d(%s)",
idx1, es->psz_language_code, idx2,
p_sys->p_es_sub->psz_language_code );
if( idx1 < 0 || ( idx2 >= 0 && idx2 <= idx1 ) )
return;
/* We found a SPU that matches our language request */
i_wanted = es->i_channel;
}
else if( idx1 >= 0 )
{
msg_Dbg( p_sys->p_input, "idx1=%d(%s)",
idx1, es->psz_language_code );
i_wanted = es->i_channel;
}
if( p_sys->i_sub_last >= 0 )
i_wanted = p_sys->i_sub_last;
}
else if( i_cat == VIDEO_ES )
{
i_wanted = es->i_channel;
}
if( i_wanted == es->i_channel && es->p_dec == NULL )
EsSelect( out, es );
}
/* FIXME TODO handle priority here */
if( es->p_dec )
{
if( i_cat == AUDIO_ES )
{
if( p_sys->i_mode == ES_OUT_MODE_AUTO &&
p_sys->p_es_audio &&
p_sys->p_es_audio != es &&
p_sys->p_es_audio->p_dec )
{
EsUnselect( out, p_sys->p_es_audio, VLC_FALSE );
}
p_sys->p_es_audio = es;
}
else if( i_cat == SPU_ES )
{
if( p_sys->i_mode == ES_OUT_MODE_AUTO &&
p_sys->p_es_sub &&
p_sys->p_es_sub != es &&
p_sys->p_es_sub->p_dec )
{
EsUnselect( out, p_sys->p_es_sub, VLC_FALSE );
}
p_sys->p_es_sub = es;
}
else if( i_cat == VIDEO_ES )
{
p_sys->p_es_video = es;
}
}
}
/**
* Send a block for the given es_out
*
* \param out the es_out to send from
* \param es the es_out_id
* \param p_block the data block to send
*/
static int EsOutSend( es_out_t *out, es_out_id_t *es, block_t *p_block )
{
es_out_sys_t *p_sys = out->p_sys;
input_thread_t *p_input = p_sys->p_input;
es_out_pgrm_t *p_pgrm = es->p_pgrm;
int64_t i_delay;
if( es->fmt.i_cat == AUDIO_ES )
i_delay = p_sys->i_audio_delay;
else if( es->fmt.i_cat == SPU_ES )
i_delay = p_sys->i_spu_delay;
else
i_delay = 0;
/* +11 -> avoid null value with non null dts/pts */
if( p_block->i_dts > 0 )
{
p_block->i_dts =
input_ClockGetTS( p_input, &p_pgrm->clock,
( p_block->i_dts + 11 ) * 9 / 100 ) + i_delay;
}
if( p_block->i_pts > 0 )
{
p_block->i_pts =
input_ClockGetTS( p_input, &p_pgrm->clock,
( p_block->i_pts + 11 ) * 9 / 100 ) + i_delay;
}
if ( es->fmt.i_codec == VLC_FOURCC( 't', 'e', 'l', 'x' ) )
{
mtime_t current_date = mdate();
if( !p_block->i_pts
|| p_block->i_pts > current_date + 10000000
|| current_date > p_block->i_pts )
{
/* ETSI EN 300 472 Annex A : do not take into account the PTS
* for teletext streams. */
p_block->i_pts = current_date + 400000
+ p_input->i_pts_delay + i_delay;
}
}
p_block->i_rate = p_input->i_rate;
/* Mark preroll blocks */
if( es->i_preroll_end >= 0 )
{
int64_t i_date = p_block->i_pts;
if( i_date <= 0 )
i_date = p_block->i_dts;
if( i_date < es->i_preroll_end )
p_block->i_flags |= BLOCK_FLAG_PREROLL;
else
es->i_preroll_end = -1;
}
/* TODO handle mute */
if( es->p_dec && ( es->fmt.i_cat != AUDIO_ES ||
p_input->i_rate == INPUT_RATE_DEFAULT ) )
{
input_DecoderDecode( es->p_dec, p_block );
}
else
{
block_Release( p_block );
}
return VLC_SUCCESS;
}
/*****************************************************************************
* EsOutDel:
*****************************************************************************/
static void EsOutDel( es_out_t *out, es_out_id_t *es )
{
es_out_sys_t *p_sys = out->p_sys;
/* We don't try to reselect */
if( es->p_dec )
EsUnselect( out, es, es->p_pgrm == p_sys->p_pgrm );
if( es->p_pgrm == p_sys->p_pgrm )
EsOutESVarUpdate( out, es, VLC_TRUE );
TAB_REMOVE( p_sys->i_es, p_sys->es, es );
es->p_pgrm->i_es--;
if( es->p_pgrm->i_es == 0 )
{
msg_Warn( p_sys->p_input, "Program doesn't contain anymore ES, "
"TODO cleaning ?" );
}
if( p_sys->p_es_audio == es ) p_sys->p_es_audio = NULL;
if( p_sys->p_es_video == es ) p_sys->p_es_video = NULL;
if( p_sys->p_es_sub == es ) p_sys->p_es_sub = NULL;
switch( es->fmt.i_cat )
{
case AUDIO_ES:
p_sys->i_audio--;
break;
case SPU_ES:
p_sys->i_sub--;
break;
case VIDEO_ES:
p_sys->i_video--;
break;
}
if( es->psz_language )
free( es->psz_language );
if( es->psz_language_code )
free( es->psz_language_code );
es_format_Clean( &es->fmt );
free( es );
}
/**
* Control query handler
*
* \param out the es_out to control
* \param i_query A es_out query as defined in include/ninput.h
* \param args a variable list of arguments for the query
* \return VLC_SUCCESS or an error code
*/
static int EsOutControl( es_out_t *out, int i_query, va_list args )
{
es_out_sys_t *p_sys = out->p_sys;
vlc_bool_t b, *pb;
int i, *pi;
es_out_id_t *es;
switch( i_query )
{
case ES_OUT_SET_ES_STATE:
es = (es_out_id_t*) va_arg( args, es_out_id_t * );
b = (vlc_bool_t) va_arg( args, vlc_bool_t );
if( b && es->p_dec == NULL )
{
EsSelect( out, es );
return es->p_dec ? VLC_SUCCESS : VLC_EGENERIC;
}
else if( !b && es->p_dec )
{
EsUnselect( out, es, es->p_pgrm == p_sys->p_pgrm );
return VLC_SUCCESS;
}
return VLC_SUCCESS;
case ES_OUT_GET_ES_STATE:
es = (es_out_id_t*) va_arg( args, es_out_id_t * );
pb = (vlc_bool_t*) va_arg( args, vlc_bool_t * );
*pb = es->p_dec ? VLC_TRUE : VLC_FALSE;
return VLC_SUCCESS;
case ES_OUT_SET_ACTIVE:
{
b = (vlc_bool_t) va_arg( args, vlc_bool_t );
p_sys->b_active = b;
/* Needed ? */
if( b )
var_SetBool( p_sys->p_input, "intf-change", VLC_TRUE );
return VLC_SUCCESS;
}
case ES_OUT_GET_ACTIVE:
pb = (vlc_bool_t*) va_arg( args, vlc_bool_t * );
*pb = p_sys->b_active;
return VLC_SUCCESS;
case ES_OUT_SET_MODE:
i = (int) va_arg( args, int );
if( i == ES_OUT_MODE_NONE || i == ES_OUT_MODE_ALL ||
i == ES_OUT_MODE_AUTO || i == ES_OUT_MODE_PARTIAL )
{
p_sys->i_mode = i;
/* Reapply policy mode */
for( i = 0; i < p_sys->i_es; i++ )
{
if( p_sys->es[i]->p_dec )
{
EsUnselect( out, p_sys->es[i],
p_sys->es[i]->p_pgrm == p_sys->p_pgrm );
}
}
for( i = 0; i < p_sys->i_es; i++ )
{
EsOutSelect( out, p_sys->es[i], VLC_FALSE );
}
return VLC_SUCCESS;
}
return VLC_EGENERIC;
case ES_OUT_GET_MODE:
pi = (int*) va_arg( args, int* );
*pi = p_sys->i_mode;
return VLC_SUCCESS;
case ES_OUT_SET_ES:
es = (es_out_id_t*) va_arg( args, es_out_id_t * );
/* Special case NULL, NULL+i_cat */
if( es == NULL )
{
for( i = 0; i < p_sys->i_es; i++ )
{
if( p_sys->es[i]->p_dec )
EsUnselect( out, p_sys->es[i],
p_sys->es[i]->p_pgrm == p_sys->p_pgrm );
}
}
else if( es == (es_out_id_t*)((uint8_t*)NULL+AUDIO_ES) )
{
for( i = 0; i < p_sys->i_es; i++ )
{
if( p_sys->es[i]->p_dec &&
p_sys->es[i]->fmt.i_cat == AUDIO_ES )
EsUnselect( out, p_sys->es[i],
p_sys->es[i]->p_pgrm == p_sys->p_pgrm );
}
}
else if( es == (es_out_id_t*)((uint8_t*)NULL+VIDEO_ES) )
{
for( i = 0; i < p_sys->i_es; i++ )
{
if( p_sys->es[i]->p_dec &&
p_sys->es[i]->fmt.i_cat == VIDEO_ES )
EsUnselect( out, p_sys->es[i],
p_sys->es[i]->p_pgrm == p_sys->p_pgrm );
}
}
else if( es == (es_out_id_t*)((uint8_t*)NULL+SPU_ES) )
{
for( i = 0; i < p_sys->i_es; i++ )
{
if( p_sys->es[i]->p_dec &&
p_sys->es[i]->fmt.i_cat == SPU_ES )
EsUnselect( out, p_sys->es[i],
p_sys->es[i]->p_pgrm == p_sys->p_pgrm );
}
}
else
{
for( i = 0; i < p_sys->i_es; i++ )
{
if( es == p_sys->es[i] )
{
EsOutSelect( out, es, VLC_TRUE );
break;
}
}
}
return VLC_SUCCESS;
case ES_OUT_SET_PCR:
case ES_OUT_SET_GROUP_PCR:
{
es_out_pgrm_t *p_pgrm = NULL;
int i_group = 0;
int64_t i_pcr;
if( i_query == ES_OUT_SET_PCR )
{
p_pgrm = p_sys->p_pgrm;
}
else
{
int i;
i_group = (int)va_arg( args, int );
for( i = 0; i < p_sys->i_pgrm; i++ )
{
if( p_sys->pgrm[i]->i_id == i_group )
{
p_pgrm = p_sys->pgrm[i];
break;
}
}
}
if( p_pgrm == NULL )
p_pgrm = EsOutProgramAdd( out, i_group ); /* Create it */
i_pcr = (int64_t)va_arg( args, int64_t );
/* search program */
/* 11 is a vodoo trick to avoid non_pcr*9/100 to be null */
input_ClockSetPCR( p_sys->p_input, &p_pgrm->clock,
(i_pcr + 11 ) * 9 / 100);
return VLC_SUCCESS;
}
case ES_OUT_RESET_PCR:
for( i = 0; i < p_sys->i_pgrm; i++ )
{
p_sys->pgrm[i]->clock.i_synchro_state = SYNCHRO_REINIT;
p_sys->pgrm[i]->clock.last_pts = 0;
}
return VLC_SUCCESS;
case ES_OUT_GET_TS:
if( p_sys->p_pgrm )
{
int64_t i_ts = (int64_t)va_arg( args, int64_t );
int64_t *pi_ts = (int64_t *)va_arg( args, int64_t * );
*pi_ts = input_ClockGetTS( p_sys->p_input,
&p_sys->p_pgrm->clock,
( i_ts + 11 ) * 9 / 100 );
return VLC_SUCCESS;
}
return VLC_EGENERIC;
case ES_OUT_GET_GROUP:
pi = (int*) va_arg( args, int* );
if( p_sys->p_pgrm )
*pi = p_sys->p_pgrm->i_id;
else
*pi = -1; /* FIXME */
return VLC_SUCCESS;
case ES_OUT_SET_GROUP:
{
int j;
i = (int) va_arg( args, int );
for( j = 0; j < p_sys->i_pgrm; j++ )
{
es_out_pgrm_t *p_pgrm = p_sys->pgrm[j];
if( p_pgrm->i_id == i )
{
EsOutProgramSelect( out, p_pgrm );
return VLC_SUCCESS;
}
}
return VLC_EGENERIC;
}
case ES_OUT_SET_FMT:
{
/* This ain't pretty but is need by some demuxers (eg. Ogg )
* to update the p_extra data */
es_format_t *p_fmt;
es = (es_out_id_t*) va_arg( args, es_out_id_t * );
p_fmt = (es_format_t*) va_arg( args, es_format_t * );
if( es == NULL ) return VLC_EGENERIC;
if( p_fmt->i_extra )
{
es->fmt.i_extra = p_fmt->i_extra;
es->fmt.p_extra = realloc( es->fmt.p_extra, p_fmt->i_extra );
memcpy( es->fmt.p_extra, p_fmt->p_extra, p_fmt->i_extra );
if( !es->p_dec ) return VLC_SUCCESS;
es->p_dec->fmt_in.i_extra = p_fmt->i_extra;
es->p_dec->fmt_in.p_extra =
realloc( es->p_dec->fmt_in.p_extra, p_fmt->i_extra );
memcpy( es->p_dec->fmt_in.p_extra,
p_fmt->p_extra, p_fmt->i_extra );
}
return VLC_SUCCESS;
}
case ES_OUT_SET_NEXT_DISPLAY_TIME:
{
int64_t i_date;
es = (es_out_id_t*) va_arg( args, es_out_id_t * );
i_date = (int64_t)va_arg( args, int64_t );
if( !es || !es->p_dec )
return VLC_EGENERIC;
es->i_preroll_end = i_date;
input_DecoderPreroll( es->p_dec, i_date );
return VLC_SUCCESS;
}
default:
msg_Err( p_sys->p_input, "unknown query in es_out_Control" );
return VLC_EGENERIC;
}
}
/****************************************************************************
* LanguageGetName: try to expend iso639 into plain name
****************************************************************************/
static char *LanguageGetName( const char *psz_code )
{
const iso639_lang_t *pl;
if( psz_code == NULL )
{
return strdup( "" );
}
if( strlen( psz_code ) == 2 )
{
pl = GetLang_1( psz_code );
}
else if( strlen( psz_code ) == 3 )
{
pl = GetLang_2B( psz_code );
if( !strcmp( pl->psz_iso639_1, "??" ) )
{
pl = GetLang_2T( psz_code );
}
}
else
{
return strdup( psz_code );
}
if( !strcmp( pl->psz_iso639_1, "??" ) )
{
return strdup( psz_code );
}
else
{
if( *pl->psz_native_name )
{
return strdup( pl->psz_native_name );
}
return strdup( pl->psz_eng_name );
}
}
/* Get a 2 char code */
static char *LanguageGetCode( const char *psz_lang )
{
const iso639_lang_t *pl;
if( psz_lang == NULL || *psz_lang == '\0' )
return strdup("??");
for( pl = p_languages; pl->psz_iso639_1 != NULL; pl++ )
{
if( !strcasecmp( pl->psz_eng_name, psz_lang ) ||
!strcasecmp( pl->psz_native_name, psz_lang ) ||
!strcasecmp( pl->psz_iso639_1, psz_lang ) ||
!strcasecmp( pl->psz_iso639_2T, psz_lang ) ||
!strcasecmp( pl->psz_iso639_2B, psz_lang ) )
break;
}
if( pl->psz_iso639_1 != NULL )
return strdup( pl->psz_iso639_1 );
return strdup("??");
}
static char **LanguageSplit( const char *psz_langs )
{
char *psz_dup;
char *psz_parser;
char **ppsz = NULL;
int i_psz = 0;
if( psz_langs == NULL )
return NULL;
psz_parser = psz_dup = strdup(psz_langs);
while( psz_parser && *psz_parser )
{
char *psz;
char *psz_code;
psz = strchr(psz_parser, ',' );
if( psz )
{
*psz++ = '\0';
}
psz_code = LanguageGetCode( psz_parser );
if( strcmp( psz_code, "??" ) )
{
TAB_APPEND( i_psz, ppsz, psz_code );
}
psz_parser = psz;
}
if( i_psz )
{
TAB_APPEND( i_psz, ppsz, NULL );
}
return ppsz;
}
static int LanguageArrayIndex( char **ppsz_langs, char *psz_lang )
{
int i;
if( !ppsz_langs || !psz_lang )
return -1;
for( i = 0; ppsz_langs[i]; i++ )
if( !strcasecmp( ppsz_langs[i], psz_lang ) )
return i;
return -1;
}
/****************************************************************************
* EsOutAddInfo:
* - add meta info to the playlist item
****************************************************************************/
static void EsOutAddInfo( es_out_t *out, es_out_id_t *es )
{
es_out_sys_t *p_sys = out->p_sys;
input_thread_t *p_input = p_sys->p_input;
es_format_t *fmt = &es->fmt;
char *psz_cat;
/* Add stream info */
asprintf( &psz_cat, _("Stream %d"), out->p_sys->i_id - 1 );
input_Control( p_input, INPUT_ADD_INFO, psz_cat, _("Codec"),
"%.4s", (char*)&fmt->i_codec );
input_Control( p_input, INPUT_ADD_INFO, psz_cat, _("Language"),
"%s", es->psz_language );
/* Add information */
switch( fmt->i_cat )
{
case AUDIO_ES:
input_Control( p_input, INPUT_ADD_INFO, psz_cat,
_("Type"), _("Audio") );
if( fmt->audio.i_channels > 0 )
input_Control( p_input, INPUT_ADD_INFO, psz_cat, _("Channels"),
"%d", fmt->audio.i_channels );
if( fmt->audio.i_rate > 0 )
input_Control( p_input, INPUT_ADD_INFO, psz_cat, _("Sample rate"),
_("%d Hz"), fmt->audio.i_rate );
if( fmt->audio.i_bitspersample > 0 )
input_Control( p_input, INPUT_ADD_INFO, psz_cat,
_("Bits per sample"), "%d",
fmt->audio.i_bitspersample );
if( fmt->i_bitrate > 0 )
input_Control( p_input, INPUT_ADD_INFO, psz_cat, _("Bitrate"),
_("%d kb/s"), fmt->i_bitrate / 1000 );
break;
case VIDEO_ES:
input_Control( p_input, INPUT_ADD_INFO, psz_cat,
_("Type"), _("Video") );
if( fmt->video.i_width > 0 && fmt->video.i_height > 0 )
input_Control( p_input, INPUT_ADD_INFO, psz_cat,
_("Resolution"), "%dx%d",
fmt->video.i_width, fmt->video.i_height );
if( fmt->video.i_visible_width > 0 &&
fmt->video.i_visible_height > 0 )
input_Control( p_input, INPUT_ADD_INFO, psz_cat,
_("Display resolution"), "%dx%d",
fmt->video.i_visible_width,
fmt->video.i_visible_height);
break;
case SPU_ES:
input_Control( p_input, INPUT_ADD_INFO, psz_cat,
_("Type"), _("Subtitle") );
break;
default:
break;
}
free( psz_cat );
}
/*****************************************************************************
* es_out.c: Es Out handler for input.
*****************************************************************************
* Copyright (C) 2003-2004 VideoLAN
* $Id$
*
* Authors: Laurent Aimar <fenrir@via.ecp.fr>
*
* 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#include <stdlib.h>
#include <vlc/vlc.h>
#include <vlc/input.h>
#include <vlc/decoder.h>
#include "input_internal.h"
#include "vlc_playlist.h"
#include "iso_lang.h"
/* FIXME we should find a better way than including that */
#include "../misc/iso-639_def.h"
/*****************************************************************************
* Local prototypes
*****************************************************************************/
typedef struct
{
/* Program ID */
int i_id;
/* Number of es for this pgrm */
int i_es;
vlc_bool_t b_selected;
/* Clock for this program */
input_clock_t clock;
} es_out_pgrm_t;
struct es_out_id_t
{
/* ES ID */
int i_id;
es_out_pgrm_t *p_pgrm;
/* */
int64_t i_preroll_end;
/* Channel in the track type */
int i_channel;
es_format_t fmt;
char *psz_language;
char *psz_language_code;
decoder_t *p_dec;
};
struct es_out_sys_t
{
input_thread_t *p_input;
/* all programs */
int i_pgrm;
es_out_pgrm_t **pgrm;
es_out_pgrm_t **pp_selected_pgrm; /* --programs */
es_out_pgrm_t *p_pgrm; /* Master program */
/* all es */
int i_id;
int i_es;
es_out_id_t **es;
/* mode gestion */
vlc_bool_t b_active;
int i_mode;
/* es count */
int i_audio;
int i_video;
int i_sub;
/* es to select */
int i_audio_last;
int i_sub_last;
char **ppsz_audio_language;
char **ppsz_sub_language;
/* current main es */
es_out_id_t *p_es_audio;
es_out_id_t *p_es_video;
es_out_id_t *p_es_sub;
/* delay */
int64_t i_audio_delay;
int64_t i_spu_delay;
};
static es_out_id_t *EsOutAdd ( es_out_t *, es_format_t * );
static int EsOutSend ( es_out_t *, es_out_id_t *, block_t * );
static void EsOutDel ( es_out_t *, es_out_id_t * );
static void EsOutSelect( es_out_t *out, es_out_id_t *es, vlc_bool_t b_force );
static int EsOutControl( es_out_t *, int i_query, va_list );
static void EsOutAddInfo( es_out_t *, es_out_id_t *es );
static void EsSelect( es_out_t *out, es_out_id_t *es );
static void EsUnselect( es_out_t *out, es_out_id_t *es, vlc_bool_t b_update );
static char *LanguageGetName( const char *psz_code );
static char *LanguageGetCode( const char *psz_lang );
static char **LanguageSplit( const char *psz_langs );
static int LanguageArrayIndex( char **ppsz_langs, char *psz_lang );
/*****************************************************************************
* input_EsOutNew:
*****************************************************************************/
es_out_t *input_EsOutNew( input_thread_t *p_input )
{
es_out_t *out = malloc( sizeof( es_out_t ) );
es_out_sys_t *p_sys = malloc( sizeof( es_out_sys_t ) );
vlc_value_t val;
int i;
out->pf_add = EsOutAdd;
out->pf_send = EsOutSend;
out->pf_del = EsOutDel;
out->pf_control = EsOutControl;
out->p_sys = p_sys;
p_sys->p_input = p_input;
p_sys->b_active = VLC_FALSE;
p_sys->i_mode = ES_OUT_MODE_AUTO;
p_sys->i_pgrm = 0;
p_sys->pgrm = NULL;
p_sys->p_pgrm = NULL;
p_sys->i_id = 0;
p_sys->i_es = 0;
p_sys->es = NULL;
p_sys->i_audio = 0;
p_sys->i_video = 0;
p_sys->i_sub = 0;
/* */
var_Get( p_input, "audio-track", &val );
p_sys->i_audio_last = val.i_int;
var_Get( p_input, "sub-track", &val );
p_sys->i_sub_last = val.i_int;
var_Get( p_input, "audio-language", &val );
p_sys->ppsz_audio_language = LanguageSplit(val.psz_string);
if( p_sys->ppsz_audio_language )
{
for( i = 0; p_sys->ppsz_audio_language[i]; i++ )
msg_Dbg( p_input, "Select audio in language[%d] %s",
i, p_sys->ppsz_audio_language[i] );
}
var_Get( p_input, "sub-language", &val );
p_sys->ppsz_sub_language = LanguageSplit(val.psz_string);
if( p_sys->ppsz_sub_language )
{
for( i = 0; p_sys->ppsz_sub_language[i]; i++ )
msg_Dbg( p_input, "Select subtitle in language[%d] %s",
i, p_sys->ppsz_sub_language[i] );
}
/* */
p_sys->p_es_audio = NULL;
p_sys->p_es_video = NULL;
p_sys->p_es_sub = NULL;
p_sys->i_audio_delay= 0;
p_sys->i_spu_delay = 0;
return out;
}
/*****************************************************************************
* input_EsOutDelete:
*****************************************************************************/
void input_EsOutDelete( es_out_t *out )
{
es_out_sys_t *p_sys = out->p_sys;
int i;
for( i = 0; i < p_sys->i_es; i++ )
{
if( p_sys->es[i]->p_dec )
{
input_DecoderDelete( p_sys->es[i]->p_dec );
}
if( p_sys->es[i]->psz_language )
free( p_sys->es[i]->psz_language );
if( p_sys->es[i]->psz_language_code )
free( p_sys->es[i]->psz_language_code );
es_format_Clean( &p_sys->es[i]->fmt );
free( p_sys->es[i] );
}
if( p_sys->ppsz_audio_language )
{
for( i = 0; p_sys->ppsz_audio_language[i]; i++ )
free( p_sys->ppsz_audio_language[i] );
free( p_sys->ppsz_audio_language );
}
if( p_sys->ppsz_sub_language )
{
for( i = 0; p_sys->ppsz_sub_language[i]; i++ )
free( p_sys->ppsz_sub_language[i] );
free( p_sys->ppsz_sub_language );
}
if( p_sys->es )
free( p_sys->es );
for( i = 0; i < p_sys->i_pgrm; i++ )
{
free( p_sys->pgrm[i] );
}
if( p_sys->pgrm )
free( p_sys->pgrm );
free( p_sys );
free( out );
}
es_out_id_t *input_EsOutGetFromID( es_out_t *out, int i_id )
{
int i;
if( i_id < 0 )
{
/* Special HACK, -i_id is tha cat of the stream */
return (es_out_id_t*)((uint8_t*)NULL-i_id);
}
for( i = 0; i < out->p_sys->i_es; i++ )
{
if( out->p_sys->es[i]->i_id == i_id )
return out->p_sys->es[i];
}
return NULL;
}
void input_EsOutDiscontinuity( es_out_t *out, vlc_bool_t b_audio )
{
es_out_sys_t *p_sys = out->p_sys;
int i;
for( i = 0; i < p_sys->i_es; i++ )
{
es_out_id_t *es = p_sys->es[i];
/* Send a dummy block to let decoder know that
* there is a discontinuity */
if( es->p_dec && ( !b_audio || es->fmt.i_cat == AUDIO_ES ) )
{
input_DecoderDiscontinuity( es->p_dec );
}
}
}
void input_EsOutSetDelay( es_out_t *out, int i_cat, int64_t i_delay )
{
es_out_sys_t *p_sys = out->p_sys;
if( i_cat == AUDIO_ES )
p_sys->i_audio_delay = i_delay;
else if( i_cat == SPU_ES )
p_sys->i_spu_delay = i_delay;
}
vlc_bool_t input_EsOutDecodersEmpty( es_out_t *out )
{
es_out_sys_t *p_sys = out->p_sys;
int i;
for( i = 0; i < p_sys->i_es; i++ )
{
es_out_id_t *es = p_sys->es[i];
if( es->p_dec && !input_DecoderEmpty( es->p_dec ) )
return VLC_FALSE;
}
return VLC_TRUE;
}
/*****************************************************************************
*
*****************************************************************************/
static void EsOutESVarUpdate( es_out_t *out, es_out_id_t *es,
vlc_bool_t b_delete )
{
es_out_sys_t *p_sys = out->p_sys;
input_thread_t *p_input = p_sys->p_input;
vlc_value_t val, text;
char *psz_var;
if( es->fmt.i_cat == AUDIO_ES )
psz_var = "audio-es";
else if( es->fmt.i_cat == VIDEO_ES )
psz_var = "video-es";
else if( es->fmt.i_cat == SPU_ES )
psz_var = "spu-es";
else
return;
if( b_delete )
{
val.i_int = es->i_id;
var_Change( p_input, psz_var, VLC_VAR_DELCHOICE, &val, NULL );
var_SetBool( p_sys->p_input, "intf-change", VLC_TRUE );
return;
}
/* Get the number of ES already added */
var_Change( p_input, psz_var, VLC_VAR_CHOICESCOUNT, &val, NULL );
if( val.i_int == 0 )
{
vlc_value_t val2;
/* First one, we need to add the "Disable" choice */
val2.i_int = -1; text.psz_string = _("Disable");
var_Change( p_input, psz_var, VLC_VAR_ADDCHOICE, &val2, &text );
val.i_int++;
}
/* Take care of the ES description */
if( es->fmt.psz_description && *es->fmt.psz_description )
{
if( es->psz_language && *es->psz_language )
{
text.psz_string = malloc( strlen( es->fmt.psz_description) + strlen( es->psz_language ) + 10 );
sprintf( text.psz_string, "%s - [%s]", es->fmt.psz_description, es->psz_language );
}
else text.psz_string = strdup( es->fmt.psz_description );
}
else
{
if( es->psz_language && *es->psz_language )
{
char *temp;
text.psz_string = malloc( strlen( _("Track %i") )+ strlen( es->psz_language ) + 30 );
asprintf( &temp, _("Track %i"), val.i_int );
sprintf( text.psz_string, "%s - [%s]", temp, es->psz_language );
free( temp );
}
else
{
text.psz_string = malloc( strlen( _("Track %i") ) + 20 );
sprintf( text.psz_string, _("Track %i"), val.i_int );
}
}
val.i_int = es->i_id;
var_Change( p_input, psz_var, VLC_VAR_ADDCHOICE, &val, &text );
free( text.psz_string );
var_SetBool( p_sys->p_input, "intf-change", VLC_TRUE );
}
/* EsOutProgramSelect:
* Select a program and update the object variable
*/
static void EsOutProgramSelect( es_out_t *out, es_out_pgrm_t *p_pgrm )
{
es_out_sys_t *p_sys = out->p_sys;
input_thread_t *p_input = p_sys->p_input;
vlc_value_t val;
int i;
if( p_sys->p_pgrm == p_pgrm )
return; /* Nothing to do */
if( p_sys->p_pgrm )
{
es_out_pgrm_t *old = p_sys->p_pgrm;
msg_Dbg( p_input, "Unselecting program id=%d", old->i_id );
for( i = 0; i < p_sys->i_es; i++ )
{
if( p_sys->es[i]->p_pgrm == old && p_sys->es[i]->p_dec &&
p_sys->i_mode != ES_OUT_MODE_ALL )
EsUnselect( out, p_sys->es[i], VLC_TRUE );
}
p_sys->p_es_audio = NULL;
p_sys->p_es_sub = NULL;
p_sys->p_es_video = NULL;
}
msg_Dbg( p_input, "Selecting program id=%d", p_pgrm->i_id );
/* Mark it selected */
p_pgrm->b_selected = VLC_TRUE;
/* Switch master stream */
if( p_sys->p_pgrm && p_sys->p_pgrm->clock.b_master )
{
p_sys->p_pgrm->clock.b_master = VLC_FALSE;
}
p_pgrm->clock.b_master = VLC_TRUE;
p_sys->p_pgrm = p_pgrm;
/* Update "program" */
val.i_int = p_pgrm->i_id;
var_Change( p_input, "program", VLC_VAR_SETVALUE, &val, NULL );
/* Update "es-*" */
var_Change( p_input, "audio-es", VLC_VAR_CLEARCHOICES, NULL, NULL );
var_Change( p_input, "video-es", VLC_VAR_CLEARCHOICES, NULL, NULL );
var_Change( p_input, "spu-es", VLC_VAR_CLEARCHOICES, NULL, NULL );
for( i = 0; i < p_sys->i_es; i++ )
{
if( p_sys->es[i]->p_pgrm == p_sys->p_pgrm )
EsOutESVarUpdate( out, p_sys->es[i], VLC_FALSE );
EsOutSelect( out, p_sys->es[i], VLC_FALSE );
}
var_SetBool( p_sys->p_input, "intf-change", VLC_TRUE );
}
/* EsOutAddProgram:
* Add a program
*/
static es_out_pgrm_t *EsOutProgramAdd( es_out_t *out, int i_group )
{
es_out_sys_t *p_sys = out->p_sys;
input_thread_t *p_input = p_sys->p_input;
vlc_value_t val;
es_out_pgrm_t *p_pgrm = malloc( sizeof( es_out_pgrm_t ) );
/* Init */
p_pgrm->i_id = i_group;
p_pgrm->i_es = 0;
p_pgrm->b_selected = VLC_FALSE;
input_ClockInit( &p_pgrm->clock, VLC_FALSE, p_input->input.i_cr_average );
/* Append it */
TAB_APPEND( p_sys->i_pgrm, p_sys->pgrm, p_pgrm );
/* Update "program" variable */
val.i_int = i_group;
var_Change( p_input, "program", VLC_VAR_ADDCHOICE, &val, NULL );
if( i_group == var_GetInteger( p_input, "program" ) )
{
EsOutProgramSelect( out, p_pgrm );
}
else
{
var_SetBool( p_sys->p_input, "intf-change", VLC_TRUE );
}
return p_pgrm;
}
/* EsOutAdd:
* Add an es_out
*/
static es_out_id_t *EsOutAdd( es_out_t *out, es_format_t *fmt )
{
es_out_sys_t *p_sys = out->p_sys;
input_thread_t *p_input = p_sys->p_input;
es_out_id_t *es = malloc( sizeof( es_out_id_t ) );
es_out_pgrm_t *p_pgrm = NULL;
int i;
if( fmt->i_group < 0 )
{
msg_Err( p_input, "invalid group number" );
return NULL;
}
/* Search the program */
for( i = 0; i < p_sys->i_pgrm; i++ )
{
if( fmt->i_group == p_sys->pgrm[i]->i_id )
{
p_pgrm = p_sys->pgrm[i];
break;
}
}
if( p_pgrm == NULL )
{
/* Create a new one */
p_pgrm = EsOutProgramAdd( out, fmt->i_group );
}
/* Increase ref count for program */
p_pgrm->i_es++;
/* Set up ES */
if( fmt->i_id < 0 )
fmt->i_id = out->p_sys->i_id;
es->i_id = fmt->i_id;
es->p_pgrm = p_pgrm;
es_format_Copy( &es->fmt, fmt );
es->i_preroll_end = -1;
switch( fmt->i_cat )
{
case AUDIO_ES:
es->i_channel = p_sys->i_audio;
break;
case VIDEO_ES:
es->i_channel = p_sys->i_video;
break;
case SPU_ES:
es->i_channel = p_sys->i_sub;
break;
default:
es->i_channel = 0;
break;
}
es->psz_language = LanguageGetName( fmt->psz_language ); /* remember so we only need to do it once */
es->psz_language_code = LanguageGetCode( fmt->psz_language );
es->p_dec = NULL;
if( es->p_pgrm == p_sys->p_pgrm )
EsOutESVarUpdate( out, es, VLC_FALSE );
/* Select it if needed */
EsOutSelect( out, es, VLC_FALSE );
TAB_APPEND( out->p_sys->i_es, out->p_sys->es, es );
p_sys->i_id++; /* always incremented */
switch( fmt->i_cat )
{
case AUDIO_ES:
p_sys->i_audio++;
break;
case SPU_ES:
p_sys->i_sub++;
break;
case VIDEO_ES:
p_sys->i_video++;
break;
}
EsOutAddInfo( out, es );
return es;
}
static void EsSelect( es_out_t *out, es_out_id_t *es )
{
es_out_sys_t *p_sys = out->p_sys;
input_thread_t *p_input = p_sys->p_input;
vlc_value_t val;
char *psz_var;
if( es->p_dec )
{
msg_Warn( p_input, "ES 0x%x is already selected", es->i_id );
return;
}
if( es->fmt.i_cat == VIDEO_ES || es->fmt.i_cat == SPU_ES )
{
if( !var_GetBool( p_input, "video" ) ||
( p_input->p_sout && !var_GetBool( p_input, "sout-video" ) ) )
{
msg_Dbg( p_input, "video is disabled, not selecting ES 0x%x",
es->i_id );
return;
}
}
else if( es->fmt.i_cat == AUDIO_ES )
{
var_Get( p_input, "audio", &val );
if( !var_GetBool( p_input, "audio" ) ||
( p_input->p_sout && !var_GetBool( p_input, "sout-audio" ) ) )
{
msg_Dbg( p_input, "audio is disabled, not selecting ES 0x%x",
es->i_id );
return;
}
}
es->i_preroll_end = -1;
es->p_dec = input_DecoderNew( p_input, &es->fmt, VLC_FALSE );
if( es->p_dec == NULL || es->p_pgrm != p_sys->p_pgrm )
return;
if( es->fmt.i_cat == VIDEO_ES )
psz_var = "video-es";
else if( es->fmt.i_cat == AUDIO_ES )
psz_var = "audio-es";
else if( es->fmt.i_cat == SPU_ES )
psz_var = "spu-es";
else
return;
/* Mark it as selected */
val.i_int = es->i_id;
var_Change( p_input, psz_var, VLC_VAR_SETVALUE, &val, NULL );
var_SetBool( p_sys->p_input, "intf-change", VLC_TRUE );
}
static void EsUnselect( es_out_t *out, es_out_id_t *es, vlc_bool_t b_update )
{
es_out_sys_t *p_sys = out->p_sys;
input_thread_t *p_input = p_sys->p_input;
vlc_value_t val;
char *psz_var;
if( es->p_dec == NULL )
{
msg_Warn( p_input, "ES 0x%x is already unselected", es->i_id );
return;
}
input_DecoderDelete( es->p_dec );
es->p_dec = NULL;
if( !b_update )
return;
/* Update var */
if( es->p_dec == NULL )
return;
if( es->fmt.i_cat == VIDEO_ES )
psz_var = "video-es";
else if( es->fmt.i_cat == AUDIO_ES )
psz_var = "audio-es";
else if( es->fmt.i_cat == SPU_ES )
psz_var = "spu-es";
else
return;
/* Mark it as selected */
val.i_int = -1;
var_Change( p_input, psz_var, VLC_VAR_SETVALUE, &val, NULL );
var_SetBool( p_sys->p_input, "intf-change", VLC_TRUE );
}
/**
* Select an ES given the current mode
* XXX: you need to take a the lock before (stream.stream_lock)
*
* \param out The es_out structure
* \param es es_out_id structure
* \param b_force ...
* \return nothing
*/
static void EsOutSelect( es_out_t *out, es_out_id_t *es, vlc_bool_t b_force )
{
es_out_sys_t *p_sys = out->p_sys;
int i_cat = es->fmt.i_cat;
if( !p_sys->b_active ||
( !b_force && es->fmt.i_priority < 0 ) )
{
return;
}
if( p_sys->i_mode == ES_OUT_MODE_ALL || b_force )
{
if( !es->p_dec )
EsSelect( out, es );
}
else if( p_sys->i_mode == ES_OUT_MODE_PARTIAL )
{
vlc_value_t val;
int i;
var_Get( p_sys->p_input, "programs", &val );
for ( i = 0; i < val.p_list->i_count; i++ )
{
if ( val.p_list->p_values[i].i_int == es->p_pgrm->i_id || b_force )
{
if( !es->p_dec )
EsSelect( out, es );
break;
}
}
var_Change( p_sys->p_input, "programs", VLC_VAR_FREELIST, &val, NULL );
}
else if( p_sys->i_mode == ES_OUT_MODE_AUTO )
{
int i_wanted = -1;
if( es->p_pgrm != p_sys->p_pgrm )
return;
if( i_cat == AUDIO_ES )
{
int idx1 = LanguageArrayIndex( p_sys->ppsz_audio_language,
es->psz_language_code );
if( p_sys->p_es_audio &&
p_sys->p_es_audio->fmt.i_priority >= es->fmt.i_priority )
{
int idx2 = LanguageArrayIndex( p_sys->ppsz_audio_language,
p_sys->p_es_audio->psz_language_code );
if( idx1 < 0 || ( idx2 >= 0 && idx2 <= idx1 ) )
return;
i_wanted = es->i_channel;
}
else
{
/* Select audio if (no audio selected yet)
* - no audio-language
* - no audio code for the ES
* - audio code in the requested list */
if( idx1 >= 0 ||
!strcmp( es->psz_language_code, "??" ) ||
!p_sys->ppsz_audio_language )
i_wanted = es->i_channel;
}
if( p_sys->i_audio_last >= 0 )
i_wanted = p_sys->i_audio_last;
}
else if( i_cat == SPU_ES )
{
int idx1 = LanguageArrayIndex( p_sys->ppsz_sub_language,
es->psz_language_code );
if( p_sys->p_es_sub &&
p_sys->p_es_sub->fmt.i_priority >= es->fmt.i_priority )
{
int idx2 = LanguageArrayIndex( p_sys->ppsz_sub_language,
p_sys->p_es_sub->psz_language_code );
msg_Dbg( p_sys->p_input, "idx1=%d(%s) idx2=%d(%s)",
idx1, es->psz_language_code, idx2,
p_sys->p_es_sub->psz_language_code );
if( idx1 < 0 || ( idx2 >= 0 && idx2 <= idx1 ) )
return;
/* We found a SPU that matches our language request */
i_wanted = es->i_channel;
}
else if( idx1 >= 0 )
{
msg_Dbg( p_sys->p_input, "idx1=%d(%s)",
idx1, es->psz_language_code );
i_wanted = es->i_channel;
}
if( p_sys->i_sub_last >= 0 )
i_wanted = p_sys->i_sub_last;
}
else if( i_cat == VIDEO_ES )
{
i_wanted = es->i_channel;
}
if( i_wanted == es->i_channel && es->p_dec == NULL )
EsSelect( out, es );
}
/* FIXME TODO handle priority here */
if( es->p_dec )
{
if( i_cat == AUDIO_ES )
{
if( p_sys->i_mode == ES_OUT_MODE_AUTO &&
p_sys->p_es_audio &&
p_sys->p_es_audio != es &&
p_sys->p_es_audio->p_dec )
{
EsUnselect( out, p_sys->p_es_audio, VLC_FALSE );
}
p_sys->p_es_audio = es;
}
else if( i_cat == SPU_ES )
{
if( p_sys->i_mode == ES_OUT_MODE_AUTO &&
p_sys->p_es_sub &&
p_sys->p_es_sub != es &&
p_sys->p_es_sub->p_dec )
{
EsUnselect( out, p_sys->p_es_sub, VLC_FALSE );
}
p_sys->p_es_sub = es;
}
else if( i_cat == VIDEO_ES )
{
p_sys->p_es_video = es;
}
}
}
/**
* Send a block for the given es_out
*
* \param out the es_out to send from
* \param es the es_out_id
* \param p_block the data block to send
*/
static int EsOutSend( es_out_t *out, es_out_id_t *es, block_t *p_block )
{
es_out_sys_t *p_sys = out->p_sys;
input_thread_t *p_input = p_sys->p_input;
es_out_pgrm_t *p_pgrm = es->p_pgrm;
int64_t i_delay;
if( es->fmt.i_cat == AUDIO_ES )
i_delay = p_sys->i_audio_delay;
else if( es->fmt.i_cat == SPU_ES )
i_delay = p_sys->i_spu_delay;
else
i_delay = 0;
/* Mark preroll blocks */
if( es->i_preroll_end >= 0 )
{
int64_t i_date = p_block->i_pts;
if( i_date <= 0 )
i_date = p_block->i_dts;
if( i_date < es->i_preroll_end )
p_block->i_flags |= BLOCK_FLAG_PREROLL;
else
es->i_preroll_end = -1;
}
/* +11 -> avoid null value with non null dts/pts */
if( p_block->i_dts > 0 )
{
p_block->i_dts =
input_ClockGetTS( p_input, &p_pgrm->clock,
( p_block->i_dts + 11 ) * 9 / 100 ) + i_delay;
}
if( p_block->i_pts > 0 )
{
p_block->i_pts =
input_ClockGetTS( p_input, &p_pgrm->clock,
( p_block->i_pts + 11 ) * 9 / 100 ) + i_delay;
}
if ( es->fmt.i_codec == VLC_FOURCC( 't', 'e', 'l', 'x' ) )
{
mtime_t current_date = mdate();
if( !p_block->i_pts
|| p_block->i_pts > current_date + 10000000
|| current_date > p_block->i_pts )
{
/* ETSI EN 300 472 Annex A : do not take into account the PTS
* for teletext streams. */
p_block->i_pts = current_date + 400000
+ p_input->i_pts_delay + i_delay;
}
}
p_block->i_rate = p_input->i_rate;
/* TODO handle mute */
if( es->p_dec && ( es->fmt.i_cat != AUDIO_ES ||
p_input->i_rate == INPUT_RATE_DEFAULT ) )
{
input_DecoderDecode( es->p_dec, p_block );
}
else
{
block_Release( p_block );
}
return VLC_SUCCESS;
}
/*****************************************************************************
* EsOutDel:
*****************************************************************************/
static void EsOutDel( es_out_t *out, es_out_id_t *es )
{
es_out_sys_t *p_sys = out->p_sys;
/* We don't try to reselect */
if( es->p_dec )
EsUnselect( out, es, es->p_pgrm == p_sys->p_pgrm );
if( es->p_pgrm == p_sys->p_pgrm )
EsOutESVarUpdate( out, es, VLC_TRUE );
TAB_REMOVE( p_sys->i_es, p_sys->es, es );
es->p_pgrm->i_es--;
if( es->p_pgrm->i_es == 0 )
{
msg_Warn( p_sys->p_input, "Program doesn't contain anymore ES, "
"TODO cleaning ?" );
}
if( p_sys->p_es_audio == es ) p_sys->p_es_audio = NULL;
if( p_sys->p_es_video == es ) p_sys->p_es_video = NULL;
if( p_sys->p_es_sub == es ) p_sys->p_es_sub = NULL;
switch( es->fmt.i_cat )
{
case AUDIO_ES:
p_sys->i_audio--;
break;
case SPU_ES:
p_sys->i_sub--;
break;
case VIDEO_ES:
p_sys->i_video--;
break;
}
if( es->psz_language )
free( es->psz_language );
if( es->psz_language_code )
free( es->psz_language_code );
es_format_Clean( &es->fmt );
free( es );
}
/**
* Control query handler
*
* \param out the es_out to control
* \param i_query A es_out query as defined in include/ninput.h
* \param args a variable list of arguments for the query
* \return VLC_SUCCESS or an error code
*/
static int EsOutControl( es_out_t *out, int i_query, va_list args )
{
es_out_sys_t *p_sys = out->p_sys;
vlc_bool_t b, *pb;
int i, *pi;
es_out_id_t *es;
switch( i_query )
{
case ES_OUT_SET_ES_STATE:
es = (es_out_id_t*) va_arg( args, es_out_id_t * );
b = (vlc_bool_t) va_arg( args, vlc_bool_t );
if( b && es->p_dec == NULL )
{
EsSelect( out, es );
return es->p_dec ? VLC_SUCCESS : VLC_EGENERIC;
}
else if( !b && es->p_dec )
{
EsUnselect( out, es, es->p_pgrm == p_sys->p_pgrm );
return VLC_SUCCESS;
}
return VLC_SUCCESS;
case ES_OUT_GET_ES_STATE:
es = (es_out_id_t*) va_arg( args, es_out_id_t * );
pb = (vlc_bool_t*) va_arg( args, vlc_bool_t * );
*pb = es->p_dec ? VLC_TRUE : VLC_FALSE;
return VLC_SUCCESS;
case ES_OUT_SET_ACTIVE:
{
b = (vlc_bool_t) va_arg( args, vlc_bool_t );
p_sys->b_active = b;
/* Needed ? */
if( b )
var_SetBool( p_sys->p_input, "intf-change", VLC_TRUE );
return VLC_SUCCESS;
}
case ES_OUT_GET_ACTIVE:
pb = (vlc_bool_t*) va_arg( args, vlc_bool_t * );
*pb = p_sys->b_active;
return VLC_SUCCESS;
case ES_OUT_SET_MODE:
i = (int) va_arg( args, int );
if( i == ES_OUT_MODE_NONE || i == ES_OUT_MODE_ALL ||
i == ES_OUT_MODE_AUTO || i == ES_OUT_MODE_PARTIAL )
{
p_sys->i_mode = i;
/* Reapply policy mode */
for( i = 0; i < p_sys->i_es; i++ )
{
if( p_sys->es[i]->p_dec )
{
EsUnselect( out, p_sys->es[i],
p_sys->es[i]->p_pgrm == p_sys->p_pgrm );
}
}
for( i = 0; i < p_sys->i_es; i++ )
{
EsOutSelect( out, p_sys->es[i], VLC_FALSE );
}
return VLC_SUCCESS;
}
return VLC_EGENERIC;
case ES_OUT_GET_MODE:
pi = (int*) va_arg( args, int* );
*pi = p_sys->i_mode;
return VLC_SUCCESS;
case ES_OUT_SET_ES:
es = (es_out_id_t*) va_arg( args, es_out_id_t * );
/* Special case NULL, NULL+i_cat */
if( es == NULL )
{
for( i = 0; i < p_sys->i_es; i++ )
{
if( p_sys->es[i]->p_dec )
EsUnselect( out, p_sys->es[i],
p_sys->es[i]->p_pgrm == p_sys->p_pgrm );
}
}
else if( es == (es_out_id_t*)((uint8_t*)NULL+AUDIO_ES) )
{
for( i = 0; i < p_sys->i_es; i++ )
{
if( p_sys->es[i]->p_dec &&
p_sys->es[i]->fmt.i_cat == AUDIO_ES )
EsUnselect( out, p_sys->es[i],
p_sys->es[i]->p_pgrm == p_sys->p_pgrm );
}
}
else if( es == (es_out_id_t*)((uint8_t*)NULL+VIDEO_ES) )
{
for( i = 0; i < p_sys->i_es; i++ )
{
if( p_sys->es[i]->p_dec &&
p_sys->es[i]->fmt.i_cat == VIDEO_ES )
EsUnselect( out, p_sys->es[i],
p_sys->es[i]->p_pgrm == p_sys->p_pgrm );
}
}
else if( es == (es_out_id_t*)((uint8_t*)NULL+SPU_ES) )
{
for( i = 0; i < p_sys->i_es; i++ )
{
if( p_sys->es[i]->p_dec &&
p_sys->es[i]->fmt.i_cat == SPU_ES )
EsUnselect( out, p_sys->es[i],
p_sys->es[i]->p_pgrm == p_sys->p_pgrm );
}
}
else
{
for( i = 0; i < p_sys->i_es; i++ )
{
if( es == p_sys->es[i] )
{
EsOutSelect( out, es, VLC_TRUE );
break;
}
}
}
return VLC_SUCCESS;
case ES_OUT_SET_PCR:
case ES_OUT_SET_GROUP_PCR:
{
es_out_pgrm_t *p_pgrm = NULL;
int i_group = 0;
int64_t i_pcr;
if( i_query == ES_OUT_SET_PCR )
{
p_pgrm = p_sys->p_pgrm;
}
else
{
int i;
i_group = (int)va_arg( args, int );
for( i = 0; i < p_sys->i_pgrm; i++ )
{
if( p_sys->pgrm[i]->i_id == i_group )
{
p_pgrm = p_sys->pgrm[i];
break;
}
}
}
if( p_pgrm == NULL )
p_pgrm = EsOutProgramAdd( out, i_group ); /* Create it */
i_pcr = (int64_t)va_arg( args, int64_t );
/* search program */
/* 11 is a vodoo trick to avoid non_pcr*9/100 to be null */
input_ClockSetPCR( p_sys->p_input, &p_pgrm->clock,
(i_pcr + 11 ) * 9 / 100);
return VLC_SUCCESS;
}
case ES_OUT_RESET_PCR:
for( i = 0; i < p_sys->i_pgrm; i++ )
{
p_sys->pgrm[i]->clock.i_synchro_state = SYNCHRO_REINIT;
p_sys->pgrm[i]->clock.last_pts = 0;
}
return VLC_SUCCESS;
case ES_OUT_GET_TS:
if( p_sys->p_pgrm )
{
int64_t i_ts = (int64_t)va_arg( args, int64_t );
int64_t *pi_ts = (int64_t *)va_arg( args, int64_t * );
*pi_ts = input_ClockGetTS( p_sys->p_input,
&p_sys->p_pgrm->clock,
( i_ts + 11 ) * 9 / 100 );
return VLC_SUCCESS;
}
return VLC_EGENERIC;
case ES_OUT_GET_GROUP:
pi = (int*) va_arg( args, int* );
if( p_sys->p_pgrm )
*pi = p_sys->p_pgrm->i_id;
else
*pi = -1; /* FIXME */
return VLC_SUCCESS;
case ES_OUT_SET_GROUP:
{
int j;
i = (int) va_arg( args, int );
for( j = 0; j < p_sys->i_pgrm; j++ )
{
es_out_pgrm_t *p_pgrm = p_sys->pgrm[j];
if( p_pgrm->i_id == i )
{
EsOutProgramSelect( out, p_pgrm );
return VLC_SUCCESS;
}
}
return VLC_EGENERIC;
}
case ES_OUT_SET_FMT:
{
/* This ain't pretty but is need by some demuxers (eg. Ogg )
* to update the p_extra data */
es_format_t *p_fmt;
es = (es_out_id_t*) va_arg( args, es_out_id_t * );
p_fmt = (es_format_t*) va_arg( args, es_format_t * );
if( es == NULL ) return VLC_EGENERIC;
if( p_fmt->i_extra )
{
es->fmt.i_extra = p_fmt->i_extra;
es->fmt.p_extra = realloc( es->fmt.p_extra, p_fmt->i_extra );
memcpy( es->fmt.p_extra, p_fmt->p_extra, p_fmt->i_extra );
if( !es->p_dec ) return VLC_SUCCESS;
es->p_dec->fmt_in.i_extra = p_fmt->i_extra;
es->p_dec->fmt_in.p_extra =
realloc( es->p_dec->fmt_in.p_extra, p_fmt->i_extra );
memcpy( es->p_dec->fmt_in.p_extra,
p_fmt->p_extra, p_fmt->i_extra );
}
return VLC_SUCCESS;
}
case ES_OUT_SET_NEXT_DISPLAY_TIME:
{
int64_t i_date;
es = (es_out_id_t*) va_arg( args, es_out_id_t * );
i_date = (int64_t)va_arg( args, int64_t );
if( !es || !es->p_dec )
return VLC_EGENERIC;
es->i_preroll_end = i_date;
input_DecoderPreroll( es->p_dec, i_date );
return VLC_SUCCESS;
}
default:
msg_Err( p_sys->p_input, "unknown query in es_out_Control" );
return VLC_EGENERIC;
}
}
/****************************************************************************
* LanguageGetName: try to expend iso639 into plain name
****************************************************************************/
static char *LanguageGetName( const char *psz_code )
{
const iso639_lang_t *pl;
if( psz_code == NULL )
{
return strdup( "" );
}
if( strlen( psz_code ) == 2 )
{
pl = GetLang_1( psz_code );
}
else if( strlen( psz_code ) == 3 )
{
pl = GetLang_2B( psz_code );
if( !strcmp( pl->psz_iso639_1, "??" ) )
{
pl = GetLang_2T( psz_code );
}
}
else
{
return strdup( psz_code );
}
if( !strcmp( pl->psz_iso639_1, "??" ) )
{
return strdup( psz_code );
}
else
{
if( *pl->psz_native_name )
{
return strdup( pl->psz_native_name );
}
return strdup( pl->psz_eng_name );
}
}
/* Get a 2 char code */
static char *LanguageGetCode( const char *psz_lang )
{
const iso639_lang_t *pl;
if( psz_lang == NULL || *psz_lang == '\0' )
return strdup("??");
for( pl = p_languages; pl->psz_iso639_1 != NULL; pl++ )
{
if( !strcasecmp( pl->psz_eng_name, psz_lang ) ||
!strcasecmp( pl->psz_native_name, psz_lang ) ||
!strcasecmp( pl->psz_iso639_1, psz_lang ) ||
!strcasecmp( pl->psz_iso639_2T, psz_lang ) ||
!strcasecmp( pl->psz_iso639_2B, psz_lang ) )
break;
}
if( pl->psz_iso639_1 != NULL )
return strdup( pl->psz_iso639_1 );
return strdup("??");
}
static char **LanguageSplit( const char *psz_langs )
{
char *psz_dup;
char *psz_parser;
char **ppsz = NULL;
int i_psz = 0;
if( psz_langs == NULL )
return NULL;
psz_parser = psz_dup = strdup(psz_langs);
while( psz_parser && *psz_parser )
{
char *psz;
char *psz_code;
psz = strchr(psz_parser, ',' );
if( psz )
{
*psz++ = '\0';
}
psz_code = LanguageGetCode( psz_parser );
if( strcmp( psz_code, "??" ) )
{
TAB_APPEND( i_psz, ppsz, psz_code );
}
psz_parser = psz;
}
if( i_psz )
{
TAB_APPEND( i_psz, ppsz, NULL );
}
return ppsz;
}
static int LanguageArrayIndex( char **ppsz_langs, char *psz_lang )
{
int i;
if( !ppsz_langs || !psz_lang )
return -1;
for( i = 0; ppsz_langs[i]; i++ )
if( !strcasecmp( ppsz_langs[i], psz_lang ) )
return i;
return -1;
}
/****************************************************************************
* EsOutAddInfo:
* - add meta info to the playlist item
****************************************************************************/
static void EsOutAddInfo( es_out_t *out, es_out_id_t *es )
{
es_out_sys_t *p_sys = out->p_sys;
input_thread_t *p_input = p_sys->p_input;
es_format_t *fmt = &es->fmt;
char *psz_cat;
/* Add stream info */
asprintf( &psz_cat, _("Stream %d"), out->p_sys->i_id - 1 );
input_Control( p_input, INPUT_ADD_INFO, psz_cat, _("Codec"),
"%.4s", (char*)&fmt->i_codec );
input_Control( p_input, INPUT_ADD_INFO, psz_cat, _("Language"),
"%s", es->psz_language );
/* Add information */
switch( fmt->i_cat )
{
case AUDIO_ES:
input_Control( p_input, INPUT_ADD_INFO, psz_cat,
_("Type"), _("Audio") );
if( fmt->audio.i_channels > 0 )
input_Control( p_input, INPUT_ADD_INFO, psz_cat, _("Channels"),
"%d", fmt->audio.i_channels );
if( fmt->audio.i_rate > 0 )
input_Control( p_input, INPUT_ADD_INFO, psz_cat, _("Sample rate"),
_("%d Hz"), fmt->audio.i_rate );
if( fmt->audio.i_bitspersample > 0 )
input_Control( p_input, INPUT_ADD_INFO, psz_cat,
_("Bits per sample"), "%d",
fmt->audio.i_bitspersample );
if( fmt->i_bitrate > 0 )
input_Control( p_input, INPUT_ADD_INFO, psz_cat, _("Bitrate"),
_("%d kb/s"), fmt->i_bitrate / 1000 );
break;
case VIDEO_ES:
input_Control( p_input, INPUT_ADD_INFO, psz_cat,
_("Type"), _("Video") );
if( fmt->video.i_width > 0 && fmt->video.i_height > 0 )
input_Control( p_input, INPUT_ADD_INFO, psz_cat,
_("Resolution"), "%dx%d",
fmt->video.i_width, fmt->video.i_height );
if( fmt->video.i_visible_width > 0 &&
fmt->video.i_visible_height > 0 )
input_Control( p_input, INPUT_ADD_INFO, psz_cat,
_("Display resolution"), "%dx%d",
fmt->video.i_visible_width,
fmt->video.i_visible_height);
break;
case SPU_ES:
input_Control( p_input, INPUT_ADD_INFO, psz_cat,
_("Type"), _("Subtitle") );
break;
default:
break;
}
free( psz_cat );
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment