diff options
Diffstat (limited to 'src/libsink')
| -rw-r--r-- | src/libsink/common.h | 2 | ||||
| -rw-r--r-- | src/libsink/sink.c | 230 | ||||
| -rw-r--r-- | src/libsink/sink.h | 5 |
3 files changed, 148 insertions, 89 deletions
diff --git a/src/libsink/common.h b/src/libsink/common.h index 0bb3ded..254e246 100644 --- a/src/libsink/common.h +++ b/src/libsink/common.h @@ -1,7 +1,5 @@ #pragma once -#define CAMU_SINK_LOCAL - enum { CAMU_SINK_SET = 0, CAMU_SINK_PAUSE, diff --git a/src/libsink/sink.c b/src/libsink/sink.c index 03dd379..716990e 100644 --- a/src/libsink/sink.c +++ b/src/libsink/sink.c @@ -2,21 +2,21 @@ #include <nnwt/multiplex.h> #include "../server/common.h" - -#include "../liana/list.h" -#include "../liana/handler.h" - #include "../buffer/common.h" +#include "../liana/list.h" #include "sink.h" #include "common.h" +#define CAMU_SINK_LOCAL //#define CAMU_SINK_ONESHOT //#define CAMU_SINK_TRACE #ifndef CAMU_SINK_NO_VIDEO +#ifdef CAMU_RENDERER_LIBPLACEBO #include "../render/renderer_libplacebo.h" #endif +#endif // Requested state of the sinks outputs. enum { @@ -91,7 +91,7 @@ enum { #define VIDEO_IS_SINGLE_FRAME(entry) camu_video_buffer_is_single_frame(&(entry)->video.buf) #endif -#if defined CAMU_SCREEN_THREADED && defined CAMU_MIXER_THREADED_START_STOP +#if defined CAMU_SCREEN_THREADED && defined CAMU_MIXER_THREADED #define BLOCKING_SLEEP(delay) nn_thread_sleep(delay) #else #define BLOCKING_SLEEP(delay) nn_event_loop_sleep(sink->loop, delay) @@ -151,7 +151,9 @@ static void remove_entry_audio_buffer(struct camu_sink *sink, struct camu_sink_e { al_assert(!entry->ended && entry->audio.state != BUFFER_ENDED); al_assert(entry->audio.state != BUFFER_INIT); + if (entry->audio.state == BUFFER_ADDED) { + entry->audio.state = BUFFER_SET_OR_BUFFERED; #ifdef CAMU_MIXER_THREADED_START_STOP sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf); #else @@ -161,7 +163,6 @@ static void remove_entry_audio_buffer(struct camu_sink *sink, struct camu_sink_e .opaque = entry }); #endif - entry->audio.state = BUFFER_SET_OR_BUFFERED; } else if (entry->audio.state == BUFFER_SET_OR_BUFFERED) { entry->audio.state = BUFFER_CONFIGURED; } else if (entry->audio.state == BUFFER_QUEUED) { @@ -175,9 +176,10 @@ static void remove_entry_video_buffer(struct camu_sink *sink, struct camu_sink_e // Don't assert !entry->ended here because of single frame handling. al_assert(entry->video.state != BUFFER_ENDED); al_assert(entry->video.state != BUFFER_INIT); + if (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; + sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_VIDEO, &entry->video.buf); } else if (entry->video.state == BUFFER_SET_OR_BUFFERED) { entry->video.state = BUFFER_CONFIGURED; } else if (entry->video.state == BUFFER_QUEUED) { @@ -235,7 +237,7 @@ static void add_or_queue_entry(struct camu_sink_entry *entry) static void sink_local_pause(struct camu_sink *sink, struct camu_sink_entry *entry) { if (camu_clock_is_paused(&entry->clock)) { - entry->audio.buffer_paused = false; + entry->buffers_paused = false; camu_clock_resume(&entry->clock, 0); if (!AUDIO_EMPTY(entry) && sink->audio.state == SINK_PAUSED) { sink->callback(sink->userdata, CAMU_SINK_START, CAMU_SINK_AUDIO, NULL); @@ -248,7 +250,7 @@ static void sink_local_pause(struct camu_sink *sink, struct camu_sink_entry *ent } #endif } else { - entry->audio.buffer_paused = true; + entry->buffers_paused = true; camu_clock_pause(&entry->clock, 0); #ifndef CAMU_SINK_NO_VIDEO if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry) && sink->video.state == SINK_PLAYING) { @@ -598,90 +600,102 @@ static void maybe_cleanup_old_entries(struct camu_sink *sink) void add_audio_if_set_and_buffered(struct camu_sink_entry *entry) { al_assert(!entry->ended); - al_assert(entry->audio.state != BUFFER_ADDED); + al_assert(entry->audio.state != BUFFER_INIT && + (entry->audio.state != BUFFER_QUEUED) && + (entry->audio.state != BUFFER_ADDED)); + if (entry->audio.state == BUFFER_ENDED) { al_log_warn("sink", "Tried to add an ended audio buffer."); return; } + if (entry->audio.state == BUFFER_CONFIGURED) { entry->audio.state = BUFFER_SET_OR_BUFFERED; } else if (entry->audio.state == BUFFER_SET_OR_BUFFERED) { entry->audio.state = BUFFER_ADDED; - bool ignore_video = true; + #ifndef CAMU_SINK_NO_VIDEO - ignore_video = VIDEO_EMPTY(entry) || VIDEO_IS_SINGLE_FRAME(entry); - if (ignore_video) { + if (VIDEO_EMPTY(entry)) { struct camu_sink *sink = entry->sink; sink->callback(sink->userdata, CAMU_SINK_REFRESH_VIDEO, CAMU_SINK_VIDEO, NULL); } #endif - camu_audio_buffer_set_no_video(&entry->audio.buf, ignore_video); -#ifdef CAMU_SINK_LOCAL - // If we're local we don't have to worry about syncing audio-only entries. - camu_audio_buffer_set_ignore_desync(&entry->audio.buf, ignore_video); -#else - // Force resync. - camu_audio_buffer_unpause(&entry->audio.buf); -#endif if (VIDEO_ADDED_OR_EMPTY(entry)) { - add_entry_audio_buffer(entry); - queue_cmd(entry->sink, (struct camu_sink_cmd){ - .op = entry->audio.buffer_paused ? STOP : START, - .value.i = CAMU_SINK_AUDIO - }); #ifndef CAMU_SINK_NO_VIDEO - if (!ignore_video) { - // Single frames are unconditionally added in add_video_if_set_and_buffered(). + bool single_frame = VIDEO_IS_SINGLE_FRAME(entry); + + // Single frames are unconditionally added in add_video_if_set_and_buffered(). + if (!VIDEO_EMPTY(entry) && !single_frame) { add_entry_video_buffer(entry); } #endif + add_entry_audio_buffer(entry); + + // This entry could be in previous. This is well defined but confusing. maybe_remove_previous(entry->sink); + #ifndef CAMU_SINK_NO_VIDEO - if (VIDEO_EMPTY(entry)) { - // This must come after maybe_remove_previous(). + // This must come after maybe_remove_previous(). + if (!single_frame) { queue_cmd(entry->sink, (struct camu_sink_cmd){ - .op = STOP, + .op = VIDEO_EMPTY(entry) || entry->buffers_paused ? STOP : START, .value.i = CAMU_SINK_VIDEO }); } #endif + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = entry->buffers_paused ? STOP : START, + .value.i = CAMU_SINK_AUDIO + }); } - } } #ifndef CAMU_SINK_NO_VIDEO void add_video_if_set_and_buffered(struct camu_sink_entry *entry) { - al_assert(entry->video.state != BUFFER_ADDED); + al_assert(entry->video.state != BUFFER_INIT && + (entry->video.state != BUFFER_QUEUED) && + (entry->video.state != BUFFER_ADDED)); + if (entry->video.state == BUFFER_ENDED) { al_log_warn("sink", "Tried to add an ended video buffer."); return; } + if (entry->video.state == BUFFER_CONFIGURED) { entry->video.state = BUFFER_SET_OR_BUFFERED; } else if (entry->video.state == BUFFER_SET_OR_BUFFERED) { entry->video.state = BUFFER_ADDED; + bool single_frame = VIDEO_IS_SINGLE_FRAME(entry); + if (AUDIO_ADDED_OR_EMPTY(entry)) { - if (entry->audio.state == BUFFER_ADDED) { + add_entry_video_buffer(entry); + if (!AUDIO_EMPTY(entry)) { add_entry_audio_buffer(entry); - queue_cmd(entry->sink, (struct camu_sink_cmd){ - .op = entry->audio.buffer_paused ? STOP : START, - .value.i = CAMU_SINK_AUDIO - }); } - add_entry_video_buffer(entry); + + // This entry could be in previous. maybe_remove_previous(entry->sink); + + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = single_frame || entry->buffers_paused ? STOP : START, + .value.i = CAMU_SINK_VIDEO + }); + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = AUDIO_EMPTY(entry) || entry->buffers_paused ? STOP : START, + .value.i = CAMU_SINK_AUDIO + }); } else if (single_frame) { add_entry_video_buffer(entry); + // Stopping video here is needed if skipping from a video to an image. + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = STOP, + .value.i = CAMU_SINK_VIDEO + }); } - // Stopping video here is needed if skipping from a video to an image. - queue_cmd(entry->sink, (struct camu_sink_cmd){ - .op = single_frame ? STOP : START, - .value.i = CAMU_SINK_VIDEO - }); } } #endif @@ -714,14 +728,30 @@ static void switch_to(struct camu_sink *sink, struct camu_sink_entry *target) if (!target->ended) { add_or_queue_entry(target); + } else { #ifndef CAMU_SINK_NO_VIDEO - } else if (VIDEO_IS_SINGLE_FRAME(target)) { - add_video_if_set_and_buffered(target); + if (VIDEO_IS_SINGLE_FRAME(target)) { + add_video_if_set_and_buffered(target); + } else { + // @TODO: current-less + sink->callback(sink->userdata, CAMU_SINK_REFRESH_VIDEO, CAMU_SINK_VIDEO, NULL); + queue_cmd(sink, (struct camu_sink_cmd){ + .op = STOP, + .value.i = CAMU_SINK_VIDEO + }); + } #endif + queue_cmd(sink, (struct camu_sink_cmd){ + .op = STOP, + .value.i = CAMU_SINK_AUDIO + }); } sink->current = target; sink->current->audio.ignore_paused = false; + if (!sink->current->buffers_paused) { + camu_audio_buffer_unpause(&sink->current->audio.buf); + } } #ifndef CAMU_SINK_LOCAL @@ -811,7 +841,7 @@ static void audio_buffer_callback(void *userdata, u8 op) remove_entry_audio_buffer(sink, entry); } // This assert likely doesn't matter due to the handling of the ENDED state. - //al_assert(entry->audio.state == BUFFER_SET_OR_BUFFERED); + al_assert(entry->audio.state == BUFFER_SET_OR_BUFFERED); entry->audio.state = BUFFER_ENDED; bool end_entry = VIDEO_ENDED_OR_EMPTY(entry); #ifndef CAMU_SINK_NO_VIDEO @@ -859,7 +889,7 @@ static void video_buffer_callback(void *userdata, u8 op) if (entry->video.state == BUFFER_ADDED) { remove_entry_video_buffer(sink, entry); } - //al_assert(entry->video.state == BUFFER_SET_OR_BUFFERED); + al_assert(entry->video.state == BUFFER_SET_OR_BUFFERED); entry->video.state = BUFFER_ENDED; if (AUDIO_ENDED_OR_EMPTY(entry)) { swapped = end_entry_and_advance_queue(sink, entry); @@ -881,17 +911,6 @@ static void video_buffer_callback(void *userdata, u8 op) } #endif -static void maybe_unset_current(struct camu_sink *sink) -{ - if (sink->current) { - if (!sink->current->ended) { - remove_entry_buffers(sink, sink->current); - } - // @TODO: A current-less state is not properly handled. - sink->current = NULL; - } -} - static void clock_callback(void *userdata, u8 op) { struct camu_sink_entry *entry = (struct camu_sink_entry *)userdata; @@ -909,11 +928,12 @@ static void clock_callback(void *userdata, u8 op) } } -static void evaluate_and_set_latency(struct camu_sink *sink, struct camu_sink_entry *entry) +static void evaluate_and_set_buffer_params(struct camu_sink *sink, struct camu_sink_entry *entry) { #ifdef CAMU_SINK_LOCAL #ifndef CAMU_SINK_NO_VIDEO - if (!AUDIO_EMPTY(entry) && !VIDEO_EMPTY(entry)) { + bool ignore_video = VIDEO_EMPTY(entry) || VIDEO_IS_SINGLE_FRAME(entry); + if (!AUDIO_EMPTY(entry) && !ignore_video) { f64 audio = camu_mixer_get_latency(sink->audio.mixer); s32 frames = audio / entry->video.buf.avg_frame_duration; frames -= sink->video.renderer->get_latency(sink->video.renderer); @@ -922,7 +942,11 @@ static void evaluate_and_set_latency(struct camu_sink *sink, struct camu_sink_en #else (void)sink; (void)entry; + bool ignore_video = true; #endif + // If we're local we don't have to worry about syncing audio-only entries. + camu_audio_buffer_set_ignore_desync(&entry->audio.buf, ignore_video); + camu_audio_buffer_set_no_video(&entry->audio.buf, ignore_video); #else // To sync clients with differing audio latencies our only option is to factor the mixer // latency directly into the audio buffer. @@ -933,8 +957,12 @@ static void evaluate_and_set_latency(struct camu_sink *sink, struct camu_sink_en frames += sink->video.renderer->get_latency(sink->video.renderer); camu_video_buffer_set_latency(&entry->video.buf, frames); } + bool ignore_video = VIDEO_EMPTY(entry) || VIDEO_IS_SINGLE_FRAME(entry); +#else + bool ignore_video = true; #endif camu_audio_buffer_set_latency(&entry->audio.buf, audio); + camu_audio_buffer_set_no_video(&entry->audio.buf, ignore_video); #endif } @@ -957,7 +985,7 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str struct camu_sink *sink = entry->sink; switch (op) { case LIANA_CLIENT_CONFIGURE: { - // No data can be sent until all selected streams are configured. + // No data will be sent until all selected streams are configured. switch (stream->type) { case CAMU_STREAM_AUDIO: entry->audio.track = (struct lia_vcr_track *)opaque; @@ -974,9 +1002,6 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str } else { al_assert(false); } - if (VIDEO_ADDED_OR_EMPTY(entry)) { - evaluate_and_set_latency(sink, entry); - } nn_mutex_unlock(&sink->mutex); break; #ifndef CAMU_SINK_NO_VIDEO @@ -995,31 +1020,37 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str } else { al_assert(false); } - if (AUDIO_ADDED_OR_EMPTY(entry)) { - evaluate_and_set_latency(sink, entry); - } nn_mutex_unlock(&sink->mutex); break; case CAMU_STREAM_SUBTITLE: #ifdef CAMU_HAVE_FFMPEG if (!camu_video_buffer_configure_subtitles(&entry->video.buf, stream->av.stream->codecpar)) { - // @TODO: + al_log_warn("sink", "Video buffer failed to configure subtitles."); } #endif break; case CAMU_STREAM_ATTACHMENT: { #ifdef CAMU_HAVE_FFMPEG +#ifdef CAMU_RENDERER_LIBPLACEBO 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 +#endif break; } #endif } break; } + case LIANA_CLIENT_CONFIGURE_COMPLETE: { + // All present buffers were configured. + nn_mutex_lock(&sink->mutex); + evaluate_and_set_buffer_params(sink, entry); + nn_mutex_unlock(&sink->mutex); + break; + } case LIANA_CLIENT_DATA: { struct camu_codec_frame *frame = (struct camu_codec_frame *)opaque; switch (stream->type) { @@ -1098,7 +1129,9 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str } #endif - // Resolve any queued REMOVE_BUFFER requests before waiting. + // Resolve any queued REMOVE_BUFFER requests before blocking. + // This is why we are safe to block the event loop thread even if + // MIXER_THREADED_START_STOP is not set. run_queue_by_opaque(sink, entry); nn_mutex_unlock(&sink->mutex); @@ -1112,7 +1145,7 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str ) { BLOCKING_SLEEP(NNWT_TS_FROM_USEC(2000)); } // Reset possible ENDED state here in case the entry ended at - // some pointer after unlocking to block above. + // some point after unlocking to block above. nn_mutex_lock(&sink->mutex); if (reconnect) { @@ -1205,19 +1238,33 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str nn_mutex_lock(&sink->mutex); if (entry == sink->target) { - maybe_unset_current(sink); + // @TODO: current-less + // current could be paused and targeting this entry. + // That could cause an error in the logic of set_command_callback() + // where we check if target (prev_target) is set. sink->target = NULL; } else if (entry == sink->current) { if (sink->reconnecting) { al_assert(sink->reconnecting == sink->current); sink->reconnecting = NULL; } - sink->current = NULL; if (sink->target) { switch_to(sink, sink->target); sink->target = NULL; + } else { + // @TODO: current-less + sink->current = NULL; + sink->callback(sink->userdata, CAMU_SINK_REFRESH_VIDEO, CAMU_SINK_VIDEO, NULL); + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = STOP, + .value.i = CAMU_SINK_VIDEO + }); + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = STOP, + .value.i = CAMU_SINK_AUDIO + }); } - // If current was never fully added we need to do this here. + // If current was never fully added we need to call this here. maybe_remove_previous(sink); } else if (entry == sink->queued) { sink->queued = NULL; @@ -1249,6 +1296,7 @@ static struct camu_sink_entry *create_entry(struct camu_sink *sink, u32 id) entry->ended = false; camu_clock_init(&entry->clock, clock_callback, entry); + entry->buffers_paused = false; entry->audio.state = BUFFER_INIT; entry->audio.ignore_paused = false; @@ -1293,7 +1341,7 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn, u8 op = nn_packet_read_u8(packet); if (op == LIANA_SINK_UNSET) { - maybe_unset_current(sink); + // @TODO: current-less goto out; } @@ -1327,7 +1375,7 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn, camu_clock_set(&entry->clock, seek_pos / 1000000.0); // ended here does not map to entry->ended, we use it as a hint. if (!ended) { - entry->audio.buffer_paused = pause == LIANA_PAUSE_NONE || pause == LIANA_PAUSE_PAUSE; + entry->buffers_paused = pause == LIANA_PAUSE_NONE || pause == LIANA_PAUSE_PAUSE; } else { camu_clock_resume(&entry->clock, 0); } @@ -1363,7 +1411,7 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn, if (!entry->ended && camu_clock_is_paused(&entry->clock)) { // This will resume user-paused entries, but whatever. - entry->audio.buffer_paused = false; + entry->buffers_paused = false; entry->audio.ignore_paused = false; camu_clock_resume(&entry->clock, 0); } @@ -1473,17 +1521,31 @@ static bool pause_command_callback(void *userdata, struct nn_rpc_connection *con #else switch (pause) { case LIANA_PAUSE_PAUSE: - entry->audio.buffer_paused = true; + entry->buffers_paused = true; camu_clock_pause(&entry->clock, at); + if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry)) { + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = STOP, + .value.i = CAMU_SINK_VIDEO + }); + } break; case LIANA_PAUSE_RESUME: - entry->audio.buffer_paused = false; + entry->buffers_paused = false; camu_clock_resume(&entry->clock, at); - camu_audio_buffer_unpause(&entry->audio.buf); - queue_cmd(entry->sink, (struct camu_sink_cmd){ - .op = START, - .value.i = CAMU_SINK_AUDIO - }); + if (!AUDIO_EMPTY(entry)) { + camu_audio_buffer_unpause(&entry->audio.buf); + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = START, + .value.i = CAMU_SINK_AUDIO + }); + } + if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry)) { + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = START, + .value.i = CAMU_SINK_VIDEO + }); + } break; } #endif diff --git a/src/libsink/sink.h b/src/libsink/sink.h index 6845c6d..9bb408d 100644 --- a/src/libsink/sink.h +++ b/src/libsink/sink.h @@ -40,16 +40,15 @@ enum { struct camu_sink_entry { u32 id; + struct lia_client client; s32 sequence; u16 lru; bool ended; u32 reset_id; struct camu_clock clock; - struct lia_client client; + bool buffers_paused; struct { u8 state; - // Don't start the audio output for this entry. - bool buffer_paused; // Don't stop the audio output when this entry is paused. bool ignore_paused; struct camu_audio_buffer buf; |