Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/VLC/src/input/   (VLC Media Plyer ©)  Datei vom 23.9.2026 mit Größe 97 kB image not shown  

Quelle  decoder.c   Sprache: 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 <limits.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_picture_pool.h>
#include <vlc_tracer.h>
#include <vlc_list.h>
#include <vlc_replay_gain.h>

#include "audio_output/aout_internal.h"
#include "stream_output/stream_output.h"
#include "../clock/clock.h"
#include "decoder.h"
#include "resource.h"
#include "decoder_prevframe.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
 * subdecs.list table is protected through the `subdecs.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 decoder_video
{
    vout_thread_t *vout;
    enum vlc_vout_order vout_order;
    bool started;
    bool drained;

    /* pool to use when the decoder doesn't use its own */
    struct picture_pool_t *out_pool;
    vlc_video_context *vctx;

    /* Mouse event */
    vlc_mutex_t mouse_lock;
    vlc_mouse_event mouse_event;
    void *mouse_opaque;

    /* previous-frame */
    struct decoder_prevframe pf;
    vlc_tick_t pf_pts;
};

struct decoder_audio
{
    /* If aout is valid, then astream is valid too */
    audio_output_t *aout;
    vlc_aout_stream *stream;
    bool drained;
};

struct decoder_spu
{
    vout_thread_t *vout;
    ssize_t channel;
    int64_t order;
};

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;

    bool hw_dec;

    const struct vlc_input_decoder_callbacks *cbs;
    void *cbs_userdata;

    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;

    /* initial and immutable category */
    enum es_format_category_e cat;
    /* Current format in use by the output */
    es_format_t    fmt;

    /* */
    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 */

    /*
     * 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()).
     */

     union {
        struct decoder_video video;
        struct decoder_audio audio;
        struct decoder_spu spu;
    };

    sout_stream_t *p_sout;
    sout_packetizer_input_t *p_sout_input;

    /* -- 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, output_pause_date;
    vlc_tick_t delay, output_delay;
    float rate, output_rate;
    int frames_countdown;
    bool paused, output_paused;

    bool error;

    /* Waiting */
    bool b_waiting;
    bool b_first;
    bool b_has_data;
    bool out_started;

    /* Flushing */
    bool flushing;
    bool b_draining;
    bool b_idle;
    bool aborting;

    /* Sub decs */
    struct
    {
        vlc_mutex_t lock;
        struct vlc_list list;
    } subdecs;

    /* CC */
    struct
    {
        /* All members guarded by subdecs.lock */
        size_t count;
        vlc_fourcc_t selected_codec;
        bool b_supported;
        decoder_cc_desc_t desc;
        bool desc_changed;
        bool b_sout_created;
        sout_packetizer_input_t *p_sout_input;
        char *sout_es_id;
    } cc;

    vlc_input_decoder_t *master_dec;

    struct vlc_list node;
};

/* 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_SeekPreviousFrame(vlc_input_decoder_t *owner, int steps,
                                      bool failed)
{
    vlc_fifo_Assert(owner->p_fifo);
    assert(steps != DEC_PF_SEEK_STEPS_NONE);

    if (steps > DEC_PF_SEEK_STEPS_MAX)
    {
        /* Too much failing attempt */
        decoder_Notify(owner, frame_previous_status, -ERANGE);
    }
    else
    {
        vlc_tick_t pts = owner->video.pf_pts;
        unsigned frame_rate = owner->fmt.video.i_frame_rate;
        unsigned frame_rate_base = owner->fmt.video.i_frame_rate_base;
        /* Request the input to seek back */
        decoder_Notify(owner, frame_previous_seek, pts,
                       frame_rate, frame_rate_base, steps, failed);
    }
}

static void Decoder_PausedForNextFrame(vlc_input_decoder_t *owner)
{
    vlc_fifo_Assert(owner->p_fifo);
    assert(owner->cat == VIDEO_ES);
    assert(owner->output_paused);

    if (owner->video.vout == NULL)
        return;

    if (likely(owner->frames_countdown <= 0))
        return;

    /* Handle all next-frame requests that were sent while the video was
    *  pausing */

    int next_request_count = owner->frames_countdown;
    owner->frames_countdown = vout_NextPicture(owner->video.vout, next_request_count);

    assert(next_request_count >= owner->frames_countdown);
    /* Notify for pictures that are processes by the vout */
    for (int i = 0; i < next_request_count - owner->frames_countdown; ++i)
        decoder_Notify(owner, frame_next_status, 0);
}

static void Decoder_DisplayPreviousFrame(vlc_input_decoder_t *owner, picture_t *pic)
{
    vlc_fifo_Assert(owner->p_fifo);

    if (owner->video.vout == NULL)
    {
        picture_Release(pic);
        return;
    }

    vout_PutPicture(owner->video.vout, pic);
    vout_NextPicture(owner->video.vout, 1);

    decoder_Notify(owner, frame_previous_status, 0);
}

static picture_t *Decoder_HandlePreviousFrame(vlc_input_decoder_t *owner,
                                              picture_t *pic)
{
    int seek_steps;
    pic = decoder_prevframe_AddPic(&owner->video.pf, pic,
                                   &owner->video.pf_pts, &seek_steps);
    if (pic != NULL)
    {
        owner->b_first = false;
        picture_t *resume_pic = pic->p_next;
        assert(resume_pic->p_next == NULL);
        pic->p_next = NULL;
        Decoder_DisplayPreviousFrame(owner, pic);
        /* Keep picture for normal playback or next-frame (if resumed) */
        pic = resume_pic;
    }

    if (seek_steps != DEC_PF_SEEK_STEPS_NONE)
        Decoder_SeekPreviousFrame(owner, seek_steps, pic == NULL);

    if (pic == NULL)
        return NULL;

    /* Wait for a new prev-frame request. If we don't wait, we will fill the
     * vout with frames following the prev-frame, they won't be displayed but
     * this will make next flush quite difficult to handle. When both
     * prev-frame and resumed frames are in the fifo, it's ambiguous which
     * frames should be flushed vs. retained during a flush operation. */

    while (owner->frames_countdown == -1
        && !decoder_prevframe_IsActive(&owner->video.pf))
        vlc_fifo_Wait(owner->p_fifo);

    if (decoder_prevframe_IsActive(&owner->video.pf))
    {
        picture_Release(pic);
        return NULL;
    }
    return pic;
}

static void Decoder_RequestFramePrevious(vlc_input_decoder_t *owner)
{
    vlc_fifo_Assert(owner->p_fifo);
    assert(owner->video.pf_pts != VLC_TICK_INVALID);

    int seek_steps;
    decoder_prevframe_Request(&owner->video.pf, &seek_steps);

    if (seek_steps != DEC_PF_SEEK_STEPS_NONE)
        Decoder_SeekPreviousFrame(owner, seek_steps, false);
    vlc_fifo_Signal(owner->p_fifo);
}

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;

    switch( p_dec->fmt_in->i_cat )
    {
        case VIDEO_ES:
            msg_Dbg( p_dec, "%s video", paused ? "pausing" : "resuming" );
            if( p_owner->video.vout != NULL && p_owner->video.started )
                vout_ChangePause( p_owner->video.vout, paused, date );
            break;
        case AUDIO_ES:
            msg_Dbg( p_dec, "%s audio", paused ? "pausing" : "resuming" );
            if( p_owner->audio.stream != NULL )
                vlc_aout_stream_ChangePause( p_owner->audio.stream, paused, date );
            break;
        case SPU_ES:
            msg_Dbg( p_dec, "not really %s SPU", paused ? "pausing" : "resuming" );
            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->video.vout != NULL && p_owner->video.started )
                vout_ChangeRate( p_owner->video.vout, rate );
            break;
        case AUDIO_ES:
            if( p_owner->audio.stream != NULL )
                vlc_aout_stream_ChangeRate( p_owner->audio.stream, rate );
            break;
        case SPU_ES:
            if( p_owner->spu.vout != NULL )
            {
                assert(p_owner->spu.channel != VOUT_SPU_CHANNEL_INVALID);
                vout_ChangeSpuRate(p_owner->spu.vout, p_owner->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->video.vout != NULL && p_owner->video.started )
                vout_ChangeDelay( p_owner->video.vout, delay );
            break;
        case AUDIO_ES:
            if( p_owner->audio.stream != NULL )
                vlc_aout_stream_ChangeDelay( p_owner->audio.stream, delay );
            break;
        case SPU_ES:
            if( p_owner->spu.vout != NULL )
            {
                assert(p_owner->spu.channel != VOUT_SPU_CHANNEL_INVALID);
                vout_ChangeSpuDelay(p_owner->spu.vout, p_owner->spu.channel,
                                    delay);
            }
            break;
        default:
            vlc_assert_unreachable();
    }
    p_owner->output_delay = delay;
}

static void Decoder_ReapplyOutState(vlc_input_decoder_t *owner)
{
    if (owner->output_paused)
        Decoder_ChangeOutputPause(owner, owner->output_paused, owner->pause_date);

    if (owner->output_rate != 1.f)
        Decoder_ChangeOutputRate(owner, owner->output_rate);

    if (owner->output_delay != 0)
        Decoder_ChangeOutputDelay(owner, owner->output_delay);
}

/**
 * Load a decoder module
 */

static int LoadDecoder(decoder_t *p_dec, bool b_packetizer, es_format_t *fmt_in)
{
    p_dec->b_frame_drop_allowed = true;

    decoder_LoadModule( p_dec, b_packetizer, true );
    if( !p_dec->p_module )
    {
        decoder_Clean( p_dec );
        es_format_Clean(fmt_in);
        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 */
    vlc_fifo_Unlock(p_owner->p_fifo);
    decoder_Clean( p_dec );
    vlc_fifo_Lock(p_owner->p_fifo);
    es_format_Clean( &p_owner->dec_fmt_in );
    p_owner->error = false;

    if( reload == RELOAD_DECODER_AOUT )
    {
        assert( p_owner->cat == AUDIO_ES );
        audio_output_t *p_aout = p_owner->audio.aout;
        vlc_aout_stream *p_astream = p_owner->audio.stream;
        // no need to lock, the decoder and ModuleThread are dead
        p_owner->audio.aout = NULL;
        p_owner->audio.stream = NULL;
        if( p_aout )
        {
            assert( p_astream );
            vlc_aout_stream_Delete( p_astream );
            input_resource_PutAout( p_owner->p_resource, p_aout );
        }
    }

    decoder_Init(p_dec, &p_owner->dec_fmt_in, &fmt_in);
    vlc_fifo_Unlock(p_owner->p_fifo);
    if (LoadDecoder(p_dec, false, &p_owner->dec_fmt_in))
    {
        vlc_fifo_Lock(p_owner->p_fifo);
        p_owner->error = true;
        es_format_Clean( &fmt_in );
        return VLC_EGENERIC;
    }
    vlc_fifo_Lock(p_owner->p_fifo);
    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 );
    assert( p_owner->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 );
    assert( owner->cat == VIDEO_ES );

    vlc_mutex_lock( &owner->video.mouse_lock );
    if( owner->video.mouse_event )
        owner->video.mouse_event( newmouse, owner->video.mouse_opaque);
    vlc_mutex_unlock( &owner->video.mouse_lock );
}

/*****************************************************************************
 * Buffers allocation callbacks for the decoders
 *****************************************************************************/

static int ModuleThread_UpdateAudioFormat( decoder_t *p_dec )
{
    vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
    assert( p_owner->cat == AUDIO_ES );

    if( p_owner->audio.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->audio.aout;
        vlc_aout_stream *p_astream = p_owner->audio.stream;

        /* Parameters changed, restart the aout */
        vlc_fifo_Lock(p_owner->p_fifo);
        p_owner->audio.stream = NULL;
        p_owner->audio.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( replay_gain_Compare( &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->audio.aout )
        {
            p_owner->fmt.audio_replay_gain = p_dec->fmt_in->audio_replay_gain;
            var_TriggerCallback( p_owner->audio.aout, "audio-replay-gain-mode" );
        }
    }

    if( p_owner->audio.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->audio.aout = p_aout;
        p_owner->audio.stream = p_astream;
        p_owner->audio.drained = false;

        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_ReapplyOutState( 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 );
    assert( p_owner->cat == VIDEO_ES );

    int created_vout = CreateVoutIfNeeded(p_owner);
    if (created_vout == -1)
        return -1; // error
    if (created_vout == 0)
    {
        // video context didn't change
        if (p_owner->video.vctx == vctx)
            return 0;
    }
    assert(p_owner->video.vout);

    if (p_owner->video.vctx)
        vlc_video_context_Release(p_owner->video.vctx);
    p_owner->video.vctx = vctx ? vlc_video_context_Hold(vctx) : NULL;

    // configure the new vout
    vlc_fifo_Lock(p_owner->p_fifo);
    if ( p_owner->video.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 ? */
        case VLC_CODEC_VVC:
            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;
        }
        size_t pic_count = dpb_size + p_dec->i_extra_picture_buffers;
        pic_count ++; /* Held by the vout */
        pic_count ++; /* Held by previous-frame handling or filters */
        picture_pool_t *pool = picture_pool_NewFromFormat( &p_dec->fmt_out.video,
                                                           pic_count );

        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->video.out_pool = pool;
    }

    vout_configuration_t cfg = {
        .vout = p_owner->video.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->video.started = true;

        if (vout_state == INPUT_RESOURCE_VOUT_STARTED)
        {
            Decoder_ReapplyOutState( p_owner );
            decoder_Notify(p_owner, on_vout_started, p_vout,
                           p_owner->video.vout_order);
        }
        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->video.vctx != NULL)
    {
        vlc_video_context_Release(p_owner->video.vctx);
        p_owner->video.vctx = NULL;
    }
    return -1;
}

static int CreateVoutIfNeeded(vlc_input_decoder_t *p_owner)
{
    decoder_t *p_dec = &p_owner->dec;
    assert( p_owner->cat == VIDEO_ES );
    bool need_vout = false;
    bool need_pool = false;

    vlc_fifo_Lock(p_owner->p_fifo);
    if( p_owner->video.vout == NULL )
    {
        msg_Dbg(p_dec, "vout: none found");
        need_vout = true;
        need_pool = 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;
        need_pool = 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;
        need_pool = true;
    }
    if( p_dec->fmt_out.i_codec != p_owner->fmt.video.i_chroma )
    {
        msg_Dbg(p_dec, "vout change: chroma");
        need_vout = true;
        need_pool = 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;
        need_pool = true;
    }
    if( p_dec->fmt_out.video.orientation != p_owner->fmt.video.orientation )
    {
        msg_Dbg(p_dec, "vout change: orientation");
        need_vout = true;
        need_pool = 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;
        // no need for a new pool if only this field changed
    }

    if( !need_vout && !need_pool )
    {
        vlc_fifo_Unlock(p_owner->p_fifo);
        return 0; // vout unchanged
    }

    vout_thread_t *p_vout = p_owner->video.vout;
    p_owner->video.vout = NULL; // the DecoderThread should not use the old vout anymore
    p_owner->video.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->video.vout = p_vout;
    p_owner->video.vout_order = order;

    DecoderUpdateFormatLocked( p_owner );
    p_owner->fmt.video.i_chroma = p_dec->fmt_out.i_codec;
    picture_pool_t *pool = NULL;
    if ( need_pool )
    {
        pool = p_owner->video.out_pool;
        p_owner->video.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 );
    assert( p_owner->cat == VIDEO_ES );

    /* Requesting a decoder device will automatically enable hw decoding */
    if (!p_owner->hw_dec)
        return NULL;

    int created_vout = CreateVoutIfNeeded(p_owner);
    if (created_vout == -1)
        return NULL;  // error

    assert(p_owner->video.vout);
    vlc_decoder_device *dec_device = vout_GetDevice(p_owner->video.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->cat == VIDEO_ES );
    assert( p_owner->video.vout );
    assert( p_owner->video.out_pool );

    picture_t *pic = picture_pool_Wait( p_owner->video.out_pool );

    if (pic)
    {
        picture_Reset( pic );
        pic->format.multiview_mode = p_dec->fmt_out.video.multiview_mode;
    }
    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 );
    assert( p_owner->cat == SPU_ES );
    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->spu.vout )
        {
            vlc_fifo_Lock( p_owner->p_fifo );
            assert(p_owner->spu.channel != VOUT_SPU_CHANNEL_INVALID);
            decoder_Notify(p_owner, on_vout_stopped, p_owner->spu.vout);

            vout_UnregisterSubpictureChannel(p_owner->spu.vout,
                                             p_owner->spu.channel);
            p_owner->spu.channel = VOUT_SPU_CHANNEL_INVALID;

            vout_Release(p_owner->spu.vout);
            p_owner->spu.vout = NULL; // the DecoderThread should not use the old vout anymore
            vlc_fifo_Unlock( p_owner->p_fifo );
        }
        return NULL;
    }

    if( p_owner->spu.vout != p_vout )
    {
        vlc_fifo_Lock(p_owner->p_fifo);

        if (p_owner->spu.vout) /* notify the previous vout deletion unlocked */
            decoder_Notify(p_owner, on_vout_stopped, p_owner->spu.vout);

        if (p_owner->spu.vout)
        {
            /* Unregister the SPU channel of the previous vout */
            assert(p_owner->spu.channel != VOUT_SPU_CHANNEL_INVALID);
            vout_UnregisterSubpictureChannel(p_owner->spu.vout,
                                             p_owner->spu.channel);
            vout_Release(p_owner->spu.vout);
            p_owner->spu.vout = NULL; // the DecoderThread should not use the old vout anymore
        }

        enum vlc_vout_order channel_order;
        p_owner->spu.channel =
            vout_RegisterSubpictureChannelInternal(p_vout, p_owner->p_clock,
                                                   &channel_order);
        p_owner->spu.order = 0;

        if (p_owner->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->spu.vout = p_vout;
        Decoder_ReapplyOutState(p_owner);

        assert(channel_order != VLC_VOUT_ORDER_NONE);
        decoder_Notify(p_owner, on_vout_started, p_vout, channel_order);
        vlc_fifo_Unlock(p_owner->p_fifo);
    }
    else
        vout_Release(p_vout);

    p_subpic = subpicture_New( p_updater );
    if( p_subpic )
    {
        p_subpic->i_channel = p_owner->spu.channel;
        p_subpic->i_order = p_owner->spu.order++;
    }

    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;

    vlc_clock_Lock( p_owner->p_clock );
    vlc_tick_t conv_ts =
        vlc_clock_ConvertToSystem( p_owner->p_clock, system_now, i_ts, rate, NULL );
    vlc_clock_Unlock( p_owner->p_clock );
    return conv_ts;
}

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 int DecoderWaitUnblock(vlc_input_decoder_t *p_owner, vlc_tick_t date)
{
    struct vlc_tracer *tracer = vlc_object_get_tracer(VLC_OBJECT(&p_owner->dec));
    vlc_fifo_Assert(p_owner->p_fifo);

    if( p_owner->b_waiting )
    {
        if (tracer != NULL)
            vlc_tracer_TraceEvent(tracer, "DEC", p_owner->psz_id, "start wait");
        p_owner->b_has_data = true;
        vlc_cond_signal( &p_owner->wait_acknowledge );
    }

    while (p_owner->b_waiting && p_owner->b_has_data && !p_owner->flushing)
        vlc_fifo_WaitCond(p_owner->p_fifo, &p_owner->wait_request);

    if (!p_owner->out_started)
    {
        p_owner->out_started = true;
        if (tracer != NULL)
            vlc_tracer_TraceEvent(tracer, "DEC", p_owner->psz_id, "stop wait");

        vlc_clock_Lock(p_owner->p_clock);
        vlc_clock_Start(p_owner->p_clock, vlc_tick_now(), date);
        vlc_clock_Unlock(p_owner->p_clock);
    }

    if (p_owner->flushing)
    {
        p_owner->b_has_data = false;
        vlc_cond_signal(&p_owner->wait_acknowledge);
        return VLC_ENOENT;
    }

    return VLC_SUCCESS;
}

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 );
}

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;

        /* Create only one es ID since we simply send the CC whole side data
         * un-decoded to the stream output.
         * Specifying the owner's ID is important for the uniqueness for the ID.
         */

        if (asprintf(&p_owner->cc.sout_es_id, "%s/cc", p_owner->psz_id) == -1)
        {
            es_format_Clean(&ccfmt);
            block_Release(p_cc);
            return;
        }

        p_owner->cc.p_sout_input =
            sout_InputNew(p_owner->p_sout, &ccfmt, p_owner->cc.sout_es_id);

        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, p_owner->psz_id );

            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;
        }
    }
}

static void GetCcChannels(vlc_input_decoder_t *owner, size_t *max_channels,
                          uint64_t *bitmap)
{
    switch (owner->cc.selected_codec)
    {
        case VLC_CODEC_CEA608:
            if (max_channels != NULL)
                *max_channels = 4;
            if (bitmap != NULL)
                *bitmap = owner->cc.desc.i_608_channels;
            break;
        case VLC_CODEC_CEA708:
            if (max_channels != NULL)
                *max_channels = 64;
            if (bitmap != NULL)
                *bitmap = owner->cc.desc.i_708_channels;
            break;
        default: vlc_assert_unreachable();
    }
}

static bool SubDecoderIsCc(vlc_input_decoder_t *subdec)
{
    return subdec->cat == SPU_ES &&
            (subdec->dec.fmt_in->i_codec == VLC_CODEC_CEA608 ||
             subdec->dec.fmt_in->i_codec == VLC_CODEC_CEA708);
}

/* */
static void DecoderPlayCcLocked( vlc_input_decoder_t *p_owner, vlc_frame_t *p_cc,
                                 const decoder_cc_desc_t *p_desc )
{
    vlc_fifo_Assert(p_owner->p_fifo);
    if (p_owner->flushing || p_owner->aborting)
    {
        vlc_frame_Release(p_cc);
        return;
    }

    vlc_mutex_lock(&p_owner->subdecs.lock);
    if (p_owner->cc.desc.i_608_channels != p_desc->i_608_channels ||
        p_owner->cc.desc.i_708_channels != p_desc->i_708_channels ||
        p_owner->cc.desc.i_reorder_depth != p_desc->i_reorder_depth)
    {
        p_owner->cc.desc = *p_desc;
        p_owner->cc.desc_changed = true;
    }

    if (p_owner->cc.count == 0)
        goto end;

    size_t cc_idx = 0;
    vlc_input_decoder_t *it;

    vlc_list_foreach(it, &p_owner->subdecs.list, node)
    {
        if (!SubDecoderIsCc(it))
            continue;

        if (++cc_idx == p_owner->cc.count)
        {
            block_FifoPut(it->p_fifo, p_cc);
            p_cc = NULL;
        }
        else
        {
            block_t *dup = block_Duplicate(p_cc);
            if (dup == NULL)
                break;
            block_FifoPut(it->p_fifo, dup);
        }
    }

end:
    vlc_mutex_unlock(&p_owner->subdecs.lock);

    if (p_cc != NULL) /* 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;

    DecoderPlayCcLocked( 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))
        return;

    if (!p_owner->cc.b_supported ||
       (p_owner->p_packetizer != NULL && p_owner->p_packetizer->pf_get_cc != NULL))
    {
        block_Release(p_cc);
        return;
    }

    vlc_fifo_Lock(p_owner->p_fifo);
    DecoderPlayCcLocked(p_owner, p_cc, p_desc);
    vlc_fifo_Unlock(p_owner->p_fifo);
}

static int ModuleThread_PlayVideo( vlc_input_decoder_t *p_owner, picture_t *p_picture )
{
    decoder_t *p_dec = &p_owner->dec;
    assert( p_owner->cat == VIDEO_ES );

    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->video.vout;

    assert( p_owner->video.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 );
    }

    /* previous-frame handling */
    if (unlikely(p_owner->paused) && p_vout != NULL &&
        decoder_prevframe_IsActive(&p_owner->video.pf))
    {
        p_picture = Decoder_HandlePreviousFrame(p_owner, p_picture);
        if (p_picture == NULL)
            return VLC_ENOENT;
    }

    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
    {
        int ret = DecoderWaitUnblock(p_owner, p_picture->date);
        if (ret != VLC_SUCCESS)
        {
            picture_Release(p_picture);
            return ret;
        }
    }

    /* */
    if( p_vout == NULL )
    {
        picture_Release( p_picture );
        return VLC_EGENERIC;
    }

    if (unlikely(p_owner->output_paused && p_owner->frames_countdown > 0))
    {
        p_owner->frames_countdown--;
        vout_PutPicture(p_vout, p_picture);
        decoder_Notify(p_owner, frame_next_status, 0);
        return VLC_SUCCESS;
    }

    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 );
    assert( p_owner->cat == VIDEO_ES );
    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->video.vout != NULL )
    {
        vout_GetResetStatistic( p_owner->video.vout, &displayed, &vout_lost,
                                &vout_late );
    }
    if (success != VLC_SUCCESS)
        vout_lost++;

    decoder_Notify(p_owner, on_new_video_stats, 1, vout_lost, displayed, vout_late);
    vlc_fifo_Unlock(p_owner->p_fifo);
}

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 );

    vlc_fifo_Lock(p_owner->p_fifo);
    if( p_owner->b_first )
        decoder_Notify(p_owner, on_thumbnail_ready, p_pic);
    p_owner->b_first = false;
    vlc_fifo_Unlock(p_owner->p_fifo);

    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_owner->cat == AUDIO_ES );

    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->audio.stream;
    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 );
    }

    int ret = DecoderWaitUnblock(p_owner, p_audio->i_pts);
    if (ret != VLC_SUCCESS)
    {
        block_Release(p_audio);
        return ret;
    }

    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 );
    assert( p_owner->cat == AUDIO_ES );
    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->audio.stream != NULL )
    {
        vlc_aout_stream_GetResetStats( p_owner->audio.stream, &aout_lost, &played );
    }
    if (success != VLC_SUCCESS)
        aout_lost++;

    decoder_Notify(p_owner, on_new_audio_stats, 1, aout_lost, played);
    vlc_fifo_Unlock(p_owner->p_fifo);
}

static void ModuleThread_PlaySpu( vlc_input_decoder_t *p_owner, subpicture_t *p_subpic )
{
    decoder_t *p_dec = &p_owner->dec;
    assert( p_owner->cat == SPU_ES );
    vout_thread_t *p_vout = p_owner->spu.vout;

    /* */
    if( p_subpic->i_start == VLC_TICK_INVALID )
    {
        msg_Warn( p_dec, "non-dated spu buffer received" );
        subpicture_Delete( p_subpic );
        return;
    }

    /* */
    int ret = DecoderWaitUnblock(p_owner, p_subpic->i_start);

    if (ret != VLC_SUCCESS || 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 );
    assert( p_owner->cat == SPU_ES );
    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->spu.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 ) )
        subpicture_Delete( p_spu );
    else
        ModuleThread_PlaySpu( p_owner, p_spu );
    vlc_fifo_Unlock(p_owner->p_fifo);
}

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 );

    vlc_fifo_Unlock(p_owner->p_fifo);

    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 );

    vlc_fifo_Lock(p_owner->p_fifo);
    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_owner the input 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;

        DecoderUpdatePreroll( &p_owner->i_preroll_end, frame );
        if( unlikely( frame->i_flags & BLOCK_FLAG_CORE_PRIVATE_RELOADED ) )
        {
            /* This frame has already been packetized */
            packetize = false;
        }
    }

    if( p_owner->p_sout != NULL )
    {
        vlc_fifo_Unlock(p_owner->p_fifo);
        DecoderThread_ProcessSout( p_owner, frame );
        vlc_fifo_Lock(p_owner->p_fifo);
        return;
    }
    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");
                es_format_LogDifferences( vlc_object_logger(p_dec),
                                          "decoder in", p_dec->fmt_in,
                                          "packetizer out", &p_packetizer->fmt_out );

                /* 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_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 );

    p_owner->error = false;
}

static void Decoder_VideoDrained(vlc_input_decoder_t *owner)
{
    owner->video.drained = true;
    if (owner->frames_countdown > 0)
    {
        if (unlikely(vout_IsEmpty(owner->video.vout)))
        {
            /* Unlikely case where all pictures are already sent to the vout
             * next queue while draining. This could happen with a burst of
             * next-frame request near EOF. */

            owner->frames_countdown = 0;
            decoder_Notify(owner, frame_next_status, -EAGAIN);
        }
    }
    else if(owner->frames_countdown == -1)
    {
        /* Also check if we need to increase seek steps near EOF */
        int seek_steps;
        picture_t *nullpic =
            decoder_prevframe_AddPic(&owner->video.pf, NULL,
                                     &owner->video.pf_pts, &seek_steps);
        assert(nullpic == NULL); (void) nullpic;
        if (seek_steps != DEC_PF_SEEK_STEPS_NONE)
            Decoder_SeekPreviousFrame(owner, seek_steps, true);
    }
}

/**
 * The decoding main loop
 *
 * \param p_data the input decoder object
 */

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->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 || p_owner->flushing )
    {
        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->out_started = false;
            p_owner->i_preroll_end = PREROLL_NONE;
            continue;
        }

        /* Also compare the request dates: a pause and a resume requested in
         * quick succession can cancel each other out, but each request must
         * still be acknowledged via decoder_Notify() */

        if( p_owner->paused != p_owner->output_paused
         || p_owner->pause_date != p_owner->output_pause_date )
        {   /* Update playing/paused status of the output */
            if( p_owner->paused != p_owner->output_paused )
                Decoder_ChangeOutputPause( p_owner, p_owner->paused,
                                           p_owner->pause_date );
            p_owner->output_pause_date = p_owner->pause_date;
            decoder_Notify(p_owner, on_output_paused, p_owner->paused,
                           p_owner->pause_date);
            if (unlikely(p_owner->paused && p_owner->cat == VIDEO_ES
                      && p_owner->frames_countdown != 0))
                Decoder_PausedForNextFrame(p_owner);
            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 );

                if (p_owner->frames_countdown > 0)
                {
                    /* next-frames are requested but the FIFO is empty, ask for
                     * more buffering */

                    decoder_Notify( p_owner, frame_next_need_data, true );
                }
                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. */

        }

        /* DecoderThread_ProcessInput will unlock when playing to the decoders
         * but will ensure it re-locks in the end. This is necessary to handle
         * reloading, CC and packetizing. */

        DecoderThread_ProcessInput( p_owner, frame );

        if( p_owner->b_draining && frame == NULL )
        {
            p_owner->b_draining = false;

            switch (p_owner->cat)
            {
                case AUDIO_ES:
                    if( p_owner->audio.stream != NULL
                     && !p_owner->audio.drained )
                    {
                        /* 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->audio.stream );
                        p_owner->audio.drained = true;
                    }
                    break;
                case VIDEO_ES:
                    Decoder_VideoDrained(p_owner);
                    break;
                default:
                    break;
            }
        }

        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_parent a VLC parent object to inherit variable from
 * \param cfg the input decoder configuration
 * \return a new input 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->hw_dec = cfg->hw_dec;
    p_owner->cbs = cfg->cbs;
    p_owner->cbs_userdata = cfg->cbs_data;
    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->output_pause_date = 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->out_started = 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->cat = fmt->i_cat;

    es_format_Init( &p_owner->fmt, p_owner->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_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 )
        {
            decoder_Init(p_owner->p_packetizer, &p_owner->pktz_fmt_in, fmt);
            if (LoadDecoder(p_owner->p_packetizer, true, &p_owner->pktz_fmt_in))
            {
                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:
            p_owner->video.vout = NULL;
            p_owner->video.started = false;
            p_owner->video.drained = false;
            vlc_mutex_init( &p_owner->video.mouse_lock );
            p_owner->video.mouse_event = NULL;
            p_owner->video.mouse_opaque = NULL;

            decoder_prevframe_Init( &p_owner->video.pf );
            p_owner->video.pf_pts = VLC_TICK_INVALID;

            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_owner->audio.aout = NULL;
            p_owner->audio.stream = NULL;
            p_owner->audio.drained = false;
            p_dec->cbs = &dec_audio_cbs;
            break;
        case SPU_ES:
            p_owner->spu.vout = NULL;
            p_owner->spu.channel = VOUT_SPU_CHANNEL_INVALID;
            p_owner->spu.order = 0;
            p_dec->cbs = &dec_spu_cbs;
            break;
        default:
            msg_Err( p_dec, "unknown ES format" );
            return p_owner;
    }

    /* Find a suitable decoder/packetizer module */
    decoder_Init(p_dec, &p_owner->dec_fmt_in, fmt);
    if (LoadDecoder(p_dec, cfg->sout != NULL, &p_owner->dec_fmt_in))
        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 )
    {
        replay_gain_Merge( &p_dec->fmt_out.audio_replay_gain, &fmt->audio_replay_gain );
    }

    /* */
    vlc_mutex_init(&p_owner->subdecs.lock);
    p_owner->cc.selected_codec = cfg->cc_decoder == 708 ?
                                 VLC_CODEC_CEA708 : VLC_CODEC_CEA608;
    p_owner->cc.b_supported = ( cfg->sout == NULL );

    p_owner->cc.desc.i_608_channels = 0;
    p_owner->cc.desc.i_708_channels = 0;
    vlc_list_init(&p_owner->subdecs.list);
    p_owner->cc.count = 0;
    p_owner->cc.p_sout_input = NULL;
    p_owner->cc.sout_es_id = NULL;
    p_owner->cc.b_sout_created = false;
    p_owner->master_dec = NULL;
    return p_owner;
}

/**
 * Destroys a decoder object
 *
 * \param p_owner the input decoder object
 * \param i_cat the elementary stream format category for the decoder
 */

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->p_packetizer != NULL )
        decoder_Clean( p_owner->p_packetizer );

    /* Free all packets still in the decoder fifo. */
    block_FifoEmpty( p_owner->p_fifo );

    /* Cleanup */
    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 );
        free( p_owner->cc.sout_es_id );
    }

    switch( i_cat )
    {
        case AUDIO_ES:
            if( p_owner->audio.aout )
            {
                /* TODO: REVISIT gap-less audio */
                assert( p_owner->audio.stream );
                vlc_aout_stream_Delete( p_owner->audio.stream );
                input_resource_PutAout( p_owner->p_resource, p_owner->audio.aout );
            }
            break;
        case VIDEO_ES: {
            vout_thread_t *vout = p_owner->video.vout;

            if ( p_owner->video.out_pool )
                picture_pool_Release( p_owner->video.out_pool );

            if (p_owner->video.vctx)
                vlc_video_context_Release( p_owner->video.vctx );

            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->spu.vout )
            {
                assert( p_owner->spu.channel != VOUT_SPU_CHANNEL_INVALID );
                decoder_Notify(p_owner, on_vout_stopped, p_owner->spu.vout);

                vout_UnregisterSubpictureChannel( p_owner->spu.vout,
                                                  p_owner->spu.channel );
                vout_Release(p_owner->spu.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 );
    if( p_owner->p_packetizer != NULL )
        vlc_object_delete( p_owner->p_packetizer );
    vlc_object_delete( &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 );

    /* 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, p_owner->psz_id );
        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;
        }
    }

    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 parent the VLC object to inherit variable from
 * \param cfg the input decoder configuration
 * \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 );
}

vlc_input_decoder_t *
vlc_input_decoder_Create( vlc_object_t *p_parent, const es_format_t *fmt, const char *es_id,
                          struct vlc_clock_t *clock, input_resource_t *p_resource )
{
    const struct vlc_input_decoder_cfg cfg = {
        .fmt = fmt,
        .str_id = es_id,
        .clock = clock,
        .resource = p_resource,
        .sout = NULL,
        .input_type = INPUT_TYPE_PLAYBACK,
        .hw_dec = var_InheritBool( p_parent, "hw-dec" ),
        .cbs = NULL, .cbs_data = NULL,
    };
    return decoder_New( p_parent, &cfg );
}

static void RemoveCcDecoder(vlc_input_decoder_t *owner,
                            vlc_input_decoder_t *subdec)
{
    vlc_mutex_lock(&owner->subdecs.lock);

    vlc_input_decoder_t *it;
    vlc_list_foreach(it, &owner->subdecs.list, node)
        if (it == subdec)
        {
            vlc_list_remove(&it->node);
            owner->cc.count--;
            vlc_mutex_unlock(&owner->subdecs.lock);
            return;
        }

    vlc_assert_unreachable();
}

void vlc_input_decoder_Delete( vlc_input_decoder_t *p_owner )
{
    if (p_owner->master_dec != NULL)
        RemoveCcDecoder(p_owner->master_dec, p_owner);

    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 );

#ifndef NDEBUG
    vlc_mutex_lock(&p_owner->subdecs.lock);
    assert(vlc_list_is_empty(&p_owner->subdecs.list));
    assert(p_owner->cc.count == 0);
    vlc_mutex_unlock(&p_owner->subdecs.lock);
#endif

    /* Delete decoder */
    DeleteDecoder(p_owner, p_owner->dec_fmt_in.i_cat);
}

void vlc_subdec_desc_Clean(struct vlc_subdec_desc *desc)
{
    for (size_t i = 0; i < desc->fmt_count; ++i)
        es_format_Clean(&desc->fmt_array[i]);
    free(desc->fmt_array);
}

static void GetCCDescLocked(vlc_input_decoder_t *owner,
                            struct vlc_subdec_desc *desc)
{
    vlc_fifo_Assert(owner->p_fifo);

    if (!owner->cc.desc_changed)
    {
        desc->fmt_array = NULL;
        desc->fmt_count = 0;
        return;
    }
    owner->cc.desc_changed = false;

    size_t max_channels;
    uint64_t bitmap;
    GetCcChannels(owner, &max_channels, &bitmap);

    int count = vlc_popcount(bitmap);
    if (count == 0)
    {
        desc->fmt_array = NULL;
        desc->fmt_count = 0;
        return;
    }

    desc->fmt_array = vlc_alloc(count, sizeof(es_format_t));
    if (unlikely(desc->fmt_array == NULL))
    {
        desc->fmt_count = 0;
        return;
    }
    desc->fmt_count = count;

    static const char CEA608_fmtdesc[] = N_("Closed captions %u");
    static const char CEA708_fmtdesc[] = N_("DTVCC Closed captions %u");
    const char *fmtdesc = owner->cc.selected_codec == VLC_CODEC_CEA608 ?
                          CEA608_fmtdesc : CEA708_fmtdesc;

    size_t array_idx = 0;
    for (int i = 0; bitmap > 0; bitmap >>= 1, i++)
    {
        if ((bitmap & 1) == 0)
            continue;
        es_format_t *fmt = &desc->fmt_array[array_idx++];
        es_format_Init(fmt, SPU_ES, owner->cc.selected_codec);
        fmt->i_id = i + 1;
        fmt->subs.cc.i_channel = i;
        fmt->subs.cc.i_reorder_depth = owner->cc.desc.i_reorder_depth;
        if (asprintf(&fmt->psz_description, fmtdesc, fmt->i_id) == -1)
            fmt->psz_description = NULL;
    }
}

static void GetStatusLocked(vlc_input_decoder_t *p_owner,
                            struct vlc_input_decoder_status *status)
{
    vlc_fifo_Assert(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 );
        }
    }
    GetCCDescLocked(p_owner, &status->subdec_desc);
}

void vlc_input_decoder_DecodeWithStatus(vlc_input_decoder_t *p_owner, vlc_frame_t *frame,
                                        bool b_do_pace,
                                        struct vlc_input_decoder_status *status)
{
    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 ) );
        vlc_fifo_Lock(p_owner->p_fifo);
        DecoderThread_ProcessInput( p_owner, frame );
        if (status != NULL)
            GetStatusLocked(p_owner, status);
        vlc_fifo_Unlock(p_owner->p_fifo);
        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 );
    }

    if (vlc_fifo_IsEmpty(p_owner->p_fifo) && p_owner->frames_countdown > 0)
        decoder_Notify(p_owner, frame_next_need_data, false);

    vlc_fifo_QueueUnlocked( p_owner->p_fifo, frame );
    if (status != NULL)
        GetStatusLocked(p_owner, status);

    struct vlc_tracer *tracer = vlc_object_get_tracer(&p_owner->dec.obj);
    if (tracer != NULL)
    {
        size_t fifo_size = vlc_fifo_GetBytes(p_owner->p_fifo);
        size_t fifo_count = vlc_fifo_GetCount(p_owner->p_fifo);
        vlc_tracer_Trace(tracer,
                         VLC_TRACE("id", p_owner->psz_id),
                         VLC_TRACE("fifo_size", (uint64_t)fifo_size),
                         VLC_TRACE("fifo_count", (uint64_t)fifo_count),
                         VLC_TRACE_END);
    }


    vlc_fifo_Unlock( p_owner->p_fifo );
}

void vlc_input_decoder_Decode(vlc_input_decoder_t *p_owner, vlc_frame_t *frame,
                              bool b_do_pace)
{
    vlc_input_decoder_DecodeWithStatus(p_owner, frame, b_do_pace, NULL);
}

static bool vlc_input_decoder_IsDrainedLocked(vlc_input_decoder_t *owner)
{
    vlc_fifo_Assert(owner->p_fifo);

    if (owner->b_draining)
        return false;
    else if (owner->p_sout_input != NULL)
        return true;
    else if (owner->cat == VIDEO_ES && owner->video.vout != NULL)
        return vout_IsEmpty(owner->video.vout);
    else if(owner->cat == AUDIO_ES && owner->audio.stream != NULL)
        return vlc_aout_stream_IsDrained( owner->audio.stream);
    else
        return true; /* TODO subtitles support */
}

bool vlc_input_decoder_IsDrained(vlc_input_decoder_t *owner)
{
    vlc_fifo_Lock(owner->p_fifo);
    bool drained = vlc_input_decoder_IsDrainedLocked(owner);
    vlc_fifo_Unlock(owner->p_fifo);
    return drained;
}

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 ) )
    {
        vlc_fifo_Unlock( p_owner->p_fifo );
        return false;
    }

    bool b_empty = vlc_input_decoder_IsDrainedLocked( p_owner );

    vlc_fifo_Unlock( p_owner->p_fifo );

    return b_empty;
}

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. */
        vlc_fifo_Lock(p_owner->p_fifo);
        DecoderThread_ProcessInput( p_owner, NULL );
        vlc_fifo_Unlock(p_owner->p_fifo);
        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 );
}

void vlc_input_decoder_Flush( vlc_input_decoder_t *p_owner )
{
    vlc_fifo_Lock( p_owner->p_fifo );
    enum es_format_category_e cat = p_owner->cat;

    /* 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 = 1;

    if ( p_owner->p_sout_input != NULL )
    {
        sout_InputFlush( p_owner->p_sout, p_owner->p_sout_input );
    }
    else if( cat == AUDIO_ES )
    {
        p_owner->audio.drained = false;
        if( p_owner->audio.stream )
            vlc_aout_stream_Flush( p_owner->audio.stream );
    }
    else if( cat == VIDEO_ES )
    {
        p_owner->video.drained = false;
        if( p_owner->video.vout && p_owner->video.started
         && p_owner->frames_countdown != -1 )
        {
            /* prev-frame: don't flush if frames_countdown == -1. If requests
             * are sent in a burst, we want to avoid flushing the previous
             * frame that is being displayed. */

            vout_FlushAll( p_owner->video.vout );
        }
        decoder_prevframe_Flush( &p_owner->video.pf );
    }
    else if( cat == SPU_ES )
    {
        if( p_owner->spu.vout )
        {
            assert( p_owner->spu.channel != VOUT_SPU_CHANNEL_INVALID );
            vout_FlushSubpictureChannel( p_owner->spu.vout, p_owner->spu.channel );
        }
    }
    vlc_fifo_Signal( p_owner->p_fifo );

    if (unlikely(p_owner->b_waiting && p_owner->b_has_data))
    {
        /* Signal the output thread to stop waiting from DecoderWaitUnblock()
         * and to discard the current frame (via 'flushing' = true). */

        vlc_cond_signal(&p_owner->wait_request);

        /* Flushing is fully asynchronous, but we need to wait for the output
         * thread to unblock in DecoderWaitUnblock() otherwise there are no
         * ways to know if the frame referenced when waiting comes from before
         * or after the flush. Waiting here is almost instantaneous since we
         * are sure that the output thread is waiting in DecoderWaitUnblock().
         */

        while (p_owner->b_has_data)
            vlc_fifo_WaitCond(p_owner->p_fifo, &p_owner->wait_acknowledge);
    }

    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);
    }
}

vlc_input_decoder_t *
vlc_input_decoder_CreateSubDec(vlc_input_decoder_t *p_owner,
                               const struct vlc_input_decoder_cfg *cfg)
{
    decoder_t *p_dec = &p_owner->dec;

    vlc_mutex_lock(&p_owner->subdecs.lock);

    vlc_input_decoder_t *p_ccowner;

    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->subdecs.lock);
        return NULL;
    }
    assert(p_ccowner->dec.p_module != NULL);

    vlc_list_append(&p_ccowner->node, &p_owner->subdecs.list);
    p_owner->cc.count++;
    p_ccowner->master_dec = p_owner;

    vlc_mutex_unlock(&p_owner->subdecs.lock);
    return p_ccowner;
}

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 ) )
        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);
}

static void StopFrameNextLocked(vlc_input_decoder_t *owner)
{
    decoder_prevframe_Reset(&owner->video.pf);
    owner->video.pf_pts = VLC_TICK_INVALID;
    if (owner->frames_countdown == -1)
        owner->frames_countdown = 0;
    vlc_fifo_Signal(owner->p_fifo);
}

void vlc_input_decoder_StopFrameNext(vlc_input_decoder_t *owner)
{
    vlc_fifo_Lock(owner->p_fifo);
    StopFrameNextLocked(owner);
    vlc_fifo_Unlock(owner->p_fifo);
}

void vlc_input_decoder_FrameNext( vlc_input_decoder_t *p_owner )
{
    assert( p_owner->paused );
    assert( p_owner->cat == VIDEO_ES );

    vlc_fifo_Lock( p_owner->p_fifo );

    if( p_owner->video.vout == NULL )
    {
        decoder_Notify( p_owner, frame_next_status, -EBUSY );
        vlc_fifo_Unlock( p_owner->p_fifo );
        return;
    }

    if( unlikely( p_owner->frames_countdown == INT_MAX ) )
    {
        decoder_Notify( p_owner, frame_next_status, -EINVAL );
        vlc_fifo_Unlock( p_owner->p_fifo );
        return;
    }

    if( p_owner->video.drained && vout_IsEmpty( p_owner->video.vout ) )
    {
        p_owner->frames_countdown = 0;
        decoder_Notify( p_owner, frame_next_status, -EAGAIN );
        vlc_fifo_Unlock( p_owner->p_fifo );
        return;
    }

    StopFrameNextLocked( p_owner );

    if (!p_owner->output_paused)
    {
        /* Request will be handled when paused, from
         * Decoder_PausedForNextFrame() */

        p_owner->frames_countdown++;
        vlc_fifo_Unlock( p_owner->p_fifo );
        return;
    }

    size_t needed_count = vout_NextPicture(p_owner->video.vout, 1);
    assert(p_owner->frames_countdown >= 0);
    if (needed_count > (unsigned) p_owner->frames_countdown)
        p_owner->frames_countdown++;
    else
        decoder_Notify(p_owner, frame_next_status, 0);

    vlc_fifo_Unlock( p_owner->p_fifo );
}

void vlc_input_decoder_FramePrevious(vlc_input_decoder_t *owner)
{
    assert(owner->paused);
    assert(owner->cat == VIDEO_ES);

    vlc_fifo_Lock(owner->p_fifo);

    if (!owner->video.started)
    {
        decoder_Notify(owner, frame_previous_status, -EBUSY);
        vlc_fifo_Unlock(owner->p_fifo);
        return;
    }

    if (owner->frames_countdown != -1)
    {
        owner->frames_countdown = -1;

        owner->video.pf_pts = vout_FlushAll(owner->video.vout);
    }

    if (owner->video.pf_pts == VLC_TICK_INVALID)
    {
        /* No frame displayed yet (that's a success) */
        decoder_Notify(owner, frame_previous_status, 0);
        vlc_fifo_Unlock(owner->p_fifo);
        return;
    }

    Decoder_RequestFramePrevious(owner);

    vlc_fifo_Unlock(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->cat == VIDEO_ES );

    vlc_mutex_lock( &owner->video.mouse_lock );

    owner->video.mouse_event = mouse_event;
    owner->video.mouse_opaque = user_data;

    vlc_mutex_unlock( &owner->video.mouse_lock );
}

int vlc_input_decoder_AddVoutOverlay( vlc_input_decoder_t *owner, subpicture_t *sub,
                                      size_t *channel )
{
    assert( owner->cat == VIDEO_ES );
    assert( sub && channel );

    vlc_fifo_Lock(owner->p_fifo);

    if( !owner->video.vout )
    {
        vlc_fifo_Unlock(owner->p_fifo);
        return VLC_EGENERIC;
    }
    ssize_t channel_id =
        vout_RegisterSubpictureChannel( owner->video.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->video.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->cat == VIDEO_ES );

    vlc_fifo_Lock(owner->p_fifo);

    if( !owner->video.vout )
    {
        vlc_fifo_Unlock(owner->p_fifo);
        return VLC_EGENERIC;
    }
    vout_UnregisterSubpictureChannel( owner->video.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->cat == SPU_ES );

    if( p_owner->p_sout_input )
        sout_InputControl( p_owner->p_sout, p_owner->p_sout_input,
                           SOUT_INPUT_SET_SPU_HIGHLIGHT, spu_hl );

    vlc_fifo_Lock(p_owner->p_fifo);
    if( !p_owner->spu.vout )
    {
        vlc_fifo_Unlock(p_owner->p_fifo);
        return VLC_EGENERIC;
    }

    vout_SetSpuHighlight( p_owner->spu.vout, spu_hl );

    vlc_fifo_Unlock(p_owner->p_fifo);
    return VLC_SUCCESS;
}

Messung
CHZG
84939791

¤ Dauer der Verarbeitung: 0.50 Sekunden  (vorverarbeitet am  2026-11-04) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.