From 588b6d8bfa1b2efdebf8c424c86f6069cceafa41 Mon Sep 17 00:00:00 2001 From: Andrew Opalach Date: Sun, 17 Nov 2024 15:26:17 -0500 Subject: Rough subtitles implementation, improve buffers Signed-off-by: Andrew Opalach --- src/libsink/common.h | 2 +- src/libsink/sink.c | 193 ++++++++++++++++++++++++++++++++------------------- src/libsink/sink.h | 1 - 3 files changed, 122 insertions(+), 74 deletions(-) (limited to 'src/libsink') diff --git a/src/libsink/common.h b/src/libsink/common.h index 0bb3ded..708b1bb 100644 --- a/src/libsink/common.h +++ b/src/libsink/common.h @@ -1,6 +1,6 @@ #pragma once -#define CAMU_SINK_LOCAL +//#define CAMU_SINK_LOCAL enum { CAMU_SINK_SET = 0, diff --git a/src/libsink/sink.c b/src/libsink/sink.c index 538515a..2caa973 100644 --- a/src/libsink/sink.c +++ b/src/libsink/sink.c @@ -10,6 +10,8 @@ #include "sink.h" #include "common.h" +#include "../render/renderer_libplacebo.h" + enum { SINK_EMPTY = 0, SINK_PAUSED, @@ -36,7 +38,7 @@ enum { CLOSE }; -#define ENTRY_MAX_AGE 7 +#define ENTRY_MAX_AGE 5 #define BUFFER_EMPTY(buf) ((buf)->state == BUFFER_INIT || (buf)->state == BUFFER_QUEUED) @@ -47,6 +49,13 @@ enum { #endif #define AUDIO_READY_OR_EMPTY(entry) (BUFFER_EMPTY(&(entry)->audio) || (entry)->audio.state == BUFFER_ADDED) +#ifdef CAMU_SINK_NO_VIDEO +#define VIDEO_REMOVED_OR_EMPTY(entry) true +#else +#define VIDEO_REMOVED_OR_EMPTY(entry) (BUFFER_EMPTY(&(entry)->video) || (entry)->video.state != BUFFER_ADDED) +#endif +#define AUDIO_REMOVED_OR_EMPTY(entry) (BUFFER_EMPTY(&(entry)->audio) || (entry)->audio.state != BUFFER_ADDED) + #if defined CAMU_SCREEN_THREADED && defined CAMU_MIXER_THREADED #define BLOCKING_SLEEP(delay) aki_thread_sleep(delay) #else @@ -379,6 +388,36 @@ void add_video_if_set_and_buffered(struct camu_sink_entry *entry) } #endif +static void switch_to(struct camu_sink *sink, struct camu_sink_entry *entry) +{ + if (sink->current) { + if (camu_clock_is_ended(&entry->clock)) { + remove_entry_buffers(sink, sink->current); + } else { + maybe_add_to_previous(sink, sink->current, entry); + } + } + set_or_queue_entry(entry); + sink->current = entry; +} + +static bool end_entry_and_advance_queue(struct camu_sink *sink, struct camu_sink_entry *entry) +{ + al_log_info("sink", "Entry ended."); + camu_clock_end(&entry->clock); + if (sink->target) { + switch_to(sink, sink->target); + sink->target = NULL; + } + if (sink->queued) { + set_or_queue_entry(sink->queued); + sink->current = sink->queued; + sink->queued = NULL; + return true; + } + return false; +} + static void audio_buffer_callback(void *userdata, u8 op) { struct camu_sink_entry *entry = (struct camu_sink_entry *)userdata; @@ -386,9 +425,7 @@ static void audio_buffer_callback(void *userdata, u8 op) switch (op) { case CAMU_BUFFER_BUFFERED: aki_mutex_lock(&sink->mutex); - if (!entry->ended) { - add_audio_if_set_and_buffered(entry); - } + add_audio_if_set_and_buffered(entry); aki_mutex_unlock(&sink->mutex); break; case CAMU_BUFFER_CORK: @@ -410,20 +447,23 @@ static void audio_buffer_callback(void *userdata, u8 op) case CAMU_BUFFER_EOF: { lia_vcr_cork(entry->audio.track); aki_mutex_lock(&sink->mutex); + al_log_info("sink", "Audio EOF."); remove_entry_audio_buffer(sink, entry); - camu_clock_end(&entry->clock); - // EOF on the audio buffer unconditionally - // advances the queue. - if (sink->queued) { - set_or_queue_entry(sink->queued); - sink->current = sink->queued; - sink->queued = NULL; + bool run_queue = VIDEO_REMOVED_OR_EMPTY(entry); +#ifndef CAMU_SINK_NO_VIDEO + bool single_frame = camu_video_buffer_is_single_frame(&entry->video.buf); + run_queue = run_queue || single_frame; +#endif + if (run_queue) { + end_entry_and_advance_queue(sink, entry); } aki_mutex_unlock(&sink->mutex); - queue_cmd(sink, (struct camu_sink_cmd){ - .op = END, - .value.i = entry->sequence - }); + if (run_queue) { + queue_cmd(sink, (struct camu_sink_cmd){ + .op = END, + .value.i = entry->sequence + }); + } break; } } @@ -436,11 +476,8 @@ static void video_buffer_callback(void *userdata, u8 op) struct camu_sink *sink = entry->sink; switch (op) { case CAMU_BUFFER_BUFFERED: { - bool single_frame = camu_video_buffer_is_single_frame(&entry->video.buf); aki_mutex_lock(&sink->mutex); - if (!entry->ended || single_frame) { - add_video_if_set_and_buffered(entry); - } + add_video_if_set_and_buffered(entry); aki_mutex_unlock(&sink->mutex); break; } @@ -455,19 +492,13 @@ static void video_buffer_callback(void *userdata, u8 op) bool single_frame = camu_video_buffer_is_single_frame(&entry->video.buf); bool swapped = false; aki_mutex_lock(&sink->mutex); + al_log_info("sink", "Video EOF."); if (!single_frame) { remove_entry_video_buffer(sink, entry); } - // If this entry has no audio, advance the queue. - bool run_queue = !single_frame && BUFFER_EMPTY(&entry->audio); + bool run_queue = !single_frame && AUDIO_REMOVED_OR_EMPTY(entry); if (run_queue) { - camu_clock_end(&entry->clock); - if (sink->queued) { - set_or_queue_entry(sink->queued); - sink->current = sink->queued; - sink->queued = NULL; - swapped = true; - } + swapped = end_entry_and_advance_queue(sink, entry); } aki_mutex_unlock(&sink->mutex); if (!swapped) { @@ -528,7 +559,7 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str switch (stream->type) { case CAMU_STREAM_AUDIO: entry->audio.track = (struct lia_vcr_track *)opaque; - if (!camu_audio_buffer_configure(&entry->audio.buf, stream)) { + if (!camu_audio_buffer_configure(&entry->audio.buf, stream, sink->audio.mixer)) { lia_client_disconnect(&entry->client); } aki_mutex_lock(&sink->mutex); @@ -545,7 +576,7 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str #ifndef CAMU_SINK_NO_VIDEO case CAMU_STREAM_VIDEO: entry->video.track = (struct lia_vcr_track *)opaque; - if (!camu_video_buffer_configure(&entry->video.buf, stream)) { + if (!camu_video_buffer_configure(&entry->video.buf, stream, sink->video.renderer)) { lia_client_disconnect(&entry->client); } aki_mutex_lock(&sink->mutex); @@ -559,6 +590,20 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str } aki_mutex_unlock(&sink->mutex); break; + case CAMU_STREAM_SUBTITLE: +#ifdef CAMU_HAVE_FFMPEG + camu_video_buffer_configure_subtitles(&entry->video.buf, stream->av.stream->codecpar); +#endif + break; + case CAMU_STREAM_ATTACHMENT: { +#ifdef CAMU_HAVE_FFMPEG + struct camu_renderer_lp *lp = (struct camu_renderer_lp *)sink->video.renderer; + AVDictionaryEntry *entry = av_dict_get(stream->av.stream->metadata, "filename", NULL, 0); + AVCodecParameters *codecpar = stream->av.stream->codecpar; + ass_add_font(lp->ass, entry->value, (const char *)codecpar->extradata, codecpar->extradata_size); +#endif + break; + } #endif } break; @@ -580,6 +625,18 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str } break; } + case LIANA_CLIENT_SUBTITLE: { + switch (stream->type) { + case CAMU_STREAM_SUBTITLE: { +#ifdef CAMU_HAVE_FFMPEG + AVPacket *pkt = (AVPacket *)opaque; + camu_video_buffer_push_subtitle(&entry->video.buf, pkt); +#endif + break; + } + } + break; + } case LIANA_CLIENT_REMOVE_BUFFERS: { aki_mutex_lock(&sink->mutex); if (entry->audio.state == BUFFER_ADDED) { @@ -588,7 +645,8 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str } #ifndef CAMU_SINK_NO_VIDEO bool reconnect = *(bool *)opaque; - bool keep_video = reconnect && camu_video_buffer_is_single_frame(&entry->video.buf); + bool single_frame = camu_video_buffer_is_single_frame(&entry->video.buf); + bool keep_video = reconnect && single_frame; if (!keep_video && entry->video.state == BUFFER_ADDED) { sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_VIDEO, &entry->video.buf); entry->video.state = BUFFER_SET_OR_BUFFERED; @@ -599,7 +657,7 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str while (keep_video ? entry_audio_buffer_held(entry) : entry_buffers_held(entry)) { BLOCKING_SLEEP(AKI_TS_FROM_USEC(2000)); } - if (!keep_video) camu_video_buffer_reset(&entry->video.buf); + if (reconnect && !single_frame) camu_video_buffer_reset(&entry->video.buf); #else while (entry_buffers_held(entry)) { BLOCKING_SLEEP(AKI_TS_FROM_USEC(2000)); @@ -634,8 +692,21 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str } case LIANA_CLIENT_CLOSED: { aki_mutex_lock(&sink->mutex); - if (entry == sink->current) sink->current = NULL; - if (entry == sink->queued) sink->queued = NULL; + if (entry == sink->target) { + if (sink->current) { + remove_entry_buffers(sink, sink->current); + sink->current = NULL; + } + sink->target = NULL; + } else if (entry == sink->current) { + sink->current = NULL; + if (sink->target) { + switch_to(sink, sink->target); + sink->target = NULL; + } + } else if (entry == sink->queued) { + sink->queued = NULL; + } lia_client_free(&entry->client); camu_audio_buffer_free(&entry->audio.buf); #ifndef CAMU_SINK_NO_VIDEO @@ -647,13 +718,13 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str if (rentry == entry) { al_array_remove_at(sink->entries, i); removed = true; - al_log_debug("sink", "Entry closed by disconnect."); + al_log_info("sink", "Entry closed by disconnect."); break; } } al_free(entry); if (!removed) { - al_log_debug("sink", "Entry closed by cleanup."); + al_log_info("sink", "Entry closed by cleanup."); } aki_mutex_unlock(&sink->mutex); break; @@ -743,20 +814,6 @@ static void maybe_cleanup_old_entries(struct camu_sink *sink) } } -static void switch_to(struct camu_sink *sink, struct camu_sink_entry *entry) -{ - if (entry->ended || camu_clock_is_ended(&entry->clock)) { - if (sink->current) { - remove_entry_buffers(sink, sink->current); - } - } else { - if (sink->current) { - maybe_add_to_previous(sink, sink->current, entry); - } - } - set_or_queue_entry(entry); - sink->current = entry; -} static void clock_callback(void *userdata, u8 op) { @@ -790,13 +847,13 @@ static struct camu_sink_entry *get_entry_from_id(struct camu_sink *sink, u16 id, entry->audio.state = BUFFER_INIT; entry->audio.armed = false; - camu_audio_buffer_init(&entry->audio.buf, &entry->clock, sink->audio.mixer); + camu_audio_buffer_init(&entry->audio.buf, &entry->clock); entry->audio.buf.callback = audio_buffer_callback; entry->audio.buf.userdata = entry; #ifndef CAMU_SINK_NO_VIDEO entry->video.state = BUFFER_INIT; - camu_video_buffer_init(&entry->video.buf, &entry->clock, sink->video.renderer); + camu_video_buffer_init(&entry->video.buf, &entry->clock); entry->video.buf.callback = video_buffer_callback; entry->video.buf.userdata = entry; #endif @@ -842,6 +899,7 @@ static bool set_command_callback(void *userdata, struct aki_rpc_connection *conn u64 seek_pos = aki_packet_read_u64(packet); u8 pause = aki_packet_read_u8(packet); bool ended = aki_packet_read_bool(packet); + (void)ended; bool created; struct camu_sink_entry *entry = get_entry_from_id(sink, node_id, &created); @@ -851,9 +909,6 @@ static bool set_command_callback(void *userdata, struct aki_rpc_connection *conn if (created) { camu_clock_set(&entry->clock, seek_pos / 1000000.0); - if (ended) { - camu_clock_end(&entry->clock); - } struct camu_renderer *renderer = NULL; #ifndef CAMU_SINK_NO_VIDEO renderer = sink->video.renderer; @@ -861,8 +916,6 @@ static bool set_command_callback(void *userdata, struct aki_rpc_connection *conn lia_client_connect(&entry->client, sink->loop, sink->type, &addr, port, node_id, seek_pos, renderer); } - entry->ended = ended; - if (op == LIANA_SINK_BUFFER) { goto out; } else if (op == LIANA_SINK_BUFFER_AND_QUEUE) { @@ -890,27 +943,25 @@ static bool set_command_callback(void *userdata, struct aki_rpc_connection *conn al_log_warn("sink", "Ignoring target on NONE."); sink->target = NULL; } - if (sink->current) { - maybe_add_to_previous(sink, sink->current, entry); + if (entry == sink->current) { + break; } - set_or_queue_entry(entry); - sink->current = entry; + switch_to(sink, entry); break; case LIANA_PAUSE_RESUME: if (sink->target) { al_log_warn("sink", "Ignoring target on RESUME."); sink->target = NULL; } + camu_clock_resume(&entry->clock, at); if (entry == sink->current) { camu_audio_buffer_unpause(&entry->audio.buf); - } else if (sink->current) { - maybe_add_to_previous(sink, sink->current, entry); + break; } - camu_clock_resume(&entry->clock, at); - set_or_queue_entry(entry); - sink->current = entry; + switch_to(sink, entry); break; case LIANA_PAUSE_PAUSE: { + // For target to be set that must mean that current is set and armed to pause. struct camu_sink_entry *prev_target = sink->target; if (prev_target) { if (prev_target == entry) { @@ -919,9 +970,8 @@ static bool set_command_callback(void *userdata, struct aki_rpc_connection *conn sink->target = entry; } camu_clock_pause(&prev_target->clock, at); - } else if (sink->current) { - if (camu_clock_is_ended(&sink->current->clock)) { - // Server thought we weren't done, be we are. + } else { + if (!sink->current || camu_clock_is_ended(&sink->current->clock)) { switch_to(sink, entry); } else { sink->target = entry; @@ -941,8 +991,8 @@ static bool set_command_callback(void *userdata, struct aki_rpc_connection *conn camu_audio_buffer_unpause(&entry->audio.buf); } camu_clock_pause(&prev_target->clock, at); - } else if (sink->current) { - if (camu_clock_is_ended(&sink->current->clock)) { + } else { + if (!sink->current || camu_clock_is_ended(&sink->current->clock)) { switch_to(sink, entry); } else { sink->target = entry; @@ -1024,7 +1074,6 @@ static bool seek_command_callback(void *userdata, struct aki_rpc_connection *con if (!current) goto out; if (current->sequence == sequence) { - current->ended = false; #ifdef CAMU_SINK_LOCAL (void)at; lia_client_seek(¤t->client, pos, 0); diff --git a/src/libsink/sink.h b/src/libsink/sink.h index 5feadf5..18ef134 100644 --- a/src/libsink/sink.h +++ b/src/libsink/sink.h @@ -42,7 +42,6 @@ struct camu_sink_entry { u16 lru; struct camu_clock clock; struct lia_client client; - bool ended; struct { u8 state; bool armed; -- cgit v1.2.3-101-g0448