summaryrefslogtreecommitdiff
path: root/src/libsink
diff options
context:
space:
mode:
authorAndrew Opalach <andrew@akon.city> 2025-03-15 20:40:38 -0400
committerAndrew Opalach <andrew@akon.city> 2025-03-15 20:40:38 -0400
commit846bd3d1b7bcec3653aa9d662f559235c4689e1b (patch)
tree56906544460079681f46d23d48d76f1a8242ea75 /src/libsink
parent46d5660a2582323e1c9c33a56694c322bc21b7bc (diff)
downloadcamu-846bd3d1b7bcec3653aa9d662f559235c4689e1b.tar.gz
camu-846bd3d1b7bcec3653aa9d662f559235c4689e1b.tar.bz2
camu-846bd3d1b7bcec3653aa9d662f559235c4689e1b.zip
Sink refactor wip
Dangling target won't stay around. I have a couple of ideas about how to massively simplify the handling of ended entries. Signed-off-by: Andrew Opalach <andrew@akon.city>
Diffstat (limited to 'src/libsink')
-rw-r--r--src/libsink/sink.c1057
-rw-r--r--src/libsink/sink.h35
2 files changed, 461 insertions, 631 deletions
diff --git a/src/libsink/sink.c b/src/libsink/sink.c
index 3f337d5..e5f9351 100644
--- a/src/libsink/sink.c
+++ b/src/libsink/sink.c
@@ -1,3 +1,5 @@
+#define AL_LOG_SECTION "sink"
+//#define AL_LOG_ENABLE_TRACE
#include <al/log.h>
#include <nnwt/multiplex.h>
@@ -10,7 +12,6 @@
#define CAMU_SINK_LOCAL
//#define CAMU_SINK_ONESHOT
-//#define CAMU_SINK_TRACE
// Requested state of the sinks outputs.
enum {
@@ -20,14 +21,15 @@ enum {
};
enum {
- BUFFER_INIT = 0,
+ // Created.
+ BUFFER_INIT,
// Set but not configured.
BUFFER_QUEUED,
// Ready to receive data.
BUFFER_CONFIGURED,
// The next call to add can add the buffer.
BUFFER_SET_OR_BUFFERED,
- // Treat the buffer like it's added, even though it might not be.
+ // Treat the buffer like it's added, even if it might not be.
BUFFER_ADDED,
// Effectively SET_OR_BUFFERED but not addable until after a reset.
BUFFER_ENDED
@@ -41,52 +43,49 @@ enum {
ADD_BUFFER,
REMOVE_BUFFER,
CLEAR_BUFFERS,
- ERROR_OUT,
+ EJECT_ENTRY,
CLOSE,
// List actions.
+ SKIP,
TOGGLE_PAUSE,
SEEK,
- SKIP,
- SHUFFLE,
RESEEK,
- UNSET,
+ SHUFFLE,
END
};
+#define SINK_LRU_MAX UINT16_MAX
+
// Number of entries to keep buffered at one time.
#define ENTRY_MAX_AGE 3
+// printf format for entries.
+#if defined C89ATOMIC_64BIT
+#define ENTRY_FMT "#%u(0x%llx)"
+#elif defined C89ATOMIC_32BIT
+#define ENTRY_FMT "#%u(0x%lx)"
+#endif
+#define ENTRY_ARG(entry) \
+ ((entry) && (entry) != (struct camu_sink_entry *)0xb00b) ? (entry)->id : 0, (entry) ? (entry) : 0x0
+
+#define AUDIO_STATE(entry) ((entry)->audio.state)
+#define VIDEO_STATE(entry) ((entry)->video.state)
+
// If a buffer is still INIT or QUEUED after the entry is configured, it's "empty".
#define BUFFER_EMPTY(buf) ((buf)->state == BUFFER_INIT || (buf)->state == BUFFER_QUEUED)
#define AUDIO_EMPTY(entry) BUFFER_EMPTY(&(entry)->audio)
-#ifndef CAMU_SINK_NO_VIDEO
#define VIDEO_EMPTY(entry) BUFFER_EMPTY(&(entry)->video)
-#endif
#define AUDIO_ADDED_OR_EMPTY(entry) ((entry)->audio.state == BUFFER_ADDED || BUFFER_EMPTY(&(entry)->audio))
-#ifndef CAMU_SINK_NO_VIDEO
#define VIDEO_ADDED_OR_EMPTY(entry) ((entry)->video.state == BUFFER_ADDED || BUFFER_EMPTY(&(entry)->video))
-#else
-#define VIDEO_ADDED_OR_EMPTY(entry) true
-#endif
#define AUDIO_ENDED_OR_EMPTY(entry) ((entry)->audio.state == BUFFER_ENDED || BUFFER_EMPTY(&(entry)->audio))
-#ifndef CAMU_SINK_NO_VIDEO
#define VIDEO_ENDED_OR_EMPTY(entry) ((entry)->video.state == BUFFER_ENDED || BUFFER_EMPTY(&(entry)->video))
-#else
-#define VIDEO_ENDED_OR_EMPTY(entry) true
-#endif
#define AUDIO_NOT_ADDED(entry) ((entry)->audio.state != BUFFER_ADDED)
-#ifndef CAMU_SINK_NO_VIDEO
#define VIDEO_NOT_ADDED(entry) ((entry)->video.state != BUFFER_ADDED)
-#else
-#define VIDEO_NOT_ADDED(entry) true
-#endif
-#ifndef CAMU_SINK_NO_VIDEO
#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
#define BLOCKING_SLEEP(delay) nn_thread_sleep(delay)
@@ -104,7 +103,6 @@ static inline bool entry_audio_buffer_held(struct camu_sink_entry *entry)
#endif
}
-#ifndef CAMU_SINK_NO_VIDEO
static inline bool entry_video_buffer_held(struct camu_sink_entry *entry)
{
#ifdef CAMU_SCREEN_THREADED
@@ -114,14 +112,23 @@ static inline bool entry_video_buffer_held(struct camu_sink_entry *entry)
return false;
#endif
}
-#endif
static void queue_cmd(struct camu_sink *sink, struct camu_sink_cmd cmd)
{
+#ifdef CAMU_SINK_NO_VIDEO
+ if (cmd.value.i == CAMU_SINK_VIDEO) return;
+#endif
camu_queue_push(sink->queue, cmd);
nn_signal_send(&sink->queue_signal);
}
+static void request_video_refresh(struct camu_sink *sink)
+{
+#ifndef CAMU_SINK_NO_VIDEO
+ sink->callback(sink->userdata, CAMU_SINK_REFRESH_VIDEO, 0, NULL);
+#endif
+}
+
static inline void add_entry_audio_buffer(struct camu_sink_entry *entry)
{
struct camu_sink *sink = entry->sink;
@@ -136,20 +143,21 @@ static inline void add_entry_audio_buffer(struct camu_sink_entry *entry)
#endif
}
-#ifndef CAMU_SINK_NO_VIDEO
static inline void add_entry_video_buffer(struct camu_sink_entry *entry)
{
+#ifndef CAMU_SINK_NO_VIDEO
struct camu_sink *sink = entry->sink;
sink->callback(sink->userdata, CAMU_SINK_ADD_BUFFER, CAMU_SINK_VIDEO, &entry->video.buf);
-}
#endif
+}
static void remove_entry_audio_buffer(struct camu_sink *sink, struct camu_sink_entry *entry)
{
- al_assert(entry->audio.state != BUFFER_ENDED && entry->audio.state != BUFFER_INIT);
-
- if (entry->audio.state == BUFFER_ADDED) {
- entry->audio.state = BUFFER_SET_OR_BUFFERED;
+ al_assert(AUDIO_STATE(entry) != BUFFER_ENDED);
+ al_assert(AUDIO_STATE(entry) != BUFFER_INIT);
+ switch (AUDIO_STATE(entry)) {
+ case BUFFER_ADDED:
+ AUDIO_STATE(entry) = 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
@@ -159,72 +167,71 @@ static void remove_entry_audio_buffer(struct camu_sink *sink, struct camu_sink_e
.opaque = entry
});
#endif
- } else if (entry->audio.state == BUFFER_SET_OR_BUFFERED) {
- entry->audio.state = BUFFER_CONFIGURED;
- } else if (entry->audio.state == BUFFER_QUEUED) {
- entry->audio.state = BUFFER_INIT;
+ break;
+ case BUFFER_SET_OR_BUFFERED:
+ AUDIO_STATE(entry) = BUFFER_CONFIGURED;
+ break;
+ case BUFFER_QUEUED:
+ AUDIO_STATE(entry) = BUFFER_INIT;
+ break;
}
}
-#ifndef CAMU_SINK_NO_VIDEO
static void remove_entry_video_buffer(struct camu_sink *sink, struct camu_sink_entry *entry)
{
// Don't assert !entry->ended here because of single frame handling.
- al_assert(entry->video.state != BUFFER_ENDED && entry->video.state != BUFFER_INIT);
-
- if (entry->video.state == BUFFER_ADDED) {
- entry->video.state = BUFFER_SET_OR_BUFFERED;
+ al_assert(VIDEO_STATE(entry) != BUFFER_ENDED);
+ al_assert(VIDEO_STATE(entry) != BUFFER_INIT);
+ switch (VIDEO_STATE(entry)) {
+ case BUFFER_ADDED:
+ VIDEO_STATE(entry) = BUFFER_SET_OR_BUFFERED;
+#ifndef CAMU_SINK_NO_VIDEO
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) {
+#endif
+ break;
+ case BUFFER_SET_OR_BUFFERED:
+ VIDEO_STATE(entry) = BUFFER_CONFIGURED;
+ break;
+ case BUFFER_QUEUED:
// This can be hit when skipping through entries very fast.
- entry->video.state = BUFFER_INIT;
+ VIDEO_STATE(entry) = BUFFER_INIT;
+ break;
}
}
-#endif
// It's possible for some of an entries buffers to be ENDED while others are still
// ADDED and playing. We handle that by making remove_entry_buffers() and
// add_audio/video_if_set_and_buffered() no-ops for ENDED buffers.
static void remove_entry_buffers(struct camu_sink *sink, struct camu_sink_entry *entry)
{
-#ifdef CAMU_SINK_TRACE
- al_log_info("sink", "remove_entry_buffers(0x%llx), audio_state: %hhu, video_state: %hhu",
- entry, entry->audio.state, entry->video.state);
-#endif
+ trace("remove_entry_buffers("ENTRY_FMT"), audio_state: %hhu, video_state: %hhu.",
+ ENTRY_ARG(entry), AUDIO_STATE(entry), VIDEO_STATE(entry));
al_assert(!entry->ended);
- if (entry->audio.state != BUFFER_ENDED) {
+ if (AUDIO_STATE(entry) != BUFFER_ENDED) {
remove_entry_audio_buffer(sink, entry);
}
-#ifndef CAMU_SINK_NO_VIDEO
- if (entry->video.state != BUFFER_ENDED) {
+ if (VIDEO_STATE(entry) != BUFFER_ENDED) {
remove_entry_video_buffer(sink, entry);
}
-#endif
}
static void add_audio_if_set_and_buffered(struct camu_sink_entry *entry);
-#ifndef CAMU_SINK_NO_VIDEO
static void add_video_if_set_and_buffered(struct camu_sink_entry *entry);
-#endif
static void add_or_queue_entry(struct camu_sink_entry *entry)
{
- if (entry->audio.state == BUFFER_INIT) {
- entry->audio.state = BUFFER_QUEUED;
+ if (AUDIO_STATE(entry) == BUFFER_INIT) {
+ AUDIO_STATE(entry) = BUFFER_QUEUED;
} else {
- al_assert(entry->audio.state != BUFFER_QUEUED);
+ al_assert(AUDIO_STATE(entry) != BUFFER_QUEUED);
add_audio_if_set_and_buffered(entry);
}
-#ifndef CAMU_SINK_NO_VIDEO
- if (entry->video.state == BUFFER_INIT) {
- entry->video.state = BUFFER_QUEUED;
+ if (VIDEO_STATE(entry) == BUFFER_INIT) {
+ VIDEO_STATE(entry) = BUFFER_QUEUED;
} else {
- al_assert(entry->video.state != BUFFER_QUEUED);
+ al_assert(VIDEO_STATE(entry) != BUFFER_QUEUED);
add_video_if_set_and_buffered(entry);
}
-#endif
}
// Disconnecting a packet stream twice before a reconnect is an error.
@@ -240,28 +247,29 @@ static void maybe_disconnect_entry(struct camu_sink_entry *entry)
#ifdef CAMU_SINK_LOCAL
static void sink_local_pause(struct camu_sink *sink, struct camu_sink_entry *entry)
{
- if (camu_clock_is_paused(&entry->clock)) {
- entry->buffers_paused = false;
+ if (!camu_clock_is_paused(&entry->clock)) {
+ entry->paused = true;
+ camu_clock_pause(&entry->clock, 0);
+ // Audio stop will be handled by a BUFFER_PAUSED callback.
+ if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry) && sink->video.state == SINK_PLAYING) {
+#ifndef CAMU_SINK_NO_VIDEO
+ sink->callback(sink->userdata, CAMU_SINK_STOP, CAMU_SINK_VIDEO, NULL);
+#endif
+ sink->video.state = SINK_PAUSED;
+ }
+ } else {
+ entry->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);
sink->audio.state = SINK_PLAYING;
}
-#ifndef CAMU_SINK_NO_VIDEO
if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry) && sink->video.state == SINK_PAUSED) {
+#ifndef CAMU_SINK_NO_VIDEO
sink->callback(sink->userdata, CAMU_SINK_START, CAMU_SINK_VIDEO, NULL);
- sink->video.state = SINK_PLAYING;
- }
#endif
- } else {
- 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) {
- sink->callback(sink->userdata, CAMU_SINK_STOP, CAMU_SINK_VIDEO, NULL);
- sink->video.state = SINK_PAUSED;
+ sink->video.state = SINK_PLAYING;
}
-#endif
}
}
#endif
@@ -272,7 +280,7 @@ static inline s32 get_sequence_for_command(struct camu_sink *sink)
s32 sequence = LIANA_SEQUENCE_ANY;
#if 0
// Setting an explicit sequence makes skip and pause act on "what you see".
- // This is likely not the expected behavior in the common case. The user might
+ // This is unlikely to be expected behavior in the common case. The user might
// feel like their input was eaten if skipping after a different skip happens
// on the server but is yet to be reflected on their end.
if (sink->target) {
@@ -297,14 +305,14 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
sink->audio.state = SINK_PLAYING;
}
break;
-#ifndef CAMU_SINK_NO_VIDEO
case CAMU_SINK_VIDEO:
if (sink->video.state == SINK_PAUSED) {
+#ifndef CAMU_SINK_NO_VIDEO
sink->callback(sink->userdata, CAMU_SINK_START, CAMU_SINK_VIDEO, NULL);
+#endif
sink->video.state = SINK_PLAYING;
}
break;
-#endif
}
break;
}
@@ -316,14 +324,14 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
sink->audio.state = SINK_PAUSED;
}
break;
-#ifndef CAMU_SINK_NO_VIDEO
case CAMU_SINK_VIDEO:
if (sink->video.state == SINK_PLAYING) {
+#ifndef CAMU_SINK_NO_VIDEO
sink->callback(sink->userdata, CAMU_SINK_STOP, CAMU_SINK_VIDEO, NULL);
+#endif
sink->video.state = SINK_PAUSED;
}
break;
-#endif
}
break;
}
@@ -333,11 +341,11 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
case CAMU_SINK_AUDIO:
sink->callback(sink->userdata, CAMU_SINK_ADD_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf);
break;
-#ifndef CAMU_SINK_NO_VIDEO
case CAMU_SINK_VIDEO:
+#ifndef CAMU_SINK_NO_VIDEO
sink->callback(sink->userdata, CAMU_SINK_ADD_BUFFER, CAMU_SINK_VIDEO, &entry->video.buf);
- break;
#endif
+ break;
}
break;
}
@@ -347,11 +355,11 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
case CAMU_SINK_AUDIO:
sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf);
break;
-#ifndef CAMU_SINK_NO_VIDEO
case CAMU_SINK_VIDEO:
+#ifndef CAMU_SINK_NO_VIDEO
sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_VIDEO, &entry->video.buf);
- break;
#endif
+ break;
}
break;
}
@@ -360,15 +368,15 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
case CAMU_SINK_AUDIO:
sink->callback(sink->userdata, CAMU_SINK_CLEAR, CAMU_SINK_AUDIO, NULL);
break;
-#ifndef CAMU_SINK_NO_VIDEO
case CAMU_SINK_VIDEO:
+#ifndef CAMU_SINK_NO_VIDEO
sink->callback(sink->userdata, CAMU_SINK_CLEAR, CAMU_SINK_VIDEO, NULL);
- break;
#endif
+ break;
}
break;
}
- case ERROR_OUT: {
+ case EJECT_ENTRY: {
struct camu_sink_entry *entry = (struct camu_sink_entry *)cmd->opaque;
maybe_disconnect_entry(entry);
break;
@@ -388,14 +396,6 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
nn_rpc_connection_command(sink->conn, packet, NULL, NULL);
break;
}
- case SHUFFLE: {
- if (!sink->conn) return;
- struct nn_packet *packet = nn_rpc_get_packet(&sink->client, CAMU_SERVER_LIST_ACTION);
- nn_packet_write_str(packet, &sink->default_list);
- nn_packet_write_u8(packet, CAMU_LIST_SHUFFLE);
- nn_rpc_connection_command(sink->conn, packet, NULL, NULL);
- break;
- }
case TOGGLE_PAUSE: {
#ifdef CAMU_SINK_LOCAL
sink_local_pause(sink, (struct camu_sink_entry *)cmd->opaque);
@@ -416,14 +416,9 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
nn_packet_write_str(packet, &sink->default_list);
nn_packet_write_u8(packet, CAMU_LIST_SEEK);
struct camu_sink_entry *entry = (struct camu_sink_entry *)cmd->opaque;
- if (entry) {
- nn_packet_write_s32(packet, entry->sequence);
- nn_packet_write_u32(packet, entry->id);
- } else {
- nn_packet_write_s32(packet, LIANA_SEQUENCE_ANY);
- nn_packet_write_u32(packet, 0);
- }
- nn_packet_write_f64(packet, cmd->value.f);
+ nn_packet_write_s32(packet, entry->sequence);
+ nn_packet_write_u32(packet, entry->id);
+ nn_packet_write_u64(packet, cmd->value.u);
nn_rpc_connection_command(sink->conn, packet, NULL, NULL);
break;
}
@@ -432,11 +427,11 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
lia_client_reseek(&entry->client);
break;
}
- case UNSET: {
+ case SHUFFLE: {
if (!sink->conn) return;
struct nn_packet *packet = nn_rpc_get_packet(&sink->client, CAMU_SERVER_LIST_ACTION);
nn_packet_write_str(packet, &sink->default_list);
- nn_packet_write_u8(packet, CAMU_LIST_UNSET);
+ nn_packet_write_u8(packet, CAMU_LIST_SHUFFLE);
nn_rpc_connection_command(sink->conn, packet, NULL, NULL);
break;
}
@@ -468,34 +463,30 @@ static void queue_signal_callback(void *userdata)
static void mixer_callback(void *userdata, u8 op)
{
- struct camu_sink *sink = (struct camu_sink *)userdata;
if (op == CAMU_MIXER_EMPTY) {
- al_log_info("sink", "Mixer empty.");
+ info("Mixer empty.");
#ifndef LIANA_LIST_SCUFFED_LOOP
+ struct camu_sink *sink = (struct camu_sink *)userdata;
queue_cmd(sink, (struct camu_sink_cmd){
.op = STOP,
.value.i = CAMU_SINK_AUDIO
});
-#else
- (void)sink;
#endif
}
}
bool camu_sink_init(struct camu_sink *sink, struct nn_event_loop *loop,
- struct camu_mixer *mixer
-#ifndef CAMU_SINK_NO_VIDEO
- , struct camu_renderer *renderer
-#endif
- )
+ struct camu_mixer *mixer, struct camu_renderer *renderer)
{
sink->loop = loop;
nn_mutex_init(&sink->lock);
nn_signal_init(&sink->queue_signal, sink->loop, queue_signal_callback, sink);
nn_signal_start(&sink->queue_signal);
camu_queue_init(sink->queue);
- sink->queued = NULL;
sink->current = NULL;
+ sink->queued = NULL;
+ sink->target = NULL;
+ sink->reconnecting = NULL;
al_array_init(sink->previous);
al_array_init(sink->entries);
sink->lru = 0;
@@ -503,18 +494,58 @@ bool camu_sink_init(struct camu_sink *sink, struct nn_event_loop *loop,
mixer->userdata = sink;
sink->audio.mixer = mixer;
sink->audio.state = SINK_PAUSED;
-#ifndef CAMU_SINK_NO_VIDEO
sink->video.renderer = renderer;
sink->video.state = SINK_PAUSED;
-#endif
return true;
}
+static s32 entry_lru_compare(const void *a, const void *b)
+{
+ struct camu_sink_entry *aa = *((struct camu_sink_entry **)a);
+ struct camu_sink_entry *bb = *((struct camu_sink_entry **)b);
+ if (aa->lru > bb->lru) return -1;
+ else if (aa->lru < bb->lru) return 1;
+ return 0;
+}
+
+static void maybe_cleanup_old_entries(struct camu_sink *sink)
+{
+ al_array_sort(sink->entries, struct camu_sink_entry *, entry_lru_compare);
+ // We check size <= MAX_AGE in the loops because sink->lru is not
+ // indicative of the amount of entries we have loaded.
+ // The most obvious reason being it's incremented when moving back
+ // and forth between two entries. As well as for buffer and queue operations.
+ u16 max_age = ENTRY_MAX_AGE;
+ // We have to handle sink->lru wrapping in a step before the default case.
+ // 0 65532 65533 65534 65535
+ // 0 1 65533 65534 65535
+ // 0 1 2 65534 65535
+ // 0 1 2 3 65535
+ // 0 1 2 3 4
+ struct camu_sink_entry *entry;
+ al_array_foreach_rev(sink->entries, i, entry) {
+ if (entry->lru > sink->lru && ((SINK_LRU_MAX - entry->lru) + 1) + sink->lru >= max_age) {
+ al_array_remove_at(sink->entries, i);
+ maybe_disconnect_entry(entry);
+ }
+ if (sink->entries.count <= max_age) return;
+ }
+ // Make sure we don't have to consider wrapping here.
+ if (sink->lru >= max_age) {
+ al_array_foreach_rev(sink->entries, i, entry) {
+ al_assert(sink->lru >= entry->lru);
+ if (sink->lru - entry->lru >= max_age) {
+ al_array_remove_at(sink->entries, i);
+ maybe_disconnect_entry(entry);
+ }
+ if (sink->entries.count <= max_age) return;
+ }
+ }
+}
+
static void maybe_remove_previous(struct camu_sink *sink)
{
-#ifdef CAMU_SINK_TRACE
- al_log_info("sink", "maybe_remove_previous(), previous_count: %u", sink->previous.count);
-#endif
+ trace("maybe_remove_previous(), previous_count: %u.", sink->previous.count);
struct camu_sink_entry *previous;
al_array_foreach(sink->previous, i, previous) {
remove_entry_buffers(sink, previous);
@@ -538,9 +569,7 @@ static void remove_previous_if_contains(struct camu_sink *sink, struct camu_sink
// An obvious example of this is at the point an entry gets freed.
static void maybe_remove_from_previous(struct camu_sink *sink, struct camu_sink_entry *entry)
{
-#ifdef CAMU_SINK_TRACE
- al_log_info("sink", "maybe_remove_from_previous(0x%llx)", entry);
-#endif
+ trace("maybe_remove_from_previous("ENTRY_FMT").", ENTRY_ARG(entry));
al_array_remove_all(sink->previous, entry);
}
@@ -548,226 +577,140 @@ static void maybe_remove_from_previous(struct camu_sink *sink, struct camu_sink_
static void maybe_add_to_previous(struct camu_sink *sink, struct camu_sink_entry *previous,
struct camu_sink_entry *target)
{
-#ifdef CAMU_SINK_TRACE
- al_log_info("sink", "maybe_add_to_previous(0x%llx, 0x%llx), audio_state: %hhu, video_state: %hhu",
- previous, target, previous->audio.state, previous->video.state);
-#endif
-
- al_assert(previous != target && !previous->ended);
-
- // If neither of the entries audio or video buffer is ADDED, we don't care about adding it
- // to previous (waiting for the next added entry to remove it).
-#ifndef CAMU_SINK_NO_VIDEO
- if (!(previous->audio.state == BUFFER_ADDED || previous->video.state == BUFFER_ADDED) || target->ended) {
-#else
- if (!(previous->audio.state == BUFFER_ADDED) || target->ended) {
-#endif
+ trace("maybe_add_to_previous("ENTRY_FMT"), "ENTRY_FMT"), audio_state: %hhu, video_state: %hhu.",
+ ENTRY_ARG(previous), ENTRY_ARG(target), AUDIO_STATE(previous), VIDEO_STATE(previous));
+ al_assert(previous != target);
+ al_assert(!previous->ended);
+ // If none of the entry's buffers are added, we don't care about adding it to previous.
+ bool dangling_target = target == (struct camu_sink_entry *)0xb00b;
+ if (dangling_target || target->ended || !(AUDIO_STATE(previous) == BUFFER_ADDED || VIDEO_STATE(previous) == BUFFER_ADDED)) {
remove_entry_buffers(sink, previous);
return;
}
-
al_array_push(sink->previous, previous);
}
-static s32 entry_lru_compare(const void *a, const void *b)
+// Call this after setting the buffer's state to ADDED because this entry might be in previous.
+static void do_add_entry(struct camu_sink_entry *entry)
{
- struct camu_sink_entry *aa = *((struct camu_sink_entry **)a);
- struct camu_sink_entry *bb = *((struct camu_sink_entry **)b);
- if (aa->lru > bb->lru) return -1;
- else if (aa->lru < bb->lru) return 1;
- return 0;
-}
-
-static void maybe_cleanup_old_entries(struct camu_sink *sink)
-{
- al_array_sort(sink->entries, struct camu_sink_entry *, entry_lru_compare);
-
- // We check size <= MAX_AGE in the loops because sink->lru is
- // not indicative of the amount of entries we have loaded.
- // The most obvious reason being it's incremented when moving
- // back and forth between two entries. As well as for buffer and
- // queue operations.
-
- struct camu_sink_entry *entry;
- // Handle sink->lru wrapping. This must happen in a step before the no wrapping case.
- al_array_foreach_rev(sink->entries, i, entry) {
- // 0 65532 65533 65534 65535
- // 0 1 65533 65534 65535
- // 0 1 2 65534 65535
- // 0 1 2 3 65535
- // 0 1 2 3 4
- if (entry->lru > sink->lru && ((UINT16_MAX - entry->lru) + 1) + sink->lru >= ENTRY_MAX_AGE) {
- al_array_remove_at(sink->entries, i);
- maybe_disconnect_entry(entry);
- }
- if (sink->entries.count <= ENTRY_MAX_AGE) return;
+ // Single frames are unconditionally added in add_video_if_set_and_buffered().
+ if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry)) {
+ add_entry_video_buffer(entry);
}
-
- // Checking sink->lru >= ENTRY_MAX_AGE should guarantee
- // we don't have to consider wrapping here.
- if (sink->lru >= ENTRY_MAX_AGE) {
- al_array_foreach_rev(sink->entries, i, entry) {
- al_assert(sink->lru >= entry->lru);
- if (sink->lru - entry->lru >= ENTRY_MAX_AGE) {
- al_array_remove_at(sink->entries, i);
- maybe_disconnect_entry(entry);
- }
- if (sink->entries.count <= ENTRY_MAX_AGE) return;
- }
+ if (!AUDIO_EMPTY(entry)) {
+ add_entry_audio_buffer(entry);
}
+ maybe_remove_previous(entry->sink);
+ queue_cmd(entry->sink, (struct camu_sink_cmd){
+ .op = VIDEO_EMPTY(entry) || VIDEO_IS_SINGLE_FRAME(entry) || entry->paused ? STOP : START,
+ .value.i = CAMU_SINK_VIDEO
+ });
+ if (VIDEO_EMPTY(entry)) {
+ // Clear the screen if skipping from a video to an audio-only entry.
+ request_video_refresh(entry->sink);
+ }
+ queue_cmd(entry->sink, (struct camu_sink_cmd){
+ .op = AUDIO_EMPTY(entry) || entry->paused ? STOP : START,
+ .value.i = CAMU_SINK_AUDIO
+ });
}
void add_audio_if_set_and_buffered(struct camu_sink_entry *entry)
{
al_assert(!entry->ended);
- 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.");
+ al_assert(AUDIO_STATE(entry) != BUFFER_INIT);
+ al_assert(AUDIO_STATE(entry) != BUFFER_QUEUED);
+ al_assert(AUDIO_STATE(entry) != BUFFER_ADDED);
+ switch (AUDIO_STATE(entry)) {
+ case BUFFER_ENDED:
+ warn("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;
-
+ case BUFFER_CONFIGURED:
+ AUDIO_STATE(entry) = BUFFER_SET_OR_BUFFERED;
+ break;
+ case BUFFER_SET_OR_BUFFERED:
+ AUDIO_STATE(entry) = BUFFER_ADDED;
if (VIDEO_ADDED_OR_EMPTY(entry)) {
-#ifndef CAMU_SINK_NO_VIDEO
- 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 must be called after setting the buffer's state to ADDED because
- // this entry might be in previous. It's confusing but well defined,
- // although could probably be avoided by a greater simplification.
- maybe_remove_previous(entry->sink);
-
-#ifndef CAMU_SINK_NO_VIDEO
- // This must come after maybe_remove_previous().
- if (!single_frame) {
- queue_cmd(entry->sink, (struct camu_sink_cmd){
- .op = VIDEO_EMPTY(entry) || entry->buffers_paused ? STOP : START,
- .value.i = CAMU_SINK_VIDEO
- });
- // This is the only place to clear the screen if skipping from a video
- // to an audio-only entry.
- if (VIDEO_EMPTY(entry)) {
- struct camu_sink *sink = entry->sink;
- sink->callback(sink->userdata, CAMU_SINK_REFRESH_VIDEO, 0, NULL);
- }
- }
-#endif
- queue_cmd(entry->sink, (struct camu_sink_cmd){
- .op = entry->buffers_paused ? STOP : START,
- .value.i = CAMU_SINK_AUDIO
- });
+ do_add_entry(entry);
}
+ break;
}
}
-#ifndef CAMU_SINK_NO_VIDEO
void add_video_if_set_and_buffered(struct camu_sink_entry *entry)
{
- // Single frames will be added/removed with ended set.
- 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.");
+ // Single frame entries will be added/removed with ended set.
+ al_assert(VIDEO_STATE(entry) != BUFFER_INIT);
+ al_assert(VIDEO_STATE(entry) != BUFFER_QUEUED);
+ al_assert(VIDEO_STATE(entry) != BUFFER_ADDED);
+ if (VIDEO_STATE(entry) == BUFFER_ENDED) {
+ warn("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)) {
+ } else if (VIDEO_STATE(entry) == BUFFER_CONFIGURED) {
+ VIDEO_STATE(entry) = BUFFER_SET_OR_BUFFERED;
+ } else if (VIDEO_STATE(entry) == BUFFER_SET_OR_BUFFERED) {
+ VIDEO_STATE(entry) = BUFFER_ADDED;
+ if (VIDEO_IS_SINGLE_FRAME(entry)) {
add_entry_video_buffer(entry);
- if (!AUDIO_EMPTY(entry)) {
- add_entry_audio_buffer(entry);
- }
-
- // This entry could be in previous, see note in add_audio_if_set_and_buffered().
- 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
- });
+ }
+ if (AUDIO_ADDED_OR_EMPTY(entry)) {
+ do_add_entry(entry);
}
}
}
-#endif
static void switch_to(struct camu_sink *sink, struct camu_sink_entry *target)
{
-#ifdef CAMU_SINK_TRACE
- al_log_info("sink", "switch_to(0x%llx)", target);
-#endif
+ trace("switch_to("ENTRY_FMT"), current: "ENTRY_FMT".",
+ ENTRY_ARG(target), ENTRY_ARG(sink->current));
+
if (sink->current) {
struct camu_sink_entry *current = sink->current;
al_assert(current != target);
-
- if (!current->ended) {
+ bool ensure_removed = sink->reconnecting || current->ended;
+ if (sink->reconnecting) {
+ al_assert(sink->reconnecting == current);
+ sink->reconnecting = NULL;
+ warn("Unset reconnecting as a substitute for remove.");
+ } else if (!current->ended) {
maybe_add_to_previous(sink, current, target);
- } else {
-#ifndef CAMU_SINK_NO_VIDEO
+ }
+ if (ensure_removed) {
if (VIDEO_IS_SINGLE_FRAME(current)) {
remove_entry_video_buffer(sink, current);
}
-#endif
- al_assert(AUDIO_NOT_ADDED(current) && VIDEO_NOT_ADDED(current));
+ al_assert(AUDIO_NOT_ADDED(current));
+ al_assert(VIDEO_NOT_ADDED(current));
}
+ }
- if (sink->reconnecting) {
- al_assert(sink->reconnecting == current);
- sink->reconnecting = NULL;
- }
+ if (target == (struct camu_sink_entry *)0xb00b) {
+ sink->current = NULL;
+ queue_cmd(sink, (struct camu_sink_cmd){
+ .op = STOP,
+ .value.i = CAMU_SINK_VIDEO
+ });
+ request_video_refresh(sink);
+ return;
}
if (!target->ended) {
remove_previous_if_contains(sink, target);
add_or_queue_entry(target);
} else {
-#ifndef CAMU_SINK_NO_VIDEO
if (VIDEO_IS_SINGLE_FRAME(target)) {
add_video_if_set_and_buffered(target);
} else {
- // @TODO: current-less
queue_cmd(sink, (struct camu_sink_cmd){
.op = STOP,
.value.i = CAMU_SINK_VIDEO
});
}
-#endif
}
sink->current = target;
sink->current->audio.ignore_paused = false;
- if (!sink->current->buffers_paused) {
+ if (!sink->current->paused) {
camu_audio_buffer_resync(&sink->current->audio.buf);
}
}
@@ -776,9 +719,8 @@ static void switch_to(struct camu_sink *sink, struct camu_sink_entry *target)
static void pause_and_swap_to(struct camu_sink *sink, struct camu_sink_entry *target, u64 at)
{
struct camu_sink_entry *current = sink->current;
-#ifdef CAMU_SINK_TRACE
- al_log_info("sink", "pause_and_swap_to(%llx, %llu), current: %llx", target, at, current);
-#endif
+ trace("pause_and_swap_to("ENTRY_FMT"), %llu), current: "ENTRY_FMT".",
+ ENTRY_ARG(target), at, ENTRY_ARG(current));
al_assert(target != current);
if (!current || current->ended) {
switch_to(sink, target);
@@ -792,20 +734,20 @@ static void pause_and_swap_to(struct camu_sink *sink, struct camu_sink_entry *ta
static bool end_entry_and_advance_queue(struct camu_sink *sink, struct camu_sink_entry *entry)
{
- al_log_info("sink", "Entry ended.");
+ info("Entry ("ENTRY_FMT") ended.", ENTRY_ARG(entry));
#ifdef CAMU_SINK_ONESHOT
sink->callback(sink->userdata, CAMU_SINK_MOCK_CLOSE, 0, NULL);
return false;
#endif
- entry->ended = true;
maybe_remove_from_previous(sink, entry);
+ entry->ended = true;
queue_cmd(sink, (struct camu_sink_cmd){
.op = END,
.value.u = entry->reset_id,
.opaque = entry
});
#ifdef LIANA_LIST_SCUFFED_LOOP
- al_log_info("sink", "Looping.");
+ info("Looping.");
return true;
#endif
if (sink->target) {
@@ -839,8 +781,8 @@ static void audio_buffer_callback(void *userdata, u8 op)
break;
case CAMU_BUFFER_PAUSED:
nn_mutex_lock(&sink->lock);
- if (!entry->audio.ignore_paused && entry->buffers_paused) {
- al_log_info("sink", "Audio buffer paused.");
+ if (!entry->audio.ignore_paused && entry->paused) {
+ info("Audio buffer paused.");
queue_cmd(sink, (struct camu_sink_cmd){
.op = STOP,
.value.i = CAMU_SINK_AUDIO
@@ -849,42 +791,33 @@ static void audio_buffer_callback(void *userdata, u8 op)
nn_mutex_unlock(&sink->lock);
break;
case CAMU_BUFFER_EOF: {
+ // @TODO: Sync around BUFFER_EOF is not well tested.
+ info("Audio EOF.");
nn_mutex_lock(&sink->lock);
- al_log_info("sink", "Audio EOF.");
- // @TODO: This is not well synced. EOF can happen at any time
- // while other stuff is happening in the sink. For example
- // while seeking, if EOF happens right after a REMOVE_BUFFERS,
- // we might assert during RECONNECTED because the entry is ended.
- //
- // state can be something other than BUFFER_ADDED here
- // because it could have changed while waiting on the lock above.
- if (entry->audio.state == BUFFER_ADDED) {
+ // Getting EOF on a buffer that isn't ADDED is very possible if the audio
+ // output is threaded. Even more if we had to wait on the lock above.
+ if (AUDIO_STATE(entry) == BUFFER_ADDED) {
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);
- entry->audio.state = BUFFER_ENDED;
- bool end_entry = VIDEO_ENDED_OR_EMPTY(entry);
-#ifndef CAMU_SINK_NO_VIDEO
- end_entry = end_entry || VIDEO_IS_SINGLE_FRAME(entry);
-#endif
- if (end_entry) {
+ al_assert(AUDIO_STATE(entry) == BUFFER_SET_OR_BUFFERED);
+ AUDIO_STATE(entry) = BUFFER_ENDED;
+ if (VIDEO_ENDED_OR_EMPTY(entry) || VIDEO_IS_SINGLE_FRAME(entry)) {
end_entry_and_advance_queue(sink, entry);
}
nn_mutex_unlock(&sink->lock);
break;
}
case CAMU_BUFFER_ERRORED:
- al_log_error("sink", "Audio buffer errored.");
+ error("Audio buffer errored.");
queue_cmd(sink, (struct camu_sink_cmd){
- .op = ERROR_OUT,
+ .op = EJECT_ENTRY,
.opaque = entry
});
break;
}
}
-#ifndef CAMU_SINK_NO_VIDEO
static void video_buffer_callback(void *userdata, u8 op)
{
struct camu_sink_entry *entry = (struct camu_sink_entry *)userdata;
@@ -904,19 +837,19 @@ static void video_buffer_callback(void *userdata, u8 op)
lia_vcr_uncork(entry->video.track);
break;
case CAMU_BUFFER_EOF: {
+ info("Video EOF.");
bool swapped = false;
bool single_frame = VIDEO_IS_SINGLE_FRAME(entry);
nn_mutex_lock(&sink->lock);
- al_log_info("sink", "Video EOF.");
if (!AUDIO_EMPTY(entry)) {
camu_audio_buffer_set_no_video(&entry->audio.buf, true);
}
if (!single_frame) {
- if (entry->video.state == BUFFER_ADDED) {
+ if (VIDEO_STATE(entry) == BUFFER_ADDED) {
remove_entry_video_buffer(sink, entry);
}
- al_assert(entry->video.state == BUFFER_SET_OR_BUFFERED);
- entry->video.state = BUFFER_ENDED;
+ al_assert(VIDEO_STATE(entry) == BUFFER_SET_OR_BUFFERED);
+ VIDEO_STATE(entry) = BUFFER_ENDED;
if (AUDIO_ENDED_OR_EMPTY(entry)) {
swapped = end_entry_and_advance_queue(sink, entry);
}
@@ -931,36 +864,32 @@ static void video_buffer_callback(void *userdata, u8 op)
break;
}
case CAMU_BUFFER_ERRORED:
- al_log_error("sink", "Video buffer errored.");
+ error("Video buffer errored.");
queue_cmd(sink, (struct camu_sink_cmd){
- .op = ERROR_OUT,
+ .op = EJECT_ENTRY,
.opaque = entry
});
break;
}
}
-#endif
static void clock_callback(void *userdata, u8 op)
{
struct camu_sink_entry *entry = (struct camu_sink_entry *)userdata;
struct camu_sink *sink = entry->sink;
if (op == CAMU_CLOCK_PAUSED) {
-#ifdef CAMU_SINK_TRACE
- al_log_info("sink", "clock_callback(), target: 0x%llx", sink->target);
-#endif
nn_mutex_lock(&sink->lock);
+ trace("clock_callback(CAMU_CLOCK_PAUSED, "ENTRY_FMT"), target: "ENTRY_FMT".",
+ ENTRY_ARG(entry), ENTRY_ARG(sink->target));
if (entry == sink->current) {
if (sink->target) {
switch_to(sink, sink->target);
sink->target = NULL;
- } else if (entry->buffers_paused) {
-#ifndef CAMU_SINK_NO_VIDEO
+ } else if (entry->paused) {
queue_cmd(entry->sink, (struct camu_sink_cmd){
.op = STOP,
.value.i = CAMU_SINK_VIDEO
});
-#endif
}
}
nn_mutex_unlock(&sink->lock);
@@ -970,7 +899,6 @@ static void clock_callback(void *userdata, u8 op)
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
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);
@@ -978,11 +906,6 @@ static void evaluate_and_set_buffer_params(struct camu_sink *sink, struct camu_s
frames -= sink->video.renderer->get_latency(sink->video.renderer);
camu_video_buffer_set_latency(&entry->video.buf, -frames);
}
-#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);
@@ -990,16 +913,12 @@ static void evaluate_and_set_buffer_params(struct camu_sink *sink, struct camu_s
// To sync clients with differing audio latencies our only option is to factor the mixer
// latency directly into the audio buffer.
f64 audio = camu_mixer_get_latency(sink->audio.mixer);
-#ifndef CAMU_SINK_NO_VIDEO
if (!VIDEO_EMPTY(entry)) {
s32 frames = audio / entry->video.buf.avg_frame_duration;
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
@@ -1018,6 +937,18 @@ static void run_queue_by_opaque(struct camu_sink *sink, void *opaque)
camu_queue_unlock(sink->queue);
}
+static void remove_from_queue_by_opaque(struct camu_sink *sink, void *opaque)
+{
+ camu_queue_lock(sink->queue);
+ struct camu_sink_cmd *cmd;
+ al_array_foreach_ptr_rev(sink->queue.a, i, cmd) {
+ if (cmd->opaque == opaque) {
+ al_array_remove_at(sink->queue.a, i);
+ }
+ }
+ camu_queue_unlock(sink->queue);
+}
+
static void client_callback(void *userdata, u8 op, struct camu_codec_stream *stream, void *opaque)
{
struct camu_sink_entry *entry = (struct camu_sink_entry *)userdata;
@@ -1029,49 +960,45 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
case CAMU_STREAM_AUDIO:
entry->audio.track = (struct lia_vcr_track *)opaque;
if (!camu_audio_buffer_configure(&entry->audio.buf, stream, sink->audio.mixer)) {
- al_log_warn("sink", "Audio buffer failed to configure.");
+ error("Audio buffer failed to configure.");
maybe_disconnect_entry(entry);
return;
}
nn_mutex_lock(&sink->lock);
- if (entry->audio.state == BUFFER_QUEUED) {
- entry->audio.state = BUFFER_SET_OR_BUFFERED;
- } else if (entry->audio.state == BUFFER_INIT) {
- entry->audio.state = BUFFER_CONFIGURED;
- } else {
- al_assert(false);
+ al_assert(AUDIO_STATE(entry) == BUFFER_QUEUED || AUDIO_STATE(entry) == BUFFER_INIT);
+ if (AUDIO_STATE(entry) == BUFFER_QUEUED) {
+ AUDIO_STATE(entry) = BUFFER_SET_OR_BUFFERED;
+ } else if (AUDIO_STATE(entry) == BUFFER_INIT) {
+ AUDIO_STATE(entry) = BUFFER_CONFIGURED;
}
nn_mutex_unlock(&sink->lock);
break;
-#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, sink->video.renderer)) {
- al_log_warn("sink", "Video buffer failed to configure.");
+ error("Video buffer failed to configure.");
maybe_disconnect_entry(entry);
return;
}
nn_mutex_lock(&sink->lock);
- if (entry->video.state == BUFFER_QUEUED) {
- entry->video.state = BUFFER_SET_OR_BUFFERED;
- } else if (entry->video.state == BUFFER_INIT) {
- entry->video.state = BUFFER_CONFIGURED;
- } else {
- al_assert(false);
+ al_assert(VIDEO_STATE(entry) == BUFFER_QUEUED || VIDEO_STATE(entry) == BUFFER_INIT);
+ if (VIDEO_STATE(entry) == BUFFER_QUEUED) {
+ VIDEO_STATE(entry) = BUFFER_SET_OR_BUFFERED;
+ } else if (VIDEO_STATE(entry) == BUFFER_INIT) {
+ VIDEO_STATE(entry) = BUFFER_CONFIGURED;
}
nn_mutex_unlock(&sink->lock);
break;
case CAMU_STREAM_SUBTITLE:
if (!camu_video_buffer_configure_subtitles(&entry->video.buf, stream)) {
- al_log_warn("sink", "Video buffer failed to configure subtitles.");
+ warn("Video buffer failed to configure subtitles.");
}
break;
case CAMU_STREAM_ATTACHMENT: {
struct camu_renderer *renderer = sink->video.renderer;
- renderer->add_font(renderer, stream);
+ if (renderer) renderer->add_font(renderer, stream);
break;
}
-#endif
}
break;
}
@@ -1086,26 +1013,19 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
struct camu_codec_frame *frame = (struct camu_codec_frame *)opaque;
switch (stream->type) {
case CAMU_STREAM_AUDIO:
- if (!AUDIO_EMPTY(entry)) {
- camu_audio_buffer_push(&entry->audio.buf, frame);
- return;
- }
- break;
-#ifndef CAMU_SINK_NO_VIDEO
+ al_assert(!AUDIO_EMPTY(entry));
+ camu_audio_buffer_push(&entry->audio.buf, frame);
+ return;
case CAMU_STREAM_VIDEO:
- if (!VIDEO_EMPTY(entry)) {
- camu_video_buffer_push(&entry->video.buf, frame);
- return;
- }
- break;
-#endif
+ al_assert(!VIDEO_EMPTY(entry));
+ camu_video_buffer_push(&entry->video.buf, frame);
+ return;
default:
+ camu_codec_frame_discard(frame);
break;
}
- camu_codec_frame_discard(frame);
break;
}
-#ifndef CAMU_SINK_NO_VIDEO
case LIANA_CLIENT_SUBTITLE: {
switch (stream->type) {
case CAMU_STREAM_SUBTITLE: {
@@ -1115,24 +1035,16 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
}
break;
}
-#endif
case LIANA_CLIENT_REMOVE_BUFFERS: {
bool reconnect = *(bool *)opaque;
nn_mutex_lock(&sink->lock);
-#ifdef CAMU_SINK_TRACE
- al_log_info("sink", "remove_buffers(%s), entry == current: %s", BOOLSTR(reconnect), BOOLSTR(entry == sink->current));
-#endif
+ trace("remove_buffers("ENTRY_FMT", %s), entry == current: %s.",
+ ENTRY_ARG(entry), BOOLSTR(reconnect), BOOLSTR(entry == sink->current));
- if (reconnect) {
- if (entry == sink->current) {
- sink->reconnecting = entry;
- if (sink->target) {
- switch_to(sink, sink->target);
- sink->target = NULL;
- }
- }
+ if (reconnect && entry == sink->current) {
+ sink->reconnecting = entry;
}
// Entry might be in previous here if it was added to previous then,
@@ -1140,19 +1052,13 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
// - it was seeked.
remove_previous_if_contains(sink, entry);
- bool skip_audio = sink->audio.state == SINK_PAUSED;
- if (entry->audio.state == BUFFER_ADDED) {
+ if (AUDIO_STATE(entry) == BUFFER_ADDED) {
remove_entry_audio_buffer(sink, entry);
}
-
-#ifndef CAMU_SINK_NO_VIDEO
- bool ignore_video = VIDEO_EMPTY(entry) || (reconnect && VIDEO_IS_SINGLE_FRAME(entry));
- if (!ignore_video) {
- if (entry->video.state == BUFFER_ADDED) {
- remove_entry_video_buffer(sink, entry);
- }
+ bool ignore_video = reconnect && VIDEO_IS_SINGLE_FRAME(entry);
+ if (!ignore_video && VIDEO_STATE(entry) == BUFFER_ADDED) {
+ remove_entry_video_buffer(sink, entry);
}
-#endif
// Resolve any queued REMOVE_BUFFER requests before blocking.
// This is why we are safe to block the event loop thread even if
@@ -1161,13 +1067,9 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
nn_mutex_unlock(&sink->lock);
- while ( // Block until buffers are removed.
-#ifndef CAMU_SINK_NO_VIDEO
- (!skip_audio && entry_audio_buffer_held(entry)) || (!ignore_video && entry_video_buffer_held(entry))
-#else
- (!skip_audio && entry_audio_buffer_held(entry))
-#endif
- ) { BLOCKING_SLEEP(NNWT_TS_FROM_USEC(2000)); }
+ while (entry_audio_buffer_held(entry) || (!ignore_video && entry_video_buffer_held(entry))) {
+ BLOCKING_SLEEP(NNWT_TS_FROM_USEC(2000));
+ }
// Reset possible ENDED state here in case the entry ended at
// some point after unlocking to block above.
@@ -1175,28 +1077,20 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
if (reconnect && entry->ended) {
entry->ended = false;
- if (entry->audio.state == BUFFER_QUEUED) {
- entry->audio.state = BUFFER_INIT;
+ if (AUDIO_STATE(entry) == BUFFER_QUEUED) {
+ AUDIO_STATE(entry) = BUFFER_INIT;
}
-#ifndef CAMU_SINK_NO_VIDEO
- if (entry->video.state == BUFFER_QUEUED) {
- entry->video.state = BUFFER_INIT;
+ if (VIDEO_STATE(entry) == BUFFER_QUEUED) {
+ VIDEO_STATE(entry) = BUFFER_INIT;
}
-#endif
}
-
// SET_OR_BUFFERED, ADDED, or ENDED.
- if (entry->audio.state > BUFFER_CONFIGURED) {
- entry->audio.state = BUFFER_CONFIGURED;
+ if (AUDIO_STATE(entry) > BUFFER_CONFIGURED) {
+ AUDIO_STATE(entry) = BUFFER_CONFIGURED;
}
-
-#ifndef CAMU_SINK_NO_VIDEO
- if (!ignore_video) {
- if (entry->video.state > BUFFER_CONFIGURED) {
- entry->video.state = BUFFER_CONFIGURED;
- }
+ if (!ignore_video && VIDEO_STATE(entry) > BUFFER_CONFIGURED) {
+ VIDEO_STATE(entry) = BUFFER_CONFIGURED;
}
-#endif
nn_mutex_unlock(&sink->lock);
@@ -1204,52 +1098,51 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
if (!AUDIO_EMPTY(entry)) {
camu_audio_buffer_reset(&entry->audio.buf);
}
-#ifndef CAMU_SINK_NO_VIDEO
if (!VIDEO_EMPTY(entry) && !ignore_video) {
camu_video_buffer_reset(&entry->video.buf);
}
-#endif
}
break;
}
case LIANA_CLIENT_RESUME_AT: {
struct lia_timing *time = (struct lia_timing *)opaque;
-#ifdef CAMU_SINK_TRACE
- al_log_info("sink", "resume_at(%llu, %llu)", time->seek_pos, time->at);
-#endif
+ trace("resume_at("ENTRY_FMT"), %llu, %llu), paused_at: %f.",
+ ENTRY_ARG(entry), time->seek_pos, time->at, entry->clock.paused_at);
nn_mutex_lock(&sink->lock);
-#if defined LIANA_LIST_SCUFFED_LOOP && !defined CAMU_SINK_NO_VIDEO
- struct camu_video_buffer *buf = &entry->video.buf;
- if (time->seek_pos == 0 && buf->last_pts >= 0.0) {
- camu_clock_loop(&entry->clock, buf->last_pts);
+#if defined LIANA_LIST_SCUFFED_LOOP
+ if (time->seek_pos == 0) {
+ camu_clock_loop(&entry->clock, camu_clock_get_last_pts(&entry->clock));
} else {
+#endif
camu_clock_seek(&entry->clock, time->seek_pos / 1000000.0, time->at);
+#if defined LIANA_LIST_SCUFFED_LOOP
}
-#else
- camu_clock_seek(&entry->clock, time->seek_pos / 1000000.0, time->at);
#endif
nn_mutex_unlock(&sink->lock);
break;
}
case LIANA_CLIENT_RECONNECTED: {
+ bool unconfigured = *(bool *)opaque;
nn_mutex_lock(&sink->lock);
+ trace("reconnected("ENTRY_FMT"), reconnecting: "ENTRY_FMT".",
+ ENTRY_ARG(entry), ENTRY_ARG(sink->reconnecting));
if (entry == sink->reconnecting) {
al_assert(entry == sink->current);
- if (entry->audio.state > BUFFER_QUEUED) {
+ if (AUDIO_STATE(entry) > BUFFER_QUEUED) {
+ al_assert(!unconfigured);
add_audio_if_set_and_buffered(entry);
} else {
- entry->audio.state = BUFFER_QUEUED;
+ AUDIO_STATE(entry) = BUFFER_QUEUED;
}
-#ifndef CAMU_SINK_NO_VIDEO
- if (entry->video.state > BUFFER_QUEUED) {
+ if (VIDEO_STATE(entry) > BUFFER_QUEUED) {
+ al_assert(!unconfigured);
if (!VIDEO_IS_SINGLE_FRAME(entry)) {
add_video_if_set_and_buffered(entry);
}
} else {
- entry->video.state = BUFFER_QUEUED;
+ VIDEO_STATE(entry) = BUFFER_QUEUED;
}
-#endif
sink->reconnecting = NULL;
}
nn_mutex_unlock(&sink->lock);
@@ -1263,7 +1156,6 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
}
break;
}
-#ifndef CAMU_SINK_NO_VIDEO
case CAMU_STREAM_VIDEO: {
// Single frames are immediately flushed inside the buffer.
if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry)) {
@@ -1271,7 +1163,6 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
}
break;
}
-#endif
}
break;
}
@@ -1279,15 +1170,20 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
// LIANA_CLIENT_REMOVE_BUFFERS has been called on this entry before we're here.
nn_mutex_lock(&sink->lock);
+ if (VIDEO_STATE(entry) == BUFFER_ADDED) {
+ al_assert(VIDEO_IS_SINGLE_FRAME(entry));
+ remove_entry_video_buffer(sink, entry);
+ while (entry_video_buffer_held(entry)) { BLOCKING_SLEEP(NNWT_TS_FROM_USEC(2000)); }
+ }
+
+ // @TODO: Mark for removal here instead.
bool removed;
al_array_remove_checked(sink->entries, entry, removed);
+ remove_from_queue_by_opaque(sink, entry);
if (entry == sink->target) {
- // @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;
+ sink->target = (struct camu_sink_entry *)0xb00b;
+ warn("Attempting to handle a disconnected target.");
} else if (entry == sink->current) {
if (sink->reconnecting) {
al_assert(sink->reconnecting == sink->current);
@@ -1297,16 +1193,13 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
switch_to(sink, sink->target);
sink->target = NULL;
} else {
- // @TODO: current-less
sink->current = NULL;
-#ifndef CAMU_SINK_NO_VIDEO
if (removed) { // Don't stop video on exit.
queue_cmd(entry->sink, (struct camu_sink_cmd){
.op = STOP,
.value.i = CAMU_SINK_VIDEO
});
}
-#endif
}
// If current was never fully added we need to call this here.
maybe_remove_previous(sink);
@@ -1318,10 +1211,8 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
lia_client_free(&entry->client);
camu_audio_buffer_free(&entry->audio.buf);
-#ifndef CAMU_SINK_NO_VIDEO
camu_video_buffer_free(&entry->video.buf);
-#endif
- al_log_info("sink", "Entry (0x%llx) closed by %s.", entry, removed ? "cleanup" : "exit");
+ info("Entry ("ENTRY_FMT") closed by %s.", ENTRY_ARG(entry), removed ? "error" : "cleanup");
al_free(entry);
break;
@@ -1338,21 +1229,20 @@ 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->paused = false;
- entry->audio.state = BUFFER_INIT;
+ AUDIO_STATE(entry) = BUFFER_INIT;
entry->audio.ignore_paused = false;
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;
+ VIDEO_STATE(entry) = BUFFER_INIT;
camu_video_buffer_init(&entry->video.buf, &entry->clock);
entry->video.buf.callback = video_buffer_callback;
entry->video.buf.userdata = entry;
-#endif
+ entry->client.renderer = sink->video.renderer;
entry->client.callback = client_callback;
entry->client.userdata = entry;
entry->client.prefs = sink->prefs;
@@ -1377,14 +1267,18 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn,
struct nn_packet *packet, struct nn_packet *rpacket)
{
struct camu_sink *sink = (struct camu_sink *)userdata;
- (void)conn;
(void)rpacket;
nn_mutex_lock(&sink->lock);
u8 op = nn_packet_read_u8(packet);
if (op == LIANA_SINK_UNSET) {
- // @TODO: current-less
+ if (sink->current) {
+ queue_cmd(sink, (struct camu_sink_cmd){
+ .op = EJECT_ENTRY,
+ .opaque = sink->current
+ });
+ }
goto out;
}
@@ -1403,32 +1297,34 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn,
bool ended = nn_packet_read_bool(packet);
u32 reset_id = nn_packet_read_u32(packet);
- bool created = false;
+ struct camu_sink_entry *current = sink->current;
+ struct camu_sink_entry *prev_target = sink->target;
struct camu_sink_entry *entry = get_entry_from_id(sink, id);
- if (!entry) {
+ bool create = !entry;
+ if (create) {
entry = create_entry(sink, id);
- created = true;
}
entry->sequence = sequence;
sink->lru = al_u16_inc_wrap(sink->lru);
entry->lru = sink->lru;
entry->reset_id = reset_id;
- if (created) {
+ if (create) {
camu_clock_set(&entry->clock, seek_pos / 1000000.0);
- // ended here does not map to entry->ended, we use it as a hint.
+ // ended here does not map to entry->ended, it means the server expects the entry to be ended.
if (!ended) {
- entry->buffers_paused = pause == LIANA_PAUSE_NONE || pause == LIANA_PAUSE_PAUSE;
+ entry->paused = pause == LIANA_PAUSE_NONE || pause == LIANA_PAUSE_PAUSE;
} else {
+#ifndef CAMU_SINK_LOCAL
camu_clock_resume(&entry->clock, 0);
- }
- struct camu_renderer *renderer = NULL;
-#ifndef CAMU_SINK_NO_VIDEO
- renderer = sink->video.renderer;
#endif
- lia_client_connect(&entry->client, sink->loop, sink->type, &addr, port, node_id, seek_pos, renderer);
+ }
+ lia_client_connect(&entry->client, sink->loop, sink->type, &addr, port, node_id, seek_pos);
}
+ trace("set("ENTRY_FMT", %llu), created: %s, pause: %hhu, current: "ENTRY_FMT", target: "ENTRY_FMT".",
+ ENTRY_ARG(entry), at, BOOLSTR(create), pause, ENTRY_ARG(current), ENTRY_ARG(prev_target));
+
if (op == LIANA_SINK_BUFFER) {
goto out;
} else if (op == LIANA_SINK_BUFFER_AND_QUEUE) {
@@ -1443,28 +1339,26 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn,
goto out;
}
- struct camu_sink_entry *current = sink->current;
-
#ifdef CAMU_SINK_LOCAL
- (void)at;
- if (current && !current->ended && !camu_clock_is_paused(&current->clock)) {
+ al_assert(entry != current);
+ if (current && !current->ended) {
current->audio.ignore_paused = true;
- camu_clock_pause(&current->clock, 0);
+ if (!current->paused) {
+ camu_clock_pause(&current->clock, 0);
+ }
}
-
- if (!entry->ended && camu_clock_is_paused(&entry->clock)) {
- // This will resume user-paused entries, but whatever.
- entry->buffers_paused = false;
+ if (!entry->ended) {
entry->audio.ignore_paused = false;
- camu_clock_resume(&entry->clock, 0);
+ if (!entry->paused) {
+ camu_clock_resume(&entry->clock, 0);
+ }
}
-
- al_assert(entry != current);
switch_to(sink, entry);
#else
+ // For target to be set that must mean current is set, armed to pause, and not ended.
switch (pause) {
- case LIANA_PAUSE_NONE: {
- if (sink->target) {
+ case LIANA_PAUSE_NONE:
+ if (prev_target) {
sink->target = NULL;
} else {
al_assert(entry != current);
@@ -1473,9 +1367,8 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn,
switch_to(sink, entry);
}
break;
- }
- case LIANA_PAUSE_RESUME: {
- if (sink->target) {
+ case LIANA_PAUSE_RESUME:
+ if (prev_target) {
sink->target = NULL;
} else {
al_assert(entry != current);
@@ -1487,30 +1380,28 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn,
}
camu_clock_resume(&entry->clock, at);
break;
- }
- case LIANA_PAUSE_PAUSE: {
- struct camu_sink_entry *prev_target = sink->target;
+ case LIANA_PAUSE_PAUSE:
if (prev_target) {
- // For target to be set that must mean that current is
- // set, armed to pause, and not ended.
- al_assert(current && !current->ended);
+ al_assert(current);
+ al_assert(!current->ended);
al_assert(prev_target != entry);
if (current != entry) {
sink->target = entry;
} else {
sink->target = NULL;
}
- camu_clock_pause(&prev_target->clock, at);
+ if (prev_target != (struct camu_sink_entry *)0xb00b) {
+ camu_clock_pause(&prev_target->clock, at);
+ }
} else {
al_assert(current != entry);
pause_and_swap_to(sink, entry, at);
}
break;
- }
- case LIANA_PAUSE_BOTH: {
- struct camu_sink_entry *prev_target = sink->target;
+ case LIANA_PAUSE_BOTH:
if (prev_target) {
- al_assert(current && !current->ended);
+ al_assert(current);
+ al_assert(!current->ended);
al_assert(prev_target != entry);
if (current != entry) {
sink->target = entry;
@@ -1518,7 +1409,9 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn,
sink->target = NULL;
camu_audio_buffer_resync(&entry->audio.buf);
}
- camu_clock_pause(&prev_target->clock, at);
+ if (prev_target != (struct camu_sink_entry *)0xb00b) {
+ camu_clock_pause(&prev_target->clock, at);
+ }
} else {
al_assert(current != entry);
pause_and_swap_to(sink, entry, at);
@@ -1526,7 +1419,6 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn,
camu_clock_resume(&entry->clock, at);
break;
}
- }
#endif
out:
@@ -1544,7 +1436,6 @@ static bool pause_command_callback(void *userdata, struct nn_rpc_connection *con
struct nn_packet *packet, struct nn_packet *rpacket)
{
struct camu_sink *sink = (struct camu_sink *)userdata;
- (void)conn;
(void)rpacket;
u32 id = nn_packet_read_u32(packet);
@@ -1557,20 +1448,22 @@ static bool pause_command_callback(void *userdata, struct nn_rpc_connection *con
al_assert(entry->sequence == sequence);
nn_mutex_lock(&sink->lock);
+
+ trace("pause("ENTRY_FMT", %llu), pause: %hhu, audio_state: %hhu, video_state: %hhu.",
+ ENTRY_ARG(entry), at, pause, AUDIO_STATE(entry), VIDEO_STATE(entry));
+
#ifdef CAMU_SINK_LOCAL
- (void)at;
- (void)pause;
sink_local_pause(sink, entry);
#else
switch (pause) {
case LIANA_PAUSE_PAUSE:
- entry->buffers_paused = true;
+ entry->paused = true;
camu_clock_pause(&entry->clock, at);
- // Audio will be stopped in a BUFFER_PAUSED callback and video
- // will be stopped in clock_callback().
+ // Audio will be stopped in a BUFFER_PAUSED callback.
+ // Video will be stopped in a CLOCK_PAUSED callback.
break;
case LIANA_PAUSE_RESUME:
- entry->buffers_paused = false;
+ entry->paused = false;
camu_clock_resume(&entry->clock, at);
if (!AUDIO_EMPTY(entry)) {
camu_audio_buffer_resync(&entry->audio.buf);
@@ -1579,17 +1472,16 @@ static bool pause_command_callback(void *userdata, struct nn_rpc_connection *con
.value.i = CAMU_SINK_AUDIO
});
}
-#ifndef CAMU_SINK_NO_VIDEO
if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry)) {
queue_cmd(entry->sink, (struct camu_sink_cmd){
.op = START,
.value.i = CAMU_SINK_VIDEO
});
}
-#endif
break;
}
#endif
+
nn_mutex_unlock(&sink->lock);
out:
@@ -1602,7 +1494,6 @@ static bool seek_command_callback(void *userdata, struct nn_rpc_connection *conn
struct nn_packet *packet, struct nn_packet *rpacket)
{
struct camu_sink *sink = (struct camu_sink *)userdata;
- (void)conn;
(void)rpacket;
u32 id = nn_packet_read_u32(packet);
@@ -1616,10 +1507,12 @@ static bool seek_command_callback(void *userdata, struct nn_rpc_connection *conn
if (!entry) goto out;
entry->reset_id = reset_id;
+ trace("seek("ENTRY_FMT"), reset_id: %u.", ENTRY_ARG(entry), reset_id);
+
#ifdef CAMU_SINK_LOCAL
at = 0;
#endif
- // The rest of the seek is handled in LIANA_CLIENT_REMOVE_BUFFERS.
+ // The rest of the seek is handled in client_callback()'s.
lia_client_seek(&entry->client, pos, at);
out:
@@ -1634,25 +1527,30 @@ static struct nn_rpc_command commands[] = {
{ .op = CAMU_SINK_SEEK, .callback = seek_command_callback, .userdata = NULL }
};
-static void idd_callback(void *userdata, struct nn_rpc_connection *conn, struct nn_packet *packet)
+static void identify_callback(void *userdata, struct nn_rpc_connection *conn, struct nn_packet *packet)
{
struct camu_sink *sink = (struct camu_sink *)userdata;
(void)sink;
nn_packet_stream_return_packet(conn->stream, packet);
}
-static void connection_callback(void *userdata, struct nn_rpc_connection *conn)
+static void identify_on_connection(struct camu_sink *sink, struct nn_rpc_connection *conn)
{
- struct camu_sink *sink = (struct camu_sink *)userdata;
sink->conn = conn;
- nn_timer_stop(&sink->periodic_timer);
struct nn_packet *packet = nn_rpc_get_packet(&sink->client, CAMU_SERVER_IDENTIFY);
nn_packet_write_u8(packet, CAMU_SINK);
nn_packet_write_str(packet, &sink->name);
- nn_rpc_connection_command(sink->conn, packet, idd_callback, sink);
+ nn_rpc_connection_command(sink->conn, packet, identify_callback, sink);
}
-static void periodic_timer_callback(void *userdata, struct nn_timer *timer)
+static void connection_callback(void *userdata, struct nn_rpc_connection *conn)
+{
+ struct camu_sink *sink = (struct camu_sink *)userdata;
+ identify_on_connection(sink, conn);
+ nn_timer_stop(&sink->reconnect_timer);
+}
+
+static void reconnect_timer_callback(void *userdata, struct nn_timer *timer)
{
struct camu_sink *sink = (struct camu_sink *)userdata;
(void)timer;
@@ -1666,17 +1564,15 @@ static void connection_closed_callback(void *userdata, struct nn_rpc_connection
al_assert(sink->conn == conn);
sink->conn = NULL;
}
- nn_timer_again(&sink->periodic_timer);
+ nn_timer_again(&sink->reconnect_timer);
}
-bool camu_sink_connect(struct camu_sink *sink, u8 type, str *addr, u16 port, str *name)
+bool camu_sink_connect(struct camu_sink *sink, str *name, u8 type, str *addr, u16 port)
{
al_str_clone(&sink->name, name);
sink->type = type;
al_str_clone(&sink->addr, addr);
sink->port = port;
- nn_timer_init(&sink->periodic_timer, sink->loop, periodic_timer_callback, sink);
- nn_timer_set_repeat(&sink->periodic_timer, NNWT_TS_FROM_USEC(1000000));
nn_rpc_init(&sink->client, sink->loop, connection_callback, connection_closed_callback, sink);
for (u32 i = 0; i < ARRAY_SIZE(commands); i++) {
commands[i].userdata = sink;
@@ -1684,8 +1580,10 @@ bool camu_sink_connect(struct camu_sink *sink, u8 type, str *addr, u16 port, str
nn_rpc_add_command(&sink->client, &commands[i]);
}
nn_rpc_prepare_client(&sink->client);
- // Directly set conn here to handle never connected case.
+ // This client may never connect, make sure conn is still set in that case.
sink->conn = sink->client.conn;
+ nn_timer_init(&sink->reconnect_timer, sink->loop, reconnect_timer_callback, sink);
+ nn_timer_set_repeat(&sink->reconnect_timer, NNWT_TS_FROM_USEC(1000000));
#ifdef CAMU_DIRECT_MODE
struct nn_rpc_connection *conn = sink->client.conn;
nn_multiplex_direct_connect(conn->stream, CAMU_MULTIPLEX_RPC);
@@ -1714,13 +1612,6 @@ void camu_sink_skip(struct camu_sink *sink, s32 n)
});
}
-void camu_sink_shuffle(struct camu_sink *sink)
-{
- queue_cmd(sink, (struct camu_sink_cmd){
- .op = SHUFFLE
- });
-}
-
void camu_sink_toggle_pause(struct camu_sink *sink)
{
nn_mutex_lock(&sink->lock);
@@ -1735,43 +1626,37 @@ void camu_sink_toggle_pause(struct camu_sink *sink)
}
}
-void camu_sink_seek(struct camu_sink *sink, f64 precent)
+void camu_sink_seek(struct camu_sink *sink, void *value, u8 mode)
{
nn_mutex_lock(&sink->lock);
struct camu_sink_entry *current = sink->current;
+ u64 duration;
+ if (current) duration = current->client.duration;
+ f64 pts = camu_clock_get_last_pts(&current->clock);
nn_mutex_unlock(&sink->lock);
- queue_cmd(sink, (struct camu_sink_cmd){
+ if (!current || duration == 0) return;
+ struct camu_sink_cmd cmd = {
.op = SEEK,
- .value.f = precent,
.opaque = current
- });
-}
-
-// @TODO: We need at standard way to get the entries pts even when paused.
-static f64 tmp_get_entry_pts(struct camu_sink_entry *entry)
-{
- f64 pts = camu_clock_get_pts(&entry->clock, 0.0, false);
- if (pts == -1.0) {
-#ifndef CAMU_SINK_NO_VIDEO
- pts = al_atomic_load(f64)(&entry->video.buf.pts, AL_ATOMIC_RELAXED);
-#endif
+ };
+ switch (mode) {
+ case CAMU_SEEK_POS: {
+ u64 pos = *(u64 *)value;
+ cmd.value.u = pos;
+ break;
}
- return pts;
-}
-
-void camu_sink_relative_seek(struct camu_sink *sink, f64 offset)
-{
- nn_mutex_lock(&sink->lock);
- struct camu_sink_entry *current = sink->current;
- nn_mutex_unlock(&sink->lock);
- if (!current) return;
- f64 pts = tmp_get_entry_pts(current) + offset;
- f64 duration = current->client.duration / 1000000.0;
- queue_cmd(sink, (struct camu_sink_cmd){
- .op = SEEK,
- .value.f = pts / duration,
- .opaque = current
- });
+ case CAMU_SEEK_RELATIVE: {
+ f64 offset = *(f64 *)value;
+ cmd.value.u = (u64)((pts + offset) * 1000000);
+ break;
+ }
+ case CAMU_SEEK_PERCENT: {
+ f64 percent = *(f64 *)value;
+ cmd.value.u = (u64)(duration * percent);
+ break;
+ }
+ }
+ queue_cmd(sink, cmd);
}
void camu_sink_reseek(struct camu_sink *sink)
@@ -1779,66 +1664,21 @@ void camu_sink_reseek(struct camu_sink *sink)
nn_mutex_lock(&sink->lock);
struct camu_sink_entry *current = sink->current;
nn_mutex_unlock(&sink->lock);
- if (!current) return;
- queue_cmd(sink, (struct camu_sink_cmd){
- .op = RESEEK,
- .opaque = current
- });
+ if (current) {
+ queue_cmd(sink, (struct camu_sink_cmd){
+ .op = RESEEK,
+ .opaque = current
+ });
+ }
}
-void camu_sink_unset(struct camu_sink *sink)
+void camu_sink_shuffle(struct camu_sink *sink)
{
queue_cmd(sink, (struct camu_sink_cmd){
- .op = UNSET
+ .op = SHUFFLE
});
}
-void camu_sink_set_volume(struct camu_sink *sink, f32 volume)
-{
- camu_mixer_set_volume(sink->audio.mixer, volume);
-}
-
-void camu_sink_offset_volume(struct camu_sink *sink, f32 amount)
-{
- camu_mixer_offset_volume(sink->audio.mixer, amount);
-}
-
-static char status[128];
-
-void camu_sink_status(struct camu_sink *sink)
-{
- nn_mutex_lock(&sink->lock);
- struct camu_sink_entry *current = sink->current;
- nn_mutex_unlock(&sink->lock);
- if (!current) {
- al_log_info("sink", "Nothing playing.");
- return;
- }
- f64 pts = tmp_get_entry_pts(current);
- f64 duration = current->client.duration / 1000000.0;
- s32 text = 0;
- u32 minute = (u32)(pts / 60);
- u32 hour = minute / 60;
- minute -= hour * 60;
- if (camu_clock_is_paused(&current->clock)) {
- text += al_snprintf(status + text, sizeof(status) - text, "⏸ ");
- } else {
- text += al_snprintf(status + text, sizeof(status) - text, "⏵ ");
- }
- text += al_snprintf(status + text, sizeof(status) - text, "[");
- bool show_hour = duration >= 60.0 * 60.0;
- if (show_hour) text += al_snprintf(status + text, sizeof(status) - text, "%.2d:", hour);
- text += al_snprintf(status + text, sizeof(status) - text, "%.2d:%.2d/", minute, (u32)pts % 60);
- minute = (u32)(duration / 60);
- hour = minute / 60;
- minute -= hour * 60;
- if (show_hour) text += al_snprintf(status + text, sizeof(status) - text, "%.2d:", hour);
- text += al_snprintf(status + text, sizeof(status) - text, "%.2d:%.2d", minute, (u32)duration % 60);
- text += al_snprintf(status + text, sizeof(status) - text, "]");
- status[text] = '\0';
- al_log_info("sink", "%s", status);
-}
-
void camu_sink_stop(struct camu_sink *sink)
{
queue_cmd(sink, (struct camu_sink_cmd){
@@ -1856,8 +1696,8 @@ void camu_sink_stop(struct camu_sink *sink)
void camu_sink_close(struct camu_sink *sink)
{
- nn_timer_stop(&sink->periodic_timer);
- nn_timer_disable(&sink->periodic_timer);
+ nn_timer_stop(&sink->reconnect_timer);
+ nn_timer_disable(&sink->reconnect_timer);
if (sink->conn) nn_rpc_conn_disconnect(sink->conn);
struct camu_sink_entry *entry;
al_array_foreach_rev(sink->entries, i, entry) {
@@ -1868,10 +1708,7 @@ void camu_sink_close(struct camu_sink *sink)
void camu_sink_free(struct camu_sink *sink)
{
- struct camu_sink_entry *entry;
- al_array_foreach(sink->entries, i, entry) {
- al_free(entry);
- }
+ al_assert(sink->entries.count == 0);
al_array_free(sink->entries);
nn_rpc_free(&sink->client);
camu_queue_free(sink->queue);
diff --git a/src/libsink/sink.h b/src/libsink/sink.h
index ae8e2e4..32fdc26 100644
--- a/src/libsink/sink.h
+++ b/src/libsink/sink.h
@@ -12,15 +12,15 @@
#include "../buffer/audio.h"
#ifndef CAMU_SINK_NO_VIDEO
#include "../buffer/video.h"
+#else
+#include "../buffer/video_null.h"
#endif
#include "../liana/client.h"
enum {
CAMU_SINK_AUDIO = 0,
-#ifndef CAMU_SINK_NO_VIDEO
CAMU_SINK_VIDEO
-#endif
};
enum {
@@ -38,6 +38,12 @@ enum {
CAMU_SINK_OK = 0
};
+enum {
+ CAMU_SEEK_POS = 0,
+ CAMU_SEEK_RELATIVE,
+ CAMU_SEEK_PERCENT
+};
+
struct camu_sink_entry {
u32 id;
struct lia_client client;
@@ -47,7 +53,7 @@ struct camu_sink_entry {
bool ended;
u32 reset_id;
struct camu_clock clock;
- bool buffers_paused;
+ bool paused;
struct {
u8 state;
// Don't stop the audio output when this entry is paused.
@@ -55,13 +61,11 @@ struct camu_sink_entry {
struct camu_audio_buffer buf;
struct lia_vcr_track *track;
} audio;
-#ifndef CAMU_SINK_NO_VIDEO
struct {
u8 state;
struct camu_video_buffer buf;
struct lia_vcr_track *track;
} video;
-#endif
struct camu_sink *sink;
};
@@ -80,7 +84,7 @@ struct camu_sink {
struct nn_rpc client;
struct nn_rpc_connection *conn;
struct nn_mutex lock;
- struct nn_timer periodic_timer;
+ struct nn_timer reconnect_timer;
struct nn_signal queue_signal;
queue(struct camu_sink_cmd) queue;
str default_list;
@@ -95,12 +99,10 @@ struct camu_sink {
u8 state;
struct camu_mixer *mixer;
} audio;
-#ifndef CAMU_SINK_NO_VIDEO
struct {
u8 state;
struct camu_renderer *renderer;
} video;
-#endif
struct lia_prefs prefs;
struct lia_server *local_server;
u8 (*callback)(void *, u8, u8, void *);
@@ -108,24 +110,15 @@ struct camu_sink {
};
bool camu_sink_init(struct camu_sink *sink, struct nn_event_loop *loop,
- struct camu_mixer *mixer
-#ifndef CAMU_SINK_NO_VIDEO
- , struct camu_renderer *renderer
-#endif
- );
-bool camu_sink_connect(struct camu_sink *sink, u8 type, str *addr, u16 port, str *name);
+ struct camu_mixer *mixer, struct camu_renderer *renderer);
+bool camu_sink_connect(struct camu_sink *sink, str *name, u8 type, str *addr, u16 port);
struct camu_sink_entry *camu_sink_get_current(struct camu_sink *sink);
void camu_sink_return_current(struct camu_sink *sink);
void camu_sink_skip(struct camu_sink *sink, s32 n);
-void camu_sink_shuffle(struct camu_sink *sink);
void camu_sink_toggle_pause(struct camu_sink *sink);
-void camu_sink_seek(struct camu_sink *sink, f64 pos);
-void camu_sink_relative_seek(struct camu_sink *sink, f64 offset);
+void camu_sink_seek(struct camu_sink *sink, void *value, u8 mode);
void camu_sink_reseek(struct camu_sink *sink);
-void camu_sink_unset(struct camu_sink *sink);
-void camu_sink_set_volume(struct camu_sink *sink, f32 volume);
-void camu_sink_offset_volume(struct camu_sink *sink, f32 amount);
-void camu_sink_status(struct camu_sink *sink);
+void camu_sink_shuffle(struct camu_sink *sink);
void camu_sink_stop(struct camu_sink *sink);
void camu_sink_close(struct camu_sink *sink);
void camu_sink_free(struct camu_sink *sink);