From c8412bbedae0fce38db96833732e8ce904721e4c Mon Sep 17 00:00:00 2001 From: Andrew Opalach Date: Sun, 30 Nov 2025 14:32:51 -0500 Subject: Build cleanup and fixes from sink testing Signed-off-by: Andrew Opalach --- src/buffer/audio.c | 11 +- src/buffer/clock.c | 77 +++++---- src/buffer/clock.h | 5 +- src/buffer/video.c | 14 +- src/buffer/video.h | 2 +- src/cache/handlers/cdio.c | 24 ++- src/cache/meson.build | 30 ++-- src/codec/codec.h | 2 +- src/codec/codecs.c | 3 + src/codec/ffmpeg/decoder.c | 3 - src/codec/ffmpeg/meson.build | 2 +- src/fruits/cmc/cmc.c | 8 +- src/fruits/cmc/meson.build | 4 +- src/fruits/cmc/ui/util/waveform.c | 4 +- src/fruits/cmsrv/cmsrv.c | 18 +- src/fruits/cmsrv/meson.build | 8 +- src/fruits/cmsrv/ui.c | 6 + src/fruits/cmsrv/ui.h | 1 + src/fruits/cmv/cmv.c | 97 +++++------ src/fruits/cmv/meson.build | 2 +- src/fruits/common.h | 24 --- src/fruits/ctv/ctv.c | 8 +- src/fruits/meson.build | 7 +- src/liana/client.c | 49 +++--- src/liana/client.h | 8 +- src/liana/handlers/codec_client.c | 2 +- src/liana/list.c | 28 +++- src/liana/list.h | 4 +- src/liana/meson.build | 11 +- src/liana/vcr.c | 73 ++++---- src/libsink/desktop.c | 9 +- src/libsink/input_simulator.c | 10 +- src/libsink/sink.c | 342 ++++++++++++++++---------------------- src/libsink/sink.h | 6 +- src/mixer/audio_miniaudio.c | 62 +++---- src/mixer/audio_miniaudio.h | 2 +- src/portal/src/search.c | 21 ++- src/render/meson.build | 56 +++++-- src/render/queue_libplacebo.c | 15 +- src/render/renderer.h | 5 +- src/render/renderer_libplacebo.c | 100 ++++++----- src/render/renderer_libplacebo.h | 2 +- src/render/renderer_momo.c | 3 +- src/screen/screen.c | 12 +- src/server/server.c | 36 ++-- src/util/queue.h | 16 -- 46 files changed, 631 insertions(+), 601 deletions(-) delete mode 100644 src/fruits/common.h (limited to 'src') diff --git a/src/buffer/audio.c b/src/buffer/audio.c index a740111..998662b 100644 --- a/src/buffer/audio.c +++ b/src/buffer/audio.c @@ -10,12 +10,12 @@ #include "common.h" #include "common_internal.h" -#define BUFFER_SIZE 6.0 +#define BUFFER_SIZE 7.0 #define BUFFER_MARK_MIN 3.25 // Must be a most half of the buffer size. #define BUFFER_MARK_BUFFERED 0.35 #ifdef CAMU_AUDIO_BUFFER_FADE -#define FADE_STEP(fmt) (2.50f / (fmt)->sample_rate) +#define FADE_STEP(fmt, down) ((down ? -2.5f : 2.5f) / (fmt)->sample_rate) #define FADE_MIN 0.175f #endif @@ -436,10 +436,9 @@ ptrdiff_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, ptrdif f32 step = 0.f; if (buf->pause == PAUSE_FADING || buf->fade.volume != buf->volume.user) { - step = FADE_STEP(fmt); - step *= (buf->pause == PAUSE_FADING || buf->fade.volume > buf->volume.user) ? -1.f : 1.f; - if (buf->volume.user > 1.f) step *= buf->volume.user; - step *= MAX(buf->fade.volume, FADE_MIN); + bool down = (buf->pause == PAUSE_FADING || buf->volume.user < buf->fade.volume); + step = FADE_STEP(fmt, down) * MAX(buf->fade.volume, FADE_MIN) * + ((buf->volume.user > 1.f) ? buf->volume.user : 1.f); // Move faster at >100%. } if (step != 0.f || buf->fade.volume != 1.f) { buf->fade.volume = apply_volume(data, req, fmt, buf->fade.volume, buf->volume.user, step); diff --git a/src/buffer/clock.c b/src/buffer/clock.c index 732185e..356ac56 100644 --- a/src/buffer/clock.c +++ b/src/buffer/clock.c @@ -7,6 +7,11 @@ #define RUNNING 0.0 #define PAUSED -DBL_MAX +// @TODO: +// Stop throwing away precision before we need to. +// Assume calc_tick_offset() could give a negative result at any point. +// Use a tick offset to set local buffer latency in a way that makes more sense. + void camu_clock_init(struct camu_clock *clock, void (*callback)(void *, u8), void *userdata) { clock->callback = callback; @@ -22,28 +27,33 @@ static f64 calc_tick_offset(f64 tick, u64 now, u64 target) } } +static void offset_tick(struct camu_clock *clock, f64 diff) +{ + diff += atomic_load(f64)(&clock->tick, AL_ATOMIC_ACQUIRE); + atomic_store(f64)(&clock->tick, diff, AL_ATOMIC_RELEASE); +} + void camu_clock_set(struct camu_clock *clock, f64 base) { clock->base = base; - clock->offset = 0.0; - al_atomic_store(f64)(&clock->tick, -1.0, AL_ATOMIC_RELAXED); - al_atomic_store(f64)(&clock->pause, PAUSED, AL_ATOMIC_RELAXED); + atomic_store(f64)(&clock->tick, -1.0, AL_ATOMIC_RELAXED); + atomic_store(f64)(&clock->pause, PAUSED, AL_ATOMIC_RELAXED); clock->paused_at = 0.0; - al_atomic_store(f64)(&clock->last_pts, base, AL_ATOMIC_RELAXED); + atomic_store(f64)(&clock->last_pts, base, AL_ATOMIC_RELAXED); } void camu_clock_seek(struct camu_clock *clock, f64 base, u64 target) { clock->base = base; - clock->offset = 0.0; - al_atomic_store(f64)(&clock->last_pts, base, AL_ATOMIC_RELAXED); + atomic_store(f64)(&clock->last_pts, base, AL_ATOMIC_RELAXED); if (clock->paused_at == -1.0) { // We are safe to directly edit the tick here. if (target == 0) { - al_atomic_store(f64)(&clock->tick, -1.0, AL_ATOMIC_RELAXED); + atomic_store(f64)(&clock->tick, -1.0, AL_ATOMIC_RELAXED); } else { - f64 tick = calc_tick_offset(nn_get_tick(), nn_get_timestamp(), target); - al_atomic_store(f64)(&clock->tick, tick, AL_ATOMIC_RELAXED); + f64 tick = nn_get_tick(); + tick = calc_tick_offset(tick, nn_get_timestamp(), target); + atomic_store(f64)(&clock->tick, tick, AL_ATOMIC_RELAXED); } } else { // Don't touch clock->pause here for the sake of sync. This means, @@ -57,28 +67,29 @@ void camu_clock_seek(struct camu_clock *clock, f64 base, u64 target) } } +// Seek to 0 but include the time it took to perform the seek. void camu_clock_loop(struct camu_clock *clock, f64 last_pts) { - // Seek to 0 but include the time it took to perform the seek. - clock->offset += last_pts - clock->base; clock->base = 0.0; + offset_tick(clock, last_pts - clock->base); } +// If late, pause immediately and on resume() there will be a long delay. void camu_clock_pause(struct camu_clock *clock, u64 target) { al_assert(clock->paused_at == -1.0); - f64 tick = nn_get_tick(); + f64 now = nn_get_tick(), tick = now; if (target > 0) { tick = calc_tick_offset(tick, nn_get_timestamp(), target); - al_atomic_store(f64)(&clock->pause, tick, AL_ATOMIC_RELAXED); - } else { - al_atomic_store(f64)(&clock->pause, PAUSED, AL_ATOMIC_RELAXED); } + atomic_store(f64)(&clock->pause, (tick > now) ? tick : -tick, AL_ATOMIC_RELAXED); + clock->paused_at = tick; } +// If late, this will be a catchup. void camu_clock_resume(struct camu_clock *clock, u64 target) { al_assert(clock->paused_at != -1.0); @@ -91,22 +102,23 @@ void camu_clock_resume(struct camu_clock *clock, u64 target) if (clock->paused_at == 0.0) { if (target == 0) { // target = 0 can never be synced. - al_atomic_store(f64)(&clock->tick, -1.0, AL_ATOMIC_RELAXED); + atomic_store(f64)(&clock->tick, -1.0, AL_ATOMIC_RELAXED); } else { - al_atomic_store(f64)(&clock->tick, tick, AL_ATOMIC_RELAXED); + atomic_store(f64)(&clock->tick, tick, AL_ATOMIC_RELAXED); } } else { - clock->offset += tick - clock->paused_at; + f64 pause = atomic_load(f64)(&clock->pause, AL_ATOMIC_ACQUIRE); + offset_tick(clock, tick - ((pause != PAUSED) ? -pause : clock->paused_at)); } - al_atomic_store(f64)(&clock->pause, RUNNING, AL_ATOMIC_RELAXED); + atomic_store(f64)(&clock->pause, RUNNING, AL_ATOMIC_RELEASE); clock->paused_at = -1.0; } bool camu_clock_is_paused(struct camu_clock *clock) { - return al_atomic_load(f64)(&clock->pause, AL_ATOMIC_RELAXED) == PAUSED; + return atomic_load(f64)(&clock->pause, AL_ATOMIC_RELAXED) < 0.0; } f64 camu_clock_get_base_pts(struct camu_clock *clock) @@ -116,35 +128,34 @@ f64 camu_clock_get_base_pts(struct camu_clock *clock) f64 camu_clock_get_pts(struct camu_clock *clock, f64 latency, bool allow_set) { - f64 pause = al_atomic_load(f64)(&clock->pause, AL_ATOMIC_ACQUIRE); - if (pause == PAUSED) return CAMU_PTS_PAUSED; + f64 pause = atomic_load(f64)(&clock->pause, AL_ATOMIC_RELAXED); + if (pause < 0.0) return CAMU_PTS_PAUSED; f64 current = nn_get_tick(); - - f64 tick = al_atomic_load(f64)(&clock->tick, AL_ATOMIC_RELAXED); + f64 tick = atomic_load(f64)(&clock->tick, AL_ATOMIC_RELAXED); if (tick == -1.0) { if (allow_set) { - tick = al_atomic_compare_and_swap(f64)(&clock->tick, -1.0, current); + tick = atomic_compare_and_swap(f64)(&clock->tick, -1.0, current); if (tick == -1.0) tick = current; } else { return CAMU_PTS_PAUSED; } } + f64 pts = clock->base + (current - tick); + bool signal_pause = false; - if (pause > 0.0 && current > pause) { // pause > 0.0 means running or pause armed. - current = pause; - pause = al_atomic_compare_and_swap(f64)(&clock->pause, pause, PAUSED); - if (pause != PAUSED) { + if (pause > 0.0 && current > pause) { // pause > 0.0 = RUNNING or armed for pause. + f64 paused_at = -current; + pause = atomic_compare_and_swap(f64)(&clock->pause, pause, paused_at); + if (pause != paused_at) { clock->callback(clock->userdata, CAMU_CLOCK_PAUSED); signal_pause = true; } } - f64 pts = (clock->base - clock->offset) + (current - tick); - if (allow_set) { - al_atomic_store(f64)(&clock->last_pts, pts, AL_ATOMIC_RELAXED); + atomic_store(f64)(&clock->last_pts, pts, AL_ATOMIC_RELAXED); } return signal_pause ? CAMU_PTS_SIGNAL_PAUSE : pts + latency; @@ -152,5 +163,5 @@ f64 camu_clock_get_pts(struct camu_clock *clock, f64 latency, bool allow_set) f64 camu_clock_get_last_pts(struct camu_clock *clock) { - return al_atomic_load(f64)(&clock->last_pts, AL_ATOMIC_RELAXED); + return atomic_load(f64)(&clock->last_pts, AL_ATOMIC_RELAXED); } diff --git a/src/buffer/clock.h b/src/buffer/clock.h index 3209119..8324b6a 100644 --- a/src/buffer/clock.h +++ b/src/buffer/clock.h @@ -23,8 +23,8 @@ // Next/Prev: // - User input -> swap -#define CAMU_PTS_PAUSED 0xffffffffffffffff -#define CAMU_PTS_SIGNAL_PAUSE 0x7fffffffffffffff +#define CAMU_PTS_PAUSED ((f64)0xffffffffffffffff) +#define CAMU_PTS_SIGNAL_PAUSE ((f64)0x7fffffffffffffff) #define CAMU_PTS_CONSIDER_PAUSED(pts) (pts == CAMU_PTS_PAUSED || pts == CAMU_PTS_SIGNAL_PAUSE) enum { @@ -33,7 +33,6 @@ enum { struct camu_clock { f64 base; - f64 offset; atomic(f64) tick; atomic(f64) pause; f64 paused_at; diff --git a/src/buffer/video.c b/src/buffer/video.c index e5c38a6..5011e9d 100644 --- a/src/buffer/video.c +++ b/src/buffer/video.c @@ -30,6 +30,7 @@ bool camu_video_buffer_init(struct camu_video_buffer *buf, struct camu_clock *cl buf->single_frame = true; buf->queue = NULL; buf->buffered = false; + buf->weighted_first_read = false; al_atomic_store(u8)(&buf->flow, FLOWING, AL_ATOMIC_RELAXED); #ifdef CAMU_SCREEN_THREADED al_atomic_store(u8)(&buf->ref, 0, AL_ATOMIC_RELAXED); @@ -63,13 +64,13 @@ bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_code } else { al_assert(frame_rate.den > 0); buf->avg_frame_duration = av_q2d(av_inv_q(frame_rate)); + buf->weighted_first_read = true; log_info("Stream: %s (%ux%u) VIDEO %.3ffps.", format_name, fmt->width, fmt->height, av_q2d(frame_rate)); } break; } #endif } - buf->weighted_read = !buf->single_frame; struct camu_video_format *in = &buf->fmt.in; struct camu_video_format *req = &buf->fmt.req; camu_video_format_copy(in, fmt); @@ -233,7 +234,8 @@ bool camu_video_buffer_read(struct camu_video_buffer *buf, void *out, bool *weig f64 base_pts = al_atomic_load(f64)(&buf->pts, AL_ATOMIC_ACQUIRE); if (!buf->single_frame) { - f64 pts = camu_clock_get_pts(buf->clock, buf->latency, !buf->weighted_read); + bool set_clock = !buf->weighted_first_read; + f64 pts = camu_clock_get_pts(buf->clock, buf->latency, set_clock); if (!CAMU_PTS_CONSIDER_PAUSED(pts) && pts > base_pts) { base_pts = pts; } @@ -268,15 +270,15 @@ bool camu_video_buffer_read(struct camu_video_buffer *buf, void *out, bool *weig } if (ret == CAMU_QUEUE_OK) { - if (UNLIKELY(buf->weighted_read)) { - *weighted = buf->weighted_read; - buf->weighted_read = false; - } // Only updating buf->pts on QUEUE_OK affects the behavior when we fall completely behind. // Setting it here will result in laggily displaying out of date frames as they come in. // Setting it on every read() will cause the video to freeze. // This option ignores sync but is easier for the user to understand what's happening. al_atomic_store(f64)(&buf->pts, base_pts, AL_ATOMIC_RELEASE); + if (UNLIKELY(buf->weighted_first_read)) { + *weighted = true; + buf->weighted_first_read = false; + } } else if (ret == CAMU_QUEUE_MORE) { log_trace("Underrun."); } diff --git a/src/buffer/video.h b/src/buffer/video.h index fdd7d0b..20644bc 100644 --- a/src/buffer/video.h +++ b/src/buffer/video.h @@ -30,7 +30,7 @@ struct camu_video_buffer { // Need to manually trigger EOF in read(). bool buffered_with_one_frame; // Flush the renderer on this read and don't allow it to set the clock. - bool weighted_read; + bool weighted_first_read; atomic(u8) flow; diff --git a/src/cache/handlers/cdio.c b/src/cache/handlers/cdio.c index 29126c0..a9e3747 100644 --- a/src/cache/handlers/cdio.c +++ b/src/cache/handlers/cdio.c @@ -1,4 +1,5 @@ #define AL_LOG_SECTION "cache_cdio" +//#define AL_LOG_ENABLE_TRACE #include #include "../../codec/codec.h" @@ -10,7 +11,7 @@ #include "cdio.h" -// 1 sector at a time to minimize skips +// 1 sector at a time to minimize skips. #define SECTORS_PER_STEP 1 #define BYTES_PER_STEP (SECTORS_PER_STEP * CDIO_CD_FRAMESIZE_RAW) @@ -18,13 +19,13 @@ static void cdio_log_messages(struct cch_handler_cdio *cdio) { char *error_messages = cdio_cddap_errors(cdio->drive); if (error_messages) { - log_error("\n%s", error_messages); + log_error("%s", error_messages); cdio_cddap_free_messages(error_messages); } char *messages = cdio_cddap_messages(cdio->drive); if (messages) { - log_info("\n%s", messages); + log_info("%s", messages); cdio_cddap_free_messages(messages); } } @@ -42,26 +43,21 @@ static nn_thread_result NNWT_THREADCALL cd_read_thread(void *userdata) for (;;) { if (!al_atomic_load(s32)(&cdio->running, AL_ATOMIC_RELAXED)) { - // We don't care about how complete the disc read was. + // We don't care about how complete the read was. return 0; } - // cdio_read() here can block for a very long time so we buffer each read - // before writing it to the backing. + // cdio_read() can block for a long time so we buffer each read before writing it to the backing. long ret = cdio_cddap_read(cdio->drive, nn_buffer_get_ptr(&cdio->buffer, 0), cdio->sector, SECTORS_PER_STEP); cdio_log_messages(cdio); al_assert(ret <= SECTORS_PER_STEP); off_t pointer = cdio->sector * CDIO_CD_FRAMESIZE_RAW; size_t size; u8 *ptr = cdio->backing->get_ptr(cdio->backing, pointer, &size); - if (size < BYTES_PER_STEP) { - log_error("Tried to write over expected size during normal operation."); - cdio->backing->unlock(cdio->backing); - break; - } + al_assert(size >= BYTES_PER_STEP); if (ret == SECTORS_PER_STEP) { al_memcpy(ptr, nn_buffer_get_ptr(&cdio->buffer, 0), BYTES_PER_STEP); } else if (ret == -10) { - log_error("I/O error, skipping sector."); + log_error("Disc read error, skipping sector."); al_memset(ptr, 0, BYTES_PER_STEP); ret = SECTORS_PER_STEP; } else if (ret < 0) { @@ -170,7 +166,8 @@ static bool open_cd_drive(struct cch_handler_cdio *cdio) lsn_t start = cdio_cddap_disc_firstsector(drive); lsn_t end = cdio_cddap_disc_lastsector(drive); - lsn_t offset = start; + lsn_t first_track_start = cdio_cddap_track_firstsector(drive, first_track); + lsn_t offset = first_track_start; s32 track = CDIO_INVALID_TRACK; while (offset < end) { track = cdio_cddap_sector_gettrack(drive, offset); @@ -178,6 +175,7 @@ static bool open_cd_drive(struct cch_handler_cdio *cdio) lsn_t track_start = cdio_cddap_track_firstsector(drive, track); lsn_t track_end = cdio_cddap_track_lastsector(drive, track); al_array_push(cdio->handler.entry->chapters, ((struct cch_chapter){ track_start, track_end })); + log_trace("Track #%u: %zd-%zd.", track, track_start, track_end); offset = track_end + 1; } // end is a valid index so add one framesize. diff --git a/src/cache/meson.build b/src/cache/meson.build index 1a198eb..61be949 100644 --- a/src/cache/meson.build +++ b/src/cache/meson.build @@ -8,37 +8,35 @@ cache_src = [ cache_deps = [] cache_args = [] -if naunet_has_mmap - cache_src += ['backings/file_mapped.c'] -else - cache_src += ['backings/file.c'] +if 'file' in get_option('sources') + if naunet_has_mmap + cache_src += ['backings/file_mapped.c'] + else + cache_src += ['backings/file.c'] + endif endif -if naunet_has_curl +if 'http' in get_option('sources') and naunet_has_curl cache_src += ['handlers/http.c'] endif -cache_have_cdio = false -if is_linux - libcdio_paranoia = dependency('libcdio_paranoia', required: false, allow_fallback: true) - libcdio_cdda = dependency('libcdio_cdda', required: false, allow_fallback: true) +if 'cdio' in get_option('sources') + libcdio_paranoia = dependency('libcdio_paranoia', allow_fallback: true) + libcdio_cdda = dependency('libcdio_cdda', allow_fallback: true) if libcdio_paranoia.found() and libcdio_cdda.found() cache_src += ['handlers/cdio.c'] cache_deps += [libcdio_paranoia, libcdio_cdda] cache_args += ['-DCACHE_HAVE_CDIO'] - cache_have_cdio = true endif endif -cache_have_libdvd = false -if is_linux - libdvdcss = dependency('libdvdcss', required: false, allow_fallback: true) - libdvdread = dependency('dvdread', required: false, allow_fallback: true) - libdvdnav = dependency('dvdnav', required: false, allow_fallback: true) +if 'libdvd' in get_option('sources') + libdvdcss = dependency('libdvdcss', allow_fallback: true) + libdvdread = dependency('dvdread', allow_fallback: true) + libdvdnav = dependency('dvdnav', allow_fallback: true) if libdvdcss.found() and libdvdread.found() and libdvdnav.found() cache_deps += [libdvdcss, libdvdread, libdvdnav] cache_args += ['-DCACHE_HAVE_LIBDVD'] - cache_have_libdvd = true endif endif diff --git a/src/codec/codec.h b/src/codec/codec.h index f32283e..6a9b654 100644 --- a/src/codec/codec.h +++ b/src/codec/codec.h @@ -428,7 +428,7 @@ static inline s32 camu_guess_format(str *short_name, str *ext, str *mime_type) (short_name && al_str_eq(short_name, &al_str_c("png"))) || (mime_type && al_str_eq(mime_type, &al_str_c("image/png")))) { return CAMU_CODEC_PNG; - } else if ((ext && (al_str_eq(ext, &al_str_c(".jpeg")) || al_str_eq(ext, &al_str_c(".jpg")))) || + } else if ((ext && (al_str_eq(ext, &al_str_c(".jpeg")) || al_str_eq(ext, &al_str_c(".jpg")) || al_str_eq(ext, &al_str_c(".jpe")) || al_str_eq(ext, &al_str_c(".jfif")))) || (short_name && al_str_eq(short_name, &al_str_c("jpeg"))) || (mime_type && al_str_eq(mime_type, &al_str_c("image/jpeg")))) { return CAMU_CODEC_JPEG; diff --git a/src/codec/codecs.c b/src/codec/codecs.c index dbec693..a373daf 100644 --- a/src/codec/codecs.c +++ b/src/codec/codecs.c @@ -83,6 +83,9 @@ struct camu_codec_info *camu_codec_info_by_type(s32 type) struct camu_codec_info *camu_codec_info_by_name(str *name) { + if (al_str_eq(name, &al_str_c("pcm_s16le"))) { + return (struct camu_codec_info *)0xcdda; + } for (u32 i = 0; i < ARRAY_SIZE(codec_information); i++) { if (al_str_eq(&codec_information[i].name, name)) { return &codec_information[i]; diff --git a/src/codec/ffmpeg/decoder.c b/src/codec/ffmpeg/decoder.c index 645a88a..7d50bca 100644 --- a/src/codec/ffmpeg/decoder.c +++ b/src/codec/ffmpeg/decoder.c @@ -114,9 +114,6 @@ static enum AVPixelFormat get_hw_format(AVCodecContext *context, const enum AVPi log_error("Failed to get %s HW surface format.", hwdevice_name); // Remake AVCodecContext as a software decoder. - // This might log an error like "Your platform doesn't support hardware accelerated AV1 decoding.". - // Even though the problem is really a lack of any decoder hardware OR software. - // This happens with wine + d3d11va. Maybe a build configuration issue or FFmpeg bug? AVCodecParameters *codecpar = av->codecpar; av->errored_hw_context = av->codec_context; alloc_codec_context_internal(av, codec, codecpar); diff --git a/src/codec/ffmpeg/meson.build b/src/codec/ffmpeg/meson.build index 6482426..e34cc92 100644 --- a/src/codec/ffmpeg/meson.build +++ b/src/codec/ffmpeg/meson.build @@ -11,7 +11,7 @@ ffmpeg_server_deps = [] ffmpeg_client_src = [ffmpeg_src, 'decoder.c', 'resampler.c', 'scaler.c'] ffmpeg_client_deps = [] -ffmpeg_force_fallback = false +ffmpeg_force_fallback = get_option('wrap_mode') == 'forcefallback' foreach dep: ['ffmpeg', 'libavutil', 'libavformat', 'libavcodec', 'libavdevice', 'libswresample', 'libswscale'] if dep in get_option('force_fallback_for') ffmpeg_force_fallback = true diff --git a/src/fruits/cmc/cmc.c b/src/fruits/cmc/cmc.c index 3d91ba6..f44f82f 100644 --- a/src/fruits/cmc/cmc.c +++ b/src/fruits/cmc/cmc.c @@ -6,8 +6,6 @@ #include "../../liana/list.h" #include "../../codec/packet_ext.h" -#include "../common.h" - #include "cmc.h" #include "ui/ui.h" @@ -281,9 +279,13 @@ s32 main(s32 argc, char *argv[]) camu_post_cache_init(&c.cache); al_array_init(c.searches); + str user = al_str_c("guest"); + char *CMC_USER = getenv("CMC_USER"); + if (CMC_USER) user = al_str_cr(CMC_USER); + c.client.callback = client_callback; c.client.userdata = &c; - if (!camu_client_login(&c.client, &al_str_c("andrew"), &c.loop, CAMU_TEST_TYPE, &CAMU_TEST_ADDR, CAMU_PORT)) { + if (!camu_client_login(&c.client, &user, &c.loop, NNWT_SOCKET_TCP, &al_str_c("127.0.0.1"), CAMU_PORT)) { goto err; } diff --git a/src/fruits/cmc/meson.build b/src/fruits/cmc/meson.build index de6d335..bb9d38d 100644 --- a/src/fruits/cmc/meson.build +++ b/src/fruits/cmc/meson.build @@ -20,7 +20,7 @@ if use_tui endif if is_windows and not is_msvc - cmc_args += ['-static', '-static-libgcc', '-static-libstdc++', '-municode', '-mwindows'] + cmc_args += ['-static', '-static-libgcc', '-static-libstdc++', '-mwindows', '-municode'] endif -executable('cmc', cmc_src, dependencies: cmc_deps, link_args: cmc_args, install: true) +executable('cmc', cmc_src, dependencies: cmc_deps, c_args: cmc_args, link_args: cmc_args, install: true) diff --git a/src/fruits/cmc/ui/util/waveform.c b/src/fruits/cmc/ui/util/waveform.c index bed34b7..624069e 100644 --- a/src/fruits/cmc/ui/util/waveform.c +++ b/src/fruits/cmc/ui/util/waveform.c @@ -22,8 +22,8 @@ // extra: #f6ac7f, #F5F4FA, #5b524b // left channel: n == 0, right channel: n == 1. #define COLOR_FOR_PIXEL(n, rms) rms ? \ - n == 0 ? nn_htonl(0xE4CAAF99) : nn_htonl(0xFF928FFF) : \ - n == 0 ? nn_htonl(0xF4EAE099) : nn_htonl(0xBC615EFF) + n == 0 ? nn_htonl(0xe4caaf99) : nn_htonl(0xff928fff) : \ + n == 0 ? nn_htonl(0xf4eae099) : nn_htonl(0xbc615eff) // ss: current sample, ns: next sample. #define HIT_WAVE(low, high, ss, ns) \ diff --git a/src/fruits/cmsrv/cmsrv.c b/src/fruits/cmsrv/cmsrv.c index ef66be8..babd5ef 100644 --- a/src/fruits/cmsrv/cmsrv.c +++ b/src/fruits/cmsrv/cmsrv.c @@ -1,8 +1,7 @@ -#define CMSRV_LOCAL_SOCKET -#define CMSRV_USE_UI - +#define AL_LOG_SECTION "cmsrv" #include #include +#define CMSRV_LOCAL_SOCKET #ifdef CMSRV_LOCAL_SOCKET #include #endif @@ -15,9 +14,9 @@ #include "../../cache/handlers/cdio.h" #endif -#include "../common.h" - +#ifdef CMSRV_USE_UI #include "ui.h" +#endif struct cmsrv { struct nn_event_loop loop; @@ -186,8 +185,13 @@ s32 main(s32 argc, char *argv[]) nn_signal_start(&s.quit_signal); signal(SIGINT, sigint_handler); + str addr = al_str_c("127.0.0.1"); + char *CMSRV_IP = getenv("CMSRV_IP"); + if (CMSRV_IP) addr = al_str_cr(CMSRV_IP); + log_info("Serving from %.*s.", al_str_x(&addr)); + camu_server_init(&s.server, &s.loop); - if (!camu_server_listen(&s.server, CAMU_TEST_TYPE, &CAMU_TEST_ADDR, CAMU_PORT)) { + if (!camu_server_listen(&s.server, NNWT_SOCKET_TCP, &addr, CAMU_PORT)) { return EXIT_FAILURE; } @@ -198,7 +202,7 @@ s32 main(s32 argc, char *argv[]) s.local.cli.userdata = &s; nn_line_processor_init(&s.local.cli, &al_str_c("\n")); nn_line_processor_open_socket(&s.local.cli, &s.local.sock); - if (nn_socket_bind(&s.local.sock, &CAMU_TEST_CONTROL_PATH, 0) && nn_socket_listen(&s.local.sock)) { + if (nn_socket_bind(&s.local.sock, &al_str_c("/tmp/camu_control"), 0) && nn_socket_listen(&s.local.sock)) { nn_line_processor_run(&s.local.cli, &s.loop); } #endif diff --git a/src/fruits/cmsrv/meson.build b/src/fruits/cmsrv/meson.build index b12d00d..bfeabe0 100644 --- a/src/fruits/cmsrv/meson.build +++ b/src/fruits/cmsrv/meson.build @@ -2,14 +2,14 @@ cmsrv_src = ['cmsrv.c'] cmsrv_deps = [common_deps, server] cmsrv_args = [] -use_tui = true -if use_tui +if use_tuis cmsrv_src += ['ui.c'] cmsrv_deps += [notcurses_core] + cmsrv_args += ['-DCMSRV_USE_UI'] endif if is_windows and not is_msvc - cmsrv_args += ['-static', '-static-libgcc', '-static-libstdc++', '-municode', '-mwindows'] + cmsrv_args += ['-static', '-static-libgcc', '-static-libstdc++', '-mwindows', '-municode'] endif -executable('cmsrv', cmsrv_src, dependencies: cmsrv_deps, link_args: cmsrv_args, install: true) +executable('cmsrv', cmsrv_src, dependencies: cmsrv_deps, c_args: cmsrv_args, link_args: cmsrv_args, install: true) diff --git a/src/fruits/cmsrv/ui.c b/src/fruits/cmsrv/ui.c index d96c09d..afbee58 100644 --- a/src/fruits/cmsrv/ui.c +++ b/src/fruits/cmsrv/ui.c @@ -29,6 +29,7 @@ bool cmsrv_ui_init(struct cmsrv_ui *ui, struct camu_server *server) ncplane_set_userptr(stdplane, ui); ui->pending_layout = true; al_array_init(ui->log.messages); + nn_mutex_init(&ui->log.lock); return true; } @@ -51,7 +52,9 @@ void cmsrv_ui_set_pane(struct cmsrv_ui *ui, u8 pane) void cmsrv_ui_push_message(struct cmsrv_ui *ui, char *message) { + nn_mutex_lock(&ui->log.lock); al_array_push(ui->log.messages, message); + nn_mutex_unlock(&ui->log.lock); } static void layout_log(struct cmsrv_ui *ui, struct ncplane *parent) @@ -80,6 +83,7 @@ static void render_log(struct cmsrv_ui *ui) u32 max_width = ncplane_dim_x(n) - 2; u32 max_height = ncplane_dim_y(n) - 2; + nn_mutex_lock(&ui->log.lock); u32 size = ui->log.messages.count; if (size > max_height) { u32 index = size - max_height; @@ -97,6 +101,7 @@ static void render_log(struct cmsrv_ui *ui) // messages will have wide characters. ncplane_putstr_yx(n, y, x, al_array_at(ui->log.messages, i)); } + nn_mutex_unlock(&ui->log.lock); u64 c = 0; ncchannels_set_fg_default(&c); @@ -251,4 +256,5 @@ void cmsrv_ui_close(struct cmsrv_ui *ui) al_free(message); } al_array_free(ui->log.messages); + nn_mutex_destroy(&ui->log.lock); } diff --git a/src/fruits/cmsrv/ui.h b/src/fruits/cmsrv/ui.h index ebf7c1a..3615d3b 100644 --- a/src/fruits/cmsrv/ui.h +++ b/src/fruits/cmsrv/ui.h @@ -19,6 +19,7 @@ struct cmsrv_ui { struct { struct ncplane *n; array(char *) messages; + struct nn_mutex lock; } log; struct { struct ncplane *n; diff --git a/src/fruits/cmv/cmv.c b/src/fruits/cmv/cmv.c index fe0fbd6..535bf72 100644 --- a/src/fruits/cmv/cmv.c +++ b/src/fruits/cmv/cmv.c @@ -13,8 +13,6 @@ #include "../../codec/ffmpeg/common.h" #endif -#include "../common.h" - #if !defined CAMU_SINK_ONLY && !defined NAUNET_ON_WINDOWS // https://unix.stackexchange.com/a/16884 #define PID_MAX_STR 7 // 0x400000 (2^22). @@ -46,13 +44,32 @@ static nn_thread_result NNWT_THREADCALL event_loop_thread(void *userdata) return 0; } -// sink-p12-108.52.160.112-aEN-v-sEN.exe -static bool parse_exe_name_params(str *exe_name, str *addr, struct lia_prefs *prefs) +static bool decode_addr(str *enc, str *addr) +{ + char value[4]; // max = 255. + for (u32 i = 0; i < 8; i += 2) { + str sub = al_str_substr(enc, i, i + 2); + bool error; + s64 l = al_str_to_long(&sub, 16, &error); + al_assert(l >= 0 && l <= 255); + if (error) return false; + al_snprintf(value, sizeof(value), "%hhu", (u8)l); + al_str_cat(addr, &al_str_cs(value)); + if (i != 6) { + al_str_cat(addr, &al_str_c(".")); + } + } + return true; +} + +// sink-p12-976502a7.exe +static bool parse_exe_name(str *exe_name, str *addr) { u32 index = al_str_rfind(exe_name, '/'); if (index == AL_STR_NO_POS) { - index = al_str_rfind(exe_name, '\\'); - if (index == AL_STR_NO_POS) goto def; + if ((index = al_str_rfind(exe_name, '\\')) == AL_STR_NO_POS) { + goto def; + } } str sub = al_str_substr(exe_name, index + 1, exe_name->length); @@ -62,51 +79,20 @@ static bool parse_exe_name_params(str *exe_name, str *addr, struct lia_prefs *pr sub = al_str_substr(&sub, 5, sub.length); // Skip version. - index = al_str_find(&sub, '-'); - if (index == AL_STR_NO_POS) goto def; + if ((index = al_str_find(&sub, '-')) == AL_STR_NO_POS) { + goto def; + } sub = al_str_substr(&sub, index + 1, sub.length); - index = al_str_find(&sub, '-'); - if (index == AL_STR_NO_POS) goto def; - al_str_clone(addr, &al_str_substr(&sub, 0, index)); - - for (;;) { - sub = al_str_substr(&sub, index + 1, sub.length); - index = al_str_find(&sub, '-'); - if (index == AL_STR_NO_POS) { - index = al_str_find(&sub, '.'); - if (index == AL_STR_NO_POS) { - index = sub.length; - } else { - sub.length = index; - } - } - - str value; - al_str_clone(&value, &al_str_substr(&sub, 0, index)); - al_str_to_lower(&value); - if (al_str_at(&value, 0) == 'a') { - prefs->enabled_mask |= 1 << CAMU_STREAM_AUDIO; - } else if (al_str_at(&value, 0) == 'v') { - prefs->enabled_mask |= 1 << CAMU_STREAM_VIDEO; - } else if (al_str_at(&value, 0) == 's') { - prefs->enabled_mask |= 1 << CAMU_STREAM_SUBTITLE; - } - al_str_free(&value); - - if (index == sub.length) break; + if ((index = al_str_find(&sub, '.')) != AL_STR_NO_POS) { + sub = al_str_substr(&sub, 0, index); } + al_str_from(addr, ""); + decode_addr(&sub, addr); + log_debug("Parsed address %.*s from exe filename.", al_str_x(addr)); return true; def: -#ifdef CAMU_HAVE_SUBTITLES - prefs->enabled_mask = (1 << CAMU_STREAM_AUDIO) | (1 << CAMU_STREAM_VIDEO) | (1 << CAMU_STREAM_SUBTITLE); -#else - prefs->enabled_mask = (1 << CAMU_STREAM_AUDIO) | (1 << CAMU_STREAM_VIDEO); -#endif - prefs->audio_lang = CAMU_LANG_JAPANESE; - prefs->subtitle_lang = CAMU_LANG_ENGLISH; - return false; } @@ -141,12 +127,12 @@ s32 window_system_main(u32 argc, str *argv, void *extra) u8 type; str addr; - if (!parse_exe_name_params(&argv[0], &addr, &c.desktop.sink.prefs)) { + if (!parse_exe_name(&argv[0], &addr)) { #ifndef CAMU_SINK_ONLY if (local) { #ifdef NAUNET_ON_WINDOWS - type = NNWT_SOCKET_TCP;; - al_str_clone(&addr, &CAMU_LOCALHOST); + type = NNWT_SOCKET_TCP; + al_str_clone(&addr, &al_str_c("127.0.0.1")); #else type = NNWT_SOCKET_UNIX; al_str_clone(&addr, &CMV_UNIX_PATH); @@ -155,17 +141,26 @@ s32 window_system_main(u32 argc, str *argv, void *extra) al_str_cat(&addr, &al_str_cs(pid_str)); #endif } else { - type = CAMU_TEST_TYPE; - al_str_clone(&addr, &CAMU_TEST_ADDR); + type = NNWT_SOCKET_TCP; + al_str_clone(&addr, &al_str_c("127.0.0.1")); } #else type = NNWT_SOCKET_TCP; - al_str_clone(&addr, &CAMU_TEST_ADDR); + al_str_clone(&addr, &al_str_c("127.0.0.1")); #endif } else { type = NNWT_SOCKET_TCP; } + struct lia_prefs *prefs = &c.desktop.sink.prefs; +#ifdef CAMU_HAVE_SUBTITLES + prefs->enabled = (1 << CAMU_STREAM_AUDIO) | (1 << CAMU_STREAM_VIDEO) | (1 << CAMU_STREAM_SUBTITLE); +#else + prefs->enabled = (1 << CAMU_STREAM_AUDIO) | (1 << CAMU_STREAM_VIDEO); +#endif + prefs->language.audio = CAMU_LANG_JAPANESE; + prefs->language.subtitles = CAMU_LANG_ENGLISH; + #ifndef CAMU_SINK_ONLY if (local) { camu_server_init(&c.server, &c.loop); diff --git a/src/fruits/cmv/meson.build b/src/fruits/cmv/meson.build index bfd2182..3e78f05 100644 --- a/src/fruits/cmv/meson.build +++ b/src/fruits/cmv/meson.build @@ -15,7 +15,7 @@ endif cmv_link_args = [] if is_windows and not is_msvc - cmv_link_args += ['-static', '-static-libgcc', '-static-libstdc++', '-municode', '-mwindows'] + cmv_link_args += ['-static', '-static-libgcc', '-static-libstdc++', '-mwindows', '-municode'] endif executable('cmv', cmv_src, dependencies: cmv_deps, c_args: cmv_args, link_args: cmv_link_args, install: true) diff --git a/src/fruits/common.h b/src/fruits/common.h deleted file mode 100644 index 66a72bc..0000000 --- a/src/fruits/common.h +++ /dev/null @@ -1,24 +0,0 @@ -#include -#include - -AL_IGNORE_WARNING("-Wunused-variable") - -static str CAMU_DB_PATH = al_str_c("/home/andrew/c/camu/data/camu_db_test"); - -static str CAMU_TEST_IP = al_str_c("108.52.160.112"); -static str CAMU_LOCALHOST = al_str_c("127.0.0.1"); - -static str CAMU_TEST_PATH = al_str_c("/tmp/camu_sock"); -static str CAMU_TEST_CONTROL_PATH = al_str_c("/tmp/camu_control_sock"); - -AL_IGNORE_WARNING_END - -#ifdef NAUNET_ON_WINDOWS -#define CAMU_TEST_TYPE NNWT_SOCKET_TCP -#define CAMU_TEST_ADDR CAMU_TEST_IP -#else -//#define CAMU_TEST_TYPE NNWT_SOCKET_UNIX -//#define CAMU_TEST_ADDR CAMU_TEST_PATH -#define CAMU_TEST_TYPE NNWT_SOCKET_TCP -#define CAMU_TEST_ADDR CAMU_TEST_IP -#endif diff --git a/src/fruits/ctv/ctv.c b/src/fruits/ctv/ctv.c index 45942e6..8b173b0 100644 --- a/src/fruits/ctv/ctv.c +++ b/src/fruits/ctv/ctv.c @@ -24,12 +24,12 @@ static nn_thread_result NNWT_THREADCALL event_loop_thread(void *userdata) struct lia_prefs *prefs = &c->desktop.sink.prefs; #ifdef CAMU_HAVE_SUBTITLES - prefs->enabled_mask = (1 << CAMU_STREAM_AUDIO) | (1 << CAMU_STREAM_VIDEO) | (1 << CAMU_STREAM_SUBTITLE); + prefs->enabled = (1 << CAMU_STREAM_AUDIO) | (1 << CAMU_STREAM_VIDEO) | (1 << CAMU_STREAM_SUBTITLE); #else - prefs->enabled_mask = (1 << CAMU_STREAM_AUDIO) | (1 << CAMU_STREAM_VIDEO); + prefs->enabled = (1 << CAMU_STREAM_AUDIO) | (1 << CAMU_STREAM_VIDEO); #endif - prefs->audio_lang = CAMU_LANG_JAPANESE; - prefs->subtitle_lang = CAMU_LANG_ENGLISH; + prefs->language.audio = CAMU_LANG_JAPANESE; + prefs->language.subtitles = CAMU_LANG_ENGLISH; if (!camu_desktop_connect(&c->desktop, &al_str_c("ctv"), &c->loop, CAMU_TEST_TYPE, &CAMU_TEST_ADDR, CAMU_PORT)) { return 0; diff --git a/src/fruits/meson.build b/src/fruits/meson.build index 37edd0b..c82d71d 100644 --- a/src/fruits/meson.build +++ b/src/fruits/meson.build @@ -1,4 +1,9 @@ -if (get_option('server').enabled() or get_option('client').enabled()) and not is_windows +use_tuis = true +if is_windows + use_tuis = false +endif + +if use_tuis and (get_option('server').enabled() or get_option('client').enabled()) notcurses = dependency('notcurses', required: false, allow_fallback: false) notcurses_core = dependency('notcurses-core', required: false, allow_fallback: false) if not notcurses.found() or not notcurses_core.found() diff --git a/src/liana/client.c b/src/liana/client.c index 380ed26..4d5689f 100644 --- a/src/liana/client.c +++ b/src/liana/client.c @@ -136,46 +136,52 @@ static void parse_info_packet(struct lia_client *client, struct nn_packet *packe nn_packet_read_str(packet, &handler); client->duration = nn_packet_read_u64(packet); collect_streams(client, packet); + if (client->streams.count == 0) { + log_warn("Resource has no streams."); + goto out; + } struct lia_prefs *prefs = &client->prefs; u8 selected = 0; - u32 accept_defaults = 0; + u8 accept_defaults = 0; for (; accept_defaults < 2; accept_defaults++) { struct camu_codec_stream *stream; al_array_foreach_ptr(client->streams, i, stream) { - u8 type_mask = 1 << stream->type; - if ((selected & type_mask) || !(prefs->enabled_mask & type_mask)) continue; + u8 type = stream->type; + if (type == CAMU_STREAM_SUBTITLE && client->mask == 0) { + log_warn("Ignoring subtitle-only resource."); + goto out; + } + if ((selected & (1 << type)) || !(prefs->enabled & (1 << type))) { + continue; + } const char *title = NULL; #ifdef CAMU_HAVE_FFMPEG if (stream->mode == CAMU_FFMPEG_COMPAT) { AVDictionary *metadata = stream->av.stream->metadata; const AVDictionaryEntry *title_entry = av_dict_get(metadata, "title", NULL, 0); - if (title_entry) title = title_entry->value; + if (title_entry) { + title = title_entry->value; + } const AVDictionaryEntry *lang_entry = av_dict_get(metadata, "language", NULL, 0); if (lang_entry && !accept_defaults) { u8 lang = CAMU_LANG_UNKNOWN; - str lang_str = al_str_cr(lang_entry->value); - if (al_str_eq(&lang_str, &al_str_c("eng"))) lang = CAMU_LANG_ENGLISH; - else if (al_str_eq(&lang_str, &al_str_c("jpn"))) lang = CAMU_LANG_JAPANESE; - switch (stream->type) { - case CAMU_STREAM_AUDIO: - if (prefs->audio_lang != lang) continue; - break; - case CAMU_STREAM_SUBTITLE: - if (prefs->subtitle_lang != lang) continue; - break; - default: - break; + str s = al_str_cr(lang_entry->value); + if (al_str_eq(&s, &al_str_c("eng"))) lang = CAMU_LANG_ENGLISH; + else if (al_str_eq(&s, &al_str_c("jpn"))) lang = CAMU_LANG_JAPANESE; + if ((type == CAMU_STREAM_AUDIO && lang != prefs->language.audio) || + (type == CAMU_STREAM_SUBTITLE && lang != prefs->language.subtitles)) { + continue; } + break; } } #endif if (title) { - log_info("Selected %s stream (index: %u, title: %s).", stream_type_to_str[stream->type], stream->index, title); + log_info("Selected %s stream (index: %u, title: %s).", stream_type_to_str[type], stream->index, title); } else { - log_info("Selected %s stream (index: %u).", stream_type_to_str[stream->type], stream->index); + log_info("Selected %s stream (index: %u).", stream_type_to_str[type], stream->index); } - selected |= type_mask; - client->mask |= 1 << stream->index; + selected |= (1 << type); struct lia_vcr_track *track = al_alloc_object(struct lia_vcr_track); track->stream = stream; track->client = lia_handler_by_name(&handler)->create_client_handler(); @@ -187,10 +193,12 @@ static void parse_info_packet(struct lia_client *client, struct nn_packet *packe // This will NOT attempt to select another stream. continue; } + client->mask |= 1 << stream->index; client->callback(client->userdata, LIANA_CLIENT_CONFIGURE, stream, track); lia_vcr_add_track(&client->vcr, track); } } +out: client->callback(client->userdata, LIANA_CLIENT_CONFIGURE_COMPLETE, NULL, NULL); } @@ -201,6 +209,7 @@ static void info_packet_callback(void *userdata, struct nn_packet_stream *stream parse_info_packet(client, packet); nn_packet_stream_return_packet(stream, packet); if (client->mask == 0 || lia_vcr_is_empty(&client->vcr)) { + log_warn("Discarding resource with no applicable streams."); al_assert(client->reconnect == RECONNECT_NONE); nn_packet_stream_disconnect(&client->data); return; diff --git a/src/liana/client.h b/src/liana/client.h index 40093e0..681b134 100644 --- a/src/liana/client.h +++ b/src/liana/client.h @@ -27,9 +27,11 @@ struct lia_reconnect_info { }; struct lia_prefs { - u8 enabled_mask; - s8 audio_lang; - s8 subtitle_lang; + u8 enabled; + struct { + u8 audio; + u8 subtitles; + } language; }; struct lia_client { diff --git a/src/liana/handlers/codec_client.c b/src/liana/handlers/codec_client.c index abb5b9a..c3dded1 100644 --- a/src/liana/handlers/codec_client.c +++ b/src/liana/handlers/codec_client.c @@ -33,7 +33,7 @@ static bool codec_client_init(struct lia_client_handler *handler, struct camu_re static bool push_av_packet(struct lia_codec_client *codec, AVPacket *pkt) { s32 ret = codec->dec->push_av_packet(codec->dec, pkt); - if (ret == AVERROR(EAGAIN)) { + while (ret == AVERROR(EAGAIN)) { codec->dec->process(codec->dec); ret = codec->dec->push_av_packet(codec->dec, pkt); } diff --git a/src/liana/list.c b/src/liana/list.c index b47a30c..253ba5f 100644 --- a/src/liana/list.c +++ b/src/liana/list.c @@ -91,6 +91,9 @@ static void unref_all_entries(struct lia_list *list, s32 trigger) } } +// @TODO: When implementing list_remove(), consider this case. +// remove() sequence 0 causes a skip to sequence 1 and sequence 1 fails to load. +// Do we properly move to list->current = -1 and an idle list? static bool entry_load_and_get_duration(struct lia_list *list, struct lia_list_entry *entry, s32 sequence, bool *error) { u8 last_load = entry->load; @@ -99,9 +102,6 @@ static bool entry_load_and_get_duration(struct lia_list *list, struct lia_list_e // If sequence is <0 that must mean entry is not yet added to list->entries. if (sequence >= 0) { al_array_remove_at(list->entries, (u32)sequence); - // @TODO: Consider this when implementing list_remove(). - // In the case of remove sequence 0, causing a skip to sequence 1, and sequence 1 - // fails to load, do we properly move to -1 and an idle list. if (list->current > sequence) { struct lia_list_sink *sink; al_array_foreach(list->sinks, i, sink) { @@ -111,6 +111,7 @@ static bool entry_load_and_get_duration(struct lia_list *list, struct lia_list_e list->current--; } struct lia_list_cmd *cmd = list->cmd; + al_assert(cmd); al_assert(cmd->sequence != sequence); if (cmd->sequence > sequence) { cmd->sequence--; @@ -141,6 +142,19 @@ static bool entry_load_and_get_duration(struct lia_list *list, struct lia_list_e static void unload_all_entires(struct lia_list *list) { + struct lia_list_cmd *cmd = list->cmd; + if (cmd && cmd->entry) { + al_assert(cmd->op == ADD); + entry_unload(list, cmd->entry); + list->cmd = NULL; + } + al_array_foreach_rev(list->command_queue, i, cmd) { + if (cmd->entry) { + al_assert(cmd->op == ADD); + entry_unload(list, cmd->entry); + al_array_remove_at(list->command_queue, i); + } + } struct lia_list_entry *entry; al_array_foreach(list->entries, i, entry) { entry_unload(list, entry); @@ -162,6 +176,7 @@ static inline void sink_pause_entry(struct lia_list_sink *sink, struct lia_list_ sink->callback(sink->userdata, LIANA_SINK_PAUSE, entry, sequence, time); } +// @TODO: This should probably give a sequence or ID. static inline void sink_unset_entry(struct lia_list_sink *sink) { sink->callback(sink->userdata, LIANA_SINK_UNSET, NULL, -1, NULL); @@ -341,13 +356,13 @@ static bool handle_skipto(struct lia_list *list, s32 sequence, s32 index) bool error; if (!entry_load_and_get_duration(list, target, index, &error)) { if (error) { - // index might point to a different entry after an error. + // On an error, sequence will address the same entry but index may not. return handle_skipto(list, sequence, index); } return false; } - u64 at = nn_get_timestamp() + LIANA_PAUSE_DELAY; + u64 at = nn_get_timestamp() + LIANA_BASE_PING; u64 pos = target->offset; u8 pause = skipto_entry(current, target, at); @@ -438,6 +453,7 @@ static void handle_toggle_pause(struct lia_list *list, s32 sequence, f64 pts) case LIANA_PAUSE_PAUSE: { al_assert(entry->start != LIANA_TIMESTAMP_INVALID); entry->paused_at = now + LIANA_PAUSE_DELAY; + // @TODO: There is no reason we couldn't pause during a pre-roll. if (entry->paused_at < entry->start) { entry->paused_at = entry->start; } @@ -603,6 +619,8 @@ static bool adjust_for_order_change(struct lia_list *list, struct lia_list_entry } } al_assert(cmd->op == SKIPTO); + // This SKIPTO being in-place is a consideration for the sequence of any + // commands that were queued after this one. return handle_skipto(list, cmd->sequence, cmd->arg0.i); } diff --git a/src/liana/list.h b/src/liana/list.h index 61dcb1d..c8b1d73 100644 --- a/src/liana/list.h +++ b/src/liana/list.h @@ -9,7 +9,7 @@ #define LIANA_SEQUENCE_ANY -1 #define LIANA_BASE_DELAY 450000u // 450ms -#define LIANA_BASE_PING 125000u // 125ms +#define LIANA_BASE_PING 150000u // 150ms #define LIANA_PAUSE_DELAY LIANA_BASE_PING #define LIANA_DELAY_IGNORE 0u @@ -100,7 +100,7 @@ struct lia_list_cmd { struct lia_list_entry *entry; s32 sequence; union { s32 i; u32 u; } arg0; - union { f64 f; u64 l; u32 u; } arg1; + union { u64 l; u32 u; f64 f; } arg1; }; struct lia_list { diff --git a/src/liana/meson.build b/src/liana/meson.build index 5c1cc42..40d4598 100644 --- a/src/liana/meson.build +++ b/src/liana/meson.build @@ -5,27 +5,28 @@ liana_server_src = [ 'handlers.c', 'process.c' ] + liana_client_src = [ 'client.c', 'vcr.c', 'handlers/codec_client.c', 'handlers.c' ] + liana_server_deps = [cache, codecs_server, codecs_client] # codecs_client for transcode. liana_client_deps = [codecs_client] -liana_args = [] -if cache_have_cdio +if 'cdio' in get_option('sources') liana_server_src += ['handlers/cdio_server.c'] liana_client_src += ['handlers/cdio_client.c'] endif -if cache_have_libdvd +if 'libdvd' in get_option('sources') liana_server_src += ['handlers/dvd_server.c'] liana_client_src += ['handlers/dvd_client.c'] endif liana_server = declare_dependency(sources: liana_server_src, - dependencies: liana_server_deps, compile_args: [liana_args, '-DLIANA_SERVER']) + dependencies: liana_server_deps, compile_args: ['-DLIANA_SERVER']) liana_client = declare_dependency(sources: liana_client_src, - dependencies: liana_client_deps, compile_args: [liana_args, '-DLIANA_CLIENT']) + dependencies: liana_client_deps, compile_args: ['-DLIANA_CLIENT']) diff --git a/src/liana/vcr.c b/src/liana/vcr.c index 13082f5..4fa548c 100644 --- a/src/liana/vcr.c +++ b/src/liana/vcr.c @@ -7,9 +7,9 @@ #include "vcr.h" #include "common.h" -#define VCR_BUFFER_BUFFERED MB(4) -#define VCR_BUFFER_GROW_FACTOR 8 -#define VCR_BUFFER_LOW_OFFSET MB(1) +#define VCR_BUFFER_BUFFERED MB(6LL) +#define VCR_BUFFER_GROW_FACTOR 8LL +#define VCR_BUFFER_LOW_OFFSET KB(500LL) AL_STATIC_ASSERT(buf_gt_low_offset, VCR_BUFFER_BUFFERED * VCR_BUFFER_GROW_FACTOR, >, VCR_BUFFER_LOW_OFFSET); enum { @@ -67,12 +67,12 @@ static void update_metrics(struct lia_vcr *vcr, u64 size) u64 diff = now - vcr->metrics.last_report_ts; u64 mark = now - vcr->metrics.last_report_mark; u64 frame = vcr->metrics.current_frame; - if (mark > 500000 || (diff > 2000000 && vcr->metrics.current_frame >= KB(500)) || (!size && frame > 0)) { + if (mark > 750000 || (diff > 5000000 && vcr->metrics.current_frame >= KB(500)) || (!size && frame > 0)) { al_assert(frame > 0); f32 kbps = (frame / 125.f) / (mark / 1000000.f); f32 average_kbps = vcr->metrics.average_kbps; average_kbps = average_kbps == 0.f ? kbps : (average_kbps + kbps) / 2.f; - f32 buffered = al_atomic_load(u64)(&vcr->count, AL_ATOMIC_RELAXED) / (f32)MB(1); + f32 buffered = atomic_load(u64)(&vcr->count, AL_ATOMIC_RELAXED) / (f32)MB(1); f32 capacity = vcr->mark.buffered / (f32)MB(1); log_info("Receiving packets at %.2fkbps (%.2f/%.2fMB).", average_kbps, buffered, capacity); vcr->metrics.average_kbps = average_kbps; @@ -88,9 +88,9 @@ void lia_vcr_init(struct lia_vcr *vcr, struct nn_event_loop *loop, struct nn_pac vcr->data = data; vcr->node_id = node_id; al_array_init(vcr->tracks); - al_atomic_store(u64)(&vcr->count, 0, AL_ATOMIC_RELAXED); + atomic_store(u64)(&vcr->count, 0, AL_ATOMIC_RELAXED); vcr->mark.buffered = VCR_BUFFER_BUFFERED; - al_atomic_store(u64)(&vcr->mark.low, 0, AL_ATOMIC_RELAXED); + atomic_store(u64)(&vcr->mark.low, 0, AL_ATOMIC_RELAXED); vcr->expand = VCR_EXPAND_UNTOUCHED; vcr->started = false; #ifndef CAMU_DIRECT_MODE @@ -149,10 +149,10 @@ static nn_thread_result NNWT_THREADCALL vcr_track_thread(void *userdata) if (packet) { // Check if we should uncork the packet stream. u32 size = nn_packet_get_size(packet); - u64 buffer = al_atomic_sub(u64)(&vcr->count, size, AL_ATOMIC_RELAXED); + u64 buffer = atomic_sub(u64)(&vcr->count, size, AL_ATOMIC_RELAXED); #ifndef CAMU_DIRECT_MODE - bool buffered = al_atomic_load(bool)(&track->buffered, AL_ATOMIC_RELAXED); - u64 low = al_atomic_load(u64)(&vcr->mark.low, AL_ATOMIC_RELAXED); + bool buffered = atomic_load(bool)(&track->buffered, AL_ATOMIC_RELAXED); + u64 low = atomic_load(u64)(&vcr->mark.low, AL_ATOMIC_RELAXED); if (buffered && (low && buffer <= low)) { nn_signal_send(&vcr->signal); } @@ -176,7 +176,7 @@ static nn_thread_result NNWT_THREADCALL vcr_track_thread(void *userdata) } // Take state again because handle_packet() could have caused the track to be corked. - state = al_atomic_load(s32)(&track->state, AL_ATOMIC_RELAXED); + state = atomic_load(s32)(&track->state, AL_ATOMIC_RELAXED); // We wait if corked (TRACK_STOPPED) or EOF. corked = (packet && state == VCR_TRACK_STOPPED) || !packet; @@ -199,7 +199,7 @@ static nn_thread_result NNWT_THREADCALL vcr_track_thread(void *userdata) nn_cond_wait(&track->cond, &track->mutex); // Check for possibly updated state. - state = al_atomic_load(s32)(&track->state, AL_ATOMIC_RELAXED); + state = atomic_load(s32)(&track->state, AL_ATOMIC_RELAXED); nn_mutex_unlock(&track->mutex); @@ -254,11 +254,11 @@ void lia_vcr_add_track(struct lia_vcr *vcr, struct lia_vcr_track *track) track->vcr = vcr; nn_cond_init(&track->cond); nn_mutex_init(&track->mutex); - al_atomic_store(bool)(&track->buffered, !VCR_TRACK_THREADED(track), AL_ATOMIC_RELAXED); + atomic_store(bool)(&track->buffered, !VCR_TRACK_THREADED(track), AL_ATOMIC_RELAXED); track->running = false; nn_packet_cache_init(&track->cache, 256); al_array_push(vcr->tracks, track); - al_atomic_store(s32)(&track->state, VCR_TRACK_RUNNING, AL_ATOMIC_RELAXED); + atomic_store(s32)(&track->state, VCR_TRACK_RUNNING, AL_ATOMIC_RELAXED); } bool lia_vcr_remove_track_by_stream(struct lia_vcr *vcr, struct camu_codec_stream *stream) @@ -298,13 +298,13 @@ static void cork_if_buffered(struct lia_vcr *vcr, u64 buffer) bool buffered = true; struct lia_vcr_track *track; al_array_foreach(vcr->tracks, i, track) { - buffered &= al_atomic_load(bool)(&track->buffered, AL_ATOMIC_RELAXED); + buffered &= atomic_load(bool)(&track->buffered, AL_ATOMIC_RELAXED); } if (buffered) { if (vcr->expand == VCR_EXPAND_UNTOUCHED) { vcr->mark.buffered = buffer * VCR_BUFFER_GROW_FACTOR; vcr->expand = VCR_EXPAND_GROWN; - log_info("Expanded buffer to size %.2fMB.", vcr->mark.buffered / (f32)MB(1)); + log_debug("Expanded buffer to size %.2fMB.", vcr->mark.buffered / (f32)MB(1)); return; } if (!vcr->corked) { @@ -316,7 +316,7 @@ static void cork_if_buffered(struct lia_vcr *vcr, u64 buffer) // Don't set low until after we corked so that vcr_track_thread() will never try uncorking // until we know what the low mark is. if (vcr->expand == VCR_EXPAND_GROWN) { - al_atomic_store(u64)(&vcr->mark.low, vcr->mark.buffered - VCR_BUFFER_LOW_OFFSET, AL_ATOMIC_RELAXED); + atomic_store(u64)(&vcr->mark.low, vcr->mark.buffered - VCR_BUFFER_LOW_OFFSET, AL_ATOMIC_RELAXED); vcr->expand = VCR_EXPAND_COMPLETE; } al_array_foreach(vcr->tracks, i, track) { @@ -342,9 +342,15 @@ void lia_vcr_push_packet(struct lia_vcr *vcr, struct nn_packet *packet) break; } if (VCR_TRACK_THREADED(track)) { - if (nn_packet_cache_send_packet(&track->cache, packet)) { - u64 buffer; - if ((buffer = al_atomic_add(u64)(&vcr->count, size, AL_ATOMIC_RELAXED)) >= vcr->mark.buffered) { + u64 buffer = 0; + bool can_send = nn_packet_cache_available(&track->cache); + if (can_send) { + buffer = atomic_add(u64)(&vcr->count, size, AL_ATOMIC_RELAXED); + nn_packet_cache_send_packet(&track->cache, packet); + } + nn_packet_cache_unlock(&track->cache); + if (can_send) { + if (buffer >= vcr->mark.buffered) { #ifndef CAMU_DIRECT_MODE cork_if_buffered(vcr, buffer); #endif @@ -360,12 +366,15 @@ void lia_vcr_push_packet(struct lia_vcr *vcr, struct nn_packet *packet) } case LIANA_PACKET_EOF: case LIANA_PACKET_ERROR: { + // @TODO: Should ERROR be passed down to LIANA_CLIENT_ERRORED? #ifndef CAMU_DIRECT_MODE update_metrics(vcr, 0); // Flush. #endif - // @TODO: Should ERROR be passed down to LIANA_CLIENT_ERRORED? al_array_foreach(vcr->tracks, i, track) { - nn_packet_cache_send_packet(&track->cache, NULL); + if (nn_packet_cache_available(&track->cache)) { + nn_packet_cache_send_packet(&track->cache, NULL); + } + nn_packet_cache_unlock(&track->cache); } #ifndef CAMU_DIRECT_MODE nn_signal_stop(&vcr->signal); @@ -373,7 +382,7 @@ void lia_vcr_push_packet(struct lia_vcr *vcr, struct nn_packet *packet) if (op == LIANA_PACKET_EOF) { log_info("Received EOF."); } else if (op == LIANA_PACKET_ERROR) { - log_warn("Forcing EOF because we got an error packet."); + log_warn("Forcing EOF due to an error packet."); } break; } @@ -386,17 +395,17 @@ void lia_vcr_push_packet(struct lia_vcr *vcr, struct nn_packet *packet) void lia_vcr_set_buffered(struct lia_vcr_track *track) { - al_atomic_store(bool)(&track->buffered, true, AL_ATOMIC_RELAXED); + atomic_store(bool)(&track->buffered, true, AL_ATOMIC_RELAXED); } void lia_vcr_cork(struct lia_vcr_track *track) { - al_atomic_store(s32)(&track->state, VCR_TRACK_STOPPED, AL_ATOMIC_RELAXED); + atomic_store(s32)(&track->state, VCR_TRACK_STOPPED, AL_ATOMIC_RELAXED); } void lia_vcr_uncork(struct lia_vcr_track *track) { - if (al_atomic_load(s32)(&track->state, AL_ATOMIC_RELAXED) != VCR_TRACK_STOPPED) { + if (atomic_load(s32)(&track->state, AL_ATOMIC_ACQUIRE) != VCR_TRACK_STOPPED) { // We will get here during normal operation. Early returning is historically // tricky in vcr_uncork(). If I'm understanding correctly, asserting that // cond_is_waiting() just below means we are safe. @@ -404,7 +413,7 @@ void lia_vcr_uncork(struct lia_vcr_track *track) } // Lock before setting track->state to avoid a race with cork(). nn_mutex_lock(&track->mutex); - al_atomic_store(s32)(&track->state, VCR_TRACK_RUNNING, AL_ATOMIC_RELAXED); + atomic_store(s32)(&track->state, VCR_TRACK_RUNNING, AL_ATOMIC_RELEASE); al_assert(nn_cond_is_waiting(&track->cond)); nn_cond_signal(&track->cond); nn_mutex_unlock(&track->mutex); @@ -416,7 +425,7 @@ static void vcr_track_close_internal(struct lia_vcr_track *track) // Calling packet_cache_disable() while holding the track mutex can very possibly deadlock. nn_packet_cache_disable(&track->cache); nn_mutex_lock(&track->mutex); - al_atomic_store(s32)(&track->state, VCR_TRACK_CLOSED, AL_ATOMIC_RELAXED); + atomic_store(s32)(&track->state, VCR_TRACK_CLOSED, AL_ATOMIC_RELAXED); if (nn_cond_is_waiting(&track->cond)) { nn_cond_signal(&track->cond); } @@ -451,17 +460,17 @@ void lia_vcr_flush(struct lia_vcr *vcr) struct lia_vcr_track *track; al_array_foreach(vcr->tracks, i, track) { if (VCR_TRACK_THREADED(track)) { - al_atomic_store(bool)(&track->buffered, false, AL_ATOMIC_RELAXED); + atomic_store(bool)(&track->buffered, false, AL_ATOMIC_RELAXED); track->client->flush(track->client); nn_packet_cache_enable(&track->cache); - al_atomic_store(s32)(&track->state, VCR_TRACK_RUNNING, AL_ATOMIC_RELAXED); + atomic_store(s32)(&track->state, VCR_TRACK_RUNNING, AL_ATOMIC_RELAXED); } else { track->client->flush(track->client); } } - al_atomic_store(u64)(&vcr->count, 0, AL_ATOMIC_RELAXED); + atomic_store(u64)(&vcr->count, 0, AL_ATOMIC_RELAXED); if (vcr->expand == VCR_EXPAND_COMPLETE) { - al_atomic_store(u64)(&vcr->mark.low, 0, AL_ATOMIC_RELAXED); + atomic_store(u64)(&vcr->mark.low, 0, AL_ATOMIC_RELAXED); vcr->expand = VCR_EXPAND_GROWN; } } diff --git a/src/libsink/desktop.c b/src/libsink/desktop.c index 8582cd4..af6fc50 100644 --- a/src/libsink/desktop.c +++ b/src/libsink/desktop.c @@ -208,14 +208,16 @@ bool camu_desktop_open(struct camu_desktop *c, const char *window_name) c->scr.callback = screen_callback; c->scr.userdata = c; if (!camu_screen_init(&c->scr) || !camu_screen_create_window(&c->scr, window_name)) { + log_error("Failed to create window."); return false; } #if defined CAMU_RENDERER_MOMO c->renderer = camu_renderer_momo_create(); #elif defined CAMU_RENDERER_LIBPLACEBO - c->renderer = camu_renderer_lp_create(); + c->renderer = camu_renderer_libplacebo_create(); #endif if (!camu_screen_create_renderer(&c->scr, c->renderer)) { + log_error("Failed to create renderer."); return false; } // Need to render twice for the window to show early. @@ -252,7 +254,10 @@ bool camu_desktop_tick(struct camu_desktop *c) { bool force; if (camu_screen_tick(&c->scr, &force) && !c->should_quit) { - c->renderer->render(c->renderer, &c->scr, force); + if (!c->renderer->render(c->renderer, &c->scr, force)) { + log_error("render() failed, forcing exit."); + c->should_quit = 1; + } } return !c->should_quit; } diff --git a/src/libsink/input_simulator.c b/src/libsink/input_simulator.c index 19786af..5b9d6e6 100644 --- a/src/libsink/input_simulator.c +++ b/src/libsink/input_simulator.c @@ -31,23 +31,23 @@ static nn_thread_result NNWT_THREADCALL input_simulation_thread(void *userdata) switch (al_random_int(0, MARK - 1)) { case SKIP: { s32 n = al_random_int(1, 5); - log_debug("SKIP (n: %d).", n); + log_info("SKIP (n: %d).", n); camu_sink_skip(sink, n); break; } case BACKSKIP: { s32 n = al_random_int(-5, -1); - log_debug("BACKSKIP (n: %d).", n); + log_info("BACKSKIP (n: %d).", n); camu_sink_skip(sink, n); break; } case SHUFFLE: { - log_debug("SHUFFLE."); + log_info("SHUFFLE."); camu_sink_shuffle(sink); break; } case TOGGLE_PAUSE: { - log_debug("TOGGLE_PAUSE."); + log_info("TOGGLE_PAUSE."); camu_sink_toggle_pause(sink); break; } @@ -56,7 +56,7 @@ static nn_thread_result NNWT_THREADCALL input_simulation_thread(void *userdata) if (pos < 0.005) pos = 0.0; if (pos > 0.995) pos = 1.0; else if (pos > 0.99) pos = 0.9999; - log_debug("SEEK (pos: %f).", pos); + log_info("SEEK (pos: %f).", pos); camu_sink_seek(sink, &pos, CAMU_SEEK_PERCENT); break; } diff --git a/src/libsink/sink.c b/src/libsink/sink.c index e9b4df4..fccbc3e 100644 --- a/src/libsink/sink.c +++ b/src/libsink/sink.c @@ -10,7 +10,7 @@ #include "sink.h" #include "common.h" -#define CAMU_SINK_LOCAL +//#define CAMU_SINK_LOCAL //#define CAMU_SINK_ONESHOT // Requested state of the sinks outputs. @@ -71,8 +71,7 @@ AL_STATIC_ASSERT(max_age_lt_lru, ENTRY_MAX_AGE, <, SINK_LRU_MAX); #define CONNECTION_NUMBER(id) ((id >> 48) & 0xffff) // Only sink->target can ever be 0xb00b. -#define OxbOOb ((struct camu_sink_entry *)0xb00b) -#define ENTRY_IS_VALID(entry) ((entry) && (entry) != OxbOOb) +#define ENTRY_IS_VALID(entry) ((entry) && (entry) != (struct camu_sink_entry *)0xb00b) // printf format for entries. #ifdef AL_DEBUG @@ -87,14 +86,19 @@ AL_STATIC_ASSERT(max_age_lt_lru, ENTRY_MAX_AGE, <, SINK_LRU_MAX); #define VIDEO_STATE(entry) ((entry)->video.state) // If a buffer is still INIT or QUEUED after the entry is configured, it's "empty". -// Also, if a buffer errors, it will be detached in CLIENT_REMOVE_BUFFERS. -// Empty is a state that cannot change while a buffer is being used (push()/read()). +// An ERRORED buffer will be DETACHED after CLIENT_REMOVE_BUFFERS and is then considered empty. +// Empty is a state that cannot change while a buffer could be in use (push()/read()). #define AUDIO_EMPTY(entry) (AUDIO_STATE(entry) <= BUFFER_DETACHED) #define VIDEO_EMPTY(entry) (VIDEO_STATE(entry) <= BUFFER_DETACHED) #define AUDIO_ENDED(entry) (AUDIO_STATE(entry) >= BUFFER_ENDED) #define VIDEO_ENDED(entry) (VIDEO_STATE(entry) >= BUFFER_ENDED) -#define ENTRY_ENDED(entry) ((AUDIO_EMPTY(entry) || AUDIO_ENDED(entry)) && (VIDEO_EMPTY(entry) || VIDEO_ENDED(entry))) +/* +#define ENTRY_ENDED(entry) \ + ((AUDIO_ENDED(entry) && (VIDEO_ENDED(entry) || VIDEO_EMPTY(entry) || VIDEO_IS_SINGLE_FRAME(entry))) || \ + (AUDIO_EMPTY(entry) && VIDEO_ENDED(entry))) +*/ +#define ENTRY_ENDED(entry) entry->ended // IGNORED = ENDED or EMPTY. #define AUDIO_ADDED_OR_IGNORED(entry) (AUDIO_STATE(entry) >= BUFFER_ADDED || AUDIO_EMPTY(entry)) @@ -113,6 +117,8 @@ AL_STATIC_ASSERT(max_age_lt_lru, ENTRY_MAX_AGE, <, SINK_LRU_MAX); #define BLOCKING_SLEEP(delay) nn_event_loop_sleep(sink->loop, delay) #endif +#define CMD(cmd, ...) ((struct camu_sink_cmd){ .op = cmd, __VA_ARGS__ }) + // Functions that might happen on separate threads. // CAMU_MIXER_THREADED: // audio_buffer_callback() @@ -127,7 +133,7 @@ AL_STATIC_ASSERT(max_age_lt_lru, ENTRY_MAX_AGE, <, SINK_LRU_MAX); static inline bool entry_audio_buffer_held(struct camu_sink_entry *entry) { #ifdef CAMU_MIXER_THREADED - return al_atomic_load(u8)(&entry->audio.buf.ref, AL_ATOMIC_RELAXED) == 1; + return atomic_load(u8)(&entry->audio.buf.ref, AL_ATOMIC_RELAXED) == 1; #else (void)entry; return false; @@ -137,19 +143,30 @@ static inline bool entry_audio_buffer_held(struct camu_sink_entry *entry) static inline bool entry_video_buffer_held(struct camu_sink_entry *entry) { #ifdef CAMU_SCREEN_THREADED - return al_atomic_load(u8)(&entry->video.buf.ref, AL_ATOMIC_RELAXED) == 1; + return atomic_load(u8)(&entry->video.buf.ref, AL_ATOMIC_RELAXED) == 1; #else (void)entry; return false; #endif } -static void queue_cmd(struct camu_sink *sink, struct camu_sink_cmd cmd) +static void queue_cmds(struct camu_sink *sink, u32 count, ...) { - camu_queue_push(sink->queue, cmd); + camu_queue_lock(sink->queue); + va_list cmds; + va_start(cmds, count); + for (u32 i = 0; i < count; i++) { + camu_queue_push(sink->queue, va_arg(cmds, struct camu_sink_cmd)); + } + camu_queue_unlock(sink->queue); nn_signal_send(&sink->queue_signal); } +static void queue_cmd(struct camu_sink *sink, struct camu_sink_cmd cmd) +{ + queue_cmds(sink, 1, cmd); +} + static void refresh_video_output(struct camu_sink *sink) { #ifndef CAMU_SINK_NO_VIDEO @@ -165,11 +182,7 @@ static inline void add_entry_audio_buffer(struct camu_sink_entry *entry) #ifdef CAMU_MIXER_THREADED_START_STOP sink->callback(sink->userdata, CAMU_SINK_ADD_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf); #else - queue_cmd(sink, (struct camu_sink_cmd){ - .op = ADD_BUFFER, - .value.i = CAMU_SINK_AUDIO, - .opaque = entry - }); + queue_cmd(sink, CMD(ADD_BUFFER, .v.u = CAMU_SINK_AUDIO, .opaque = entry)); #endif } @@ -194,11 +207,7 @@ static void remove_entry_audio_buffer(struct camu_sink_entry *entry) #ifdef CAMU_MIXER_THREADED_START_STOP sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf); #else - queue_cmd(sink, (struct camu_sink_cmd){ - .op = REMOVE_BUFFER, - .value.i = CAMU_SINK_AUDIO, - .opaque = entry - }); + queue_cmd(sink, CMD(REMOVE_BUFFER, .v.u = CAMU_SINK_AUDIO, .opaque = entry)); #endif break; case BUFFER_SET_OR_BUFFERED: @@ -243,7 +252,6 @@ static void remove_entry_video_buffer(struct camu_sink_entry *entry) static void remove_entry_buffers(struct camu_sink_entry *entry) { log_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 (!AUDIO_ENDED(entry)) remove_entry_audio_buffer(entry); if (!VIDEO_ENDED(entry)) remove_entry_video_buffer(entry); } @@ -256,14 +264,15 @@ static void add_or_queue_entry(struct camu_sink_entry *entry) log_trace("add_or_queue_entry("ENTRY_FMT"), audio_state: %hhu, video_state: %hhu.", ENTRY_ARG(entry), AUDIO_STATE(entry), VIDEO_STATE(entry)); al_assert(AUDIO_STATE(entry) != BUFFER_QUEUED); al_assert(VIDEO_STATE(entry) != BUFFER_QUEUED); + // Either buffer could be DETACHED. if (AUDIO_STATE(entry) == BUFFER_INIT) { AUDIO_STATE(entry) = BUFFER_QUEUED; - } else if (!AUDIO_ENDED(entry)) { + } else if (!AUDIO_ENDED_OR_EMPTY(entry)) { add_audio_if_set_and_buffered(entry); } if (VIDEO_STATE(entry) == BUFFER_INIT) { VIDEO_STATE(entry) = BUFFER_QUEUED; - } else if (!VIDEO_ENDED(entry)) { + } else if (!VIDEO_ENDED_OR_EMPTY(entry)) { add_video_if_set_and_buffered(entry); } } @@ -279,14 +288,14 @@ static void maybe_disconnect_entry(struct camu_sink_entry *entry) } #ifdef CAMU_SINK_LOCAL -static void local_pause(struct camu_sink *sink, struct camu_sink_entry *entry) +static void local_entry_pause(struct camu_sink *sink, struct camu_sink_entry *entry) { if (!camu_clock_is_paused(&entry->clock)) { entry->paused = true; camu_clock_pause(&entry->clock, 0); log_info("Clock paused."); // Audio stop will be handled by a BUFFER_PAUSED callback. - if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry) && sink->video.state == SINK_PLAYING) { + if (!VIDEO_ENDED_OR_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 @@ -296,13 +305,13 @@ static void local_pause(struct camu_sink *sink, struct camu_sink_entry *entry) entry->paused = false; camu_clock_resume(&entry->clock, 0); log_info("Clock resumed."); - if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry) && sink->video.state == SINK_PAUSED) { + if (!VIDEO_ENDED_OR_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); #endif sink->video.state = SINK_PLAYING; } - if (!AUDIO_EMPTY(entry) && sink->audio.state == SINK_PAUSED) { + if (!AUDIO_ENDED_OR_EMPTY(entry) && sink->audio.state == SINK_PAUSED) { sink->callback(sink->userdata, CAMU_SINK_START, CAMU_SINK_AUDIO, NULL); sink->audio.state = SINK_PLAYING; } @@ -310,6 +319,12 @@ static void local_pause(struct camu_sink *sink, struct camu_sink_entry *entry) } #endif +// sink->current could be NULL. +static inline struct camu_sink_entry *get_entry_for_command(struct camu_sink *sink) +{ + return ENTRY_IS_VALID(sink->target) ? sink->target : sink->current; +} + static inline s32 get_sequence_for_command(struct camu_sink_entry *entry) { // SEQUENCE_ANY resolves order on the server. @@ -329,7 +344,7 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd) { switch (cmd->op) { case START: { - switch (cmd->value.i) { + switch (cmd->v.u) { case CAMU_SINK_AUDIO: if (sink->audio.state == SINK_PAUSED) { sink->callback(sink->userdata, CAMU_SINK_START, CAMU_SINK_AUDIO, NULL); @@ -348,7 +363,7 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd) break; } case STOP: { - switch (cmd->value.i) { + switch (cmd->v.u) { case CAMU_SINK_AUDIO: if (sink->audio.state == SINK_PLAYING) { sink->callback(sink->userdata, CAMU_SINK_STOP, CAMU_SINK_AUDIO, NULL); @@ -368,7 +383,7 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd) } case ADD_BUFFER: { struct camu_sink_entry *entry = (struct camu_sink_entry *)cmd->opaque; - switch (cmd->value.i) { + switch (cmd->v.u) { case CAMU_SINK_AUDIO: sink->callback(sink->userdata, CAMU_SINK_ADD_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf); break; @@ -382,7 +397,7 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd) } case REMOVE_BUFFER: { struct camu_sink_entry *entry = (struct camu_sink_entry *)cmd->opaque; - switch (cmd->value.i) { + switch (cmd->v.u) { case CAMU_SINK_AUDIO: sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf); break; @@ -395,7 +410,7 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd) break; } case CLEAR_BUFFERS: { - switch (cmd->value.i) { + switch (cmd->v.u) { case CAMU_SINK_AUDIO: sink->callback(sink->userdata, CAMU_SINK_CLEAR, CAMU_SINK_AUDIO, NULL); break; @@ -424,21 +439,21 @@ 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_SKIP); nn_packet_write_s32(packet, get_sequence_for_command(entry)); - nn_packet_write_s32(packet, (s32)cmd->value.i); + nn_packet_write_s32(packet, (s32)cmd->v.i); nn_rpc_connection_command(sink->conn, packet, NULL, NULL); break; } case TOGGLE_PAUSE: { struct camu_sink_entry *entry = (struct camu_sink_entry *)cmd->opaque; #ifdef CAMU_SINK_LOCAL - if (!entry->held) local_pause(sink, entry); + if (!entry->held) local_entry_pause(sink, entry); #else 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_TOGGLE_PAUSE); nn_packet_write_s32(packet, get_sequence_for_command(entry)); - nn_packet_write_f64(packet, cmd->value.f); + nn_packet_write_f64(packet, cmd->v.f); nn_rpc_connection_command(sink->conn, packet, NULL, NULL); #endif break; @@ -451,7 +466,7 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd) nn_packet_write_u8(packet, CAMU_LIST_SEEK); nn_packet_write_s32(packet, entry->sequence); nn_packet_write_u32(packet, REMOTE_ENTRY_ID(entry->id)); - nn_packet_write_u64(packet, cmd->value.u); + nn_packet_write_u64(packet, cmd->v.u); nn_rpc_connection_command(sink->conn, packet, NULL, NULL); break; } @@ -476,7 +491,7 @@ 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_END); nn_packet_write_u32(packet, REMOTE_ENTRY_ID(entry->id)); - nn_packet_write_u32(packet, (u32)cmd->value.u); + nn_packet_write_u32(packet, (u32)cmd->v.u); nn_rpc_connection_command(sink->conn, packet, NULL, NULL); break; } @@ -486,11 +501,11 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd) static void queue_signal_callback(void *userdata) { struct camu_sink *sink = (struct camu_sink *)userdata; - u32 size; + u32 count; struct camu_sink_cmd cmd; for (;;) { - camu_queue_try_pop(sink->queue, size, cmd); - if (size == 0) break; + camu_queue_try_pop(sink->queue, count, cmd); + if (count == 0) break; handle_sink_cmd(sink, &cmd); } } @@ -498,7 +513,8 @@ static void queue_signal_callback(void *userdata) static void mixer_callback(void *userdata, u8 op) { struct camu_sink *sink = (struct camu_sink *)userdata; - // @TODO: Isn't the idea of MIXER_EMPTY to not STOP audio when we know the mixer will be empty? + // @TODO: Isn't the idea of MIXER_EMPTY to not explicitly STOP the audio in places we + // expect the mixer to be empty (rely on MIXER_EMPTY). if (op == CAMU_MIXER_EMPTY) { log_info("Mixer empty."); // Regardless of if we are checking an entry's state here, we have to sync with @@ -506,10 +522,7 @@ static void mixer_callback(void *userdata, u8 op) nn_mutex_lock(&sink->lock); // This feels a bit too loose. if (!(sink->current && AUDIO_STATE(sink->current) == BUFFER_ADDED)) { - queue_cmd(sink, (struct camu_sink_cmd){ - .op = STOP, - .value.i = CAMU_SINK_AUDIO - }); + queue_cmd(sink, CMD(STOP, .v.u = CAMU_SINK_AUDIO)); } nn_mutex_unlock(&sink->lock); } @@ -568,10 +581,7 @@ static void maybe_remove_previous(struct camu_sink *sink) // Cleanup entries from old connections. This is especially important to // keep reseek() from being overly wasteful. if (CONNECTION_NUMBER(previous->id) != sink->connection_number) { - queue_cmd(sink, (struct camu_sink_cmd){ - .op = EJECT_ENTRY, - .opaque = previous - }); + queue_cmd(sink, CMD(EJECT_ENTRY, .opaque = previous)); } } sink->previous.count = 0; @@ -600,10 +610,9 @@ static void maybe_add_to_previous(struct camu_sink *sink, struct camu_sink_entry { log_trace("maybe_add_to_previous("ENTRY_FMT", "ENTRY_FMT").", ENTRY_ARG(previous), ENTRY_ARG(target)); 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 == OxbOOb; - if (dangling_target || target->ended || (AUDIO_STATE(previous) != BUFFER_ADDED && VIDEO_STATE(previous) != BUFFER_ADDED)) { + // If none of an 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 || ENTRY_ENDED(target) || (AUDIO_STATE(previous) != BUFFER_ADDED && VIDEO_STATE(previous) != BUFFER_ADDED)) { remove_entry_buffers(previous); return; } @@ -613,18 +622,14 @@ static void maybe_add_to_previous(struct camu_sink *sink, struct camu_sink_entry static void after_add_entry(struct camu_sink_entry *entry, bool skip_audio, bool skip_video) { maybe_remove_previous(entry->sink); - queue_cmd(entry->sink, (struct camu_sink_cmd){ - .op = (skip_video || entry->paused) ? STOP : START, - .value.i = CAMU_SINK_VIDEO - }); + queue_cmds(entry->sink, 2, + CMD((skip_video || entry->paused) ? STOP : START, .v.u = CAMU_SINK_VIDEO), + CMD((skip_audio || entry->paused) ? STOP : START, .v.u = CAMU_SINK_AUDIO) + ); if (VIDEO_ENDED_OR_EMPTY(entry)) { // Clear the screen if skipping from a video to an audio-only entry. refresh_video_output(entry->sink); } - queue_cmd(entry->sink, (struct camu_sink_cmd){ - .op = (skip_audio || entry->paused) ? STOP : START, - .value.i = CAMU_SINK_AUDIO - }); } // Call this after setting state to ADDED because this entry might be in previous. @@ -641,7 +646,7 @@ static void do_add_entry(struct camu_sink_entry *entry) void add_audio_if_set_and_buffered(struct camu_sink_entry *entry) { - al_assert(!entry->ended); + al_assert(!ENTRY_ENDED(entry)); al_assert(AUDIO_STATE(entry) != BUFFER_INIT); al_assert(AUDIO_STATE(entry) != BUFFER_QUEUED); al_assert(AUDIO_STATE(entry) != BUFFER_ADDED); @@ -662,7 +667,7 @@ void add_audio_if_set_and_buffered(struct camu_sink_entry *entry) void add_video_if_set_and_buffered(struct camu_sink_entry *entry) { // Single frame entries will be added/removed with ended set. - if (entry->ended) al_assert(VIDEO_IS_SINGLE_FRAME(entry)); + if (ENTRY_ENDED(entry)) al_assert(VIDEO_IS_SINGLE_FRAME(entry)); al_assert(VIDEO_STATE(entry) != BUFFER_INIT); al_assert(VIDEO_STATE(entry) != BUFFER_QUEUED); al_assert(VIDEO_STATE(entry) != BUFFER_ADDED); @@ -690,56 +695,34 @@ static void switch_to(struct camu_sink *sink, struct camu_sink_entry *target) { struct camu_sink_entry *current = sink->current; - log_trace("switch_to("ENTRY_FMT"(ended: %s)), current: "ENTRY_FMT"(ended: %s).", - ENTRY_ARG(target), BOOLSTR(ENTRY_IS_VALID(target) ? target->ended : false), - ENTRY_ARG(current), BOOLSTR(current ? current->ended : false)); + log_trace("switch_to("ENTRY_FMT"), current: "ENTRY_FMT".", ENTRY_ARG(target), ENTRY_ARG(current)); - bool ensure_removed = false; if (current) { struct camu_sink_entry *suspended = sink->suspended; al_assert(current != target); - ensure_removed = suspended || current->ended; if (suspended) { al_assert(suspended == current); - // A buffer's state being QUEUED should be impossible while it's - // entry is suspended. + // It should be impossible for a buffer to be QUEUED while it's entry is suspended. al_assert(AUDIO_STATE(suspended) != BUFFER_QUEUED); al_assert(VIDEO_STATE(suspended) != BUFFER_QUEUED); sink->suspended = NULL; log_warn("Unset suspended entry as a substitute for remove."); - } else if (!current->ended) { + } else { maybe_add_to_previous(sink, current, target); } } - // @TODO: Cleanup stop_video? and log_trace lengths. - bool stop_video = false; - bool dangling_target = target == OxbOOb; + bool dangling_target = target == (struct camu_sink_entry *)0xb00b; + bool stop_video = dangling_target; if (dangling_target) { log_trace("Ignored dangling target."); } else { - if (!target->ended) { - remove_previous_if_contains(sink, target); - add_or_queue_entry(target); - } else { - log_trace("Target ended in switch_to()."); - if (!VIDEO_ENDED_OR_EMPTY(target) && VIDEO_IS_SINGLE_FRAME(target)) { - add_video_if_set_and_buffered(target); - } - stop_video = true; - } - } - - if (ensure_removed) { - if (!VIDEO_ENDED_OR_EMPTY(current) && VIDEO_IS_SINGLE_FRAME(current)) { - remove_entry_video_buffer(current); - } - al_assert(AUDIO_STATE(current) != BUFFER_ADDED); - al_assert(VIDEO_STATE(current) != BUFFER_ADDED); + remove_previous_if_contains(sink, target); + add_or_queue_entry(target); + stop_video = VIDEO_ENDED_OR_EMPTY(target) || VIDEO_IS_SINGLE_FRAME(target); } if (dangling_target) { - stop_video = true; sink->current = NULL; } else { target->audio.ignore_paused = false; @@ -750,10 +733,7 @@ static void switch_to(struct camu_sink *sink, struct camu_sink_entry *target) } if (stop_video) { - queue_cmd(sink, (struct camu_sink_cmd){ - .op = STOP, - .value.i = CAMU_SINK_VIDEO - }); + queue_cmd(sink, CMD(STOP, .v.u = CAMU_SINK_VIDEO)); refresh_video_output(sink); } } @@ -781,17 +761,19 @@ static void pause_and_swap_to(struct camu_sink *sink, struct camu_sink_entry *ta al_assert(target != current); al_assert(!sink->target); // This is extra verbose because the order is important. - // 1. sink->target has to be set before calling clock_pause(). - // 2. current must still be paused even if it's ended. - // 3. In the immediate case, switch_to() has to come last. - if (current && !current->ended) { + // 1. sink->target has to be set before calling clock_pause(). + if (current && !ENTRY_ENDED(current)) { sink->target = target; } + // 2. current must still be paused, even if it's ended. if (current) { current->audio.ignore_paused = true; camu_clock_pause(¤t->clock, at); } - if (!current || current->ended) switch_to(sink, target); + // 3. In the immediate swap case, switch_to() has to come last. + if (!current || ENTRY_ENDED(current)) { + switch_to(sink, target); + } } #endif @@ -805,16 +787,14 @@ static bool end_entry_and_advance_queue(struct camu_sink *sink, struct camu_sink #endif // This entry's buffers cannot be added again until after a reset. entry->ended = true; - queue_cmd(sink, (struct camu_sink_cmd){ - .op = END, - .value.u = entry->reset_token, - .opaque = entry - }); + al_assert(ENTRY_ENDED(entry)); + queue_cmd(sink, CMD(END, .v.u = entry->reset_token, .opaque = entry)); #ifdef LIANA_LIST_SCUFFED_LOOP log_info("Looping."); return true; #endif if (sink->target) { + log_info("Buffers swapped on end() (Gapless if queued)."); switch_to(sink, sink->target); sink->target = NULL; return true; @@ -843,17 +823,18 @@ static void audio_buffer_callback(void *userdata, u8 op) nn_mutex_lock(&sink->lock); if (!entry->audio.ignore_paused && entry->paused) { log_info("Audio buffer paused."); - queue_cmd(sink, (struct camu_sink_cmd){ - .op = STOP, - .value.i = CAMU_SINK_AUDIO - }); + queue_cmd(sink, CMD(STOP, .v.u = CAMU_SINK_AUDIO)); } nn_mutex_unlock(&sink->lock); break; case CAMU_BUFFER_EOF: case CAMU_BUFFER_ERRORED: { bool error = op == CAMU_BUFFER_ERRORED; - log_debug(error ? "Audio buffer errored." : "Audio EOF."); + if (error) { + log_error("Audio buffer errored."); + } else { + log_debug("Audio EOF."); + } nn_mutex_lock(&sink->lock); // EOF and ERRORED come from the outputs read() thread. So, having threaded // outputs means anything could have happened while waiting on the lock above. @@ -894,7 +875,11 @@ static void video_buffer_callback(void *userdata, u8 op) case CAMU_BUFFER_EOF: case CAMU_BUFFER_ERRORED: { bool error = op == CAMU_BUFFER_ERRORED; - log_debug(error ? "Video buffer errored." : "Video EOF."); + if (error) { + log_error("Video buffer errored."); + } else { + log_debug("Video EOF."); + } nn_mutex_lock(&sink->lock); if (!AUDIO_EMPTY(entry)) { camu_audio_buffer_set_no_video(&entry->audio.buf, true); @@ -915,10 +900,7 @@ static void video_buffer_callback(void *userdata, u8 op) } nn_mutex_unlock(&sink->lock); if (!swapped) { - queue_cmd(sink, (struct camu_sink_cmd){ - .op = STOP, - .value.i = CAMU_SINK_VIDEO - }); + queue_cmd(sink, CMD(STOP, .v.u = CAMU_SINK_VIDEO)); } break; } @@ -937,10 +919,7 @@ static void clock_callback(void *userdata, u8 op) switch_to(sink, sink->target); sink->target = NULL; } else if (entry->paused) { - queue_cmd(entry->sink, (struct camu_sink_cmd){ - .op = STOP, - .value.i = CAMU_SINK_VIDEO - }); + queue_cmd(sink, CMD(STOP, .v.u = CAMU_SINK_VIDEO)); } } nn_mutex_unlock(&sink->lock); @@ -953,20 +932,11 @@ static void evaluate_and_set_buffer_params(struct camu_sink *sink, struct camu_s f64 avg_frame_duration = entry->video.buf.avg_frame_duration; #ifdef CAMU_SINK_LOCAL if (!AUDIO_EMPTY(entry) && !ignore_video) { - // Delay either the audio or video so we can start the clock as - // soon as possible while keeping A/V sync. f64 audio = camu_mixer_get_latency(sink->audio.mixer); struct camu_renderer *renderer = sink->video.renderer; f64 video = renderer->get_latency(renderer) * avg_frame_duration; - // If the audio buffer is delayed by less than the audio latency, data will be skipped. - f64 base = -audio; - if (audio > video) { - camu_audio_buffer_set_latency(&entry->audio.buf, base); - camu_video_buffer_set_latency(&entry->video.buf, base - (audio - video)); - } else if (video > audio) { - camu_audio_buffer_set_latency(&entry->audio.buf, base); - camu_video_buffer_set_latency(&entry->video.buf, base + (video - audio)); - } + camu_audio_buffer_set_latency(&entry->audio.buf, -audio); + camu_video_buffer_set_latency(&entry->video.buf, -video); } // 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); @@ -1013,6 +983,7 @@ static void remove_from_queue_by_opaque(struct camu_sink *sink, void *opaque) 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; + al_assert(ENTRY_IS_VALID(entry)); struct camu_sink *sink = entry->sink; switch (op) { case LIANA_CLIENT_CONFIGURE: { @@ -1106,13 +1077,8 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str log_trace("remove_buffers("ENTRY_FMT", %s, %s), entry == current: %s.", ENTRY_ARG(entry), BOOLSTR(rec->reconnect), BOOLSTR(rec->unconfigured), BOOLSTR(entry == sink->current)); if (entry == sink->current) { - if (!entry->ended) { - // It should only be possible for a buffer to be INIT if entry is not current or ended. - // Ended case is: (audio or video buffer empty) -> entry switched off of -> entry ended -> switch_to()'d. - // - The empty buffer doesn't get re-QUEUED because the entry is ended. - al_assert(AUDIO_STATE(entry) != BUFFER_INIT); - al_assert(VIDEO_STATE(entry) != BUFFER_INIT); - } + al_assert(AUDIO_STATE(entry) != BUFFER_INIT); + al_assert(VIDEO_STATE(entry) != BUFFER_INIT); if (sink->target) { // This is necessary to avoid re-adding an entry with an in-between clock state. // See note in clock.c::camu_clock_seek(). @@ -1140,7 +1106,6 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str // Ignore unconfigured entries. if (rec->unconfigured) { - al_assert(!entry->ended); al_assert(AUDIO_EMPTY(entry) && VIDEO_EMPTY(entry)); nn_mutex_unlock(&sink->lock); return; @@ -1309,7 +1274,7 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str remove_from_queue_by_opaque(sink, entry); if (entry == sink->target) { - sink->target = OxbOOb; + sink->target = (struct camu_sink_entry *)0xb00b; log_warn("Attempting to handle a disconnected target."); } else if (entry == sink->current) { if (sink->suspended) { @@ -1322,10 +1287,7 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str } else { sink->current = NULL; if (removed) { // Don't stop video on exit. - queue_cmd(entry->sink, (struct camu_sink_cmd){ - .op = STOP, - .value.i = CAMU_SINK_VIDEO - }); + queue_cmd(sink, CMD(STOP, .v.u = CAMU_SINK_VIDEO)); } } // If current was never fully added we need to call this here. @@ -1401,9 +1363,12 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn, u8 op = nn_packet_read_u8(packet); if (op == LIANA_SINK_UNSET) { nn_mutex_lock(&sink->lock); - if (sink->current) { - maybe_disconnect_entry(sink->current); + struct camu_sink_entry *current = sink->current; + nn_mutex_unlock(&sink->lock); + if (current) { + maybe_disconnect_entry(current); } + nn_mutex_lock(&sink->lock); goto out; } @@ -1499,7 +1464,7 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn, case LIANA_PAUSE_PAUSE: if (prev_target) { al_assert(current); - al_assert(!current->ended); + al_assert(!ENTRY_ENDED(current)); al_assert(prev_target != entry); if (entry == current) { // pause_and_swap_to() negated. @@ -1507,7 +1472,7 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn, } else { sink->target = entry; } - if (prev_target != OxbOOb) { + if (prev_target != (struct camu_sink_entry *)0xb00b) { camu_clock_pause(&prev_target->clock, at); } } else { @@ -1518,7 +1483,7 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn, case LIANA_PAUSE_BOTH: if (prev_target) { al_assert(current); - al_assert(!current->ended); + al_assert(!ENTRY_ENDED(current)); al_assert(prev_target != entry); if (entry == current) { sink->target = NULL; @@ -1526,7 +1491,7 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn, } else { sink->target = entry; } - if (prev_target != OxbOOb) { + if (prev_target != (struct camu_sink_entry *)0xb00b) { camu_clock_pause(&prev_target->clock, at); } } else { @@ -1562,12 +1527,14 @@ static bool pause_command_callback(void *userdata, struct nn_rpc_connection *con struct camu_sink_entry *entry = get_entry_from_id(sink, id); if (!entry) goto out; - al_assert(entry->sequence == sequence); nn_mutex_lock(&sink->lock); - log_trace("pause("ENTRY_FMT"), %s, audio_state: %hhu, video_state: %hhu.", ENTRY_ARG(entry), lia_pause_op_name(pause), AUDIO_STATE(entry), VIDEO_STATE(entry)); + // As long as the list discards skips with a non-current sequence, this should hold true. + al_assert(entry->sequence == sequence); + log_trace("pause("ENTRY_FMT"), %s, audio_state: %hhu, video_state: %hhu.", + ENTRY_ARG(entry), lia_pause_op_name(pause), AUDIO_STATE(entry), VIDEO_STATE(entry)); #ifdef CAMU_SINK_LOCAL (void)at; - if (!entry->held) local_pause(sink, entry); + if (!entry->held) local_entry_pause(sink, entry); #else switch (pause) { case LIANA_PAUSE_PAUSE: { @@ -1582,18 +1549,12 @@ static bool pause_command_callback(void *userdata, struct nn_rpc_connection *con entry->paused = false; camu_clock_resume(&entry->clock, at); log_info("Clock resumed."); - if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry)) { - queue_cmd(entry->sink, (struct camu_sink_cmd){ - .op = START, - .value.i = CAMU_SINK_VIDEO - }); + if (!VIDEO_ENDED_OR_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry)) { + queue_cmd(sink, CMD(START, .v.u = CAMU_SINK_VIDEO)); } - if (!AUDIO_EMPTY(entry)) { + if (!AUDIO_ENDED_OR_EMPTY(entry)) { camu_audio_buffer_resync(&entry->audio.buf); - queue_cmd(entry->sink, (struct camu_sink_cmd){ - .op = START, - .value.i = CAMU_SINK_AUDIO - }); + queue_cmd(sink, CMD(START, .v.u = CAMU_SINK_AUDIO)); } break; } @@ -1615,7 +1576,6 @@ static bool seek_command_callback(void *userdata, struct nn_rpc_connection *conn u64 id = LOCAL_ENTRY_ID(sink, nn_packet_read_u32(packet)); s32 sequence = nn_packet_read_s32(packet); - (void)sequence; u64 at = nn_packet_read_u64(packet); u64 pos = nn_packet_read_u64(packet); u32 reset_token = nn_packet_read_u32(packet); @@ -1623,7 +1583,8 @@ static bool seek_command_callback(void *userdata, struct nn_rpc_connection *conn struct camu_sink_entry *entry = get_entry_from_id(sink, id); if (!entry) goto out; nn_mutex_lock(&sink->lock); - log_trace("seek("ENTRY_FMT"), reset_token: %u.", ENTRY_ARG(entry), reset_token); + entry->sequence = sequence; + log_trace("seek("ENTRY_FMT", %.2f), reset_token: %u.", ENTRY_ARG(entry), pos / 1000000.0, reset_token); entry->reset_token = reset_token; nn_mutex_unlock(&sink->lock); // The rest of the seek is handled in CLIENT_REMOVE_BUFFERS/RESUME_AT/RECONNECTED. @@ -1761,22 +1722,12 @@ void camu_sink_return_current(struct camu_sink *sink) nn_mutex_unlock(&sink->lock); } -// sink->current could be NULL. -static inline struct camu_sink_entry *get_entry_for_command(struct camu_sink *sink) -{ - return ENTRY_IS_VALID(sink->target) ? sink->target : sink->current; -} - void camu_sink_skip(struct camu_sink *sink, s32 n) { nn_mutex_lock(&sink->lock); struct camu_sink_entry *current = get_entry_for_command(sink); nn_mutex_unlock(&sink->lock); - queue_cmd(sink, (struct camu_sink_cmd){ - .op = SKIP, - .value.i = n, - .opaque = current - }); + queue_cmd(sink, CMD(SKIP, .v.i = n, .opaque = current)); } void camu_sink_toggle_pause(struct camu_sink *sink) @@ -1785,11 +1736,8 @@ void camu_sink_toggle_pause(struct camu_sink *sink) struct camu_sink_entry *current = get_entry_for_command(sink); nn_mutex_unlock(&sink->lock); if (!current) return; - queue_cmd(sink, (struct camu_sink_cmd){ - .op = TOGGLE_PAUSE, - .value.f = camu_clock_get_pts(¤t->clock, 0.0, false), - .opaque = current - }); + f64 pts = camu_clock_get_pts(¤t->clock, 0.0, false); + queue_cmd(sink, CMD(TOGGLE_PAUSE, .v.f = pts, .opaque = current)); } void camu_sink_seek(struct camu_sink *sink, void *value, u8 mode) @@ -1811,19 +1759,19 @@ void camu_sink_seek(struct camu_sink *sink, void *value, u8 mode) switch (mode) { case CAMU_SEEK_POS: { u64 pos = *(u64 *)value; - cmd.value.u = pos; + cmd.v.u = pos; break; } case CAMU_SEEK_RELATIVE: { f64 offset = *(f64 *)value; pts = MAX(pts + offset, 0.0); - cmd.value.u = (u64)(pts * 1000000); + cmd.v.u = (u64)(pts * 1000000); break; } case CAMU_SEEK_PERCENT: { // There's probably a way to lose less precision here. f64 percent = *(f64 *)value; - cmd.value.u = (u64)(duration * percent); + cmd.v.u = (u64)(duration * percent); break; } } @@ -1832,31 +1780,21 @@ void camu_sink_seek(struct camu_sink *sink, void *value, u8 mode) void camu_sink_reseek(struct camu_sink *sink) { - queue_cmd(sink, (struct camu_sink_cmd){ - .op = RESEEK - }); + queue_cmd(sink, CMD(RESEEK)); } void camu_sink_shuffle(struct camu_sink *sink) { - queue_cmd(sink, (struct camu_sink_cmd){ - .op = SHUFFLE - }); + queue_cmd(sink, CMD(SHUFFLE)); } void camu_sink_stop(struct camu_sink *sink) { - queue_cmd(sink, (struct camu_sink_cmd){ - .op = STOP, - .value.i = CAMU_SINK_AUDIO - }); - queue_cmd(sink, (struct camu_sink_cmd){ - .op = CLEAR_BUFFERS, - .value.i = CAMU_SINK_AUDIO - }); - queue_cmd(sink, (struct camu_sink_cmd){ - .op = CLOSE - }); + queue_cmds(sink, 3, + CMD(STOP, .v.u = CAMU_SINK_AUDIO), + CMD(CLEAR_BUFFERS, .v.u = CAMU_SINK_AUDIO), + CMD(CLOSE) + ); } void camu_sink_close(struct camu_sink *sink) diff --git a/src/libsink/sink.h b/src/libsink/sink.h index 5958829..1e8bbf1 100644 --- a/src/libsink/sink.h +++ b/src/libsink/sink.h @@ -56,7 +56,7 @@ struct camu_sink_entry { bool paused; struct { u8 state; - // Don't stop the audio output on BUFFER_PAUSED from this entry. + // Don't stop audio on a BUFFER_PAUSED from this entry. bool ignore_paused; struct camu_audio_buffer buf; struct lia_vcr_track *track; @@ -71,7 +71,7 @@ struct camu_sink_entry { struct camu_sink_cmd { u8 op; - union { s64 i; u64 u; f64 f; } value; + union { s64 i; u64 u; f64 f; } v; void *opaque; }; @@ -90,7 +90,7 @@ struct camu_sink { queue(struct camu_sink_cmd) queue; struct camu_sink_entry *current; struct camu_sink_entry *target; - // If an entry that was current was removed for a reconnect, it's "suspended". + // If current was removed for a reconnect, it's "suspended". struct camu_sink_entry *suspended; array(struct camu_sink_entry *) previous; array(struct camu_sink_entry *) entries; diff --git a/src/mixer/audio_miniaudio.c b/src/mixer/audio_miniaudio.c index 993bcca..c0e7cf4 100644 --- a/src/mixer/audio_miniaudio.c +++ b/src/mixer/audio_miniaudio.c @@ -34,19 +34,19 @@ static ma_backend backends[] = { #endif }; -static void *miniaudio_malloc(size_t size, void *userdata) +static void *ma_on_malloc(size_t size, void *userdata) { (void)userdata; return al_malloc(size); } -static void *miniaudio_realloc(void *ptr, size_t size, void *userdata) +static void *ma_on_realloc(void *ptr, size_t size, void *userdata) { (void)userdata; return al_realloc(ptr, size); } -static void miniaudio_free(void *ptr, void *userdata) +static void ma_on_free(void *ptr, void *userdata) { (void)userdata; al_free(ptr); @@ -54,12 +54,12 @@ static void miniaudio_free(void *ptr, void *userdata) static ma_allocation_callbacks alloc_callbacks = { .pUserData = NULL, - .onMalloc = miniaudio_malloc, - .onRealloc = miniaudio_realloc, - .onFree = miniaudio_free + .onMalloc = ma_on_malloc, + .onRealloc = ma_on_realloc, + .onFree = ma_on_free }; -static void miniaudio_log_callback(void *userdata, u32 level, const char *message) +static void log_callback(void *userdata, u32 level, const char *message) { (void)userdata; if (level == MA_LOG_LEVEL_ERROR) { @@ -71,14 +71,14 @@ static void miniaudio_log_callback(void *userdata, u32 level, const char *messag } } -static bool audio_miniaudio_init(struct camu_audio *audio, str *name) +static bool audio_ma_init(struct camu_audio *audio, str *name) { - struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)audio; + struct camu_audio_ma *ma = (struct camu_audio_ma *)audio; #ifdef CAMU_NO_MINIAUDIO_BACKENDS al_assert_and_return(false); #endif ma_log_init(&alloc_callbacks, &ma->log); - ma_log_register_callback(&ma->log, ma_log_callback_init(miniaudio_log_callback, NULL)); + ma_log_register_callback(&ma->log, ma_log_callback_init(log_callback, NULL)); ma->context_config = ma_context_config_init(); ma->context_config.pLog = &ma->log; #ifdef MA_ENABLE_PULSEAUDIO @@ -140,9 +140,9 @@ static s32 camu_sample_format_from_miniaudio(s32 fmt) } } -static void audio_miniaudio_pick_format(struct camu_audio *audio, struct camu_resampler_format *fmt) +static void audio_ma_pick_format(struct camu_audio *audio, struct camu_resampler_format *fmt) { - struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)audio; + struct camu_audio_ma *ma = (struct camu_audio_ma *)audio; fmt->req.format = camu_sample_format_from_miniaudio(ma->device.playback.format); fmt->req.sample_rate = ma->device.playback.internalSampleRate; u32 channels = ma->device.playback.channels; @@ -154,7 +154,7 @@ static void audio_miniaudio_pick_format(struct camu_audio *audio, struct camu_re static void data_callback(ma_device *device, void *output, const void *input, u32 frame_count) { - struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)device->pUserData; + struct camu_audio_ma *ma = (struct camu_audio_ma *)device->pUserData; (void)input; bool silence; ma->a.data_callback(ma->a.userdata, (u8 *)output, frame_count, &silence); @@ -170,9 +170,9 @@ static void data_callback(ma_device *device, void *output, const void *input, u3 #define DEFAULT_PERIOD_SIZE_IN_MILLISECONDS 48 #endif -static bool audio_miniaudio_configure_stream(struct camu_audio *audio, void *opaque) +static bool audio_ma_configure_stream(struct camu_audio *audio, void *opaque) { - struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)audio; + struct camu_audio_ma *ma = (struct camu_audio_ma *)audio; struct camu_ff_resample_fmt *fmt = (struct camu_ff_resample_fmt *)opaque; (void)fmt; ma->config = ma_device_config_init(ma_device_type_playback); @@ -225,9 +225,9 @@ static bool audio_miniaudio_configure_stream(struct camu_audio *audio, void *opa return true; } -static u64 audio_miniaudio_get_latency(struct camu_audio *audio) +static u64 audio_ma_get_latency(struct camu_audio *audio) { - struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)audio; + struct camu_audio_ma *ma = (struct camu_audio_ma *)audio; log_debug("periods: %u, period_size_in_frames: %u, sample_rate: %u.", ma->device.playback.internalPeriods, ma->device.playback.internalPeriodSizeInFrames, @@ -237,21 +237,21 @@ static u64 audio_miniaudio_get_latency(struct camu_audio *audio) (ma->device.playback.internalSampleRate / 1000000.0); } -static void audio_miniaudio_start(struct camu_audio *audio) +static void audio_ma_start(struct camu_audio *audio) { - struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)audio; + struct camu_audio_ma *ma = (struct camu_audio_ma *)audio; ma_device_start(&ma->device); } -static void audio_miniaudio_stop(struct camu_audio *audio) +static void audio_ma_stop(struct camu_audio *audio) { - struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)audio; + struct camu_audio_ma *ma = (struct camu_audio_ma *)audio; ma_device_stop(&ma->device); } -static void audio_miniaudio_free(struct camu_audio **audio) +static void audio_ma_free(struct camu_audio **audio) { - struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)*audio; + struct camu_audio_ma *ma = (struct camu_audio_ma *)*audio; ma_device_uninit(&ma->device); ma_context_uninit(&ma->context); #ifdef MA_ENABLE_PULSEAUDIO @@ -263,15 +263,15 @@ static void audio_miniaudio_free(struct camu_audio **audio) struct camu_audio *camu_audio_miniaudio_create(void) { - struct camu_audio_miniaudio *ma = al_alloc_object(struct camu_audio_miniaudio); + struct camu_audio_ma *ma = al_alloc_object(struct camu_audio_ma); ma->a = (struct camu_audio){ - .init = audio_miniaudio_init, - .pick_format = audio_miniaudio_pick_format, - .configure_stream = audio_miniaudio_configure_stream, - .get_latency = audio_miniaudio_get_latency, - .start = audio_miniaudio_start, - .stop = audio_miniaudio_stop, - .free = audio_miniaudio_free, + .init = audio_ma_init, + .pick_format = audio_ma_pick_format, + .configure_stream = audio_ma_configure_stream, + .get_latency = audio_ma_get_latency, + .start = audio_ma_start, + .stop = audio_ma_stop, + .free = audio_ma_free, .data_callback = NULL, .userdata = NULL }; diff --git a/src/mixer/audio_miniaudio.h b/src/mixer/audio_miniaudio.h index c067024..47e1088 100644 --- a/src/mixer/audio_miniaudio.h +++ b/src/mixer/audio_miniaudio.h @@ -9,7 +9,7 @@ #include "audio.h" -struct camu_audio_miniaudio { +struct camu_audio_ma { struct camu_audio a; ma_log log; ma_context_config context_config; diff --git a/src/portal/src/search.c b/src/portal/src/search.c index a1b7310..3f012bf 100644 --- a/src/portal/src/search.c +++ b/src/portal/src/search.c @@ -75,6 +75,7 @@ static nn_thread_result NNWT_THREADCALL queue_thread(void *userdata) // Defer python init. if (!(have_python = camu_python_init())) { log_error("Failed to initialize python."); + bridge->quit = 1; break; } } @@ -125,13 +126,15 @@ static nn_thread_result NNWT_THREADCALL queue_thread(void *userdata) } } } else { - log_info("Requested search doesn't exist (id: %d).", search->id); result.id = -1; + log_info("Requested search doesn't exist (id: %d).", cmd->id); } break; } } + camu_queue_lock(bridge->results); camu_queue_push(bridge->results, result); + camu_queue_unlock(bridge->results); } bridge->queue.count = 0; nn_signal_send(&bridge->results_signal); @@ -145,7 +148,9 @@ static nn_thread_result NNWT_THREADCALL queue_thread(void *userdata) result.callback = cmd->callback; result.userdata = cmd->userdata; result.id = -1; + camu_queue_lock(bridge->results); camu_queue_push(bridge->results, result); + camu_queue_unlock(bridge->results); } bridge->queue.count = 0; nn_signal_send(&bridge->results_signal); @@ -187,13 +192,18 @@ void camu_portal_init(struct camu_portal_bridge *bridge, struct camu_post_cache void camu_portal_create_search(struct camu_portal_bridge *bridge, str *module, str *query, void (*callback)(void *, void *, struct camu_portal_result *), void *userdata) { + nn_mutex_lock(&bridge->mutex); + if (bridge->quit) { + callback(bridge->userdata, userdata, NULL); + nn_mutex_unlock(&bridge->mutex); + return; + } struct camu_portal_cmd cmd = { 0 }; cmd.op = CAMU_CLIENT_CREATE_SEARCH; al_str_clone(&cmd.module, module); al_str_clone(&cmd.query, query); cmd.callback = callback; cmd.userdata = userdata; - nn_mutex_lock(&bridge->mutex); al_array_push(bridge->queue, cmd); if (nn_cond_is_waiting(&bridge->cond)) { nn_cond_signal(&bridge->cond); @@ -204,13 +214,18 @@ void camu_portal_create_search(struct camu_portal_bridge *bridge, str *module, s void camu_portal_get_page(struct camu_portal_bridge *bridge, s32 id, u32 num, void (*callback)(void *, void *, struct camu_portal_result *), void *userdata) { + nn_mutex_lock(&bridge->mutex); + if (bridge->quit) { + callback(bridge->userdata, userdata, NULL); + nn_mutex_unlock(&bridge->mutex); + return; + } struct camu_portal_cmd cmd = { 0 }; cmd.op = CAMU_CLIENT_GET_PAGE; cmd.id = id; cmd.num = num; cmd.callback = callback; cmd.userdata = userdata; - nn_mutex_lock(&bridge->mutex); al_array_push(bridge->queue, cmd); if (nn_cond_is_waiting(&bridge->cond)) { nn_cond_signal(&bridge->cond); diff --git a/src/render/meson.build b/src/render/meson.build index 157cd63..a7ccb58 100644 --- a/src/render/meson.build +++ b/src/render/meson.build @@ -9,11 +9,9 @@ if get_option('renderer') == 'momo' render_args += ['-DCAMU_RENDERER_MOMO'] elif get_option('renderer') == 'libplacebo' libplacebo_found = false - if 'libplacebo' not in get_option('force_fallback_for') + if 'libplacebo' not in get_option('force_fallback_for') and get_option('wrap_mode') != 'forcefallback' libplacebo = dependency('libplacebo', required: false, allow_fallback: false) - if libplacebo.found() - libplacebo_found = true - endif + libplacebo_found = libplacebo.found() endif if not libplacebo_found libplacebo_opts = ['warning_level=2', 'demos=false', 'tests=false', 'bench=false', 'fuzz=false', @@ -28,7 +26,7 @@ elif get_option('renderer') == 'libplacebo' if get_option('renderer-api') == 'vulkan' or get_option('renderer-api') == 'dx11' if get_option('renderer-compiler') == 'shaderc' shaderc_found = false - if 'shaderc' not in get_option('force_fallback_for') + if 'shaderc' not in get_option('force_fallback_for') and get_option('wrap_mode') != 'forcefallback' if is_windows shaderc = dependency('shaderc_combined', required: false, allow_fallback: false) else @@ -47,11 +45,14 @@ elif get_option('renderer') == 'libplacebo' shaderc_opts.add_cmake_defines({ 'SHADERC_SKIP_TESTS': true }) shaderc_opts.add_cmake_defines({ 'SHADERC_SKIP_EXAMPLES': true }) shaderc_opts.add_cmake_defines({ 'SHADERC_SKIP_COPYRIGHT_CHECK': true }) + # glslang shaderc_opts.add_cmake_defines({ 'ENABLE_GLSLANG_BINARIES': false }) - shaderc_opts.add_cmake_defines({ 'ENABLE_SPVREMAPPER': false }) shaderc_opts.add_cmake_defines({ 'ENABLE_HLSL': false }) + shaderc_opts.add_cmake_defines({ 'ENABLE_SPVREMAPPER': false }) + shaderc_opts.add_cmake_defines({ 'ENABLE_EXCEPTIONS': false }) spirv_optimizer = true shaderc_opts.add_cmake_defines({ 'ENABLE_OPT': spirv_optimizer }) + # spirv-tools shaderc_opts.add_cmake_defines({ 'ENABLE_EXCEPTIONS_ON_MSVC': false }) shaderc_proj = cmake.subproject('shaderc', options: shaderc_opts) shaderc = shaderc_proj.dependency('shaderc') @@ -66,9 +67,7 @@ elif get_option('renderer') == 'libplacebo' shaderc_proj.dependency('SPIRV-Tools-static') ] if spirv_optimizer - render_deps += [ - shaderc_proj.dependency('SPIRV-Tools-opt'), - ] + render_deps += [shaderc_proj.dependency('SPIRV-Tools-opt')] endif endif libplacebo_opts += ['shaderc=enabled', 'glslang=disabled'] @@ -77,19 +76,36 @@ elif get_option('renderer') == 'libplacebo' if glslang.found() render_deps += [glslang] else + # Currently does not build. + prefer_static = get_option('prefer_static') + spirv_optimizer = false + if spirv_optimizer + spirv_headers_proj = cmake.subproject('SPIRV-Headers') + spirv_headers = spirv_headers_proj.dependency('SPIRV-Headers') + spirv_tools_opts = cmake.subproject_options() + spirv_tools_opts.add_cmake_defines({ 'SPIRV-Headers_SOURCE_DIR': meson.source_root() + '/subprojects/SPIRV-Headers-b824a46' }) + spirv_tools_opts.add_cmake_defines({ 'ENABLE_EXCEPTIONS_ON_MSVC': false }) + spirv_tools_proj = cmake.subproject('SPIRV-Tools', options: spirv_tools_opts) + spirv_tools = spirv_tools_proj.dependency('SPIRV-Tools-static') + spirv_tools_opt = spirv_tools_proj.dependency('SPIRV-Tools-opt') + endif glslang_opts = cmake.subproject_options() glslang_opts.set_override_option('warning_level', '0') reltype = is_minsize ? 'MinSizeRel' : 'Release' glslang_opts.add_cmake_defines({ 'CMAKE_BUILD_TYPE': is_debug ? 'Debug' : reltype }) - spirv_optimizer = false + glslang_opts.add_cmake_defines({ 'ENABLE_GLSLANG_BINARIES': false }) + glslang_opts.add_cmake_defines({ 'ENABLE_HLSL': false }) + glslang_opts.add_cmake_defines({ 'ENABLE_SPVREMAPPER': false }) + glslang_opts.add_cmake_defines({ 'ENABLE_EXCEPTIONS': false }) glslang_opts.add_cmake_defines({ 'ENABLE_OPT': spirv_optimizer }) + glslang_opts.add_cmake_defines({ 'ALLOW_EXTERNAL_SPIRV_TOOLS': spirv_optimizer }) glslang_proj = cmake.subproject('glslang', options: glslang_opts) spirv = glslang_proj.dependency('SPIRV') glslang = glslang_proj.dependency('glslang') glslang_default_resource_limits = glslang_proj.dependency('glslang-default-resource-limits') - meson.override_dependency('SPIRV', spirv) - meson.override_dependency('glslang', glslang) - meson.override_dependency('glslang-default-resource-limits', glslang_default_resource_limits) + meson.override_dependency('SPIRV', spirv, static: prefer_static) + meson.override_dependency('glslang', glslang, static: prefer_static) + meson.override_dependency('glslang-default-resource-limits', glslang_default_resource_limits, static: prefer_static) render_deps += [ glslang_proj.dependency('MachineIndependent'), glslang_proj.dependency('OSDependent'), @@ -98,6 +114,13 @@ elif get_option('renderer') == 'libplacebo' glslang, glslang_default_resource_limits ] + if spirv_optimizer + render_deps += [ + spirv_tools, + spirv_headers, + spirv_tools_opt + ] + endif endif libplacebo_opts += ['glslang=enabled', 'shaderc=disabled'] endif @@ -140,11 +163,9 @@ endif if get_option('subtitles').enabled() libass_found = false - if 'libass' not in get_option('force_fallback_for') + if 'libass' not in get_option('force_fallback_for') and get_option('wrap_mode') != 'forcefallback' libass = dependency('libass', required: false, allow_fallback: false) - if libass.found() - libass_found = true - endif + libass_found = libass.found() endif if not libass_found libass_opts = ['test=disabled'] @@ -191,6 +212,7 @@ if get_option('subtitles').enabled() 'gobject=disabled', 'cairo=disabled', 'chafa=disabled', + 'icu=disabled', 'freetype=disabled', # This does not override yield, and yielding breaks the build if tests=enabled. # https://github.com/mesonbuild/meson/issues/5214 diff --git a/src/render/queue_libplacebo.c b/src/render/queue_libplacebo.c index c78562c..e3540c3 100644 --- a/src/render/queue_libplacebo.c +++ b/src/render/queue_libplacebo.c @@ -164,12 +164,12 @@ static struct camu_overlay *first_nonref_overlay(struct camu_frame_queue_lp *lq, static struct camu_overlay *create_subtitle_overlay(struct camu_overlay *prev, pl_gpu gpu, ASS_Image *ass_frame) { - u32 frame_count = 1; + u16 frame_count = 1; ASS_Image *head_ass_frame = ass_frame; - while (ass_frame->next) { frame_count++; ass_frame = ass_frame->next; } + while (ass_frame->next) { al_assert(frame_count < UINT16_MAX); frame_count++; ass_frame = ass_frame->next; } ass_frame = head_ass_frame; - u32 prev_alloc = prev ? prev->alloc : 0; + u16 prev_alloc = prev ? prev->alloc : 0; struct camu_overlay *overlay = camu_overlay_alloc(prev, frame_count, sizeof(struct pl_overlay) + sizeof(struct pl_overlay_part)); if (frame_count > prev_alloc) { @@ -225,9 +225,9 @@ static struct camu_overlay *create_subtitle_overlay(struct camu_overlay *prev, p }; u32 c = ass_frame->color; current_part->color[0] = (c >> 24) / 255.0; - current_part->color[1] = ((c >> 16) & 0xFF) / 255.0; - current_part->color[2] = ((c >> 8) & 0xFF) / 255.0; - current_part->color[3] = 1.0 - (c & 0xFF) / 255.0; + current_part->color[1] = ((c >> 16) & 0xff) / 255.0; + current_part->color[2] = ((c >> 8) & 0xff) / 255.0; + current_part->color[3] = 1.0 - (c & 0xff) / 255.0; current->parts = current_part; current->num_parts = 1; @@ -317,8 +317,9 @@ static bool map_av_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame * if (overlay) { overlay->ref++; out_frame->overlays = CAMU_OVERLAY_OFFSET(overlay, 0); - out_frame->num_overlays = overlay->num; ((struct pl_source_frame *)src)->frame_data = overlay; + } else { + out_frame->overlays = NULL; } } nn_mutex_unlock(&lq->subtitle_lock); diff --git a/src/render/renderer.h b/src/render/renderer.h index 3331baa..0eae5cb 100644 --- a/src/render/renderer.h +++ b/src/render/renderer.h @@ -30,12 +30,13 @@ enum { struct camu_overlay { u16 ref; - u32 alloc; + u16 alloc; s32 num; struct nn_mutex *lock; }; #define CAMU_OVERLAY_OFFSET(overlay, n) ((void *)(((u8 *)(overlay)) + sizeof(struct camu_overlay) + (n))) +#define CAMU_OVERLAY_FROM_HEAD(p) (struct camu_overlay *)(((u8 *)p) - sizeof(struct camu_overlay)) struct camu_frame_queue; struct camu_codec_stream; @@ -67,7 +68,7 @@ struct camu_renderer { u32 (*get_latency)(struct camu_renderer *); void (*resize)(struct camu_renderer *, u32 *, u32 *); void (*set)(struct camu_renderer *, u8, u8); - void (*render)(struct camu_renderer *, struct camu_screen *, bool); + bool (*render)(struct camu_renderer *, struct camu_screen *, bool); #ifdef CAMU_HAVE_FFMPEG s32 (*get_buffer2)(struct AVCodecContext *s, AVFrame *frame, s32 flags); void *opaque; diff --git a/src/render/renderer_libplacebo.c b/src/render/renderer_libplacebo.c index 3b69c64..01aa764 100644 --- a/src/render/renderer_libplacebo.c +++ b/src/render/renderer_libplacebo.c @@ -13,7 +13,8 @@ #include "queue_libplacebo.h" #ifdef AL_DEBUG -#define RENDERER_DEBUG 1 +// vkQueueSubmit2: VK_ERROR_VALIDATION_FAILED_EXT leads to an annoying deadlock. +#define RENDERER_DEBUG 0 #else #define RENDERER_DEBUG 0 #endif @@ -37,8 +38,11 @@ static void renderer_lp_resize(struct camu_renderer *renderer, u32 *width, u32 * struct camu_renderer_lp *lr = (struct camu_renderer_lp *)renderer; if (lr->swapchain) { if (lr->have_frame) { - pl_swapchain_submit_frame(lr->swapchain); - lr->have_frame = false; + if (pl_swapchain_submit_frame(lr->swapchain)) { + lr->have_frame = false; + } else { + log_error("Failed to submit frame on resize."); + } } pl_swapchain_resize_compat(lr->swapchain, width, height); } @@ -365,7 +369,9 @@ static inline intptr_t float_64_hash(f64 value) return hash; } -static void renderer_lp_render(struct camu_renderer *renderer, struct camu_screen *scr, bool force) + + +static bool renderer_lp_render(struct camu_renderer *renderer, struct camu_screen *scr, bool force) { struct camu_renderer_lp *lr = (struct camu_renderer_lp *)renderer; @@ -377,15 +383,14 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree if (!lr->have_frame) { struct pl_swapchain_frame frame; - if (!pl_swapchain_start_frame(lr->swapchain, &frame)) { - if (force) { - pl_swapchain_swap_buffers(lr->swapchain); - } - return; + if (pl_swapchain_start_frame(lr->swapchain, &frame)) { + pl_frame_from_swapchain(&lr->target, &frame); + pl_frame_clear_rgba(lr->gpu, &lr->target, clear_color); + lr->have_frame = true; + } else { + log_error("Failed to start frame, fatal."); + return false; } - pl_frame_from_swapchain(&lr->target, &frame); - pl_frame_clear_rgba(lr->gpu, &lr->target, clear_color); - lr->have_frame = true; } f64 mouse_x, mouse_y; @@ -405,11 +410,14 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree lr->params.hooks[0]->parameters[1].data->f = (f32)mouse_y; } + enum { + RESULT_SUBMIT = 1, + RESULT_SUBMIT_AND_WAIT = 1 << 1 + }; + + u8 result = 0; struct pl_frame *target = &lr->target; struct pl_frame_mix mix; - - bool do_gpu_finish = false; - struct camu_screen_video *video; while (scr->videos.count > 0) { bool any_eof = false; @@ -419,7 +427,7 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree // If mix.frames is NULL, read() returned QUEUE_MORE. if (mix.frames) { // weighted is only set when read() returns QUEUE_OK. - do_gpu_finish |= weighted; + result |= (1 | 1 << weighted); // Terrible hack. Lets us distinguish single frames with the same dimensions. // Tied to a libplacebo patch to consider info_priv in the hash. intptr_t hash = (intptr_t)video->buf; @@ -431,15 +439,22 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree hash += float_64_hash(video->view.fov); lr->params.hooks[0]->parameters[2].data->f = (f32)video->view.fov; } - lr->params.info_priv = (void *)hash; - target->crop = mix.frames[0]->crop; - if (!scr->subtitles_enabled) { - // This basically discards the subtitles of each frame before it's rendered. - // So, toggling the subtitles of a single frame (paused) won't work. - for (s32 j = 0; j < mix.num_frames; j++) { - ((struct pl_frame *)mix.frames[j])->num_overlays = 0; +#ifdef CAMU_HAVE_SUBTITLES + for (u32 j = 0; j < (u32)mix.num_frames; j++) { + struct pl_frame *mix_frame = (struct pl_frame *)mix.frames[j]; + if (!mix_frame->overlays) continue; + struct camu_overlay *overlay = CAMU_OVERLAY_FROM_HEAD(mix_frame->overlays); + if (!overlay->num) continue; + if (scr->subtitles_enabled) { + mix_frame->num_overlays = overlay->num; + hash += overlay->num; + } else { + mix_frame->num_overlays = 0; } } +#endif + lr->params.info_priv = (void *)hash; + target->crop = mix.frames[0]->crop; target->crop.x1 *= video->view.zoom / video->view.stretch; target->crop.y1 *= video->view.zoom * video->view.stretch; target->crop.x0 += video->view.x_offset; @@ -447,7 +462,14 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree target->crop.x1 += video->view.x_offset; target->crop.y1 += video->view.y_offset; target->rotation = video->view.rotation; - //lr->params.color_adjustment = pl_color_adjustment(.saturation = 0.0); + /* + //lr->params.color_map_params = pl_color_map_params(.inverse_tone_mapping = true); + if () { + target->color = pl_color_space_srgb; + target->color.hdr.max_luma = PL_COLOR_SDR_WHITE - 50.f; + target->color.hdr.min_luma = (PL_COLOR_SDR_WHITE - 50.f) / PL_COLOR_SDR_CONTRAST; + } + */ pl_render_image_mix(lr->renderer, &mix, target, &lr->params); } } else { @@ -468,32 +490,30 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree lr->params.hooks = NULL; lr->params.num_hooks = 0; - if (!scr->videos.count && !force) { - // Don't spin too hard on a potential error state. + // Considerations about the result of the loop above. + // 1. Any given call to video_buffer_read() may not produce a frame. + // 2. If a buffer signals EOF, by the time run_queue() happens at the bottom of the loop all + // previous buffers could have been queued for removal. Meaning, at this point, videos.count + // would be 0 while there could be data we want to display rendered to the frame. + if (!(result & RESULT_SUBMIT) && !force) { nn_thread_sleep(NNWT_TS_FROM_USEC(192)); - return; + return true; } - if (!pl_swapchain_submit_frame(lr->swapchain)) { + if (pl_swapchain_submit_frame(lr->swapchain)) { + lr->have_frame = false; + } else { log_error("Failed to submit frame, possibly fatal."); + return false; } - lr->have_frame = false; pl_swapchain_swap_buffers(lr->swapchain); - if (do_gpu_finish) { - // Block until render completes. + if (result & RESULT_SUBMIT_AND_WAIT) { pl_gpu_finish(lr->gpu); } -#if 0 - f64 tick = nn_get_tick(); - f64 frame_time = tick - lr->last_render_tick; - if (frame_time > (1.0 / 24.0)) { // 24fps is totally arbitrary. - log_info("FRAME_TIME: %fs (%.2ffps)", frame_time, 1.0 / frame_time); - } - lr->last_render_tick = tick; -#endif + return true; } void renderer_lp_free(struct camu_renderer **renderer) @@ -521,7 +541,7 @@ void renderer_lp_free(struct camu_renderer **renderer) *renderer = NULL; } -struct camu_renderer *camu_renderer_lp_create(void) +struct camu_renderer *camu_renderer_libplacebo_create(void) { struct camu_renderer_lp *lr = al_alloc_object(struct camu_renderer_lp); lr->r.create_renderer = renderer_lp_create_renderer; diff --git a/src/render/renderer_libplacebo.h b/src/render/renderer_libplacebo.h index 5d5cf9b..727e0b3 100644 --- a/src/render/renderer_libplacebo.h +++ b/src/render/renderer_libplacebo.h @@ -42,4 +42,4 @@ struct camu_renderer_lp { #endif }; -struct camu_renderer *camu_renderer_lp_create(void); +struct camu_renderer *camu_renderer_libplacebo_create(void); diff --git a/src/render/renderer_momo.c b/src/render/renderer_momo.c index 3a0e7ed..02ccaee 100644 --- a/src/render/renderer_momo.c +++ b/src/render/renderer_momo.c @@ -85,12 +85,13 @@ static u32 renderer_momo_get_latency(struct camu_renderer *renderer) return 0; } -static void renderer_momo_render(struct camu_renderer *renderer, struct camu_screen *scr, bool force) +static bool renderer_momo_render(struct camu_renderer *renderer, struct camu_screen *scr, bool force) { struct camu_renderer_momo *mr = (struct camu_renderer_momo *)renderer; (void)scr; (void)force; MOMO_Render(&mr->mmr); + return true; } static void renderer_momo_free(struct camu_renderer **renderer) diff --git a/src/screen/screen.c b/src/screen/screen.c index 844076e..f6833dd 100644 --- a/src/screen/screen.c +++ b/src/screen/screen.c @@ -259,8 +259,8 @@ static bool key_callback(void *userdata, u8 state, u16 button) break; case STELA_KEY_RIGHT: if (SCREEN_MOD1(scr)) { - f64 plus_five_seconds = 5.0; - scr->callback(scr->userdata, CAMU_SCREEN_RELATIVE_SEEK, &plus_five_seconds); + f64 plus_ten_seconds = 10.0; + scr->callback(scr->userdata, CAMU_SCREEN_RELATIVE_SEEK, &plus_ten_seconds); break; } // fallthrough @@ -276,8 +276,8 @@ static bool key_callback(void *userdata, u8 state, u16 button) f64 zero = 0.0; scr->callback(scr->userdata, CAMU_SCREEN_PERCENT_SEEK, &zero); } else { - f64 minus_five_seconds = -5.0; - scr->callback(scr->userdata, CAMU_SCREEN_RELATIVE_SEEK, &minus_five_seconds); + f64 minus_ten_seconds = -10.0; + scr->callback(scr->userdata, CAMU_SCREEN_RELATIVE_SEEK, &minus_ten_seconds); } break; } @@ -294,9 +294,11 @@ static bool key_callback(void *userdata, u8 state, u16 button) if (SCREEN_MOD1(scr)) { scr->callback(scr->userdata, CAMU_SCREEN_SHUFFLE, NULL); } else { +#ifdef CAMU_HAVE_SUBTITLES scr->subtitles_enabled = !scr->subtitles_enabled; log_info(scr->subtitles_enabled ? "Subtitles enabled." : "Subtitles disabled."); return true; +#endif } break; case STELA_KEY_E: @@ -484,7 +486,9 @@ bool camu_screen_init(struct camu_screen *scr) scr->flags = CAMU_SCREEN_ZOOM_PAN_SIMPLE; scr->scaling_disabled = false; scr->transparent_background = false; +#ifdef CAMU_HAVE_SUBTITLES scr->subtitles_enabled = true; +#endif scr->last_click_ts = SCREEN_INVALID_TS; scr->last_pointer_x = 0.0; scr->last_pointer_y = 0.0; diff --git a/src/server/server.c b/src/server/server.c index ae77ef0..59a0f7a 100644 --- a/src/server/server.c +++ b/src/server/server.c @@ -590,28 +590,31 @@ out: #ifdef CAMU_HAVE_PORTAL static void simple_search_portal_callback(void *userdata0, void *userdata1, struct camu_portal_result *result) { - struct camu_server *server = (struct camu_server *)userdata0; struct camu_resource *resource = (struct camu_resource *)userdata1; - switch (result->op) { - case CAMU_CLIENT_CREATE_SEARCH: - if (result->id == -1) { - break; - } - camu_portal_get_page(&server->bridge, result->id, 0, simple_search_portal_callback, resource); - return; - case CAMU_CLIENT_GET_PAGE: - if (result->id == -1 || !result->page || !result->page->list.count) { - break; + if (result) { + struct camu_server *server = (struct camu_server *)userdata0; + switch (result->op) { + case CAMU_CLIENT_CREATE_SEARCH: + if (result->id == -1) { + break; + } + camu_portal_get_page(&server->bridge, result->id, 0, simple_search_portal_callback, resource); + return; + case CAMU_CLIENT_GET_PAGE: + if (result->id == -1 || !result->page || !result->page->list.count) { + break; + } + resource->post = camu_post_cache_get(&server->cache, &al_array_at(result->page->list, 0)); + resource->type = CAMU_RESOURCE_PORTAL; + prepare_server_resource(server, resource); + process_pending(resource); + return; } - resource->post = camu_post_cache_get(&server->cache, &al_array_at(result->page->list, 0)); - resource->type = CAMU_RESOURCE_PORTAL; - prepare_server_resource(server, resource); - process_pending(resource); - return; } // No return is the error case. log_warn("Failed to process search request."); resource->load = LIANA_ENTRY_ERRORED; + process_pending(resource); } #endif @@ -684,6 +687,7 @@ static void handle_add_command(struct camu_server *server, struct lia_list *list al_str_from(&query, "link:"); al_str_cat(&query, &line); } + log_info("Processing search request: %.*s.", al_str_x(&query)); camu_portal_create_search(&server->bridge, &al_str_c("youtube"), &query, simple_search_portal_callback, resource); al_str_free(&query); diff --git a/src/util/queue.h b/src/util/queue.h index 17880e0..08b0a30 100644 --- a/src/util/queue.h +++ b/src/util/queue.h @@ -19,13 +19,6 @@ nn_mutex_unlock(&(q).mutex); \ } while (0) -#define camu_queue_count(q, r) \ - do { \ - nn_mutex_lock(&(q).mutex); \ - r = (q).a.count; \ - nn_mutex_unlock(&(q).mutex); \ - } while (0) - #define camu_queue_init(q) \ do { \ al_array_init((q).a); \ @@ -34,16 +27,7 @@ #define camu_queue_push(q, item) \ do { \ - nn_mutex_lock(&(q).mutex); \ al_array_push((q).a, item); \ - nn_mutex_unlock(&(q).mutex); \ - } while (0) - -#define camu_queue_pop(q, r) \ - do { \ - nn_mutex_lock(&(q).mutex); \ - al_array_pop_at((q).a, 0, r); \ - nn_mutex_unlock(&(q).mutex); \ } while (0) #define camu_queue_try_pop(q, s, r) \ -- cgit v1.2.3-101-g0448