You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
532 lines
14 KiB
532 lines
14 KiB
/*****************************************************************************
|
|
* es_out.c: test for es_out state machine
|
|
*****************************************************************************
|
|
* Copyright (C) 2024 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.
|
|
*****************************************************************************/
|
|
|
|
#undef NDEBUG
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
/* Define a builtin module for mocked parts */
|
|
#define MODULE_NAME test_es_out_mock
|
|
#undef VLC_DYNAMIC_PLUGIN
|
|
|
|
#include <vlc_common.h>
|
|
#include <vlc_plugin.h>
|
|
#include <vlc_threads.h>
|
|
#include <vlc_input.h>
|
|
#include <vlc_es_out.h>
|
|
#include <vlc_decoder.h>
|
|
#include <vlc_input_item.h>
|
|
#include <vlc_list.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include "../src/libvlc.h"
|
|
#include "../lib/libvlc_internal.h"
|
|
#include "../src/input/es_out.h"
|
|
#include "../src/input/input_internal.h"
|
|
#include "../src/input/decoder.h"
|
|
#include "../src/input/source.h"
|
|
|
|
const char vlc_module_name[] = MODULE_STRING;
|
|
|
|
struct vlc_input_decoder_t {
|
|
struct vlc_list node;
|
|
|
|
bool drained;
|
|
bool started;
|
|
};
|
|
|
|
static struct vlc_list opened_decoders;
|
|
|
|
VLC_EXPORT vlc_input_decoder_t *
|
|
vlc_input_decoder_Create(vlc_object_t *, const es_format_t *, const char *es_id,
|
|
struct vlc_clock_t *, input_resource_t *) VLC_USED;
|
|
|
|
vlc_input_decoder_t *
|
|
vlc_input_decoder_Create(vlc_object_t *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,
|
|
.cbs = NULL, .cbs_data = NULL,
|
|
};
|
|
return vlc_input_decoder_New(parent, &cfg);
|
|
}
|
|
|
|
|
|
vlc_input_decoder_t *
|
|
vlc_input_decoder_New(vlc_object_t *parent,
|
|
const struct vlc_input_decoder_cfg *cfg)
|
|
{
|
|
(void)cfg;
|
|
msg_Dbg(parent, "Creating input decoder from test");
|
|
|
|
struct vlc_input_decoder_t *dec = malloc(sizeof(struct vlc_input_decoder_t));
|
|
assert(dec != NULL);
|
|
|
|
dec->drained = false;
|
|
dec->started = false;
|
|
vlc_list_append(&dec->node, &opened_decoders);
|
|
return dec;
|
|
}
|
|
|
|
void vlc_input_decoder_Delete(vlc_input_decoder_t *owner)
|
|
{
|
|
vlc_list_remove(&owner->node);
|
|
free(owner);
|
|
}
|
|
|
|
void vlc_input_decoder_ChangeDelay(
|
|
vlc_input_decoder_t *owner,
|
|
vlc_tick_t delay)
|
|
{
|
|
(void)owner; (void)delay;
|
|
}
|
|
|
|
void vlc_input_decoder_ChangePause(
|
|
vlc_input_decoder_t *owner,
|
|
bool paused,
|
|
vlc_tick_t date)
|
|
{
|
|
(void)owner; (void)paused; (void)date;
|
|
}
|
|
|
|
void vlc_input_decoder_ChangeRate(
|
|
vlc_input_decoder_t *owner,
|
|
float rate)
|
|
{
|
|
(void)owner; (void)rate;
|
|
}
|
|
|
|
void vlc_input_decoder_StartWait(vlc_input_decoder_t *owner)
|
|
{
|
|
(void)owner;
|
|
}
|
|
|
|
void vlc_input_decoder_Wait(vlc_input_decoder_t *owner)
|
|
{
|
|
(void)owner;
|
|
}
|
|
|
|
void vlc_input_decoder_StopWait(vlc_input_decoder_t *owner)
|
|
{
|
|
(void)owner;
|
|
owner->started = true;
|
|
}
|
|
|
|
bool vlc_input_decoder_IsEmpty(vlc_input_decoder_t *owner)
|
|
{
|
|
(void)owner;
|
|
return owner->drained;
|
|
}
|
|
|
|
void vlc_input_decoder_DecodeWithStatus(
|
|
vlc_input_decoder_t *owner,
|
|
vlc_frame_t *frame,
|
|
bool do_pace,
|
|
struct vlc_input_decoder_status *status)
|
|
{
|
|
(void)owner; (void)do_pace;
|
|
|
|
if (status != NULL)
|
|
*status = (struct vlc_input_decoder_status){ .format.changed = false };
|
|
vlc_frame_Release(frame);
|
|
}
|
|
|
|
void vlc_input_decoder_Drain(vlc_input_decoder_t *owner)
|
|
{
|
|
owner->drained = true;
|
|
}
|
|
|
|
void vlc_input_decoder_Flush(vlc_input_decoder_t *owner)
|
|
{
|
|
(void)owner;
|
|
}
|
|
|
|
size_t vlc_input_decoder_GetFifoSize(vlc_input_decoder_t *owner)
|
|
{
|
|
(void)owner;
|
|
return 0;
|
|
}
|
|
|
|
/* VBI */
|
|
|
|
int vlc_input_decoder_GetVbiPage(
|
|
vlc_input_decoder_t *owner,
|
|
bool *opaque)
|
|
{
|
|
(void)owner; (void)opaque;
|
|
return 0;
|
|
}
|
|
|
|
int vlc_input_decoder_SetVbiPage(
|
|
vlc_input_decoder_t *owner,
|
|
unsigned page)
|
|
{
|
|
(void)owner; (void)page;
|
|
return 0;
|
|
}
|
|
|
|
int vlc_input_decoder_SetVbiOpaque(
|
|
vlc_input_decoder_t *owner,
|
|
bool opaque)
|
|
{
|
|
(void)owner; (void)opaque;
|
|
return 0;
|
|
}
|
|
|
|
/* Vout */
|
|
|
|
void vlc_input_decoder_SetVoutMouseEvent(
|
|
vlc_input_decoder_t *owner,
|
|
vlc_mouse_event event,
|
|
void *opaque)
|
|
{
|
|
(void)owner; (void)event; (void)opaque;
|
|
}
|
|
|
|
int vlc_input_decoder_AddVoutOverlay(
|
|
vlc_input_decoder_t *owner,
|
|
subpicture_t *subpic,
|
|
size_t *_)
|
|
{
|
|
(void)owner; (void)subpic; (void)_;
|
|
return 0;
|
|
}
|
|
|
|
int vlc_input_decoder_DelVoutOverlay(
|
|
vlc_input_decoder_t *owner,
|
|
size_t _)
|
|
{
|
|
(void)owner; (void)_;
|
|
return 0;
|
|
}
|
|
|
|
void vlc_input_decoder_FrameNext(vlc_input_decoder_t *p_dec)
|
|
{
|
|
(void)p_dec;
|
|
}
|
|
|
|
vlc_input_decoder_t *
|
|
vlc_input_decoder_CreateSubDec(vlc_input_decoder_t *dec,
|
|
const struct vlc_input_decoder_cfg *cfg)
|
|
{
|
|
(void)dec; (void)cfg;
|
|
return NULL;
|
|
}
|
|
|
|
void var_OptionParse(vlc_object_t *obj, const char *chain, bool trusted)
|
|
{
|
|
(void)obj; (void)chain; (void)trusted;
|
|
}
|
|
|
|
bool input_CanPaceControl(input_thread_t *input)
|
|
{
|
|
(void)input;
|
|
return false;
|
|
}
|
|
|
|
int input_GetAttachments(input_thread_t *input,
|
|
input_attachment_t ***attachments)
|
|
{
|
|
(void)input; (void)attachments;
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
void input_ExtractAttachmentAndCacheArt(input_thread_t *p_input,
|
|
const char *name)
|
|
{
|
|
(void)p_input; (void)name;
|
|
}
|
|
|
|
void input_resource_StopFreeVout(input_resource_t *p_resource);
|
|
void input_resource_StopFreeVout(input_resource_t *p_resource)
|
|
{
|
|
(void)p_resource;
|
|
}
|
|
|
|
void input_rate_Add(input_rate_t *counter, uintmax_t val)
|
|
{
|
|
(void)counter; (void)val;
|
|
}
|
|
|
|
void vlc_subdec_desc_Clean(struct vlc_subdec_desc *desc)
|
|
{
|
|
(void)desc;
|
|
}
|
|
|
|
bool input_Stopped(input_thread_t *input)
|
|
{
|
|
(void)input;
|
|
return false;
|
|
}
|
|
|
|
input_item_t * input_GetItem(input_thread_t *input)
|
|
{
|
|
return input_priv(input)->p_item;
|
|
}
|
|
|
|
sout_stream_t *sout_NewInstance(vlc_object_t *p_parent, const char *psz_dest);
|
|
sout_stream_t *sout_NewInstance(vlc_object_t *p_parent, const char *psz_dest)
|
|
{
|
|
(void)p_parent; (void)psz_dest;
|
|
return NULL;
|
|
}
|
|
|
|
static input_source_t *InputSourceNew(void)
|
|
{
|
|
input_source_t *in = calloc(1, sizeof(*in));
|
|
if (unlikely(in == NULL))
|
|
return NULL;
|
|
|
|
vlc_atomic_rc_init( &in->rc );
|
|
in->i_normal_time = VLC_TICK_0;
|
|
|
|
return in;
|
|
}
|
|
|
|
static void LogText(void *opaque, int type, const vlc_log_t *meta,
|
|
const char *format, va_list ap)
|
|
{
|
|
(void)opaque;
|
|
|
|
static const char msg_type[4][9] = { "", " error", " warning", " debug" };
|
|
|
|
flockfile(stderr);
|
|
fprintf(stderr, "%s%s: ", meta->psz_module, msg_type[type]);
|
|
vfprintf(stderr, format, ap);
|
|
putc_unlocked('\n', stderr);
|
|
funlockfile(stderr);
|
|
}
|
|
|
|
static const struct vlc_logger_operations test_logger_operations = {
|
|
.log = LogText,
|
|
};
|
|
|
|
static void test_playback(void)
|
|
{
|
|
vlc_list_init(&opened_decoders);
|
|
struct vlc_logger logger = { .ops = &test_logger_operations };
|
|
libvlc_priv_t *libvlc = (vlc_object_create)(NULL, sizeof(*libvlc));
|
|
vlc_object_t *root = &libvlc->public_data.obj;
|
|
root->logger = &logger;
|
|
|
|
var_Create(root, "clock-master", VLC_VAR_STRING);
|
|
var_SetString(root, "clock-master", "auto");
|
|
|
|
var_Create(root, "captions", VLC_VAR_INTEGER);
|
|
var_SetInteger(root, "captions", 608);
|
|
|
|
input_item_t *item = input_item_NewStream("mock://", "mock", 0);
|
|
|
|
input_thread_private_t *priv = vlc_object_create(root, sizeof(*priv));
|
|
assert(priv != NULL);
|
|
priv->p_item = item;
|
|
|
|
input_thread_t *input = &priv->input;
|
|
var_Create(input, "video", VLC_VAR_BOOL);
|
|
var_SetBool(input, "video", true);
|
|
var_Create(input, "audio", VLC_VAR_BOOL);
|
|
var_SetBool(input, "audio", true);
|
|
|
|
input_source_t *source = InputSourceNew();
|
|
|
|
struct vlc_input_es_out *out =
|
|
input_EsOutNew(input, source, 1.f, INPUT_TYPE_PLAYBACK);
|
|
assert(out != NULL);
|
|
|
|
es_out_SetMode(out, ES_OUT_MODE_AUTO);
|
|
|
|
vlc_tick_t pts_delay = VLC_TICK_FROM_MS(300);
|
|
es_out_SetJitter(out, pts_delay, 0, 40 * pts_delay / DEFAULT_PTS_DELAY);
|
|
|
|
es_format_t video_fmt;
|
|
es_format_Init(&video_fmt, VIDEO_ES, VLC_CODEC_RGBA);
|
|
video_fmt.i_id = 1;
|
|
video_fmt.i_group = 1;
|
|
video_format_Init(&video_fmt.video, VLC_CODEC_RGBA);
|
|
video_fmt.video.i_width = video_fmt.video.i_visible_width = 64;
|
|
video_fmt.video.i_height = video_fmt.video.i_visible_height = 64;
|
|
es_out_id_t *video_track = es_out_Add(&out->out, &video_fmt);
|
|
assert(video_track != NULL);
|
|
es_format_Clean(&video_fmt);
|
|
|
|
es_format_t audio_fmt;
|
|
es_format_Init(&audio_fmt, AUDIO_ES, VLC_CODEC_F32L);
|
|
audio_fmt.i_id = 2;
|
|
video_fmt.i_group = 1;
|
|
audio_fmt.audio.i_format = VLC_CODEC_F32L;
|
|
audio_fmt.audio.i_rate = 44100;
|
|
audio_fmt.audio.i_physical_channels = 2;
|
|
audio_fmt.audio.i_channels = 2;
|
|
es_out_id_t *audio_track = es_out_Add(&out->out, &audio_fmt);
|
|
assert(audio_track != NULL);
|
|
es_format_Clean(&audio_fmt);
|
|
|
|
block_t *block = block_Alloc(10);
|
|
assert(block);
|
|
es_out_Send(&out->out, video_track, block);
|
|
|
|
block = block_Alloc(10);
|
|
assert(block);
|
|
es_out_Send(&out->out, audio_track, block);
|
|
|
|
es_out_SetPCR(&out->out, VLC_TICK_0);
|
|
es_out_SetPCR(&out->out, VLC_TICK_0 + VLC_TICK_FROM_MS(100));
|
|
es_out_SetPCR(&out->out, VLC_TICK_0 + VLC_TICK_FROM_MS(200));
|
|
es_out_SetPCR(&out->out, VLC_TICK_0 + VLC_TICK_FROM_MS(300) - 1);
|
|
|
|
{
|
|
size_t count = 0;
|
|
struct vlc_input_decoder_t *dec;
|
|
vlc_list_foreach(dec, &opened_decoders, node)
|
|
{
|
|
assert(dec->started == false);
|
|
count++;
|
|
}
|
|
assert(count == 2);
|
|
}
|
|
|
|
es_out_SetPCR(&out->out, VLC_TICK_0 + VLC_TICK_FROM_MS(300));
|
|
|
|
{
|
|
size_t count = 0;
|
|
struct vlc_input_decoder_t *dec;
|
|
vlc_list_foreach(dec, &opened_decoders, node)
|
|
{
|
|
assert(dec->started == true);
|
|
count++;
|
|
}
|
|
assert(count == 2);
|
|
}
|
|
|
|
es_out_Del(&out->out, audio_track);
|
|
es_out_Del(&out->out, video_track);
|
|
|
|
es_out_SetMode(out, ES_OUT_MODE_END);
|
|
es_out_Delete(&out->out);
|
|
|
|
input_source_Release(source);
|
|
input_item_Release(item);
|
|
vlc_object_delete(&input->obj);
|
|
vlc_object_delete(root);
|
|
|
|
}
|
|
|
|
static void test_multiple_programs(void)
|
|
{
|
|
vlc_list_init(&opened_decoders);
|
|
struct vlc_logger logger = { .ops = &test_logger_operations };
|
|
libvlc_priv_t *libvlc = (vlc_object_create)(NULL, sizeof(*libvlc));
|
|
vlc_object_t *root = &libvlc->public_data.obj;
|
|
root->logger = &logger;
|
|
|
|
var_Create(root, "clock-master", VLC_VAR_STRING);
|
|
var_SetString(root, "clock-master", "auto");
|
|
|
|
var_Create(root, "captions", VLC_VAR_INTEGER);
|
|
var_SetInteger(root, "captions", 608);
|
|
|
|
input_item_t *item = input_item_NewStream("mock://", "mock", 0);
|
|
|
|
input_thread_private_t *priv = vlc_object_create(root, sizeof(*priv));
|
|
assert(priv != NULL);
|
|
priv->p_item = item;
|
|
|
|
input_thread_t *input = &priv->input;
|
|
var_Create(input, "video", VLC_VAR_BOOL);
|
|
var_SetBool(input, "video", true);
|
|
var_Create(input, "audio", VLC_VAR_BOOL);
|
|
var_SetBool(input, "audio", true);
|
|
|
|
input_source_t *source = InputSourceNew();
|
|
|
|
struct vlc_input_es_out *out =
|
|
input_EsOutNew(input, source, 1.f, INPUT_TYPE_PLAYBACK);
|
|
assert(out != NULL);
|
|
|
|
es_out_SetMode(out, ES_OUT_MODE_AUTO);
|
|
|
|
vlc_tick_t pts_delay = VLC_TICK_FROM_MS(300);
|
|
es_out_SetJitter(out, pts_delay, 0, 40 * pts_delay / DEFAULT_PTS_DELAY);
|
|
|
|
es_format_t video_fmt;
|
|
es_format_Init(&video_fmt, VIDEO_ES, VLC_CODEC_RGBA);
|
|
video_fmt.i_id = 1;
|
|
video_fmt.i_group = 1;
|
|
video_format_Init(&video_fmt.video, VLC_CODEC_RGBA);
|
|
video_fmt.video.i_width = video_fmt.video.i_visible_width = 64;
|
|
video_fmt.video.i_height = video_fmt.video.i_visible_height = 64;
|
|
es_out_id_t *video_track = es_out_Add(&out->out, &video_fmt);
|
|
assert(video_track != NULL);
|
|
es_format_Clean(&video_fmt);
|
|
|
|
es_format_t audio_fmt;
|
|
es_format_Init(&audio_fmt, AUDIO_ES, VLC_CODEC_F32L);
|
|
audio_fmt.i_id = 2;
|
|
video_fmt.i_group = 2;
|
|
audio_fmt.audio.i_format = VLC_CODEC_F32L;
|
|
audio_fmt.audio.i_rate = 44100;
|
|
audio_fmt.audio.i_physical_channels = 2;
|
|
audio_fmt.audio.i_channels = 2;
|
|
es_out_id_t *audio_track = es_out_Add(&out->out, &audio_fmt);
|
|
assert(audio_track != NULL);
|
|
es_format_Clean(&audio_fmt);
|
|
|
|
block_t *block = block_Alloc(10);
|
|
assert(block);
|
|
es_out_Send(&out->out, video_track, block);
|
|
|
|
block = block_Alloc(10);
|
|
assert(block);
|
|
es_out_Send(&out->out, audio_track, block);
|
|
|
|
es_out_Control(&out->out, ES_OUT_SET_GROUP_PCR, 1, VLC_TICK_0);
|
|
es_out_Control(&out->out, ES_OUT_SET_GROUP_PCR, 1, VLC_TICK_0 + VLC_TICK_FROM_MS(100));
|
|
es_out_Control(&out->out, ES_OUT_SET_GROUP_PCR, 2, VLC_TICK_0 + VLC_TICK_FROM_MS(200));
|
|
es_out_Control(&out->out, ES_OUT_SET_GROUP_PCR, 2, VLC_TICK_0 + VLC_TICK_FROM_MS(300) - 1);
|
|
|
|
es_out_Del(&out->out, audio_track);
|
|
es_out_Del(&out->out, video_track);
|
|
|
|
es_out_SetMode(out, ES_OUT_MODE_END);
|
|
es_out_Delete(&out->out);
|
|
|
|
input_source_Release(source);
|
|
input_item_Release(item);
|
|
vlc_object_delete(&input->obj);
|
|
vlc_object_delete(root);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
test_playback();
|
|
test_multiple_programs();
|
|
return 0;
|
|
}
|
|
|