summaryrefslogtreecommitdiff
path: root/src/libsink
diff options
context:
space:
mode:
Diffstat (limited to 'src/libsink')
-rw-r--r--src/libsink/common.h2
-rw-r--r--src/libsink/sink.c230
-rw-r--r--src/libsink/sink.h5
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;