From a48a68cfb04b2020737c0adfb0e7667451a22b5c Mon Sep 17 00:00:00 2001 From: Andrew Opalach Date: Mon, 6 Nov 2023 11:54:08 -0500 Subject: Add camu - Subprojects temporarily omitted Signed-off-by: Andrew Opalach --- src/libsink/meson.build | 2 + src/libsink/sink.c | 632 ++++++++++++++++++++++++++++++++++++++++++++++++ src/libsink/sink.h | 109 +++++++++ 3 files changed, 743 insertions(+) create mode 100644 src/libsink/meson.build create mode 100644 src/libsink/sink.c create mode 100644 src/libsink/sink.h (limited to 'src/libsink') diff --git a/src/libsink/meson.build b/src/libsink/meson.build new file mode 100644 index 0000000..5cbe36d --- /dev/null +++ b/src/libsink/meson.build @@ -0,0 +1,2 @@ +libsink_src = ['sink.c'] +libsink = declare_dependency(sources: libsink_src) diff --git a/src/libsink/sink.c b/src/libsink/sink.c new file mode 100644 index 0000000..cf8328a --- /dev/null +++ b/src/libsink/sink.c @@ -0,0 +1,632 @@ +#include + +#include "sink.h" + +enum { + SINK_EMPTY = 0, + SINK_PAUSED, + SINK_PLAYING +}; + +enum { + ENTRY_LOADED = 0, + ENTRY_DISREGUARDED +}; + +enum { + BUFFER_INIT = 0, + BUFFER_CONFIGURED, + BUFFER_SET_OR_BUFFERED, + BUFFER_ADDED, +}; + +enum { + ADD_BUFFER = 0, + REMOVE_BUFFER, + START, + STOP, + TOGGLE_PAUSE, + SEEK, + CLOSE +}; + +#define ENTRY_AUDIO_BUFFER_HELD(entry) \ + (al_atomic_bool_load(&entry->audio.buf.ref, AL_ATOMIC_RELAXED)) +#ifdef CAMU_SINK_NO_VIDEO +#define ENTRY_BUFFERS_HELD(entry) ENTRY_AUDIO_BUFFER_HELD(entry) +#else +#define ENTRY_VIDEO_BUFFER_HELD(entry) \ + (al_atomic_bool_load(&entry->video.buf.ref, AL_ATOMIC_RELAXED)) +#define ENTRY_BUFFERS_HELD(entry) (ENTRY_AUDIO_BUFFER_HELD(entry) || ENTRY_VIDEO_BUFFER_HELD(entry)) +#endif + +static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd) +{ + switch (cmd->op) { + case ADD_BUFFER: { + switch (cmd->value.i) { + case CAMU_SINK_AUDIO: { + struct camu_sink_entry *entry = (struct camu_sink_entry *)cmd->opaque; + sink->callback(sink->userdata, CAMU_SINK_ADD_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf); + break; + } +#ifndef CAMU_SINK_NO_VIDEO + case CAMU_SINK_VIDEO: { + struct camu_sink_entry *entry = (struct camu_sink_entry *)cmd->opaque; + sink->callback(sink->userdata, CAMU_SINK_ADD_BUFFER, CAMU_SINK_VIDEO, &entry->video.buf); + break; + } +#endif + } + break; + } + case REMOVE_BUFFER: { + switch (cmd->value.i) { + case CAMU_SINK_AUDIO: { + struct camu_sink_entry *entry = (struct camu_sink_entry *)cmd->opaque; + sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf); + break; + } +#ifndef CAMU_SINK_NO_VIDEO + case CAMU_SINK_VIDEO: { + struct camu_sink_entry *entry = (struct camu_sink_entry *)cmd->opaque; + sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_VIDEO, &entry->video.buf); + break; + } +#endif + } + break; + } + case START: { + struct camu_sink *sink = (struct camu_sink *)cmd->opaque; + switch (cmd->value.i) { + case CAMU_SINK_AUDIO: + if (sink->audio.state == SINK_PAUSED) { + sink->callback(sink->userdata, CAMU_SINK_START, CAMU_SINK_AUDIO, NULL); + sink->audio.state = SINK_PLAYING; + } + break; +#ifndef CAMU_SINK_NO_VIDEO + case CAMU_SINK_VIDEO: + if (sink->video.state == SINK_PAUSED) { + sink->callback(sink->userdata, CAMU_SINK_START, CAMU_SINK_VIDEO, NULL); + sink->video.state = SINK_PLAYING; + } + break; +#endif + } + break; + } + case STOP: { + struct camu_sink *sink = (struct camu_sink *)cmd->opaque; + switch (cmd->value.i) { + case CAMU_SINK_AUDIO: + if (sink->audio.state == SINK_PLAYING) { + sink->callback(sink->userdata, CAMU_SINK_STOP, CAMU_SINK_AUDIO, NULL); + sink->audio.state = SINK_PAUSED; + } + break; +#ifndef CAMU_SINK_NO_VIDEO + case CAMU_SINK_VIDEO: + if (sink->video.state == SINK_PLAYING) { + sink->callback(sink->userdata, CAMU_SINK_STOP, CAMU_SINK_VIDEO, NULL); + sink->video.state = SINK_PAUSED; + } + break; +#endif + } + break; + } + case TOGGLE_PAUSE: { + struct camu_sink_entry *entry = (struct camu_sink_entry *)cmd->opaque; + if (camu_clock_is_paused(&entry->clock)) { + camu_clock_resume(&entry->clock); + if (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 (sink->video.state == SINK_PAUSED) { + sink->callback(sink->userdata, CAMU_SINK_START, CAMU_SINK_VIDEO, NULL); + sink->video.state = SINK_PLAYING; + } +#endif + } else { + camu_clock_pause(&entry->clock); +#ifndef CAMU_SINK_NO_VIDEO + if (sink->video.state == SINK_PLAYING) { + sink->callback(sink->userdata, CAMU_SINK_STOP, CAMU_SINK_VIDEO, NULL); + sink->video.state = SINK_PAUSED; + } +#endif + } + break; + } + case SEEK: { + struct camu_sink_entry *entry = (struct camu_sink_entry *)cmd->opaque; + bmu_local_seek(&entry->runner, cmd->value.f); + break; + } + case CLOSE: { + sink->callback(sink->userdata, CAMU_SINK_EXIT, 0, NULL); + break; + } + } +} + +static void queue_signal_callback(void *userdata) +{ + struct camu_sink *sink = (struct camu_sink *)userdata; + u32 size; + struct camu_sink_cmd cmd; + do { + camu_queue_size(sink->queue, size); + if (!size) break; + camu_queue_pop(sink->queue, cmd); + handle_sink_cmd(sink, &cmd); + } while (1); +} + +static void queue_cmd(struct camu_sink *sink, struct camu_sink_cmd cmd) +{ + camu_queue_push(sink->queue, cmd); + aki_signal_send(&sink->signal); +} + +static void add_audio_if_set_and_buffered(struct camu_sink_entry *entry) +{ + u8 state = entry->audio.state; + if (state == BUFFER_SET_OR_BUFFERED) { + entry->sink->callback(entry->sink->userdata, CAMU_SINK_ADD_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf); + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = START, + .value.i = CAMU_SINK_AUDIO, + .opaque = entry->sink + }); +#ifndef CAMU_SINK_NO_VIDEO + if (entry->video.state == BUFFER_INIT || entry->video.state == BUFFER_ADDED) { +#endif + camu_clock_resume(&entry->clock); +#ifndef CAMU_SINK_NO_VIDEO + } +#endif + state = BUFFER_ADDED; + } else if (state == BUFFER_CONFIGURED) { + state = BUFFER_SET_OR_BUFFERED; + } + entry->audio.state = state; +} + +static void audio_buffer_callback(void *userdata, u8 op) +{ + struct camu_sink_entry *entry = (struct camu_sink_entry *)userdata; + switch (op) { + case CAMU_BUFFER_BUFFERED: + aki_mutex_lock(&entry->sink->mutex); + add_audio_if_set_and_buffered(entry); + aki_mutex_unlock(&entry->sink->mutex); + break; + case CAMU_BUFFER_STOP: + bmu_local_stream_stop((struct bmu_local_stream *)entry->audio.stream); + break; + case CAMU_BUFFER_CONTINUE: + bmu_local_stream_continue((struct bmu_local_stream *)entry->audio.stream); + break; + case CAMU_BUFFER_PAUSED: + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = STOP, + .value.i = CAMU_SINK_AUDIO, + .opaque = entry->sink + }); + break; + case CAMU_BUFFER_EOF: { + aki_mutex_lock(&entry->sink->mutex); + u8 ret = entry->sink->callback(entry->sink->userdata, CAMU_SINK_SWAP_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf); + entry->audio.state = BUFFER_SET_OR_BUFFERED; + aki_mutex_unlock(&entry->sink->mutex); + if (ret != CAMU_SINK_BUFFERS_SWAPPED) { + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = STOP, + .value.i = CAMU_SINK_AUDIO, + .opaque = entry->sink + }); + } + break; + } + } +} + +#ifndef CAMU_SINK_NO_VIDEO +static void add_video_if_set_and_buffered(struct camu_sink_entry *entry) +{ + u8 state = entry->video.state; + if (state == BUFFER_SET_OR_BUFFERED) { + entry->sink->callback(entry->sink->userdata, CAMU_SINK_ADD_BUFFER, CAMU_SINK_VIDEO, &entry->video.buf); + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = START, + .value.i = CAMU_SINK_VIDEO, + .opaque = entry->sink + }); + if (entry->audio.state == BUFFER_INIT || entry->audio.state == BUFFER_ADDED) { + camu_clock_resume(&entry->clock); + } + state = BUFFER_ADDED; + } else if (state == BUFFER_CONFIGURED) { + state = BUFFER_SET_OR_BUFFERED; + } + entry->video.state = state; +} + +static void video_buffer_callback(void *userdata, u8 op) +{ + struct camu_sink_entry *entry = (struct camu_sink_entry *)userdata; + switch (op) { + case CAMU_BUFFER_BUFFERED: + aki_mutex_lock(&entry->sink->mutex); + add_video_if_set_and_buffered(entry); + aki_mutex_unlock(&entry->sink->mutex); + break; + case CAMU_BUFFER_STOP: + bmu_local_stream_stop((struct bmu_local_stream *)entry->video.stream); + break; + case CAMU_BUFFER_CONTINUE: + bmu_local_stream_continue((struct bmu_local_stream *)entry->video.stream); + break; + case CAMU_BUFFER_EOF: + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = STOP, + .value.i = CAMU_SINK_VIDEO, + .opaque = entry->sink + }); + break; + } +} +#endif + +static void client_callback(void *userdata, u8 op, struct bmu_client_stream *stream, void *opaque) +{ + struct camu_sink_entry *entry = (struct camu_sink_entry *)userdata; + switch (op) { + case BIMU_CLIENT_CONFIGURE: { + switch (stream->type) { + case BIMU_STREAM_AUDIO: + entry->audio.stream = stream; + camu_audio_buffer_configure(&entry->audio.buf, &stream->stream); + entry->audio.state = BUFFER_CONFIGURED; + break; +#ifndef CAMU_SINK_NO_VIDEO + case BIMU_STREAM_VIDEO: + entry->video.stream = stream; + camu_video_buffer_configure(&entry->video.buf, &stream->stream); + entry->video.state = BUFFER_CONFIGURED; + break; +#endif + } + break; + } + case BIMU_CLIENT_DATA: { + struct camu_frame *frame = (struct camu_frame *)opaque; + switch (stream->type) { + case BIMU_STREAM_AUDIO: + camu_audio_buffer_push(&entry->audio.buf, frame); + break; +#ifndef CAMU_SINK_NO_VIDEO + case BIMU_STREAM_VIDEO: + camu_video_buffer_push(&entry->video.buf, frame); + break; +#endif + default: +#ifdef HAVE_FFMPEG + if (frame->type == CAMU_FFMPEG_COMPAT) { + av_frame_free(&frame->av.frame); + } +#endif + al_free(frame); + } + break; + } + case BIMU_CLIENT_SEEK: { + f64 seek_req = *(f64 *)opaque; + aki_mutex_lock(&entry->sink->mutex); + if (entry->audio.state == BUFFER_ADDED) { + entry->sink->callback(entry->sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf); + entry->audio.state = BUFFER_SET_OR_BUFFERED; + } +#ifndef CAMU_SINK_NO_VIDEO + bool single_frame = camu_video_buffer_is_single_frame(&entry->video.buf); + if (entry->video.state == BUFFER_ADDED && !single_frame) { + entry->sink->callback(entry->sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_VIDEO, &entry->video.buf); + entry->video.state = BUFFER_SET_OR_BUFFERED; + } +#endif + aki_mutex_unlock(&entry->sink->mutex); +#ifndef CAMU_SINK_NO_VIDEO + if (single_frame) { + while (ENTRY_AUDIO_BUFFER_HELD(entry)) { + aki_thread_sleep(AKI_TS_FROM_USEC(200)); + } + } else { +#endif + while (ENTRY_BUFFERS_HELD(entry)) { + aki_thread_sleep(AKI_TS_FROM_USEC(200)); + } +#ifndef CAMU_SINK_NO_VIDEO + } +#endif + camu_clock_seek(&entry->clock, seek_req); + camu_audio_buffer_reset(&entry->audio.buf); +#ifndef CAMU_SINK_NO_VIDEO + if (!single_frame) { + camu_video_buffer_reset(&entry->video.buf); + } +#endif + break; + } + case BIMU_CLIENT_EOF: { + switch (stream->type) { + case BIMU_STREAM_AUDIO: { + camu_audio_buffer_flush(&entry->audio.buf); + break; + } +#ifndef CAMU_SINK_NO_VIDEO + case BIMU_STREAM_VIDEO: { + bool single_frame = camu_video_buffer_is_single_frame(&entry->video.buf); + if (!single_frame) { + camu_video_buffer_flush(&entry->video.buf); + } + break; + } +#endif + } + break; + } + case BIMU_CLIENT_CLOSED: { + bmu_local_close(&entry->runner); + camu_audio_buffer_free(&entry->audio.buf); +#ifndef CAMU_SINK_NO_VIDEO + camu_video_buffer_free(&entry->video.buf); +#endif + cch_entry_free(&entry->entry); + al_free(entry); + al_log_debug("sink", "Entry closed."); + break; + } + } +} + +bool camu_sink_init(struct camu_sink *sink, struct aki_event_loop *loop, + struct camu_mixer *mixer +#ifndef CAMU_SINK_NO_VIDEO + , struct camu_renderer *renderer +#endif + ) +{ + sink->loop = loop; + aki_signal_init(&sink->signal, queue_signal_callback, sink); + aki_signal_start(&sink->signal, sink->loop); + camu_queue_init(sink->queue); + aki_mutex_init(&sink->mutex); + sink->current = NULL; + al_array_init(sink->entries); + 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; +} + +bool camu_sink_connect(struct camu_sink *sink, str *addr, s32 port) +{ + (void)sink; + (void)addr; + (void)port; + return false; +} + +void camu_sink_toggle_pause(struct camu_sink *sink) +{ + aki_mutex_lock(&sink->mutex); + if (sink->current) { + queue_cmd(sink, (struct camu_sink_cmd){ + .op = TOGGLE_PAUSE, + .opaque = sink->current + }); + } + aki_mutex_unlock(&sink->mutex); +} + +void camu_sink_seek(struct camu_sink *sink, f64 pos) +{ + aki_mutex_lock(&sink->mutex); + if (sink->current) { + queue_cmd(sink, (struct camu_sink_cmd){ + .op = SEEK, + .value.f = pos, + .opaque = sink->current + }); + } + aki_mutex_unlock(&sink->mutex); +} + +void camu_sink_skip(struct camu_sink *sink, s32 n) +{ + (void)sink; + (void)n; +} + +struct camu_sink_entry *camu_sink_get_current(struct camu_sink *sink) +{ + aki_mutex_lock(&sink->mutex); + return sink->current; +} + +void camu_sink_return_current(struct camu_sink *sink) +{ + aki_mutex_unlock(&sink->mutex); +} + +static void remove_entry_buffers(struct camu_sink *sink, struct camu_sink_entry *entry) +{ +#ifndef CAMU_SINK_NO_VIDEO + if (entry->video.state == BUFFER_ADDED) { + sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_VIDEO, &entry->video.buf); + entry->video.state = BUFFER_SET_OR_BUFFERED; + } +#endif + if (entry->audio.state == BUFFER_ADDED) { + sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf); + entry->audio.state = BUFFER_SET_OR_BUFFERED; + } +} + +void camu_sink_stop(struct camu_sink *sink) +{ + aki_mutex_lock(&sink->mutex); + if (sink->current) { + remove_entry_buffers(sink, sink->current); + sink->current = NULL; + } + aki_mutex_unlock(&sink->mutex); + queue_cmd(sink, (struct camu_sink_cmd){ + .op = STOP, + .value.i = CAMU_SINK_AUDIO, + .opaque = sink + }); + queue_cmd(sink, (struct camu_sink_cmd){ + .op = CLOSE + }); +} + +void camu_sink_close(struct camu_sink *sink) +{ + aki_mutex_lock(&sink->mutex); + struct camu_sink_entry *entry; + al_array_foreach(sink->entries, i, entry) { + bmu_local_stop(&entry->runner); + } + aki_mutex_unlock(&sink->mutex); + al_array_free(sink->entries); + aki_signal_stop(&sink->signal); + camu_queue_free(sink->queue); + aki_mutex_destroy(&sink->mutex); +} + +static struct camu_sink_entry *entry_from_unique_id(struct camu_sink *sink, str *unique_id) +{ + struct camu_sink_entry *entry; + al_array_foreach(sink->entries, i, entry) { + if (al_str_eq(&entry->unique_id, unique_id)) { + aki_mutex_unlock(&sink->mutex); + return entry; + } + } + return NULL; +} + +bool camu_sink_local_buffer(struct camu_sink *sink, str *unique_id, struct cch_entry *centry) +{ + aki_mutex_lock(&sink->mutex); + + bool add_entry = false; + struct camu_sink_entry *entry = entry_from_unique_id(sink, unique_id); + if (!entry) { + entry = al_alloc_object(struct camu_sink_entry); + add_entry = true; + } + entry->state = ENTRY_LOADED; + if (!add_entry) { + queue_cmd(sink, (struct camu_sink_cmd){ + .op = SEEK, + .value.f = 0, + .opaque = entry + }); + return true; + } + + entry->entry = centry; + if (!bmu_local_init(&entry->runner, centry)) { + al_free(entry); + aki_mutex_unlock(&sink->mutex); + return false; + } + + entry->sink = sink; + al_str_clone(&entry->unique_id, unique_id); + + camu_clock_init(&entry->clock); + + entry->audio.state = BUFFER_INIT; + camu_audio_buffer_init(&entry->audio.buf, &entry->clock, sink->audio.mixer); + entry->audio.buf.callback = audio_buffer_callback; + entry->audio.buf.userdata = entry; + + struct camu_renderer *renderer = NULL; +#ifndef CAMU_SINK_NO_VIDEO + renderer = sink->video.renderer; + entry->video.state = BUFFER_INIT; + camu_video_buffer_init(&entry->video.buf, &entry->clock, renderer); + entry->video.buf.callback = video_buffer_callback; + entry->video.buf.userdata = entry; +#endif + + entry->runner.callback = client_callback; + entry->runner.userdata = entry; + bmu_local_prepare_clients(&entry->runner, renderer); + bmu_local_run(&entry->runner); + + if (add_entry) al_array_push(sink->entries, entry); + + aki_mutex_unlock(&sink->mutex); + + return true; +} + +static void maybe_cleanup_entries(struct camu_sink *sink) +{ + struct camu_sink_entry *entry; + al_array_foreach(sink->entries, i, entry) { + if (entry->state == ENTRY_DISREGUARDED && !ENTRY_BUFFERS_HELD(entry)) { + bmu_local_stop(&entry->runner); + al_array_remove_at_iter(sink->entries, i); + } + } +} + +void camu_sink_local_set(struct camu_sink *sink, str *unique_id) +{ + aki_mutex_lock(&sink->mutex); + struct camu_sink_entry *entry = entry_from_unique_id(sink, unique_id); + if (sink->current) { + if (!camu_clock_is_paused(&sink->current->clock)) { + camu_clock_pause(&sink->current->clock); + } + remove_entry_buffers(sink, sink->current); + } + sink->current = entry; + add_audio_if_set_and_buffered(entry); +#ifndef CAMU_SINK_NO_VIDEO + add_video_if_set_and_buffered(entry); +#endif + maybe_cleanup_entries(sink); + aki_mutex_unlock(&sink->mutex); +} + +void camu_sink_local_swap(struct camu_sink *sink, str *unique_id) +{ + struct camu_sink_entry *entry = entry_from_unique_id(sink, unique_id); + sink->current = entry; + add_audio_if_set_and_buffered(entry); +#ifndef CAMU_SINK_NO_VIDEO + add_video_if_set_and_buffered(entry); +#endif +} + +void camu_sink_local_unload(struct camu_sink *sink, str *unique_id) +{ + aki_mutex_lock(&sink->mutex); + struct camu_sink_entry *entry = entry_from_unique_id(sink, unique_id); + if (entry->state == ENTRY_LOADED) entry->state = ENTRY_DISREGUARDED; + aki_mutex_unlock(&sink->mutex); +} diff --git a/src/libsink/sink.h b/src/libsink/sink.h new file mode 100644 index 0000000..e105245 --- /dev/null +++ b/src/libsink/sink.h @@ -0,0 +1,109 @@ +#pragma once + +//#define CAMU_SINK_NO_VIDEO + +#include +#include +#include +#include + +#include "../util/queue.h" + +#include "../cache/entry.h" + +#include "../buffer/clock.h" +#include "../buffer/audio.h" +#ifndef CAMU_SINK_NO_VIDEO +#include "../buffer/video.h" +#endif + +#include "../bimu/local.h" + +enum { + CAMU_SINK_AUDIO = 0, +#ifndef CAMU_SINK_NO_VIDEO + CAMU_SINK_VIDEO +#endif +}; + +enum { + CAMU_SINK_ADD_BUFFER = 0, + CAMU_SINK_REMOVE_BUFFER, + CAMU_SINK_SWAP_BUFFER, + CAMU_SINK_START, + CAMU_SINK_STOP, + CAMU_SINK_EXIT +}; + +struct camu_sink_entry { + u8 state; + str unique_id; + struct cch_entry *entry; + struct bmu_local runner; + struct camu_clock clock; + struct { + u8 state; + struct camu_audio_buffer buf; + struct bmu_client_stream *stream; + } audio; +#ifndef CAMU_SINK_NO_VIDEO + struct { + u8 state; + struct camu_video_buffer buf; + struct bmu_client_stream *stream; + } video; +#endif + struct camu_sink *sink; +}; + +struct camu_sink_cmd { + u8 op; + union { s64 i; f64 f; } value; + void *opaque; +}; + +enum { + CAMU_SINK_OK = 0, + CAMU_SINK_BUFFERS_SWAPPED = 1 +}; + +struct camu_sink { + struct aki_event_loop *loop; + struct aki_signal signal; + queue(struct camu_sink_cmd) queue; + struct aki_mutex mutex; + struct camu_sink_entry *current; + array(struct camu_sink_entry *) entries; + struct { + u8 state; + struct camu_mixer *mixer; + } audio; +#ifndef CAMU_SINK_NO_VIDEO + struct { + u8 state; + struct camu_renderer *renderer; + } video; +#endif + u8 (*callback)(void *, u8, u8, void *); + void *userdata; +}; + +bool camu_sink_init(struct camu_sink *sink, struct aki_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, str *addr, s32 port); +void camu_sink_toggle_pause(struct camu_sink *sink); +void camu_sink_seek(struct camu_sink *sink, f64 pos); +void camu_sink_skip(struct camu_sink *sink, s32 n); +struct camu_sink_entry *camu_sink_get_current(struct camu_sink *sink); +void camu_sink_return_current(struct camu_sink *sink); +void camu_sink_stop(struct camu_sink *sink); +void camu_sink_close(struct camu_sink *sink); + +bool camu_sink_local_buffer(struct camu_sink *sink, str *unique_id, struct cch_entry *entry); +void camu_sink_local_set(struct camu_sink *sink, str *unique_id); +void camu_sink_local_swap(struct camu_sink *sink, str *unique_id); +void camu_sink_local_unload(struct camu_sink *sink, str *unique_id); -- cgit v1.2.3-101-g0448