vlc/src/input/decoder.c

2760 lines
83 KiB
C

/*****************************************************************************
* decoder.c: Functions for the management of decoders
*****************************************************************************
* Copyright (C) 1999-2019 VLC authors, VideoLAN and Videolabs SAS
*
* Authors: Christophe Massiot <massiot@via.ecp.fr>
* Gildas Bazin <gbazin@videolan.org>
* 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 Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <assert.h>
#include <stdatomic.h>
#include <vlc_common.h>
#include <vlc_block.h>
#include <vlc_vout.h>
#include <vlc_aout.h>
#include <vlc_sout.h>
#include <vlc_codec.h>
#include <vlc_spu.h>
#include <vlc_meta.h>
#include <vlc_dialog.h>
#include <vlc_modules.h>
#include <vlc_decoder.h>
#include <vlc_picture_pool.h>
#include <vlc_tracer.h>
#include "audio_output/aout_internal.h"
#include "stream_output/stream_output.h"
#include "../clock/clock.h"
#include "input_internal.h"
#include "decoder.h"
#include "resource.h"
#include "libvlc.h"
#include "../video_output/vout_internal.h"
/**
* \file src/input/decoder.c
*
* The input decoder connects the input client pushing data to the
* decoder implementation (through the matching elementary stream)
* and the following output for audio, video and subtitles.
*
* It follows the locking rules below:
*
* - The fifo cannot be locked when calling function from the
* decoder module implementation.
*
* - However, the decoder module implementation might indirectly
* lock the fifo when calling the owner methods, in particular
* to send a frame or update the output status.
*
* - The input code can lock the fifo to modify the global state
* of the input decoder.
*
* Backpressure preventing starvation is done by the pacing of the
* decoder, the calls into the decoder implementation, and the
* limits of the fifo queue.
*
* Basically a very fast decoder will often wait since the fifo will be
* consumed really quickly and thus almost never stay under the lock.
* Likewise, when the decoder is slower and the fifo can grow, it also
* means that the decoder thread will wait more often on the
* `decoder_t::pf_decode` call, which is done without the fifo lock as
* per above rules.
*
* In addition with the standard input/output cycle from the decoder,
* the video decoders can create sub-decoders for the closed captions
* support embedded in the supplementary information from the codecs.
*
* To do so, they need to create a `decoder_cc_desc_t` matching with the
* format that needs to be described (number of channels, type of
* channels) and they then create them along with the closed-captions
* content with `decoder_QueueCc`.
*
* In the `input/decoder.c` code, the access to the sub-decoders in the
* cc.pp_decoders table is protected through the `cc.lock` mutex.
* Taking this lock ensures that the sub-decoder won't get
* asynchronously removed while using it, and any mutex from the
* sub-decoder can then be taken under this lock.
**/
/*
* Possibles values set in p_owner->reload atomic
*/
enum reload
{
RELOAD_NO_REQUEST,
RELOAD_DECODER, /* Reload the decoder module */
RELOAD_DECODER_AOUT /* Stop the aout and reload the decoder module */
};
struct vlc_input_decoder_t
{
decoder_t dec;
es_format_t dec_fmt_in;
input_resource_t*p_resource;
vlc_clock_t *p_clock;
const char *psz_id;
const struct vlc_input_decoder_callbacks *cbs;
void *cbs_userdata;
ssize_t i_spu_channel;
int64_t i_spu_order;
sout_stream_t *p_sout;
sout_packetizer_input_t *p_sout_input;
vlc_thread_t thread;
/* Some decoders require already packetized data (ie. not truncated) */
decoder_t *p_packetizer;
es_format_t pktz_fmt_in;
bool b_packetizer;
/* Current format in use by the output */
es_format_t fmt;
vlc_video_context *vctx;
/* */
bool b_fmt_description;
vlc_meta_t *p_description;
atomic_int reload;
/* fifo */
block_fifo_t *p_fifo;
/* Lock for communication with decoder thread */
vlc_cond_t wait_request;
vlc_cond_t wait_acknowledge;
vlc_cond_t wait_fifo; /* TODO: merge with wait_acknowledge */
/* pool to use when the decoder doesn't use its own */
struct picture_pool_t *out_pool;
/*
* 3 threads can read/write these output variables, the DecoderThread, the
* input thread, and the ModuleThread. The ModuleThread is either the
* DecoderThread for synchronous modules or any thread for asynchronous
* modules.
*
* Asynchronous modules are responsible for serializing/locking every
* output calls in any thread as long as the decoder_UpdateVideoFormat() or
* decoder_NewPicture() calls are not concurrent, cf.
* decoder_UpdateVideoFormat() and decoder_NewPicture() notes.
*
* The ModuleThread is the owner of these variables, it should hold
* the lock when writing them but doesn't have to hold it when using them.
*
* The DecoderThread should always hold the lock when reading/using
* aout/vouts.
*
* The input thread can read these variables in order to stop outputs, when
* both ModuleThread and DecoderThread are stopped (from DecoderDelete()).
*/
/* If p_aout is valid, then p_astream is valid too */
audio_output_t *p_aout;
vlc_aout_stream *p_astream;
vout_thread_t *p_vout;
bool vout_started;
enum vlc_vout_order vout_order;
/* -- Theses variables need locking on read *and* write -- */
/* Preroll */
vlc_tick_t i_preroll_end;
#define PREROLL_NONE VLC_TICK_MIN
#define PREROLL_FORCED VLC_TICK_MAX
/* Pause & Rate */
vlc_tick_t pause_date;
vlc_tick_t delay, output_delay;
float rate, output_rate;
unsigned frames_countdown;
bool paused, output_paused;
bool error;
/* Waiting */
bool b_waiting;
bool b_first;
bool b_has_data;
/* Flushing */
bool flushing;
bool b_draining;
bool b_idle;
bool aborting;
/* CC */
#define MAX_CC_DECODERS 64 /* The es_out only creates one type of es */
struct
{
vlc_mutex_t lock;
bool b_supported;
decoder_cc_desc_t desc;
vlc_input_decoder_t *pp_decoder[MAX_CC_DECODERS];
bool b_sout_created;
sout_packetizer_input_t *p_sout_input;
} cc;
/* Mouse event */
vlc_mutex_t mouse_lock;
vlc_mouse_event mouse_event;
void *mouse_opaque;
};
/* Pictures which are DECODER_BOGUS_VIDEO_DELAY or more in advance probably have
* a bogus PTS and won't be displayed */
#define DECODER_BOGUS_VIDEO_DELAY ((vlc_tick_t)(DEFAULT_PTS_DELAY * 30))
/* */
#define DECODER_SPU_VOUT_WAIT_DURATION VLC_TICK_FROM_MS(200)
#define BLOCK_FLAG_CORE_PRIVATE_RELOADED (1 << BLOCK_FLAG_CORE_PRIVATE_SHIFT)
#define decoder_Notify(decoder_priv, event, ...) \
if (decoder_priv->cbs && decoder_priv->cbs->event) \
decoder_priv->cbs->event(decoder_priv, __VA_ARGS__, \
decoder_priv->cbs_userdata);
static inline vlc_input_decoder_t *dec_get_owner( decoder_t *p_dec )
{
return container_of( p_dec, vlc_input_decoder_t, dec );
}
/**
* When the input decoder is being used only for packetizing (happen in stream output
* configuration.), there's no need to spawn a decoder thread. The input_decoder is then considered
* *synchronous*.
*
* @retval true When no decoder thread will be spawned.
* @retval false When a decoder thread will be spawned.
*/
static inline bool vlc_input_decoder_IsSynchronous( const vlc_input_decoder_t *dec )
{
return dec->p_sout != NULL;
}
static void Decoder_ChangeOutputPause( vlc_input_decoder_t *p_owner, bool paused, vlc_tick_t date )
{
vlc_fifo_Assert(p_owner->p_fifo);
decoder_t *p_dec = &p_owner->dec;
msg_Dbg( p_dec, "toggling %s", paused ? "resume" : "pause" );
switch( p_dec->fmt_in->i_cat )
{
case VIDEO_ES:
if( p_owner->p_vout != NULL && p_owner->vout_started )
vout_ChangePause( p_owner->p_vout, paused, date );
break;
case AUDIO_ES:
if( p_owner->p_astream != NULL )
vlc_aout_stream_ChangePause( p_owner->p_astream, paused, date );
break;
case SPU_ES:
break;
default:
vlc_assert_unreachable();
}
p_owner->output_paused = paused;
}
static void Decoder_ChangeOutputRate( vlc_input_decoder_t *p_owner, float rate )
{
vlc_fifo_Assert(p_owner->p_fifo);
decoder_t *p_dec = &p_owner->dec;
msg_Dbg( p_dec, "changing rate: %f", rate );
switch( p_dec->fmt_in->i_cat )
{
case VIDEO_ES:
if( p_owner->p_vout != NULL && p_owner->vout_started )
vout_ChangeRate( p_owner->p_vout, rate );
break;
case AUDIO_ES:
if( p_owner->p_astream != NULL )
vlc_aout_stream_ChangeRate( p_owner->p_astream, rate );
break;
case SPU_ES:
if( p_owner->p_vout != NULL )
{
assert(p_owner->i_spu_channel != VOUT_SPU_CHANNEL_INVALID);
vout_ChangeSpuRate(p_owner->p_vout, p_owner->i_spu_channel,
rate );
}
break;
default:
vlc_assert_unreachable();
}
p_owner->output_rate = rate;
}
static void Decoder_ChangeOutputDelay( vlc_input_decoder_t *p_owner, vlc_tick_t delay )
{
vlc_fifo_Assert(p_owner->p_fifo);
decoder_t *p_dec = &p_owner->dec;
msg_Dbg( p_dec, "changing delay: %"PRId64, delay );
switch( p_dec->fmt_in->i_cat )
{
case VIDEO_ES:
if( p_owner->p_vout != NULL && p_owner->vout_started )
vout_ChangeDelay( p_owner->p_vout, delay );
break;
case AUDIO_ES:
if( p_owner->p_astream != NULL )
vlc_aout_stream_ChangeDelay( p_owner->p_astream, delay );
break;
case SPU_ES:
if( p_owner->p_vout != NULL )
{
assert(p_owner->i_spu_channel != VOUT_SPU_CHANNEL_INVALID);
vout_ChangeSpuDelay(p_owner->p_vout, p_owner->i_spu_channel,
delay);
}
break;
default:
vlc_assert_unreachable();
}
p_owner->output_delay = delay;
}
static void Decoder_UpdateOutState(vlc_input_decoder_t *owner)
{
if (owner->paused)
Decoder_ChangeOutputPause(owner, owner->paused, owner->pause_date);
if (owner->rate != 1.f)
Decoder_ChangeOutputRate(owner, owner->rate);
if (owner->delay != 0)
Decoder_ChangeOutputDelay(owner, owner->delay);
}
/**
* Load a decoder module
*/
static int LoadDecoder( decoder_t *p_dec, bool b_packetizer, es_format_t *fmt_in,
const es_format_t *restrict p_fmt )
{
decoder_Init( p_dec, fmt_in, p_fmt );
p_dec->b_frame_drop_allowed = true;
/* Find a suitable decoder/packetizer module */
if( !b_packetizer )
{
static const char caps[ES_CATEGORY_COUNT][16] = {
[VIDEO_ES] = "video decoder",
[AUDIO_ES] = "audio decoder",
[SPU_ES] = "spu decoder",
};
p_dec->p_module = module_need_var( p_dec, caps[p_dec->fmt_in->i_cat],
"codec" );
}
else
p_dec->p_module = module_need_var( p_dec, "packetizer", "packetizer" );
if( !p_dec->p_module )
{
es_format_Clean(fmt_in);
decoder_Clean( p_dec );
return -1;
}
return 0;
}
static int DecoderThread_Reload( vlc_input_decoder_t *p_owner,
const es_format_t *restrict p_fmt,
enum reload reload )
{
/* Copy p_fmt since it can be destroyed by decoder_Clean */
decoder_t *p_dec = &p_owner->dec;
es_format_t fmt_in;
if( es_format_Copy( &fmt_in, p_fmt ) != VLC_SUCCESS )
{
p_owner->error = true;
return VLC_EGENERIC;
}
/* Restart the decoder module */
decoder_Clean( p_dec );
es_format_Clean( &p_owner->dec_fmt_in );
p_owner->error = false;
if( reload == RELOAD_DECODER_AOUT )
{
assert( p_owner->fmt.i_cat == AUDIO_ES );
audio_output_t *p_aout = p_owner->p_aout;
vlc_aout_stream *p_astream = p_owner->p_astream;
// no need to lock, the decoder and ModuleThread are dead
p_owner->p_aout = NULL;
p_owner->p_astream = NULL;
if( p_aout )
{
assert( p_astream );
vlc_aout_stream_Delete( p_astream );
input_resource_PutAout( p_owner->p_resource, p_aout );
}
}
if( LoadDecoder( p_dec, false, &p_owner->dec_fmt_in, &fmt_in ) )
{
p_owner->error = true;
es_format_Clean( &fmt_in );
return VLC_EGENERIC;
}
es_format_Clean( &fmt_in );
return VLC_SUCCESS;
}
static void DecoderUpdateFormatLocked( vlc_input_decoder_t *p_owner )
{
decoder_t *p_dec = &p_owner->dec;
vlc_fifo_Assert(p_owner->p_fifo);
es_format_Clean( &p_owner->fmt );
es_format_Copy( &p_owner->fmt, &p_dec->fmt_out );
assert( p_owner->fmt.i_cat == p_dec->fmt_in->i_cat );
/* Move p_description */
if( p_dec->p_description != NULL )
{
if( p_owner->p_description != NULL )
vlc_meta_Delete( p_owner->p_description );
p_owner->p_description = p_dec->p_description;
p_dec->p_description = NULL;
}
p_owner->b_fmt_description = true;
}
static void MouseEvent( const vlc_mouse_t *newmouse, void *user_data )
{
decoder_t *dec = user_data;
vlc_input_decoder_t *owner = dec_get_owner( dec );
vlc_mutex_lock( &owner->mouse_lock );
if( owner->mouse_event )
owner->mouse_event( newmouse, owner->mouse_opaque);
vlc_mutex_unlock( &owner->mouse_lock );
}
/*****************************************************************************
* Buffers allocation callbacks for the decoders
*****************************************************************************/
static bool aout_replaygain_changed( const audio_replay_gain_t *a,
const audio_replay_gain_t *b )
{
for( size_t i=0; i<AUDIO_REPLAY_GAIN_MAX; i++ )
{
if( a->pb_gain[i] != b->pb_gain[i] ||
a->pb_peak[i] != b->pb_peak[i] ||
(a->pb_gain[i] && a->pf_gain[i] != b->pf_gain[i]) ||
(a->pb_peak[i] && a->pf_peak[i] != b->pf_peak[i]) )
return true;
}
return false;
}
static int ModuleThread_UpdateAudioFormat( decoder_t *p_dec )
{
vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
if( p_owner->p_aout &&
( !AOUT_FMTS_IDENTICAL(&p_dec->fmt_out.audio, &p_owner->fmt.audio) ||
p_dec->fmt_out.i_codec != p_dec->fmt_out.audio.i_format ||
p_dec->fmt_out.i_profile != p_owner->fmt.i_profile ) )
{
audio_output_t *p_aout = p_owner->p_aout;
vlc_aout_stream *p_astream = p_owner->p_astream;
/* Parameters changed, restart the aout */
vlc_fifo_Lock(p_owner->p_fifo);
p_owner->p_astream = NULL;
p_owner->p_aout = NULL; // the DecoderThread should not use the old aout anymore
vlc_fifo_Unlock(p_owner->p_fifo);
vlc_aout_stream_Delete( p_astream );
input_resource_PutAout( p_owner->p_resource, p_aout );
}
/* Check if only replay gain has changed */
if( aout_replaygain_changed( &p_dec->fmt_in->audio_replay_gain,
&p_owner->fmt.audio_replay_gain ) )
{
p_dec->fmt_out.audio_replay_gain = p_dec->fmt_in->audio_replay_gain;
if( p_owner->p_aout )
{
p_owner->fmt.audio_replay_gain = p_dec->fmt_in->audio_replay_gain;
var_TriggerCallback( p_owner->p_aout, "audio-replay-gain-mode" );
}
}
if( p_owner->p_aout == NULL )
{
p_dec->fmt_out.audio.i_format = p_dec->fmt_out.i_codec;
audio_sample_format_t format = p_dec->fmt_out.audio;
aout_FormatPrepare( &format );
const int i_force_dolby = var_InheritInteger( p_dec, "force-dolby-surround" );
if( i_force_dolby &&
format.i_physical_channels == (AOUT_CHAN_LEFT|AOUT_CHAN_RIGHT) )
{
if( i_force_dolby == 1 )
format.i_chan_mode |= AOUT_CHANMODE_DOLBYSTEREO;
else /* i_force_dolby == 2 */
format.i_chan_mode &= ~AOUT_CHANMODE_DOLBYSTEREO;
}
audio_output_t *p_aout;
vlc_aout_stream *p_astream;
p_aout = input_resource_GetAout( p_owner->p_resource );
if( p_aout )
{
const struct vlc_aout_stream_cfg cfg = {
.fmt = &format,
.profile = p_dec->fmt_out.i_profile,
.clock = p_owner->p_clock,
.str_id = p_owner->psz_id,
.replay_gain = &p_dec->fmt_out.audio_replay_gain,
};
p_astream = vlc_aout_stream_New( p_aout, &cfg );
if( p_astream == NULL )
{
input_resource_PutAout( p_owner->p_resource, p_aout );
p_aout = NULL;
}
}
else
p_astream = NULL;
vlc_fifo_Lock(p_owner->p_fifo);
p_owner->p_aout = p_aout;
p_owner->p_astream = p_astream;
DecoderUpdateFormatLocked( p_owner );
aout_FormatPrepare( &p_owner->fmt.audio );
if( p_aout == NULL )
{
vlc_fifo_Unlock(p_owner->p_fifo);
return -1;
}
p_dec->fmt_out.audio.i_bytes_per_frame =
p_owner->fmt.audio.i_bytes_per_frame;
p_dec->fmt_out.audio.i_bitspersample =
p_owner->fmt.audio.i_bitspersample;
p_dec->fmt_out.audio.i_frame_length =
p_owner->fmt.audio.i_frame_length;
Decoder_UpdateOutState( p_owner );
vlc_fifo_Unlock( p_owner->p_fifo );
}
return 0;
}
static int CreateVoutIfNeeded(vlc_input_decoder_t *);
static int ModuleThread_UpdateVideoFormat( decoder_t *p_dec, vlc_video_context *vctx )
{
vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
int created_vout = CreateVoutIfNeeded(p_owner);
if (created_vout == -1)
return -1; // error
if (created_vout == 0)
{
// video context didn't change
if (vctx != NULL && p_owner->vctx == vctx)
return 0;
}
assert(p_owner->p_vout);
if (p_owner->vctx)
vlc_video_context_Release(p_owner->vctx);
p_owner->vctx = vctx ? vlc_video_context_Hold(vctx) : NULL;
// configure the new vout
vlc_fifo_Lock(p_owner->p_fifo);
if ( p_owner->out_pool == NULL )
{
unsigned dpb_size;
switch( p_dec->fmt_in->i_codec )
{
case VLC_CODEC_HEVC:
case VLC_CODEC_H264:
case VLC_CODEC_DIRAC: /* FIXME valid ? */
dpb_size = 18;
break;
case VLC_CODEC_AV1:
dpb_size = 8; /* NUM_REF_FRAMES from the AV1 spec */
break;
case VLC_CODEC_MP4V:
case VLC_CODEC_VP5:
case VLC_CODEC_VP6:
case VLC_CODEC_VP6F:
case VLC_CODEC_VP8:
dpb_size = 3;
break;
default:
dpb_size = 2;
break;
}
picture_pool_t *pool = picture_pool_NewFromFormat( &p_dec->fmt_out.video,
dpb_size + p_dec->i_extra_picture_buffers + 1 );
if( pool == NULL)
{
msg_Err(p_dec, "Failed to create a pool of %d %4.4s pictures",
dpb_size + p_dec->i_extra_picture_buffers + 1,
(char*)&p_dec->fmt_out.video.i_chroma);
vlc_fifo_Unlock(p_owner->p_fifo);
goto error;
}
p_owner->out_pool = pool;
}
vout_configuration_t cfg = {
.vout = p_owner->p_vout, .clock = p_owner->p_clock,
.str_id = p_owner->psz_id,
.fmt = &p_dec->fmt_out.video,
.mouse_event = MouseEvent, .mouse_opaque = p_dec,
};
vlc_fifo_Unlock(p_owner->p_fifo);
enum input_resource_vout_state vout_state;
vout_thread_t *p_vout =
input_resource_RequestVout(p_owner->p_resource, vctx, &cfg, NULL,
&vout_state);
if (p_vout != NULL)
{
assert(vout_state == INPUT_RESOURCE_VOUT_NOTCHANGED ||
vout_state == INPUT_RESOURCE_VOUT_STARTED);
vlc_fifo_Lock(p_owner->p_fifo);
p_owner->vout_started = true;
if (vout_state == INPUT_RESOURCE_VOUT_STARTED)
{
Decoder_UpdateOutState( p_owner );
vlc_fifo_Unlock(p_owner->p_fifo);
decoder_Notify(p_owner, on_vout_started, p_vout, p_owner->vout_order);
}
else
vlc_fifo_Unlock(p_owner->p_fifo);
return 0;
}
else
{
assert(vout_state == INPUT_RESOURCE_VOUT_NOTCHANGED ||
vout_state == INPUT_RESOURCE_VOUT_STOPPED);
if (vout_state == INPUT_RESOURCE_VOUT_STOPPED)
decoder_Notify(p_owner, on_vout_stopped, cfg.vout);
}
error:
/* Clean fmt and vctx to trigger a new vout creation on the next update
* call */
vlc_fifo_Lock(p_owner->p_fifo);
es_format_Clean( &p_owner->fmt );
vlc_fifo_Unlock(p_owner->p_fifo);
if (p_owner->vctx != NULL)
{
vlc_video_context_Release(p_owner->vctx);
p_owner->vctx = NULL;
}
return -1;
}
static int CreateVoutIfNeeded(vlc_input_decoder_t *p_owner)
{
decoder_t *p_dec = &p_owner->dec;
bool need_vout = false;
vlc_fifo_Lock(p_owner->p_fifo);
if( p_owner->p_vout == NULL )
{
msg_Dbg(p_dec, "vout: none found");
need_vout = true;
}
if( p_dec->fmt_out.video.i_width != p_owner->fmt.video.i_width
|| p_dec->fmt_out.video.i_height != p_owner->fmt.video.i_height )
{
msg_Dbg(p_dec, "vout change: decoder size");
need_vout = true;
}
if( p_dec->fmt_out.video.i_visible_width != p_owner->fmt.video.i_visible_width
|| p_dec->fmt_out.video.i_visible_height != p_owner->fmt.video.i_visible_height
|| p_dec->fmt_out.video.i_x_offset != p_owner->fmt.video.i_x_offset
|| p_dec->fmt_out.video.i_y_offset != p_owner->fmt.video.i_y_offset )
{
msg_Dbg(p_dec, "vout change: visible size");
need_vout = true;
}
if( p_dec->fmt_out.i_codec != p_owner->fmt.video.i_chroma )
{
msg_Dbg(p_dec, "vout change: chroma");
need_vout = true;
}
if( (int64_t)p_dec->fmt_out.video.i_sar_num * p_owner->fmt.video.i_sar_den !=
(int64_t)p_dec->fmt_out.video.i_sar_den * p_owner->fmt.video.i_sar_num )
{
msg_Dbg(p_dec, "vout change: SAR");
need_vout = true;
}
if( p_dec->fmt_out.video.orientation != p_owner->fmt.video.orientation )
{
msg_Dbg(p_dec, "vout change: orientation");
need_vout = true;
}
if( p_dec->fmt_out.video.multiview_mode != p_owner->fmt.video.multiview_mode )
{
msg_Dbg(p_dec, "vout change: multiview");
need_vout = true;
}
if( !need_vout )
{
vlc_fifo_Unlock(p_owner->p_fifo);
return 0; // vout unchanged
}
vout_thread_t *p_vout = p_owner->p_vout;
p_owner->p_vout = NULL; // the DecoderThread should not use the old vout anymore
p_owner->vout_started = false;
vlc_fifo_Unlock( p_owner->p_fifo );
enum vlc_vout_order order;
const vout_configuration_t cfg = { .vout = p_vout, .fmt = NULL };
p_vout = input_resource_RequestVout( p_owner->p_resource, NULL, &cfg, &order, NULL );
vlc_fifo_Lock( p_owner->p_fifo );
p_owner->p_vout = p_vout;
p_owner->vout_order = order;
DecoderUpdateFormatLocked( p_owner );
p_owner->fmt.video.i_chroma = p_dec->fmt_out.i_codec;
picture_pool_t *pool = p_owner->out_pool;
p_owner->out_pool = NULL;
vlc_fifo_Unlock( p_owner->p_fifo );
if ( pool != NULL )
picture_pool_Release( pool );
if( p_vout == NULL )
{
msg_Err( p_dec, "failed to create video output" );
return -1;
}
return 1; // new vout was created
}
static vlc_decoder_device * ModuleThread_GetDecoderDevice( decoder_t *p_dec )
{
vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
/* Requesting a decoder device will automatically enable hw decoding */
if( !var_InheritBool( p_dec, "hw-dec" ) )
return NULL;
int created_vout = CreateVoutIfNeeded(p_owner);
if (created_vout == -1)
return NULL; // error
assert(p_owner->p_vout);
vlc_decoder_device *dec_device = vout_GetDevice(p_owner->p_vout);
if (created_vout == 1)
return dec_device; // new vout was created with a decoder device
bool need_format_update = false;
if ( memcmp( &p_dec->fmt_out.video.mastering,
&p_owner->fmt.video.mastering,
sizeof(p_owner->fmt.video.mastering)) )
{
msg_Dbg(p_dec, "vout update: mastering data");
need_format_update = true;
}
if ( p_dec->fmt_out.video.lighting.MaxCLL !=
p_owner->fmt.video.lighting.MaxCLL ||
p_dec->fmt_out.video.lighting.MaxFALL !=
p_owner->fmt.video.lighting.MaxFALL )
{
msg_Dbg(p_dec, "vout update: lighting data");
need_format_update = true;
}
if ( need_format_update )
{
/* the format has changed but we don't need a new vout */
vlc_fifo_Lock(p_owner->p_fifo);
DecoderUpdateFormatLocked( p_owner );
vlc_fifo_Unlock(p_owner->p_fifo);
}
return dec_device;
}
static picture_t *ModuleThread_NewVideoBuffer( decoder_t *p_dec )
{
vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
assert( p_owner->p_vout );
assert( p_owner->out_pool );
picture_t *pic = picture_pool_Wait( p_owner->out_pool );
if (pic)
picture_Reset( pic );
return pic;
}
static subpicture_t *ModuleThread_NewSpuBuffer( decoder_t *p_dec,
const subpicture_updater_t *p_updater )
{
vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
vout_thread_t *p_vout = NULL;
subpicture_t *p_subpic;
int i_attempts = 30;
while( i_attempts-- )
{
if( p_owner->error )
break;
p_vout = input_resource_HoldVout( p_owner->p_resource );
if( p_vout )
break;
vlc_tick_sleep( DECODER_SPU_VOUT_WAIT_DURATION );
}
if( !p_vout )
{
msg_Warn( p_dec, "no vout found, dropping subpicture" );
if( p_owner->p_vout )
{
assert(p_owner->i_spu_channel != VOUT_SPU_CHANNEL_INVALID);
decoder_Notify(p_owner, on_vout_stopped, p_owner->p_vout);
vlc_fifo_Lock( p_owner->p_fifo );
vout_UnregisterSubpictureChannel(p_owner->p_vout,
p_owner->i_spu_channel);
p_owner->i_spu_channel = VOUT_SPU_CHANNEL_INVALID;
vout_Release(p_owner->p_vout);
p_owner->p_vout = NULL; // the DecoderThread should not use the old vout anymore
vlc_fifo_Lock( p_owner->p_fifo );
}
return NULL;
}
if( p_owner->p_vout != p_vout )
{
if (p_owner->p_vout) /* notify the previous vout deletion unlocked */
decoder_Notify(p_owner, on_vout_stopped, p_owner->p_vout);
vlc_fifo_Lock(p_owner->p_fifo);
if (p_owner->p_vout)
{
/* Unregister the SPU channel of the previous vout */
assert(p_owner->i_spu_channel != VOUT_SPU_CHANNEL_INVALID);
vout_UnregisterSubpictureChannel(p_owner->p_vout,
p_owner->i_spu_channel);
vout_Release(p_owner->p_vout);
p_owner->p_vout = NULL; // the DecoderThread should not use the old vout anymore
}
enum vlc_vout_order channel_order;
p_owner->i_spu_channel =
vout_RegisterSubpictureChannelInternal(p_vout, p_owner->p_clock,
&channel_order);
p_owner->i_spu_order = 0;
if (p_owner->i_spu_channel == VOUT_SPU_CHANNEL_INVALID)
{
/* The new vout doesn't support SPU, aborting... */
vlc_fifo_Unlock(p_owner->p_fifo);
vout_Release(p_vout);
return NULL;
}
p_owner->p_vout = p_vout;
p_owner->vout_order = channel_order;
vlc_fifo_Unlock(p_owner->p_fifo);
assert(channel_order != VLC_VOUT_ORDER_NONE);
decoder_Notify(p_owner, on_vout_started, p_vout, channel_order);
}
else
vout_Release(p_vout);
p_subpic = subpicture_New( p_updater );
if( p_subpic )
{
p_subpic->i_channel = p_owner->i_spu_channel;
p_subpic->i_order = p_owner->i_spu_order++;
p_subpic->b_subtitle = true;
}
return p_subpic;
}
static int InputThread_GetInputAttachments( decoder_t *p_dec,
input_attachment_t ***ppp_attachment,
int *pi_attachment )
{
vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
if (!p_owner->cbs || !p_owner->cbs->get_attachments)
return VLC_EINVAL;
int ret = p_owner->cbs->get_attachments(p_owner, ppp_attachment,
p_owner->cbs_userdata);
if (ret < 0)
return VLC_EGENERIC;
*pi_attachment = ret;
return VLC_SUCCESS;
}
static vlc_tick_t ModuleThread_GetDisplayDate( decoder_t *p_dec,
vlc_tick_t system_now, vlc_tick_t i_ts )
{
vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
vlc_fifo_Lock(p_owner->p_fifo);
if( p_owner->b_waiting || p_owner->paused )
i_ts = VLC_TICK_INVALID;
float rate = p_owner->output_rate;
vlc_fifo_Unlock(p_owner->p_fifo);
if( !p_owner->p_clock || i_ts == VLC_TICK_INVALID )
return i_ts;
return vlc_clock_ConvertToSystem( p_owner->p_clock, system_now, i_ts, rate );
}
static float ModuleThread_GetDisplayRate( decoder_t *p_dec )
{
vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
if( !p_owner->p_clock )
return 1.f;
vlc_fifo_Lock(p_owner->p_fifo);
float rate = p_owner->output_rate;
vlc_fifo_Unlock(p_owner->p_fifo);
return rate;
}
/*****************************************************************************
* Public functions
*****************************************************************************/
vlc_frame_t *decoder_NewAudioBuffer( decoder_t *dec, int samples )
{
assert( dec->fmt_out.audio.i_frame_length > 0
&& dec->fmt_out.audio.i_bytes_per_frame > 0 );
size_t length = samples * dec->fmt_out.audio.i_bytes_per_frame
/ dec->fmt_out.audio.i_frame_length;
vlc_frame_t *frame = block_Alloc( length );
if( likely(frame != NULL) )
{
frame->i_nb_samples = samples;
frame->i_pts = frame->i_length = 0;
}
return frame;
}
static void RequestReload( vlc_input_decoder_t *p_owner )
{
/* Don't override reload if it's RELOAD_DECODER_AOUT */
int expected = RELOAD_NO_REQUEST;
atomic_compare_exchange_strong( &p_owner->reload, &expected, RELOAD_DECODER );
}
static void DecoderWaitUnblock( vlc_input_decoder_t *p_owner )
{
vlc_fifo_Assert(p_owner->p_fifo);
if( p_owner->b_waiting )
{
p_owner->b_has_data = true;
vlc_cond_signal( &p_owner->wait_acknowledge );
}
while( p_owner->b_waiting && p_owner->b_has_data )
vlc_fifo_WaitCond(p_owner->p_fifo, &p_owner->wait_request);
}
static inline void DecoderUpdatePreroll( vlc_tick_t *pi_preroll, const vlc_frame_t *p )
{
if( p->i_flags & BLOCK_FLAG_PREROLL )
*pi_preroll = PREROLL_FORCED;
/* Check if we can use the packet for end of preroll */
else if( (p->i_flags & BLOCK_FLAG_DISCONTINUITY) &&
(p->i_buffer == 0 || (p->i_flags & BLOCK_FLAG_CORRUPTED)) )
*pi_preroll = PREROLL_FORCED;
else if( p->i_dts != VLC_TICK_INVALID )
*pi_preroll = __MIN( *pi_preroll, p->i_dts );
else if( p->i_pts != VLC_TICK_INVALID )
*pi_preroll = __MIN( *pi_preroll, p->i_pts );
}
#ifdef ENABLE_SOUT
static void DecoderSendSubstream(vlc_input_decoder_t *p_owner)
{
decoder_t *p_dec = &p_owner->dec;
if (p_dec->pf_get_cc == NULL)
return;
bool b_wants_substreams;
int ret = sout_StreamControl(p_owner->p_sout,
SOUT_STREAM_WANTS_SUBSTREAMS,
&b_wants_substreams);
if (ret != VLC_SUCCESS || !b_wants_substreams)
return;
if (p_owner->cc.p_sout_input == NULL && p_owner->cc.b_sout_created)
return;
decoder_cc_desc_t desc;
vlc_frame_t *p_cc = p_dec->pf_get_cc( p_dec, &desc );
if (p_cc == NULL)
return;
if (!p_owner->cc.b_sout_created)
{
es_format_t ccfmt;
es_format_Init(&ccfmt, SPU_ES, VLC_CODEC_CEA608);
ccfmt.i_group = p_owner->fmt.i_group;
ccfmt.subs.cc.i_reorder_depth = desc.i_reorder_depth;
p_owner->cc.p_sout_input = sout_InputNew( p_owner->p_sout, &ccfmt );
es_format_Clean(&ccfmt);
p_owner->cc.b_sout_created = true;
}
if (!p_owner->cc.p_sout_input ||
sout_InputSendBuffer(p_owner->p_sout, p_owner->cc.p_sout_input, p_cc))
{
block_Release(p_cc);
}
}
/* This function process a frame for sout
*/
static void DecoderThread_ProcessSout( vlc_input_decoder_t *p_owner, vlc_frame_t *frame )
{
decoder_t *p_dec = &p_owner->dec;
vlc_frame_t *sout_frame;
vlc_frame_t **ppframe = frame ? &frame : NULL;
while( ( sout_frame =
p_dec->pf_packetize( p_dec, ppframe ) ) )
{
if( p_owner->p_sout_input == NULL )
{
vlc_fifo_Lock(p_owner->p_fifo);
DecoderUpdateFormatLocked( p_owner );
p_owner->fmt.i_group = p_dec->fmt_in->i_group;
p_owner->fmt.i_id = p_dec->fmt_in->i_id;
if( p_dec->fmt_in->psz_language )
{
free( p_owner->fmt.psz_language );
p_owner->fmt.psz_language =
strdup( p_dec->fmt_in->psz_language );
}
vlc_fifo_Unlock(p_owner->p_fifo);
p_owner->p_sout_input =
sout_InputNew( p_owner->p_sout, &p_owner->fmt );
if( p_owner->p_sout_input == NULL )
{
msg_Err( p_dec, "cannot create packetized sout output (%4.4s)",
(char *)&p_owner->fmt.i_codec );
p_owner->error = true;
if(frame)
block_Release(frame);
block_ChainRelease(sout_frame);
break;
}
}
while( sout_frame )
{
vlc_frame_t *p_next = sout_frame->p_next;
sout_frame->p_next = NULL;
DecoderSendSubstream( p_owner );
/* FIXME --VLC_TICK_INVALID inspect stream_output*/
if ( sout_InputSendBuffer( p_owner->p_sout, p_owner->p_sout_input, sout_frame ) ==
VLC_EGENERIC )
{
msg_Err( p_dec, "cannot continue streaming due to errors with codec %4.4s",
(char *)&p_owner->fmt.i_codec );
p_owner->error = true;
/* Cleanup */
if( frame )
block_Release( frame );
block_ChainRelease( p_next );
return;
}
sout_frame = p_next;
}
}
}
#endif
static void DecoderPlayCc( vlc_input_decoder_t *p_owner, vlc_frame_t *p_cc,
const decoder_cc_desc_t *p_desc )
{
vlc_fifo_Lock(p_owner->p_fifo);
if (p_owner->flushing || p_owner->aborting)
{
vlc_fifo_Unlock(p_owner->p_fifo);
vlc_frame_Release(p_cc);
return;
}
vlc_mutex_lock(&p_owner->cc.lock);
p_owner->cc.desc = *p_desc;
/* Fanout data to all decoders. We do not know if es_out
selected 608 or 708. */
uint64_t i_bitmap = p_owner->cc.desc.i_608_channels |
p_owner->cc.desc.i_708_channels;
for( int i=0; i_bitmap > 0; i_bitmap >>= 1, i++ )
{
vlc_input_decoder_t *p_ccowner = p_owner->cc.pp_decoder[i];
if( !p_ccowner )
continue;
if( i_bitmap > 1 )
{
block_FifoPut( p_ccowner->p_fifo, block_Duplicate(p_cc) );
}
else
{
block_FifoPut( p_ccowner->p_fifo, p_cc );
p_cc = NULL; /* was last dec */
}
}
vlc_mutex_unlock(&p_owner->cc.lock);
vlc_fifo_Unlock(p_owner->p_fifo);
if( p_cc ) /* can have bitmap set but no created decs */
block_Release( p_cc );
}
static void PacketizerGetCc( vlc_input_decoder_t *p_owner, decoder_t *p_dec_cc )
{
vlc_frame_t *p_cc;
decoder_cc_desc_t desc;
/* Do not try retrieving CC if not wanted (sout) or cannot be retrieved */
if( !p_owner->cc.b_supported )
return;
assert( p_dec_cc->pf_get_cc != NULL );
p_cc = p_dec_cc->pf_get_cc( p_dec_cc, &desc );
if( !p_cc )
return;
DecoderPlayCc( p_owner, p_cc, &desc );
}
static void ModuleThread_QueueCc( decoder_t *p_videodec, vlc_frame_t *p_cc,
const decoder_cc_desc_t *p_desc )
{
vlc_input_decoder_t *p_owner = dec_get_owner( p_videodec );
if( unlikely( p_cc != NULL ) )
{
if( p_owner->cc.b_supported &&
( !p_owner->p_packetizer || !p_owner->p_packetizer->pf_get_cc ) )
DecoderPlayCc( p_owner, p_cc, p_desc );
else
block_Release( p_cc );
}
}
static int ModuleThread_PlayVideo( vlc_input_decoder_t *p_owner, picture_t *p_picture )
{
decoder_t *p_dec = &p_owner->dec;
if( p_picture->date == VLC_TICK_INVALID )
/* FIXME: VLC_TICK_INVALID -- verify video_output */
{
msg_Warn( p_dec, "non-dated video buffer received" );
picture_Release( p_picture );
return VLC_EGENERIC;
}
if (p_owner->flushing || p_owner->aborting)
{
picture_Release(p_picture);
return VLC_SUCCESS;
}
vout_thread_t *p_vout = p_owner->p_vout;
assert( p_owner->vout_started );
bool prerolled = p_owner->i_preroll_end != PREROLL_NONE;
if( prerolled && p_owner->i_preroll_end > p_picture->date )
{
picture_Release( p_picture );
return VLC_SUCCESS;
}
p_owner->i_preroll_end = PREROLL_NONE;
if( unlikely(prerolled) )
{
msg_Dbg( p_dec, "end of video preroll" );
if( p_vout )
vout_FlushAll( p_vout );
}
if( p_owner->b_first && p_owner->b_waiting )
{
msg_Dbg( p_dec, "Received first picture" );
p_owner->b_first = false;
p_picture->b_force = true;
}
else
{
DecoderWaitUnblock( p_owner );
}
if( unlikely(p_owner->paused) && likely(p_owner->frames_countdown > 0) )
p_owner->frames_countdown--;
/* */
if( p_vout == NULL )
{
picture_Release( p_picture );
return VLC_EGENERIC;
}
if( p_picture->b_still )
{
/* Ensure no earlier higher pts breaks still state */
vout_Flush( p_vout, p_picture->date );
}
vout_PutPicture( p_vout, p_picture );
return VLC_SUCCESS;
}
static void ModuleThread_QueueVideo( decoder_t *p_dec, picture_t *p_pic )
{
assert( p_pic );
vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
struct vlc_tracer *tracer = vlc_object_get_tracer( &p_dec->obj );
if ( tracer != NULL )
{
vlc_tracer_TraceStreamPTS( tracer, "DEC", p_owner->psz_id,
"OUT", p_pic->date );
}
vlc_fifo_Lock( p_owner->p_fifo );
int success = ModuleThread_PlayVideo( p_owner, p_pic );
unsigned displayed = 0;
unsigned vout_lost = 0;
unsigned vout_late = 0;
if( p_owner->p_vout != NULL )
{
vout_GetResetStatistic( p_owner->p_vout, &displayed, &vout_lost, &vout_late );
}
if (success != VLC_SUCCESS)
vout_lost++;
vlc_fifo_Unlock(p_owner->p_fifo);
decoder_Notify(p_owner, on_new_video_stats, 1, vout_lost, displayed, vout_late);
}
static vlc_decoder_device * thumbnailer_get_device( decoder_t *p_dec )
{
VLC_UNUSED(p_dec);
// no hardware decoder on purpose
// we don't want to load many DLLs and allocate many pictures
// just to decode one picture
return NULL;
}
static picture_t *thumbnailer_buffer_new( decoder_t *p_dec )
{
vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
/* Avoid decoding more than one frame when a thumbnail was
* already generated */
vlc_fifo_Lock(p_owner->p_fifo);
if( !p_owner->b_first )
{
vlc_fifo_Unlock(p_owner->p_fifo);
return NULL;
}
vlc_fifo_Unlock(p_owner->p_fifo);
return picture_NewFromFormat( &p_dec->fmt_out.video );
}
static void ModuleThread_QueueThumbnail( decoder_t *p_dec, picture_t *p_pic )
{
vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
bool b_first;
vlc_fifo_Lock(p_owner->p_fifo);
b_first = p_owner->b_first;
p_owner->b_first = false;
vlc_fifo_Unlock(p_owner->p_fifo);
if( b_first )
decoder_Notify(p_owner, on_thumbnail_ready, p_pic);
picture_Release( p_pic );
}
static int ModuleThread_PlayAudio( vlc_input_decoder_t *p_owner, vlc_frame_t *p_audio )
{
decoder_t *p_dec = &p_owner->dec;
assert( p_audio != NULL );
if( p_audio->i_pts == VLC_TICK_INVALID ) // FIXME --VLC_TICK_INVALID verify audio_output/*
{
msg_Warn( p_dec, "non-dated audio buffer received" );
block_Release( p_audio );
return VLC_EGENERIC;
}
vlc_aout_stream *p_astream = p_owner->p_astream;
if( p_astream == NULL )
{
msg_Dbg( p_dec, "discarded audio buffer" );
block_Release( p_audio );
return VLC_EGENERIC;
}
if (p_owner->flushing || p_owner->aborting)
{
block_Release(p_audio);
return VLC_SUCCESS;
}
bool prerolled = p_owner->i_preroll_end != PREROLL_NONE;
if( prerolled && p_owner->i_preroll_end > p_audio->i_pts )
{
block_Release( p_audio );
return VLC_SUCCESS;
}
p_owner->i_preroll_end = PREROLL_NONE;
if( unlikely(prerolled) )
{
msg_Dbg( p_dec, "end of audio preroll" );
vlc_aout_stream_Flush( p_astream );
}
DecoderWaitUnblock( p_owner );
int status = vlc_aout_stream_Play( p_astream, p_audio );
if( status == AOUT_DEC_CHANGED )
{
/* Only reload the decoder */
RequestReload( p_owner );
}
else if( status == AOUT_DEC_FAILED )
{
/* If we reload because the aout failed, we should release it. That
* way, a next call to ModuleThread_UpdateAudioFormat() won't re-use the
* previous (failing) aout but will try to create a new one. */
atomic_store( &p_owner->reload, RELOAD_DECODER_AOUT );
}
return VLC_SUCCESS;
}
static void ModuleThread_QueueAudio( decoder_t *p_dec, vlc_frame_t *p_aout_buf )
{
vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
struct vlc_tracer *tracer = vlc_object_get_tracer( &p_dec->obj );
if ( tracer != NULL && p_aout_buf != NULL )
{
vlc_tracer_TraceStreamDTS( tracer, "DEC", p_owner->psz_id, "OUT",
p_aout_buf->i_pts, p_aout_buf->i_dts );
}
vlc_fifo_Lock(p_owner->p_fifo);
int success = ModuleThread_PlayAudio( p_owner, p_aout_buf );
unsigned played = 0;
unsigned aout_lost = 0;
if( p_owner->p_astream != NULL )
{
vlc_aout_stream_GetResetStats( p_owner->p_astream, &aout_lost, &played );
}
if (success != VLC_SUCCESS)
aout_lost++;
vlc_fifo_Unlock(p_owner->p_fifo);
decoder_Notify(p_owner, on_new_audio_stats, 1, aout_lost, played);
}
static void ModuleThread_PlaySpu( vlc_input_decoder_t *p_owner, subpicture_t *p_subpic )
{
decoder_t *p_dec = &p_owner->dec;
vout_thread_t *p_vout = p_owner->p_vout;
/* */
if( p_subpic->i_start == VLC_TICK_INVALID )
{
msg_Warn( p_dec, "non-dated spu buffer received" );
subpicture_Delete( p_subpic );
return;
}
/* */
vlc_fifo_Lock(p_owner->p_fifo);
DecoderWaitUnblock( p_owner );
vlc_fifo_Unlock(p_owner->p_fifo);
if( p_subpic->i_start == VLC_TICK_INVALID )
{
subpicture_Delete( p_subpic );
return;
}
vout_PutSubpicture( p_vout, p_subpic );
}
static void ModuleThread_QueueSpu( decoder_t *p_dec, subpicture_t *p_spu )
{
assert( p_spu );
vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
struct vlc_tracer *tracer = vlc_object_get_tracer( &p_dec->obj );
if ( tracer != NULL && p_spu != NULL )
{
vlc_tracer_TraceStreamPTS( tracer, "DEC", p_owner->psz_id,
"OUT", p_spu->i_start );
}
/* The vout must be created from a previous decoder_NewSubpicture call. */
assert( p_owner->p_vout );
/* Preroll does not work very well with subtitle */
vlc_fifo_Lock(p_owner->p_fifo);
if( p_spu->i_start != VLC_TICK_INVALID &&
p_spu->i_start < p_owner->i_preroll_end &&
( p_spu->i_stop == VLC_TICK_INVALID || p_spu->i_stop < p_owner->i_preroll_end ) )
{
vlc_fifo_Unlock(p_owner->p_fifo);
subpicture_Delete( p_spu );
}
else
{
vlc_fifo_Unlock(p_owner->p_fifo);
ModuleThread_PlaySpu( p_owner, p_spu );
}
}
static void DecoderThread_ProcessInput( vlc_input_decoder_t *p_owner, vlc_frame_t *frame );
static void DecoderThread_DecodeBlock( vlc_input_decoder_t *p_owner, vlc_frame_t *frame )
{
decoder_t *p_dec = &p_owner->dec;
struct vlc_tracer *tracer = vlc_object_get_tracer( &p_dec->obj );
if ( tracer != NULL && frame != NULL )
{
vlc_tracer_TraceStreamDTS( tracer, "DEC", p_owner->psz_id, "IN",
frame->i_pts, frame->i_dts );
}
int ret = p_dec->pf_decode( p_dec, frame );
switch( ret )
{
case VLCDEC_SUCCESS:
break;
case VLCDEC_ECRITICAL:
p_owner->error = true;
break;
case VLCDEC_RELOAD:
RequestReload( p_owner );
if( unlikely( frame == NULL ) )
break;
if( !( frame->i_flags & BLOCK_FLAG_CORE_PRIVATE_RELOADED ) )
{
frame->i_flags |= BLOCK_FLAG_CORE_PRIVATE_RELOADED;
DecoderThread_ProcessInput( p_owner, frame );
}
else /* We prefer loosing this frame than an infinite recursion */
block_Release( frame );
break;
default:
vlc_assert_unreachable();
}
}
/**
* Decode a frame
*
* \param p_dec the decoder object
* \param frame the block to decode
*/
static void DecoderThread_ProcessInput( vlc_input_decoder_t *p_owner, vlc_frame_t *frame )
{
decoder_t *p_dec = &p_owner->dec;
if( p_owner->error )
goto error;
/* Here, the atomic doesn't prevent to miss a reload request.
* DecoderThread_ProcessInput() can still be called after the decoder module or the
* audio output requested a reload. This will only result in a drop of an
* input frame or an output buffer. */
enum reload reload;
if( ( reload = atomic_exchange( &p_owner->reload, RELOAD_NO_REQUEST ) ) )
{
msg_Warn( p_dec, "Reloading the decoder module%s",
reload == RELOAD_DECODER_AOUT ? " and the audio output" : "" );
if( DecoderThread_Reload( p_owner, p_dec->fmt_in, reload ) != VLC_SUCCESS )
goto error;
}
bool packetize = p_owner->p_packetizer != NULL;
if( frame )
{
if( frame->i_buffer <= 0 )
goto error;
vlc_fifo_Lock(p_owner->p_fifo);
DecoderUpdatePreroll( &p_owner->i_preroll_end, frame );
vlc_fifo_Unlock(p_owner->p_fifo);
if( unlikely( frame->i_flags & BLOCK_FLAG_CORE_PRIVATE_RELOADED ) )
{
/* This frame has already been packetized */
packetize = false;
}
}
#ifdef ENABLE_SOUT
if( p_owner->p_sout != NULL )
{
DecoderThread_ProcessSout( p_owner, frame );
return;
}
#endif
if( packetize )
{
vlc_frame_t *packetized_frame;
vlc_frame_t **ppframe = frame ? &frame : NULL;
decoder_t *p_packetizer = p_owner->p_packetizer;
while( (packetized_frame =
p_packetizer->pf_packetize( p_packetizer, ppframe ) ) )
{
if( !es_format_IsSimilar( p_dec->fmt_in, &p_packetizer->fmt_out ) )
{
msg_Dbg( p_dec, "restarting module due to input format change");
/* Drain the decoder module */
DecoderThread_DecodeBlock( p_owner, NULL );
if( DecoderThread_Reload( p_owner, &p_packetizer->fmt_out,
RELOAD_DECODER ) != VLC_SUCCESS )
{
block_ChainRelease( packetized_frame );
return;
}
}
if( p_packetizer->pf_get_cc )
PacketizerGetCc( p_owner, p_packetizer );
while( packetized_frame )
{
vlc_frame_t *p_next = packetized_frame->p_next;
packetized_frame->p_next = NULL;
DecoderThread_DecodeBlock( p_owner, packetized_frame );
if( p_owner->error )
{
block_ChainRelease( p_next );
return;
}
packetized_frame = p_next;
}
}
/* Drain the decoder after the packetizer is drained */
if( !ppframe )
DecoderThread_DecodeBlock( p_owner, NULL );
}
else
DecoderThread_DecodeBlock( p_owner, frame );
return;
error:
if( frame )
block_Release( frame );
}
static void DecoderThread_Flush( vlc_input_decoder_t *p_owner )
{
decoder_t *p_dec = &p_owner->dec;
decoder_t *p_packetizer = p_owner->p_packetizer;
if( p_owner->error )
return;
if( p_packetizer != NULL && p_packetizer->pf_flush != NULL )
p_packetizer->pf_flush( p_packetizer );
if ( p_dec->pf_flush != NULL )
p_dec->pf_flush( p_dec );
/* flush CC sub decoders */
vlc_mutex_lock(&p_owner->cc.lock);
if( p_owner->cc.b_supported )
{
for( int i=0; i<MAX_CC_DECODERS; i++ )
{
vlc_input_decoder_t *p_ccowner = p_owner->cc.pp_decoder[i];
if(p_ccowner == NULL)
continue;
vlc_input_decoder_Flush(p_ccowner);
}
}
vlc_mutex_unlock(&p_owner->cc.lock);
}
/**
* The decoding main loop
*
* \param p_dec the decoder
*/
static void *DecoderThread( void *p_data )
{
vlc_input_decoder_t *p_owner = (vlc_input_decoder_t *)p_data;
const char *thread_name;
switch (p_owner->dec.fmt_in->i_cat)
{
case VIDEO_ES: thread_name = "vlc-dec-video"; break;
case AUDIO_ES: thread_name = "vlc-dec-audio"; break;
case SPU_ES: thread_name = "vlc-dec-spu"; break;
case DATA_ES: thread_name = "vlc-dec-data"; break;
default: thread_name = "vlc-decoder"; break;
}
vlc_thread_set_name(thread_name);
/* The decoder's main loop */
vlc_fifo_Lock( p_owner->p_fifo );
while( !p_owner->aborting )
{
if( p_owner->flushing )
{ /* Flush before/regardless of pause. We do not want to resume just
* for the sake of flushing (glitches could otherwise happen). */
vlc_fifo_Unlock( p_owner->p_fifo );
/* Flush the decoder (and the output) */
DecoderThread_Flush( p_owner );
vlc_fifo_Lock( p_owner->p_fifo );
/* Reset flushing after DecoderThread_ProcessInput in case vlc_input_decoder_Flush
* is called again. This will avoid a second useless flush (but
* harmless). */
p_owner->flushing = false;
p_owner->i_preroll_end = PREROLL_NONE;
continue;
}
if( p_owner->paused != p_owner->output_paused )
{ /* Update playing/paused status of the output */
Decoder_ChangeOutputPause( p_owner, p_owner->paused, p_owner->pause_date );
continue;
}
if( p_owner->rate != p_owner->output_rate )
{
Decoder_ChangeOutputRate( p_owner, p_owner->rate );
continue;
}
if( p_owner->delay != p_owner->output_delay )
{
Decoder_ChangeOutputDelay( p_owner, p_owner->delay );
continue;
}
if( p_owner->paused && p_owner->frames_countdown == 0 )
{ /* Wait for resumption from pause */
p_owner->b_idle = true;
vlc_cond_signal( &p_owner->wait_acknowledge );
vlc_fifo_Wait( p_owner->p_fifo );
p_owner->b_idle = false;
continue;
}
vlc_cond_signal( &p_owner->wait_fifo );
vlc_frame_t *frame = vlc_fifo_DequeueUnlocked( p_owner->p_fifo );
if( frame == NULL )
{
if( likely(!p_owner->b_draining) )
{ /* Wait for a block to decode (or a request to drain) */
p_owner->b_idle = true;
vlc_cond_signal( &p_owner->wait_acknowledge );
vlc_fifo_Wait( p_owner->p_fifo );
p_owner->b_idle = false;
continue;
}
/* We have emptied the FIFO and there is a pending request to
* drain. Pass frame = NULL to decoder just once. */
}
vlc_fifo_Unlock( p_owner->p_fifo );
DecoderThread_ProcessInput( p_owner, frame );
vlc_fifo_Lock(p_owner->p_fifo);
if( p_owner->b_draining && frame == NULL )
{
p_owner->b_draining = false;
if( p_owner->dec.fmt_in->i_cat == AUDIO_ES && p_owner->p_astream != NULL )
{ /* Draining: the decoder is drained and all decoded buffers are
* queued to the output at this point. Now drain the output. */
vlc_aout_stream_Drain( p_owner->p_astream );
}
}
vlc_cond_signal( &p_owner->wait_acknowledge );
}
vlc_fifo_Unlock( p_owner->p_fifo );
return NULL;
}
static const struct decoder_owner_callbacks dec_video_cbs =
{
.video = {
.get_device = ModuleThread_GetDecoderDevice,
.format_update = ModuleThread_UpdateVideoFormat,
.buffer_new = ModuleThread_NewVideoBuffer,
.queue = ModuleThread_QueueVideo,
.queue_cc = ModuleThread_QueueCc,
.get_display_date = ModuleThread_GetDisplayDate,
.get_display_rate = ModuleThread_GetDisplayRate,
},
.get_attachments = InputThread_GetInputAttachments,
};
static const struct decoder_owner_callbacks dec_thumbnailer_cbs =
{
.video = {
.get_device = thumbnailer_get_device,
.buffer_new = thumbnailer_buffer_new,
.queue = ModuleThread_QueueThumbnail,
},
.get_attachments = InputThread_GetInputAttachments,
};
static const struct decoder_owner_callbacks dec_audio_cbs =
{
.audio = {
.format_update = ModuleThread_UpdateAudioFormat,
.queue = ModuleThread_QueueAudio,
},
.get_attachments = InputThread_GetInputAttachments,
};
static const struct decoder_owner_callbacks dec_spu_cbs =
{
.spu = {
.buffer_new = ModuleThread_NewSpuBuffer,
.queue = ModuleThread_QueueSpu,
},
.get_attachments = InputThread_GetInputAttachments,
};
/**
* Create a decoder object
*
* \param p_input the input thread
* \param p_es the es descriptor
* \param b_packetizer instead of a decoder
* \return the decoder object
*/
static vlc_input_decoder_t *
CreateDecoder( vlc_object_t *p_parent, const struct vlc_input_decoder_cfg *cfg )
{
decoder_t *p_dec;
vlc_input_decoder_t *p_owner;
static_assert(offsetof(vlc_input_decoder_t, dec) == 0,
"the decoder must be first in the owner structure");
assert(cfg->input_type != INPUT_TYPE_PREPARSING);
const es_format_t *fmt = cfg->fmt;
p_owner = vlc_custom_create( p_parent, sizeof( *p_owner ), "decoder" );
if( p_owner == NULL )
return NULL;
p_dec = &p_owner->dec;
p_owner->psz_id = cfg->str_id;
p_owner->p_clock = cfg->clock;
p_owner->i_preroll_end = PREROLL_NONE;
p_owner->p_resource = cfg->resource;
p_owner->cbs = cfg->cbs;
p_owner->cbs_userdata = cfg->cbs_data;
p_owner->p_aout = NULL;
p_owner->p_astream = NULL;
p_owner->p_vout = NULL;
p_owner->vout_started = false;
p_owner->i_spu_channel = VOUT_SPU_CHANNEL_INVALID;
p_owner->i_spu_order = 0;
p_owner->p_sout = cfg->sout;
p_owner->p_sout_input = NULL;
p_owner->p_packetizer = NULL;
p_owner->b_fmt_description = false;
p_owner->p_description = NULL;
p_owner->output_delay = p_owner->delay = 0;
p_owner->output_rate = p_owner->rate = 1.f;
p_owner->output_paused = p_owner->paused = false;
p_owner->pause_date = VLC_TICK_INVALID;
p_owner->frames_countdown = 0;
p_owner->b_waiting = false;
p_owner->b_first = true;
p_owner->b_has_data = false;
p_owner->error = false;
p_owner->flushing = false;
p_owner->b_draining = false;
atomic_init( &p_owner->reload, RELOAD_NO_REQUEST );
p_owner->b_idle = false;
p_owner->mouse_event = NULL;
p_owner->mouse_opaque = NULL;
es_format_Init( &p_owner->fmt, fmt->i_cat, 0 );
/* decoder fifo */
p_owner->p_fifo = block_FifoNew();
if( unlikely(p_owner->p_fifo == NULL) )
{
vlc_object_delete(p_dec);
return NULL;
}
vlc_mutex_init( &p_owner->mouse_lock );
vlc_cond_init( &p_owner->wait_request );
vlc_cond_init( &p_owner->wait_acknowledge );
vlc_cond_init( &p_owner->wait_fifo );
/* Load a packetizer module if the input is not already packetized */
if( cfg->sout == NULL && !fmt->b_packetized )
{
p_owner->p_packetizer =
vlc_custom_create( p_parent, sizeof( decoder_t ), "packetizer" );
if( p_owner->p_packetizer )
{
if( LoadDecoder( p_owner->p_packetizer, true, &p_owner->pktz_fmt_in, fmt ) )
{
vlc_object_delete(p_owner->p_packetizer);
p_owner->p_packetizer = NULL;
}
else
{
p_owner->p_packetizer->fmt_out.b_packetized = true;
fmt = &p_owner->p_packetizer->fmt_out;
}
}
}
switch( fmt->i_cat )
{
case VIDEO_ES:
if( cfg->input_type == INPUT_TYPE_THUMBNAILING )
p_dec->cbs = &dec_thumbnailer_cbs;
else
p_dec->cbs = &dec_video_cbs;
break;
case AUDIO_ES:
p_dec->cbs = &dec_audio_cbs;
break;
case SPU_ES:
p_dec->cbs = &dec_spu_cbs;
break;
default:
msg_Err( p_dec, "unknown ES format" );
return p_owner;
}
/* Find a suitable decoder/packetizer module */
if( LoadDecoder( p_dec, cfg->sout != NULL, &p_owner->dec_fmt_in, fmt ) )
return p_owner;
assert( p_dec->fmt_in->i_cat == p_dec->fmt_out.i_cat && fmt->i_cat == p_dec->fmt_in->i_cat);
/* Copy ourself the input replay gain */
if( fmt->i_cat == AUDIO_ES )
{
for( unsigned i = 0; i < AUDIO_REPLAY_GAIN_MAX; i++ )
{
if( !p_dec->fmt_out.audio_replay_gain.pb_peak[i] )
{
p_dec->fmt_out.audio_replay_gain.pb_peak[i] = fmt->audio_replay_gain.pb_peak[i];
p_dec->fmt_out.audio_replay_gain.pf_peak[i] = fmt->audio_replay_gain.pf_peak[i];
}
if( !p_dec->fmt_out.audio_replay_gain.pb_gain[i] )
{
p_dec->fmt_out.audio_replay_gain.pb_gain[i] = fmt->audio_replay_gain.pb_gain[i];
p_dec->fmt_out.audio_replay_gain.pf_gain[i] = fmt->audio_replay_gain.pf_gain[i];
}
}
}
/* */
vlc_mutex_init(&p_owner->cc.lock);
p_owner->cc.b_supported = ( cfg->sout == NULL );
p_owner->cc.desc.i_608_channels = 0;
p_owner->cc.desc.i_708_channels = 0;
for( unsigned i = 0; i < MAX_CC_DECODERS; i++ )
p_owner->cc.pp_decoder[i] = NULL;
p_owner->cc.p_sout_input = NULL;
p_owner->cc.b_sout_created = false;
return p_owner;
}
/**
* Destroys a decoder object
*
* \param p_dec the decoder object
* \return nothing
*/
static void DeleteDecoder( vlc_input_decoder_t *p_owner, enum es_format_category_e i_cat )
{
decoder_t *p_dec = &p_owner->dec;
msg_Dbg( p_dec, "killing decoder fourcc `%4.4s'",
(char*)&p_dec->fmt_in->i_codec );
decoder_Clean( p_dec );
if ( p_owner->out_pool )
{
picture_pool_Release( p_owner->out_pool );
p_owner->out_pool = NULL;
}
if (p_owner->vctx)
vlc_video_context_Release( p_owner->vctx );
/* Free all packets still in the decoder fifo. */
block_FifoEmpty( p_owner->p_fifo );
/* Cleanup */
#ifdef ENABLE_SOUT
if( p_owner->p_sout_input )
{
sout_InputDelete( p_owner->p_sout, p_owner->p_sout_input );
if( p_owner->cc.p_sout_input )
sout_InputDelete( p_owner->p_sout, p_owner->cc.p_sout_input );
}
#endif
switch( i_cat )
{
case AUDIO_ES:
if( p_owner->p_aout )
{
/* TODO: REVISIT gap-less audio */
assert( p_owner->p_astream );
vlc_aout_stream_Delete( p_owner->p_astream );
input_resource_PutAout( p_owner->p_resource, p_owner->p_aout );
}
break;
case VIDEO_ES: {
vout_thread_t *vout = p_owner->p_vout;
if (vout != NULL)
{
/* Hold the vout since PutVout will likely release it and a
* last reference is needed for notify callbacks */
vout_Hold(vout);
enum input_resource_vout_state vout_state;
input_resource_PutVout(p_owner->p_resource, vout, &vout_state);
if (vout_state == INPUT_RESOURCE_VOUT_STOPPED)
decoder_Notify(p_owner, on_vout_stopped, vout);
vout_Release(vout);
}
break;
}
case SPU_ES:
{
if( p_owner->p_vout )
{
assert( p_owner->i_spu_channel != VOUT_SPU_CHANNEL_INVALID );
decoder_Notify(p_owner, on_vout_stopped, p_owner->p_vout);
vout_UnregisterSubpictureChannel( p_owner->p_vout,
p_owner->i_spu_channel );
vout_Release(p_owner->p_vout);
}
break;
}
case DATA_ES:
case UNKNOWN_ES:
break;
default:
vlc_assert_unreachable();
}
es_format_Clean( &p_owner->dec_fmt_in );
es_format_Clean( &p_owner->pktz_fmt_in );
es_format_Clean( &p_owner->fmt );
if( p_owner->p_description )
vlc_meta_Delete( p_owner->p_description );
block_FifoRelease( p_owner->p_fifo );
decoder_Destroy( p_owner->p_packetizer );
decoder_Destroy( &p_owner->dec );
}
/* */
static void DecoderUnsupportedCodec( decoder_t *p_dec, const es_format_t *fmt, bool b_decoding )
{
if (fmt->i_codec != VLC_CODEC_UNKNOWN && fmt->i_codec) {
const char *desc = vlc_fourcc_GetDescription(fmt->i_cat, fmt->i_codec);
if (!desc || !*desc)
desc = N_("No description for this codec");
msg_Err( p_dec, "Codec `%4.4s' (%s) is not supported.", (char*)&fmt->i_codec, desc );
vlc_dialog_display_error( p_dec, _("Codec not supported"),
_("VLC could not decode the format \"%4.4s\" (%s)"),
(char*)&fmt->i_codec, desc );
} else if( b_decoding ){
msg_Err( p_dec, "could not identify codec" );
vlc_dialog_display_error( p_dec, _("Unidentified codec"),
_("VLC could not identify the audio or video codec" ) );
}
}
/* TODO: pass p_sout through p_resource? -- Courmisch */
static vlc_input_decoder_t *
decoder_New( vlc_object_t *p_parent, const struct vlc_input_decoder_cfg *cfg )
{
const char *psz_type = cfg->sout ? N_("packetizer") : N_("decoder");
/* Create the decoder configuration structure */
vlc_input_decoder_t *p_owner = CreateDecoder( p_parent, cfg );
if( p_owner == NULL )
{
msg_Err( p_parent, "could not create %s", cfg->str_id );
vlc_dialog_display_error( p_parent, _("Streaming / Transcoding failed"),
_("VLC could not open the %s module."), vlc_gettext( psz_type ) );
return NULL;
}
decoder_t *p_dec = &p_owner->dec;
if( !p_dec->p_module )
{
DecoderUnsupportedCodec( p_dec, cfg->fmt, !cfg->sout );
/* Don't use dec->fmt_in->i_cat since it may not be initialized here. */
DeleteDecoder( p_owner, cfg->fmt->i_cat );
return NULL;
}
assert( p_dec->fmt_in->i_cat != UNKNOWN_ES );
#ifdef ENABLE_SOUT
/* Do not delay sout creation for SPU or DATA. */
if( cfg->sout && cfg->fmt->b_packetized &&
(cfg->fmt->i_cat != VIDEO_ES && cfg->fmt->i_cat != AUDIO_ES) )
{
p_owner->p_sout_input = sout_InputNew( p_owner->p_sout, cfg->fmt );
if( p_owner->p_sout_input == NULL )
{
msg_Err( p_dec, "cannot create sout input (%4.4s)",
(char *)&cfg->fmt->i_codec );
p_owner->error = true;
}
}
#endif
if( !vlc_input_decoder_IsSynchronous( p_owner ) )
{
/* Spawn the decoder thread in asynchronous scenario. */
if( vlc_clone( &p_owner->thread, DecoderThread, p_owner ) )
{
msg_Err( p_dec, "cannot spawn decoder thread" );
DeleteDecoder( p_owner, p_dec->fmt_in->i_cat );
return NULL;
}
}
return p_owner;
}
/**
* Spawns a new decoder thread from the input thread
*
* \param p_input the input thread
* \param p_es the es descriptor
* \return the spawned decoder object
*/
vlc_input_decoder_t *
vlc_input_decoder_New( vlc_object_t *parent, const struct vlc_input_decoder_cfg *cfg )
{
return decoder_New( parent, cfg );
}
/**
* Spawn a decoder thread outside of the input thread.
*/
vlc_input_decoder_t *
vlc_input_decoder_Create( vlc_object_t *p_parent, const es_format_t *fmt,
struct vlc_clock_t *clock, input_resource_t *p_resource )
{
const struct vlc_input_decoder_cfg cfg = {
.fmt = fmt,
.str_id = NULL,
.clock = clock,
.resource = p_resource,
.sout = NULL,
.input_type = INPUT_TYPE_NONE,
.cbs = NULL, .cbs_data = NULL,
};
return decoder_New( p_parent, &cfg );
}
/**
* Kills a decoder thread and waits until it's finished
*
* \param p_input the input thread
* \param p_es the es descriptor
* \return nothing
*/
void vlc_input_decoder_Delete( vlc_input_decoder_t *p_owner )
{
decoder_t *p_dec = &p_owner->dec;
vlc_fifo_Lock( p_owner->p_fifo );
p_owner->aborting = true;
p_owner->b_waiting = false;
vlc_fifo_Signal( p_owner->p_fifo );
/* Make sure we aren't waiting/decoding anymore */
vlc_cond_signal( &p_owner->wait_request );
vlc_fifo_Unlock( p_owner->p_fifo );
if( !vlc_input_decoder_IsSynchronous( p_owner ) )
vlc_join( p_owner->thread, NULL );
/* */
if( p_owner->cc.b_supported )
{
for( int i = 0; i < MAX_CC_DECODERS; i++ )
vlc_input_decoder_SetCcState( p_owner, VLC_CODEC_CEA608, i, false );
}
/* Delete decoder */
DeleteDecoder( p_owner, p_dec->fmt_in->i_cat );
}
/**
* Put a vlc_frame_t in the decoder's fifo.
* Thread-safe w.r.t. the decoder. May be a cancellation point.
*
* \param p_dec the decoder object
* \param frame the data frame
*/
void vlc_input_decoder_Decode( vlc_input_decoder_t *p_owner, vlc_frame_t *frame,
bool b_do_pace )
{
if( vlc_input_decoder_IsSynchronous( p_owner ) )
{
/* DecoderThread's fifo should be empty as no decoder thread is running. */
assert( vlc_fifo_IsEmpty( p_owner->p_fifo ) );
DecoderThread_ProcessInput( p_owner, frame );
return;
}
vlc_fifo_Lock( p_owner->p_fifo );
if( !b_do_pace )
{
/* FIXME: ideally we would check the time amount of data
* in the FIFO instead of its size. */
/* 400 MiB, i.e. ~ 50mb/s for 60s */
if( vlc_fifo_GetBytes( p_owner->p_fifo ) > 400*1024*1024 )
{
msg_Warn( &p_owner->dec, "decoder/packetizer fifo full (data not "
"consumed quickly enough), resetting fifo!" );
block_ChainRelease( vlc_fifo_DequeueAllUnlocked( p_owner->p_fifo ) );
frame->i_flags |= BLOCK_FLAG_DISCONTINUITY;
}
}
else
if( !p_owner->b_waiting )
{ /* The FIFO is not consumed when waiting, so pacing would deadlock VLC.
* Locking is not necessary as b_waiting is only read, not written by
* the decoder thread. */
while( vlc_fifo_GetCount( p_owner->p_fifo ) >= 10 )
vlc_fifo_WaitCond( p_owner->p_fifo, &p_owner->wait_fifo );
}
vlc_fifo_QueueUnlocked( p_owner->p_fifo, frame );
vlc_fifo_Unlock( p_owner->p_fifo );
}
bool vlc_input_decoder_IsEmpty( vlc_input_decoder_t * p_owner )
{
assert( !p_owner->b_waiting );
vlc_fifo_Lock( p_owner->p_fifo );
if( !vlc_fifo_IsEmpty( p_owner->p_fifo ) || p_owner->b_draining )
{
vlc_fifo_Unlock( p_owner->p_fifo );
return false;
}
bool b_empty;
#ifdef ENABLE_SOUT
if( p_owner->p_sout_input != NULL )
b_empty = true;
else
#endif
if( p_owner->fmt.i_cat == VIDEO_ES && p_owner->p_vout != NULL )
b_empty = vout_IsEmpty( p_owner->p_vout );
else if( p_owner->fmt.i_cat == AUDIO_ES && p_owner->p_astream != NULL )
b_empty = vlc_aout_stream_IsDrained( p_owner->p_astream );
else
b_empty = true; /* TODO subtitles support */
vlc_fifo_Unlock( p_owner->p_fifo );
return b_empty;
}
/**
* Signals that there are no further frames to decode, and requests that the
* decoder drain all pending buffers. This is used to ensure that all
* intermediate buffers empty and no samples get lost at the end of the stream.
*
* @note The function does not actually wait for draining. It just signals that
* draining should be performed once the decoder has emptied FIFO.
*/
void vlc_input_decoder_Drain( vlc_input_decoder_t *p_owner )
{
if ( vlc_input_decoder_IsSynchronous( p_owner ) )
{
/* Process a NULL frame synchronously to signal draining to packetizer/decoder. */
DecoderThread_ProcessInput( p_owner, NULL );
return;
}
vlc_fifo_Lock( p_owner->p_fifo );
p_owner->b_draining = true;
vlc_fifo_Signal( p_owner->p_fifo );
vlc_fifo_Unlock( p_owner->p_fifo );
}
/**
* Requests that the decoder immediately discard all pending buffers.
* This is useful when seeking or when deselecting a stream.
*/
void vlc_input_decoder_Flush( vlc_input_decoder_t *p_owner )
{
enum es_format_category_e cat = p_owner->dec.fmt_in->i_cat;
vlc_fifo_Lock( p_owner->p_fifo );
/* Empty the fifo */
block_ChainRelease( vlc_fifo_DequeueAllUnlocked( p_owner->p_fifo ) );
/* Don't need to wait for the DecoderThread to flush. Indeed, if called a
* second time, this function will clear the FIFO again before anything was
* dequeued by DecoderThread and there is no need to flush a second time in
* a row. */
p_owner->flushing = true;
p_owner->b_draining = false;
/* Flush video/spu decoder when paused: increment frames_countdown in order
* to display one frame/subtitle */
if( p_owner->paused && ( cat == VIDEO_ES || cat == SPU_ES )
&& p_owner->frames_countdown == 0 )
p_owner->frames_countdown++;
if ( p_owner->p_sout_input != NULL )
{
#ifdef ENABLE_SOUT
sout_InputFlush( p_owner->p_sout, p_owner->p_sout_input );
#endif
}
else if( cat == AUDIO_ES )
{
if( p_owner->p_astream )
vlc_aout_stream_Flush( p_owner->p_astream );
}
else if( cat == VIDEO_ES )
{
if( p_owner->p_vout && p_owner->vout_started )
vout_FlushAll( p_owner->p_vout );
}
else if( cat == SPU_ES )
{
if( p_owner->p_vout )
{
assert( p_owner->i_spu_channel != VOUT_SPU_CHANNEL_INVALID );
vout_FlushSubpictureChannel( p_owner->p_vout, p_owner->i_spu_channel );
}
}
vlc_fifo_Signal( p_owner->p_fifo );
vlc_fifo_Unlock( p_owner->p_fifo );
if (vlc_input_decoder_IsSynchronous(p_owner))
{
/* With a synchronous decoder,there is no decoder thread which
* can process the flush request. We flush synchronously from
* here and reset the flushing state. */
DecoderThread_Flush(p_owner);
vlc_fifo_Lock(p_owner->p_fifo);
p_owner->flushing = false;
p_owner->i_preroll_end = PREROLL_NONE;
vlc_fifo_Unlock(p_owner->p_fifo);
}
}
static bool vlc_input_decoder_HasCCChanFlag( vlc_input_decoder_t *p_owner,
vlc_fourcc_t codec, int i_channel )
{
int i_max_channels;
uint64_t i_bitmap;
if( codec == VLC_CODEC_CEA608 )
{
i_max_channels = 4;
i_bitmap = p_owner->cc.desc.i_608_channels;
}
else if( codec == VLC_CODEC_CEA708 )
{
i_max_channels = 64;
i_bitmap = p_owner->cc.desc.i_708_channels;
}
else return false;
return ( i_channel >= 0 && i_channel < i_max_channels &&
( i_bitmap & ((uint64_t)1 << i_channel) ) );
}
int vlc_input_decoder_SetCcState( vlc_input_decoder_t *p_owner, vlc_fourcc_t codec,
int i_channel, bool b_decode )
{
decoder_t *p_dec = &p_owner->dec;
//msg_Warn( p_dec, "vlc_input_decoder_SetCcState: %d @%x", b_decode, i_channel );
vlc_mutex_lock(&p_owner->cc.lock);
if( !vlc_input_decoder_HasCCChanFlag( p_owner, codec, i_channel ) )
{
vlc_mutex_unlock(&p_owner->cc.lock);
return VLC_EGENERIC;
}
if( b_decode )
{
vlc_input_decoder_t *p_ccowner;
es_format_t fmt;
es_format_Init( &fmt, SPU_ES, codec );
fmt.subs.cc.i_channel = i_channel;
fmt.subs.cc.i_reorder_depth = p_owner->cc.desc.i_reorder_depth;
const struct vlc_input_decoder_cfg cfg = {
.fmt = &fmt,
.str_id = p_owner->psz_id,
.clock = p_owner->p_clock,
.resource = p_owner->p_resource,
.sout = p_owner->p_sout,
.input_type = INPUT_TYPE_NONE,
.cbs = NULL, .cbs_data = NULL,
};
p_ccowner = vlc_input_decoder_New( VLC_OBJECT(p_dec), &cfg );
if( !p_ccowner )
{
msg_Err( p_dec, "could not create decoder" );
vlc_dialog_display_error( p_dec,
_("Streaming / Transcoding failed"), "%s",
_("VLC could not open the decoder module.") );
vlc_mutex_unlock(&p_owner->cc.lock);
return VLC_EGENERIC;
}
else if( !p_ccowner->dec.p_module )
{
DecoderUnsupportedCodec( p_dec, &fmt, true );
vlc_input_decoder_Flush(p_ccowner);
vlc_input_decoder_Delete(p_ccowner);
vlc_mutex_unlock(&p_owner->cc.lock);
return VLC_EGENERIC;
}
p_ccowner->p_clock = p_owner->p_clock;
p_owner->cc.pp_decoder[i_channel] = p_ccowner;
}
else
{
vlc_input_decoder_t *p_cc;
p_cc = p_owner->cc.pp_decoder[i_channel];
p_owner->cc.pp_decoder[i_channel] = NULL;
if( p_cc )
{
vlc_input_decoder_Flush(p_cc);
vlc_input_decoder_Delete(p_cc);
}
}
vlc_mutex_unlock(&p_owner->cc.lock);
return VLC_SUCCESS;
}
int vlc_input_decoder_GetCcState( vlc_input_decoder_t *p_owner, vlc_fourcc_t codec,
int i_channel, bool *pb_decode )
{
vlc_mutex_lock(&p_owner->cc.lock);
if( !vlc_input_decoder_HasCCChanFlag( p_owner, codec, i_channel ) )
{
vlc_mutex_unlock(&p_owner->cc.lock);
return VLC_EGENERIC;
}
*pb_decode = p_owner->cc.pp_decoder[i_channel] != NULL;
vlc_mutex_unlock(&p_owner->cc.lock);
return VLC_SUCCESS;
}
void vlc_input_decoder_ChangePause( vlc_input_decoder_t *p_owner,
bool b_paused, vlc_tick_t i_date )
{
/* Normally, p_owner->b_paused != b_paused here. But if a track is added
* while the input is paused (e.g. add sub file), then b_paused is
* (incorrectly) false. FIXME: This is a bug in the decoder owner. */
vlc_fifo_Lock( p_owner->p_fifo );
p_owner->paused = b_paused;
p_owner->pause_date = i_date;
p_owner->frames_countdown = 0;
vlc_fifo_Signal( p_owner->p_fifo );
vlc_fifo_Unlock( p_owner->p_fifo );
}
void vlc_input_decoder_ChangeRate( vlc_input_decoder_t *owner, float rate )
{
vlc_fifo_Lock( owner->p_fifo );
owner->rate = rate;
vlc_fifo_Unlock( owner->p_fifo );
}
void vlc_input_decoder_ChangeDelay( vlc_input_decoder_t *owner, vlc_tick_t delay )
{
vlc_fifo_Lock( owner->p_fifo );
owner->delay = delay;
vlc_fifo_Unlock( owner->p_fifo );
}
void vlc_input_decoder_StartWait( vlc_input_decoder_t *p_owner )
{
if( vlc_input_decoder_IsSynchronous( p_owner ) )
return;
assert( !p_owner->b_waiting );
vlc_fifo_Lock(p_owner->p_fifo);
p_owner->b_has_data = false;
p_owner->b_first = true;
p_owner->b_waiting = true;
vlc_cond_signal(&p_owner->wait_request);
vlc_fifo_Unlock(p_owner->p_fifo);
}
void vlc_input_decoder_StopWait( vlc_input_decoder_t *p_owner )
{
if( vlc_input_decoder_IsSynchronous( p_owner ) )
return;
vlc_fifo_Lock(p_owner->p_fifo);
assert( p_owner->b_waiting );
p_owner->b_waiting = false;
vlc_cond_signal( &p_owner->wait_request );
vlc_fifo_Unlock(p_owner->p_fifo);
}
void vlc_input_decoder_Wait( vlc_input_decoder_t *p_owner )
{
if( vlc_input_decoder_IsSynchronous( p_owner ) )
{
/* Nothing to wait for. There's no decoder thread running. */
return;
}
assert( p_owner->b_waiting );
vlc_fifo_Lock(p_owner->p_fifo);
while( !p_owner->b_has_data )
{
/* Don't need to lock p_owner->paused since it's only modified by the
* owner */
if( p_owner->paused )
break;
if( p_owner->b_idle && vlc_fifo_IsEmpty( p_owner->p_fifo ) )
{
msg_Err( &p_owner->dec, "buffer deadlock prevented" );
break;
}
vlc_fifo_WaitCond(p_owner->p_fifo, &p_owner->wait_acknowledge);
}
vlc_fifo_Unlock(p_owner->p_fifo);
}
void vlc_input_decoder_FrameNext( vlc_input_decoder_t *p_owner )
{
assert( p_owner->paused );
vlc_fifo_Lock( p_owner->p_fifo );
p_owner->frames_countdown++;
vlc_fifo_Signal( p_owner->p_fifo );
vlc_fifo_Unlock( p_owner->p_fifo );
vlc_fifo_Lock(p_owner->p_fifo);
if( p_owner->dec.fmt_in->i_cat == VIDEO_ES )
{
if( p_owner->p_vout )
vout_NextPicture( p_owner->p_vout );
}
vlc_fifo_Unlock(p_owner->p_fifo);
}
void vlc_input_decoder_GetStatus( vlc_input_decoder_t *p_owner,
struct vlc_input_decoder_status *status )
{
vlc_fifo_Lock(p_owner->p_fifo);
status->format.changed = p_owner->b_fmt_description;
p_owner->b_fmt_description = false;
if( status->format.changed && p_owner->fmt.i_cat == UNKNOWN_ES )
{
/* The format changed but the output creation failed */
status->format.changed = false;
}
if( status->format.changed )
{
es_format_Copy( &status->format.fmt, &p_owner->fmt );
status->format.meta = NULL;
if( p_owner->p_description )
{
status->format.meta = vlc_meta_New();
if( status->format.meta )
vlc_meta_Merge( status->format.meta, p_owner->p_description );
}
}
status->cc.desc = p_owner->cc.desc;
vlc_fifo_Unlock(p_owner->p_fifo);
}
size_t vlc_input_decoder_GetFifoSize( vlc_input_decoder_t *p_owner )
{
return block_FifoSize( p_owner->p_fifo );
}
static bool DecoderHasVbi( decoder_t *dec )
{
return dec->fmt_in->i_cat == SPU_ES && dec->fmt_in->i_codec == VLC_CODEC_TELETEXT
&& var_Type( dec, "vbi-page" ) == VLC_VAR_INTEGER;
}
int vlc_input_decoder_GetVbiPage( vlc_input_decoder_t *owner, bool *opaque )
{
decoder_t *dec = &owner->dec;
if( !DecoderHasVbi( dec ) )
return -1;
*opaque = var_GetBool( dec, "vbi-opaque" );
return var_GetInteger( dec, "vbi-page" );
}
int vlc_input_decoder_SetVbiPage( vlc_input_decoder_t *owner, unsigned page )
{
decoder_t *dec = &owner->dec;
if( !DecoderHasVbi( dec ) )
return VLC_EGENERIC;
return var_SetInteger( dec, "vbi-page", page );
}
int vlc_input_decoder_SetVbiOpaque( vlc_input_decoder_t *owner, bool opaque )
{
decoder_t *dec = &owner->dec;
if( !DecoderHasVbi( dec ) )
return VLC_EGENERIC;
return var_SetBool( dec, "vbi-opaque", opaque );
}
void vlc_input_decoder_SetVoutMouseEvent( vlc_input_decoder_t *owner,
vlc_mouse_event mouse_event,
void *user_data )
{
assert( owner->dec.fmt_in->i_cat == VIDEO_ES );
vlc_mutex_lock( &owner->mouse_lock );
owner->mouse_event = mouse_event;
owner->mouse_opaque = user_data;
vlc_mutex_unlock( &owner->mouse_lock );
}
int vlc_input_decoder_AddVoutOverlay( vlc_input_decoder_t *owner, subpicture_t *sub,
size_t *channel )
{
assert( owner->dec.fmt_in->i_cat == VIDEO_ES );
assert( sub && channel );
vlc_fifo_Lock(owner->p_fifo);
if( !owner->p_vout )
{
vlc_fifo_Unlock(owner->p_fifo);
return VLC_EGENERIC;
}
ssize_t channel_id =
vout_RegisterSubpictureChannel( owner->p_vout );
if (channel_id == -1)
{
vlc_fifo_Unlock(owner->p_fifo);
return VLC_EGENERIC;
}
sub->i_start = sub->i_stop = vlc_tick_now();
sub->i_channel = *channel = channel_id;
sub->i_order = 0;
sub->b_ephemer = true;
vout_PutSubpicture( owner->p_vout, sub );
vlc_fifo_Unlock(owner->p_fifo);
return VLC_SUCCESS;
}
int vlc_input_decoder_DelVoutOverlay( vlc_input_decoder_t *owner, size_t channel )
{
assert( owner->dec.fmt_in->i_cat == VIDEO_ES );
vlc_fifo_Lock(owner->p_fifo);
if( !owner->p_vout )
{
vlc_fifo_Unlock(owner->p_fifo);
return VLC_EGENERIC;
}
vout_UnregisterSubpictureChannel( owner->p_vout, channel );
vlc_fifo_Unlock(owner->p_fifo);
return VLC_SUCCESS;
}
int vlc_input_decoder_SetSpuHighlight( vlc_input_decoder_t *p_owner,
const vlc_spu_highlight_t *spu_hl )
{
assert( p_owner->dec.fmt_in->i_cat == SPU_ES );
#ifdef ENABLE_SOUT
if( p_owner->p_sout_input )
sout_InputControl( p_owner->p_sout, p_owner->p_sout_input,
SOUT_INPUT_SET_SPU_HIGHLIGHT, spu_hl );
#endif
vlc_fifo_Lock(p_owner->p_fifo);
if( !p_owner->p_vout )
{
vlc_fifo_Unlock(p_owner->p_fifo);
return VLC_EGENERIC;
}
vout_SetSpuHighlight( p_owner->p_vout, spu_hl );
vlc_fifo_Unlock(p_owner->p_fifo);
return VLC_SUCCESS;
}