/*****************************************************************************
 * input_decoder_scenario.c: testflight for input_decoder state machine
 *****************************************************************************
 * Copyright (C) 2022-2023 VideoLabs
 *
 * Author: Alexandre Janniaux <ajanni@videolabs.io>
 *
 * 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.
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#define MODULE_STRING "test_input_decoder_mock"

#include <vlc_common.h>
#include <vlc_messages.h>
#include <vlc_player.h>
#include <vlc_interface.h>
#include <vlc_frame.h>
#include <vlc_codec.h>
#include <vlc_filter.h>
#include <vlc_vout_display.h>

#include "input_decoder.h"

static struct scenario_data
{
    vlc_sem_t wait_stop;
    vlc_sem_t wait_ready_to_flush;
    struct vlc_video_context *decoder_vctx;
    bool skip_decoder;
    bool has_reload;
    bool stream_out_sent;
    size_t decoder_image_sent;
    size_t cc_track_idx;
} scenario_data;

static void decoder_fixed_size(decoder_t *dec, vlc_fourcc_t chroma,
        unsigned width, unsigned height)
{
    dec->fmt_out.video.i_chroma
        = dec->fmt_out.i_codec
        = chroma;
    dec->fmt_out.video.i_visible_width
        = dec->fmt_out.video.i_width
        = width;
    dec->fmt_out.video.i_visible_height
        = dec->fmt_out.video.i_height
        = height;
}

static void decoder_i420_800_600(decoder_t *dec)
    { decoder_fixed_size(dec, VLC_CODEC_I420, 800, 600); }

static void decoder_i420_800_600_update(decoder_t *dec)
{
    decoder_fixed_size(dec, VLC_CODEC_I420, 800, 600);
    int ret = decoder_UpdateVideoOutput(dec, NULL);
    assert(ret == VLC_SUCCESS);
}

static void decoder_i420_800_600_stop(decoder_t *dec)
{
    decoder_i420_800_600(dec);
    vlc_sem_post(&scenario_data.wait_stop);
}

static const char cc_block_input[] = "cc01_input";
static vlc_frame_t *create_cc_frame(vlc_tick_t ts)
{
    vlc_frame_t *f = vlc_frame_Alloc(sizeof(cc_block_input));
    assert(f != NULL);
    memcpy(f->p_buffer, cc_block_input, sizeof(cc_block_input));
    f->i_dts = f->i_pts = ts;
    f->i_length = VLC_TICK_FROM_MS(20);
    return f;
}

static void decoder_decode_check_cc_common(decoder_t *dec, picture_t *pic)
{
    vlc_tick_t date = pic->date;

    vlc_frame_t *cc = create_cc_frame(date);

    decoder_cc_desc_t desc = {
        .i_608_channels = 1,
        .i_reorder_depth = 4,
    };
    decoder_QueueCc(dec, cc, &desc);
}

static int decoder_decode_check_cc(decoder_t *dec, picture_t *pic)
{
    decoder_decode_check_cc_common(dec, pic);
    picture_Release(pic);

    vlc_sem_post(&scenario_data.wait_ready_to_flush);

    return VLC_SUCCESS;
}

static int decoder_decode_check_cc_queue_video(decoder_t *dec, picture_t *pic)
{
    decoder_decode_check_cc_common(dec, pic);
    decoder_QueueVideo(dec, pic);

    return VLC_SUCCESS;
}

struct picture_watcher_context {
    picture_context_t context;
    vlc_sem_t wait_picture;
    vlc_sem_t wait_prepare;
    vlc_atomic_rc_t rc;
};

static void context_destroy(picture_context_t *context)
{
    struct picture_watcher_context *watcher =
        container_of(context, struct picture_watcher_context, context);

    if (vlc_atomic_rc_dec(&watcher->rc))
        vlc_sem_post(&watcher->wait_picture);
}

static picture_context_t * context_copy(picture_context_t *context)
{
    struct picture_watcher_context *watcher =
        container_of(context, struct picture_watcher_context, context);
    vlc_atomic_rc_inc(&watcher->rc);
    return context;
}

static int decoder_decode_drop(decoder_t *dec, picture_t *pic)
{
    (void)dec;
    picture_Release(pic);
    return VLC_SUCCESS;
}

static int decoder_decode(decoder_t *dec, picture_t *pic)
{
    decoder_QueueVideo(dec, pic);
    return VLC_SUCCESS;
}

static int decoder_decode_check_flush_video(decoder_t *dec, picture_t *pic)
{
    if (scenario_data.skip_decoder)
    {
        picture_Release(pic);
        return VLC_SUCCESS;
    }

    /* Only update the output format the first time. */
    if (scenario_data.decoder_image_sent == 0)
    {
        int ret = decoder_UpdateVideoOutput(dec, NULL);
        assert(ret == VLC_SUCCESS);
    }

    /* Workaround: the input decoder needs multiple frame (at least 2
     * currently) to start correctly. We're not testing this, but we're
     * testing the flush here, so provide additional picture at the
     * beginning of the test to avoid issues with this.
     * They must not be linked to any picture context. */
    if (scenario_data.decoder_image_sent < 3)
    {
        msg_Info(dec, "Queueing workaround picture number %zu", scenario_data.decoder_image_sent);
        decoder_QueueVideo(dec, pic);
        scenario_data.decoder_image_sent++;
        return VLC_SUCCESS;
    }

    /* Now the input decoder should be completely started. */

    struct picture_watcher_context context1 = {
        .context.destroy = context_destroy,
        .context.copy = context_copy,
    };
    vlc_sem_init(&context1.wait_picture, 0);
    vlc_atomic_rc_init(&context1.rc);

    picture_t *second_pic = picture_Clone(pic);
    second_pic->b_force = true;
    second_pic->date = VLC_TICK_0;
    second_pic->b_progressive = true;
    second_pic->context = &context1.context;

    msg_Info(dec, "Send first frame from decoder to video output");
    decoder_QueueVideo(dec, second_pic);

    msg_Info(dec, "Wait for the display to prepare the frame");
    vlc_sem_wait(&context1.wait_prepare);

    msg_Info(dec, "Trigger decoder and vout flush");
    vlc_sem_post(&scenario_data.wait_ready_to_flush);

    /* Wait for the picture to be flushed by the vout. */
    msg_Info(dec, "Wait for the picture to be flushed from the vout");
    vlc_sem_wait(&context1.wait_picture);

    /* Reinit the picture context waiter. */

    struct picture_watcher_context context2 = {
        .context.destroy = context_destroy,
        .context.copy = context_copy,
    };
    vlc_sem_init(&context2.wait_picture, 0);
    vlc_atomic_rc_init(&context2.rc);

    picture_t *third_pic = picture_Clone(pic);
    third_pic->b_force = true;
    third_pic->date = VLC_TICK_0 + 1;
    third_pic->b_progressive = true;
    third_pic->context = &context2.context;

    msg_Info(dec, "Re-queue the picture to the vout before decoder::pf_flush is called");
    decoder_QueueVideo(dec, third_pic);

    /* Wait for the picture to be flushed by the input decoder. */
    msg_Info(dec, "Ensure the picture has been discarded by the input decoder");
    vlc_sem_wait(&context2.wait_picture);

    /* Ok, since the picture has been released, we should have decode succeed
     * and we can now check that flush is called. */

    picture_Release(pic);

    msg_Info(dec, "Nothing to do from pf_decode, let pf_flush be called by the input decoder");
    scenario_data.skip_decoder = true;
    return VLC_SUCCESS;
}

static void decoder_flush_signal(decoder_t *dec)
{
    (void)dec;
    vlc_sem_post(&scenario_data.wait_stop);
}

static void* SendUpdateOutput(void *opaque)
{
    decoder_t *dec = opaque;
    decoder_UpdateVideoOutput(dec, NULL);
    return NULL;
}

static void decoder_destroy_trigger_update(decoder_t *dec)
{
    /* Use another thread to ensure we don't double-lock, but
     * at most deadlock instead. */
    vlc_thread_t thread;
    int ret = vlc_clone(&thread, SendUpdateOutput, dec);
    assert(ret == VLC_SUCCESS);
    vlc_join(thread, NULL);
}

static int decoder_decode_trigger_reload(decoder_t *dec, picture_t *pic)
{
    (void)dec;
    picture_Release(pic);

    if (!scenario_data.has_reload)
    {
        vlc_sem_post(&scenario_data.wait_stop);
        return VLCDEC_RELOAD;
    }
    return VLCDEC_SUCCESS;
}

static void display_prepare_signal(vout_display_t *vd, picture_t *pic)
{
    (void)vd;

    if (pic->context == NULL)
        return;

    msg_Info(vd, "Signal that the frame has been prepared from display");
    struct picture_watcher_context *watcher =
        container_of(pic->context, struct picture_watcher_context, context);
    vlc_sem_post(&watcher->wait_prepare);
}

static void PlayerOnTrackListChanged(vlc_player_t *player,
        enum vlc_player_list_action action,
        const struct vlc_player_track *track,
        void *data)
{
    (void)data;

    if (action != VLC_PLAYER_LIST_ADDED ||
        track->fmt.i_cat != SPU_ES)
        return;

    vlc_player_SelectTrack(player, track, VLC_PLAYER_SELECT_EXCLUSIVE);
}

static void interface_setup_select_cc(intf_thread_t *intf)
{
    vlc_player_t *player = (vlc_player_t *)intf->p_sys;

    static const struct vlc_player_cbs player_cbs =
    {
        .on_track_list_changed = PlayerOnTrackListChanged,
    };

    vlc_player_Lock(player);
    vlc_player_listener_id *listener_id =
        vlc_player_AddListener(player, &player_cbs, NULL);
    vlc_player_Unlock(player);

    vlc_sem_wait(&scenario_data.wait_ready_to_flush);
    vlc_player_Lock(player);
    vlc_player_SetPosition(player, 0);
    vlc_player_Unlock(player);

    vlc_sem_wait(&scenario_data.wait_ready_to_flush);
    vlc_player_Lock(player);
    vlc_player_RemoveListener(player, listener_id);
    vlc_player_Unlock(player);
}

static void interface_setup_check_flush(intf_thread_t *intf)
{
    vlc_player_t *player = (vlc_player_t *)intf->p_sys;
    vlc_sem_wait(&scenario_data.wait_ready_to_flush);

    vlc_player_Lock(player);
    vlc_player_SetPosition(player, 0);
    vlc_player_Unlock(player);
}

static int sout_filter_send(sout_stream_t *stream, void *id, block_t *block)
{
    (void)stream; (void)id;
    block_Release(block);
    scenario_data.stream_out_sent = true;
    vlc_sem_post(&scenario_data.wait_ready_to_flush);
    return VLC_SUCCESS;
}

static int sout_filter_wait_cc(sout_stream_t *stream, void *id, block_t *block)
{
    (void)stream; (void)id;
    block_Release(block);
    vlc_fourcc_t *codec = id;
    if (*codec == VLC_CODEC_CEA608)
    {
        scenario_data.stream_out_sent = true;
        vlc_sem_post(&scenario_data.wait_ready_to_flush);
    }
    return VLC_SUCCESS;
}

static void sout_filter_flush(sout_stream_t *stream, void *id)
{
    (void)stream; (void)id;
    assert(scenario_data.stream_out_sent);
    vlc_sem_post(&scenario_data.wait_stop);
}

static vlc_frame_t *packetizer_getcc(decoder_t *dec, decoder_cc_desc_t *cc_desc)
{
    (void)dec;

    cc_desc->i_608_channels = 1;
    cc_desc->i_708_channels = 0;
    cc_desc->i_reorder_depth = 4;

    return create_cc_frame(VLC_TICK_0);
}

static vlc_frame_t *packetizer_getcc_cea608_max(decoder_t *dec, decoder_cc_desc_t *cc_desc)
{
    (void)dec;

    cc_desc->i_608_channels = 0xF;
    cc_desc->i_708_channels = 0;
    cc_desc->i_reorder_depth = 4;

    return create_cc_frame(VLC_TICK_0);
}

static vlc_frame_t *packetizer_getcc_cea708_max(decoder_t *dec, decoder_cc_desc_t *cc_desc)
{
    (void)dec;

    cc_desc->i_608_channels = 0;
    cc_desc->i_708_channels = UINT64_MAX;
    cc_desc->i_reorder_depth = 4;

    return create_cc_frame(VLC_TICK_0);
}

static void on_track_list_changed_check_cea608(
        enum vlc_player_list_action action,
        const struct vlc_player_track *track)
{
    if (action != VLC_PLAYER_LIST_ADDED)
        return;

    if (strcmp(vlc_es_id_GetStrId(track->es_id), "video/0/cc/spu/1") != 0)
        return;

    assert(track->fmt.i_codec == VLC_CODEC_CEA608);
    vlc_sem_post(&scenario_data.wait_stop);
}

static void on_track_list_changed_check_count(
        enum vlc_player_list_action action,
        const struct vlc_player_track *track,
        vlc_fourcc_t codec, unsigned track_max)
{
    if (action != VLC_PLAYER_LIST_ADDED)
        return;

    char buffer[] = "video/0/cc/spu/xx";
    assert(scenario_data.cc_track_idx < 100);
    sprintf(buffer, "video/0/cc/spu/%zu", scenario_data.cc_track_idx);

    if (strcmp(vlc_es_id_GetStrId(track->es_id), buffer) != 0)
        return;

    assert(track->fmt.i_codec == codec);

    scenario_data.cc_track_idx++;

    if (scenario_data.cc_track_idx == track_max)
        vlc_sem_post(&scenario_data.wait_stop);
}

static void on_track_list_changed_check_cea608_count(
        enum vlc_player_list_action action,
        const struct vlc_player_track *track)
{
    on_track_list_changed_check_count(action, track, VLC_CODEC_CEA608, 4);
}

static void on_track_list_changed_check_cea708_count(
        enum vlc_player_list_action action,
        const struct vlc_player_track *track)
{
    on_track_list_changed_check_count(action, track, VLC_CODEC_CEA708, 64);
}

static void player_setup_select_cc(vlc_player_t *player)
{
    vlc_player_SelectTracksByStringIds(player, SPU_ES, "video/0/cc/spu/1");
}

static void cc_decoder_setup_01(decoder_t *dec)
{
    assert(dec->fmt_in->i_codec == VLC_CODEC_CEA608);
    assert(dec->fmt_in->subs.cc.i_channel == 0);
    assert(dec->fmt_in->subs.cc.i_reorder_depth == 4);

    dec->fmt_out.i_codec = VLC_CODEC_TEXT;
}

static int cc_decoder_decode_common(decoder_t *dec, vlc_frame_t *in,
                                    const char *text)
{
    assert(memcmp(in->p_buffer, cc_block_input, sizeof(cc_block_input)) == 0);

    subpicture_t *subpic = decoder_NewSubpicture(dec, NULL);
    assert(subpic != NULL);

    subpic->i_start = in->i_pts;
    subpic->i_stop = subpic->i_start + in->i_length;

    subpicture_region_t *p_region = subpicture_region_NewText();;
    assert(p_region != NULL);
    vlc_spu_regions_push( &subpic->regions, p_region );

    p_region->b_absolute = true; p_region->b_in_window = false;
    p_region->i_x = 0;
    p_region->i_y = 0;
    p_region->p_text = text_segment_New(text);
    assert(p_region->p_text != NULL);

    decoder_QueueSub(dec, subpic);

    return VLC_SUCCESS;
}

static const char cc_block_decoded[] = "cc01_dec";
static int cc_decoder_decode(decoder_t *dec, vlc_frame_t *in)
{
    return cc_decoder_decode_common(dec, in, cc_block_decoded);
}

static void display_prepare_noop(vout_display_t *vd, picture_t *pic)
{
    (void)vd; (void) pic;
}

static void cc_text_renderer_render(filter_t *filter, const subpicture_region_t *region_in)
{
    (void) filter;
    assert(strcmp(region_in->p_text->psz_text, cc_block_decoded) == 0);
    vlc_sem_post(&scenario_data.wait_stop);
}

static vlc_frame_t *packetizer_getcc_cea708_1064(decoder_t *dec, decoder_cc_desc_t *cc_desc)
{
    (void)dec;

    cc_desc->i_608_channels = 0;
    cc_desc->i_708_channels = (1ULL << 9) | (1ULL << 63);
    cc_desc->i_reorder_depth = 4;

    return create_cc_frame(VLC_TICK_0);
}

static void cc_decoder_setup_708_1064(decoder_t *dec)
{
    assert(dec->fmt_in->i_codec == VLC_CODEC_CEA708);
    assert(dec->fmt_in->subs.cc.i_channel == 9
        || dec->fmt_in->subs.cc.i_channel == 63);
    assert(dec->fmt_in->subs.cc.i_reorder_depth == 4);

    dec->fmt_out.i_codec = VLC_CODEC_TEXT;
}

static int cc_decoder_decode_channel(decoder_t *dec, vlc_frame_t *in)
{
    char buf[] = "ccxxx_dec";
    assert(dec->fmt_in->subs.cc.i_channel < 64);
    sprintf(buf, "cc%02u_dec", dec->fmt_in->subs.cc.i_channel + 1);
    return cc_decoder_decode_common(dec, in, buf);
}

static void cc_text_renderer_render_708_1064(filter_t *filter,
                                             const subpicture_region_t *region_in)
{
    (void) filter;
    /* Make sure each tracks are rendered once */
    static unsigned chans[] = { 10, 64 };
    static bool rendered[] = { false, false };

    char buf[] = "ccxx_dec";
    for (size_t i = 0; i < ARRAY_SIZE(chans); ++i)
    {
        unsigned chan = chans[i];
        sprintf(buf, "cc%u_dec", chan);

        if (strcmp(region_in->p_text->psz_text, buf) == 0)
        {
            if (rendered[i])
                return;
            rendered[i] = true;
        }
    }

    for (size_t i = 0; i < ARRAY_SIZE(chans); ++i)
    {
        if (!rendered[i])
            return;
    }

    vlc_sem_post(&scenario_data.wait_stop);
}

static void player_setup_select_ccdec_708_1064(vlc_player_t *player)
{
    vlc_player_SelectTracksByStringIds(player, SPU_ES,
        "video/0/cc/spu/10,video/0/cc/spu/64");
}

static vlc_frame_t *packetizer_getcc_cea608_02(decoder_t *dec, decoder_cc_desc_t *cc_desc)
{
    (void)dec;

    cc_desc->i_608_channels = 1 << 1;
    cc_desc->i_708_channels = 0;
    cc_desc->i_reorder_depth = 4;

    return create_cc_frame(VLC_TICK_0);
}

static void cc_decoder_setup_608(decoder_t *dec)
{
    assert(dec->fmt_in->i_codec == VLC_CODEC_CEA608);

    dec->fmt_out.i_codec = VLC_CODEC_TEXT;
}

static void cc_text_renderer_render_608_02(filter_t *filter,
                                           const subpicture_region_t *region_in)
{
    (void) filter;
    assert(strcmp(region_in->p_text->psz_text, "cc02_dec") == 0);
    vlc_sem_post(&scenario_data.wait_stop);
}

static const vlc_fourcc_t subpicture_chromas[] = {
    VLC_CODEC_RGBA, 0
};

#define source_800_600 "mock://video_track_count=1;length=100000000000;video_width=800;video_height=600"
struct input_decoder_scenario input_decoder_scenarios[] =
{{
    .name = "decoder is flushed",
    .source = source_800_600,
    .decoder_setup = decoder_i420_800_600,
    .decoder_flush = decoder_flush_signal,
    .decoder_decode = decoder_decode_check_cc,
    .interface_setup = interface_setup_select_cc,
},
{
    .name = "video output is flushed",
    .source = source_800_600,
    .decoder_setup = decoder_i420_800_600,
    .decoder_decode = decoder_decode_check_flush_video,
    .decoder_flush = decoder_flush_signal,
    .display_prepare = display_prepare_signal,
    .interface_setup = interface_setup_check_flush,
},
{
    /* Check that stream output is also flushed:
      - the test cannot work if the stream_out filter is not added
      - sout_filter_send(), signal the interface that it can flush
      - the interface change player position to trigger a flush
      - the flush is signaled to the stream_out filter
      - the stream_out filter signal the end of the test */
    .name = "stream output is also flushed",
    .source = source_800_600,
    .item_option = ":sout=#" MODULE_STRING,
    .sout_filter_send = sout_filter_send,
    .sout_filter_flush = sout_filter_flush,
    .interface_setup = interface_setup_check_flush,
},
{
    /* Check that releasing a decoder while it is triggering an update
     * of the video output format doesn't lead to a crash. Non-regression
     * test from issue #27532. */
    .name = "releasing a decoder while updating the vout doesn't lead to a crash",
    .source = source_800_600,
    .decoder_setup = decoder_i420_800_600_stop,
    .decoder_decode = decoder_decode_drop,
    .decoder_destroy = decoder_destroy_trigger_update,
},
{
    /* Check that reloading a decoder while it is triggering an update
     * of the video output format doesn't lead to a crash. Non-regression
     * test from issue #27532. */
    .name = "reloading a decoder while updating the vout doesn't lead to a crash",
    .source = source_800_600,
    .decoder_setup = decoder_i420_800_600,
    .decoder_decode = decoder_decode_trigger_reload,
    .decoder_destroy = decoder_destroy_trigger_update,
},
{
    .name = "CC frames are sent to the sout",
    .source = source_800_600,
    .item_option = ":sout=#" MODULE_STRING,
    .packetizer_getcc = packetizer_getcc,
    .sout_filter_send = sout_filter_wait_cc,
    .sout_filter_flush = sout_filter_flush,
    .interface_setup = interface_setup_check_flush,
},
{
    .name = "CC tracks are added",
    .source = source_800_600 ";video_packetized=false",
    .packetizer_getcc = packetizer_getcc,
    .decoder_setup = decoder_i420_800_600,
    .decoder_decode = decoder_decode_drop,
    .on_track_list_changed = on_track_list_changed_check_cea608,
},
{
    /* Check that the CC coming from the packetizer is decoded and rendered:
     * - A valid vout is needed (to blend subtitles into)
     * - Text vlc_frames are passed from the packetizer to the cc decoder to
     *   the text renderer and are checked at each step
     *  - A text renderer is used, this is the last place a text can be checked
     *  before it is rendered (and converted to RGB/YUV)
     */
    .name = "CC coming from the packetizer is decoded and rendered",
    .source = source_800_600 ";video_packetized=false",
    .subpicture_chromas = subpicture_chromas,
    .packetizer_getcc = packetizer_getcc,
    .decoder_setup = decoder_i420_800_600_update,
    .decoder_decode = decoder_decode,
    .cc_decoder_setup = cc_decoder_setup_01,
    .cc_decoder_decode = cc_decoder_decode,
    .display_prepare = display_prepare_noop,
    .text_renderer_render = cc_text_renderer_render,
    .player_setup_before_start = player_setup_select_cc,
},
{
    /* Check that the CC coming from the video decoder is decoded and rendered,
     * cf. the previous scenario for more details. */
    .name = "CC coming from the video decoder is decoded and rendered",
    .source = source_800_600,
    .subpicture_chromas = subpicture_chromas,
    .decoder_setup = decoder_i420_800_600_update,
    .decoder_decode = decoder_decode_check_cc_queue_video,
    .cc_decoder_setup = cc_decoder_setup_01,
    .cc_decoder_decode = cc_decoder_decode,
    .display_prepare = display_prepare_noop,
    .text_renderer_render = cc_text_renderer_render,
    .player_setup_before_start = player_setup_select_cc,
},
{
    .name = "we can create 4 cea608 tracks",
    .source = source_800_600 ";video_packetized=false",
    .packetizer_getcc = packetizer_getcc_cea608_max,
    .decoder_setup = decoder_i420_800_600,
    .decoder_decode = decoder_decode_drop,
    .on_track_list_changed = on_track_list_changed_check_cea608_count,
},
{
    .name = "we can create 64 cea708 tracks",
    .source = source_800_600 ";video_packetized=false",
    .item_option = ":captions=708",
    .packetizer_getcc = packetizer_getcc_cea708_max,
    .decoder_setup = decoder_i420_800_600,
    .decoder_decode = decoder_decode_drop,
    .on_track_list_changed = on_track_list_changed_check_cea708_count,
},
{
    .name = "cea708 10 and 63 tracks can be rendered simultaneously",
    .source = source_800_600 ";video_packetized=false",
    .item_option = ":captions=708",
    .packetizer_getcc = packetizer_getcc_cea708_1064,
    .decoder_setup = decoder_i420_800_600_update,
    .decoder_decode = decoder_decode,
    .cc_decoder_setup = cc_decoder_setup_708_1064,
    .cc_decoder_decode = cc_decoder_decode_channel,
    .display_prepare = display_prepare_noop,
    .text_renderer_render = cc_text_renderer_render_708_1064,
    .player_setup_before_start = player_setup_select_ccdec_708_1064,
},
{
    /* The "--sub-track" option use channel starting with 0 whereas CC tracks
     * indexes start with 1. Ensure channel 1 match with the CC track 2. */
    .name = "A cea608 track can be selected via command line",
    .source = source_800_600 ";video_packetized=false",
    .item_option = ":sub-track=1",
    .packetizer_getcc = packetizer_getcc_cea608_02,
    .decoder_setup = decoder_i420_800_600_update,
    .decoder_decode = decoder_decode,
    .cc_decoder_setup = cc_decoder_setup_608,
    .cc_decoder_decode = cc_decoder_decode_channel,
    .display_prepare = display_prepare_noop,
    .text_renderer_render = cc_text_renderer_render_608_02,
},
};

size_t input_decoder_scenarios_count = ARRAY_SIZE(input_decoder_scenarios);

void input_decoder_scenario_init(void)
{
    scenario_data.decoder_vctx = NULL;
    scenario_data.skip_decoder = false;
    scenario_data.has_reload = false;
    scenario_data.stream_out_sent = false;
    scenario_data.decoder_image_sent = 0;
    scenario_data.cc_track_idx = 1;
    vlc_sem_init(&scenario_data.wait_stop, 0);
    vlc_sem_init(&scenario_data.wait_ready_to_flush, 0);
}

void input_decoder_scenario_wait(intf_thread_t *intf, struct input_decoder_scenario *scenario)
{
    if (scenario->interface_setup)
        scenario->interface_setup(intf);

    vlc_sem_wait(&scenario_data.wait_stop);
}

void input_decoder_scenario_check(struct input_decoder_scenario *scenario)
{
    (void)scenario;
}
