diff options
| author | 2025-03-15 20:40:38 -0400 | |
|---|---|---|
| committer | 2025-03-15 20:40:38 -0400 | |
| commit | 846bd3d1b7bcec3653aa9d662f559235c4689e1b (patch) | |
| tree | 56906544460079681f46d23d48d76f1a8242ea75 /src/libsink | |
| parent | 46d5660a2582323e1c9c33a56694c322bc21b7bc (diff) | |
| download | camu-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.c | 1057 | ||||
| -rw-r--r-- | src/libsink/sink.h | 35 |
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(¤t->clock)) { + al_assert(entry != current); + if (current && !current->ended) { current->audio.ignore_paused = true; - camu_clock_pause(¤t->clock, 0); + if (!current->paused) { + camu_clock_pause(¤t->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(¤t->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(¤t->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); |