diff options
| author | 2025-02-19 13:40:38 -0500 | |
|---|---|---|
| committer | 2025-02-19 13:40:38 -0500 | |
| commit | 2bee71a7e032c0972418e324bb1d7e6b02330b18 (patch) | |
| tree | fa5819e9d9efe75cbc6f50d462dd0eb6bd19b6a2 | |
| parent | b36f022defd8d4ec5a8c29578bb583bea05dfbb6 (diff) | |
| download | camu-2bee71a7e032c0972418e324bb1d7e6b02330b18.tar.gz camu-2bee71a7e032c0972418e324bb1d7e6b02330b18.tar.bz2 camu-2bee71a7e032c0972418e324bb1d7e6b02330b18.zip | |
Server resource unload, many tweaks and fixes
- Initial liana client preferences.
- Hook up libplacebo dx11 backend.
- Make usage of FFmpeg hardware decoding api make some sense.
Signed-off-by: Andrew Opalach <andrew@akon.city>
60 files changed, 1675 insertions, 811 deletions
@@ -12,6 +12,7 @@ src/portal/cpy/portal.h src/portal/py/config.py src/portal/vendor/* subprojects/BLAKE3-*/ +subprojects/SPIRV-Cross-*/ subprojects/c89atomic-*/ subprojects/c89atomic.wrap subprojects/cairo.wrap @@ -28,6 +29,7 @@ subprojects/glfw3-*/ subprojects/glfw3.wrap subprojects/glib-*/ subprojects/glib.wrap +subprojects/glslang-*/ subprojects/gperf.wrap subprojects/gperf/ subprojects/gvdb.wrap @@ -68,10 +70,10 @@ subprojects/stela subprojects/stela/ subprojects/vulkan-headers-*/ subprojects/vulkan.wrap +subprojects/wayland-protocols-*/ subprojects/wayland-protocols.wrap -subprojects/wayland-protocols/ +subprojects/wlr-protocols-*/ subprojects/wlr-protocols.wrap -subprojects/wlr-protocols/ subprojects/wuffs-*/ subprojects/zlib-*/ subprojects/zlib.wrap @@ -3,7 +3,7 @@ == camu === Simple Build and Test -[source,sh] +[source,console] ---- $ (optional) nix develop . $ meson setup build @@ -11,7 +11,7 @@ $ cd build && ninja $ ./src/fruits/cmv/cmv [path_to_file] ---- ==== MinGW -[source,sh] +[source,console] ---- $ nix develop .#mingw64 $ meson setup build-mingw64 --cross-file ./cross/x86_64-w64-mingw32.txt --default-library=static diff --git a/docs/references/references.txt b/docs/references/references.txt index f2430d1..456b4a5 100644 --- a/docs/references/references.txt +++ b/docs/references/references.txt @@ -6,6 +6,11 @@ Video ----- Vsync/VRR ~~~~~~~~~ +* https://forums.blurbusters.com/viewtopic.php?t=12530#p98237 + - http://www.blurbusters.com/scanout + - https://forums.blurbusters.com/viewtopic.php?f=22&t=4710 + - https://forums.blurbusters.com/viewtopic.php?t=3248 + - https://forums.blurbusters.com/viewtopic.php?f=22&t=4213 * https://github.com/swaywm/sway/issues/5076 * https://en.wikipedia.org/wiki/Motion_interpolation * https://gamedev.stackexchange.com/questions/82318/what-problem-does-double-or-triple-buffering-solve-in-modern-games @@ -23,11 +23,11 @@ ] }, "locked": { - "lastModified": 1738709900, - "narHash": "sha256-8Bo5xFlCH5q72ExvAnH7TzStMlLZldKOSLMClRSfmTc=", + "lastModified": 1739815344, + "narHash": "sha256-onBUUObtFXauBU42IlCMtvVUv8vmluEFfCSxNG6guKM=", "owner": "nix-community", "repo": "home-manager", - "rev": "f2d32e46fac9d51da6912948ae1156044c71774b", + "rev": "f4f6dd26985f1e44721325cb6d783d0cf4cd4dbc", "type": "github" }, "original": { @@ -39,11 +39,11 @@ }, "nixos-hardware": { "locked": { - "lastModified": 1738638143, - "narHash": "sha256-ZYMe4c4OCtIUBn5hx15PEGr0+B1cNEpl2dsaLxwY2W0=", + "lastModified": 1739798439, + "narHash": "sha256-GyipmjbbQEaosel/+wq1xihCKbv0/e1LU00x/8b/fP4=", "owner": "NixOS", "repo": "nixos-hardware", - "rev": "9bdd53f5908453e4d03f395eb1615c3e9a351f70", + "rev": "3e2ea8a49d4d76276b0f4e2041df8ca5c0771371", "type": "github" }, "original": { @@ -59,11 +59,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1736095716, - "narHash": "sha256-csysw/Szu98QDiA2lhWk9seYOyCebeVEWL89zh1cduM=", + "lastModified": 1739782332, + "narHash": "sha256-bPZKuual2WKfyHv7Wq+Owcj9R+i/qFzOWByqSS9rlxI=", "owner": "nix-community", "repo": "NixOS-WSL", - "rev": "63c3b4ed1712a3a0621002cd59bfdc80875ecbb0", + "rev": "59bdbb1e75232bf1dd6d2a9313b9b6ddc5049ba2", "type": "github" }, "original": { @@ -75,27 +75,27 @@ }, "nixpkgs": { "locked": { - "lastModified": 1733384649, - "narHash": "sha256-K5DJ2LpPqht7K76bsxetI+YHhGGRyVteTPRQaIIKJpw=", + "lastModified": 1739624908, + "narHash": "sha256-f84lBmLl4tkDp1ZU5LBTSFzlxXP4926DVW3KnXrke10=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "190c31a89e5eec80dd6604d7f9e5af3802a58a13", + "rev": "a60651b217d2e529729cbc7d989c19f3941b9250", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-24.05", + "ref": "nixos-24.11", "repo": "nixpkgs", "type": "github" } }, "nixpkgs_2": { "locked": { - "lastModified": 1738680400, - "narHash": "sha256-ooLh+XW8jfa+91F1nhf9OF7qhuA/y1ChLx6lXDNeY5U=", + "lastModified": 1739736696, + "narHash": "sha256-zON2GNBkzsIyALlOCFiEBcIjI4w38GYOb+P+R4S8Jsw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "799ba5bffed04ced7067a91798353d360788b30d", + "rev": "d74a2335ac9c133d6bbec9fc98d91a77f1604c1f", "type": "github" }, "original": { @@ -204,7 +204,22 @@ ninja meson cmake + gdb wineWowPackages.staging + # wine build + flex + bison + libGLU + libGL + mesa.osmesa + libdrm + wayland + wayland-scanner + libxkbcommon + wayland-protocols + wayland.dev + libxkbcommon.dev + libgbm ]; buildInputs = with pkgsCross.ucrt64; [ (zlib.override { shared = false; static = true; }) diff --git a/meson.build b/meson.build index 1e6e42a..ee77926 100644 --- a/meson.build +++ b/meson.build @@ -32,6 +32,8 @@ if not no_video endif if get_option('sink-use-vulkan') stela_opts += 'api=vulkan' + elif get_option('sink-use-dx11') + stela_opts += 'api=dx11' else stela_opts += get_option('renderer') == 'tiger' ? 'api=gles' : 'api=gl' endif diff --git a/meson_options.txt b/meson_options.txt index 27efc08..c6c0f00 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,6 +1,7 @@ option('sink', type: 'feature', value: 'enabled') option('sink-only', type: 'boolean', value: false) option('sink-use-vulkan', type: 'boolean', value: false) +option('sink-use-dx11', type: 'boolean', value: false) option('sink-for-rpi', type: 'boolean', value: false) option('codecs', type: 'array', choices: ['ffmpeg', 'stb_image', 'spng', 'wuffs'], value: ['ffmpeg']) option('renderer', type: 'combo', choices: ['libplacebo', 'tiger'], value: 'libplacebo') diff --git a/scripts/run_valgrind.sh b/scripts/run_valgrind.sh index bccc34c..ed7e6ef 100755 --- a/scripts/run_valgrind.sh +++ b/scripts/run_valgrind.sh @@ -1,5 +1,5 @@ #! /usr/bin/env sh source ../scripts/python_env #valgrind --leak-check=full ./src/fruits/cmv/cmv "$@" -#valgrind --leak-check=full --show-leak-kinds=all ./src/fruits/cmv/cmv "$@" -valgrind --leak-check=no --show-error-list=yes ./src/fruits/cmv/cmv "$@" +valgrind --leak-check=full --show-leak-kinds=all ./src/fruits/cmv/cmv "$@" +#valgrind --leak-check=no --show-error-list=yes ./src/fruits/cmv/cmv "$@" diff --git a/src/buffer/audio.c b/src/buffer/audio.c index eaac16f..8e0d428 100644 --- a/src/buffer/audio.c +++ b/src/buffer/audio.c @@ -11,11 +11,11 @@ #define BUFFER_SIZE 8.0 #define BUFFER_MARK_MIN 3.25 // Must be a most half of the buffer size. -#define BUFFER_MARK_BUFFERED 1.0 +#define BUFFER_MARK_BUFFERED 0.25 #ifdef CAMU_AUDIO_BUFFER_FADE #define FADE_STEP(fmt) (2.50f / (fmt)->sample_rate) -#define FADE_MIN(fmt) (8000.f / (fmt)->sample_rate) +#define FADE_MIN 0.175f #endif enum { @@ -32,6 +32,7 @@ static void reset_buffer_state(struct camu_audio_buffer *buf) al_atomic_store(f64)(&buf->pts, -1.0, AL_ATOMIC_RELAXED); buf->pause = PAUSE_PAUSED; al_atomic_store(u32)(&buf->unpause, 0, AL_ATOMIC_RELAXED); + buf->logged_delay = false; al_atomic_store(u32)(&buf->volume.set, 0, AL_ATOMIC_RELAXED); buf->buffered = false; al_atomic_store(u8)(&buf->flow, FLOWING, AL_ATOMIC_RELAXED); @@ -120,8 +121,7 @@ static bool push_internal(struct camu_audio_buffer *buf, f64 pts, u8 **data, s32 { f64 base_pts = al_atomic_load(f64)(&buf->pts, AL_ATOMIC_ACQUIRE); - if (sample_count > 0) { - al_assert(data); + if (data && sample_count > 0) { f64 duration = camu_audio_format_samples_to_sec(&buf->fmt.in, sample_count); if (frame_is_late(buf->clock, base_pts, pts, duration)) { return true; @@ -131,7 +131,6 @@ static bool push_internal(struct camu_audio_buffer *buf, f64 pts, u8 **data, s32 data = buf->resampler->get_data(buf->resampler); } } else { - al_assert(!data); if (buf->fmt.resampler_needed) { sample_count = buf->resampler->flush(buf->resampler); data = buf->resampler->get_data(buf->resampler); @@ -148,10 +147,10 @@ static bool push_internal(struct camu_audio_buffer *buf, f64 pts, u8 **data, s32 return true; } + // The maximum space is buf->size - 1. ptrdiff_t space = al_ring_buffer_space(&buf->rb); - if (!buf->buffered && buf->size - space > buf->mark.buffered) { - al_log_debug("audio_buffer", "Buffered (mark: %.2fs).", - camu_audio_format_bytes_to_sec(&buf->fmt.req, buf->size - space)); + if (!buf->buffered && (buf->size - 1) - space >= buf->mark.buffered) { + al_log_debug("audio_buffer", "Buffered (mark: %.1fkb).", buf->mark.buffered / 1024.0); buf->callback(buf->userdata, CAMU_BUFFER_BUFFERED); buf->buffered = true; } @@ -230,8 +229,7 @@ void camu_audio_buffer_flush(struct camu_audio_buffer *buf) } if (!buf->buffered) { - al_log_debug("audio_buffer", "Buffered (mark: %.2fs).", - camu_audio_format_bytes_to_sec(&buf->fmt.req, al_ring_buffer_occupied(&buf->rb))); + al_log_debug("audio_buffer", "Buffered (flush)."); buf->callback(buf->userdata, CAMU_BUFFER_BUFFERED); buf->buffered = true; } @@ -275,12 +273,16 @@ ptrdiff_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, ptrdif (buf->fade.volume == 0.f)) { al_memset(data, 0, req); if (buf->pause == PAUSE_FADING_COMPLETE) { - buf->callback(buf->userdata, CAMU_BUFFER_PAUSED); - buf->pause = PAUSE_PAUSED; + if (!--buf->fade.tail) { + buf->callback(buf->userdata, CAMU_BUFFER_PAUSED); + buf->pause = PAUSE_PAUSED; + } } else if (buf->pause != PAUSE_PAUSED) { // Don't signal PAUSED until the next read to ensure at least 1 silent // frame is included in the fade out. buf->pause = PAUSE_FADING_COMPLETE; + // How many frames of silence to append after fading. + buf->fade.tail = 1; } return req; } @@ -314,7 +316,7 @@ ptrdiff_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, ptrdif if (!buf->ignore_desync && al_atomic_load(u32)(&buf->unpause, AL_ATOMIC_ACQUIRE) > 0) { // Queuing multiple resyncs before resuming the stream will cause pops! - al_log_debug("audio_buffer", "Forcing resync."); + al_log_info("audio_buffer", "Forcing resync."); buf->pause = PAUSE_PAUSED; al_atomic_sub(u32)(&buf->unpause, 1, AL_ATOMIC_RELEASE); } @@ -342,13 +344,17 @@ ptrdiff_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, ptrdif } else if (pts < 0.0) { // Delay. pts = -pts; ret = MIN((ptrdiff_t)camu_audio_format_sec_to_bytes(fmt, pts), req); - al_log_info("audio_buffer", "Delaying audio by %fs.", pts); + if (!buf->logged_delay) { + al_log_info("audio_buffer", "Delaying audio by %fs.", pts); + buf->logged_delay = true; + } al_memset(data, 0, ret); data += ret; req -= ret; // We can continue to delay. if (req == 0) goto out; } + buf->logged_delay = false; } buf->pause = PAUSE_PLAYING; } @@ -420,7 +426,7 @@ ptrdiff_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, ptrdif // fades if the volume is low. step *= buf->volume.user; } - step *= MAX(buf->fade.volume, FADE_MIN(fmt)); + step *= MAX(buf->fade.volume, FADE_MIN); buf->fade.volume = apply_volume(data, req, fmt, buf->fade.volume, buf->volume.user, step); } #else @@ -443,8 +449,8 @@ ptrdiff_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, ptrdif } out: - // We are not safe to increment buf->pts from a different thread. - // We would need to accumulate the difference and do an atomic add here. + // We aren't safe to increment buf->pts from a different thread. + // For that we could accumulate the difference and atomic_add here instead. al_atomic_store(f64)(&buf->pts, base_pts, AL_ATOMIC_RELEASE); // To signal EOF, return less then req. diff --git a/src/buffer/audio.h b/src/buffer/audio.h index ef907bd..5fa2473 100644 --- a/src/buffer/audio.h +++ b/src/buffer/audio.h @@ -21,6 +21,7 @@ struct camu_audio_buffer { atomic(bool) no_video; u8 pause; + bool logged_delay; atomic(u32) unpause; struct camu_resampler_format fmt; @@ -47,6 +48,7 @@ struct camu_audio_buffer { struct { f32 volume; ptrdiff_t offset; + u32 tail; } fade; #endif diff --git a/src/buffer/frame_queue.h b/src/buffer/frame_queue.h index fdb49b5..ca47457 100644 --- a/src/buffer/frame_queue.h +++ b/src/buffer/frame_queue.h @@ -16,14 +16,12 @@ enum { struct camu_frame_queue { struct camu_video_buffer *buf; -#ifdef CAMU_HAVE_FFMPEG - bool (*configure_subtitles)(struct camu_frame_queue *, u32, u32, AVCodecParameters *); -#endif + bool (*configure_subtitles)(struct camu_frame_queue *, u32, u32, struct camu_codec_stream *); void (*push)(struct camu_frame_queue *, struct camu_codec_frame *, f64); #ifdef CAMU_HAVE_FFMPEG void (*push_av_frame)(struct camu_frame_queue *, AVFrame *, f64); - void (*push_subtitle)(struct camu_frame_queue *, AVPacket *); #endif + void (*push_subtitle)(struct camu_frame_queue *, struct camu_codec_packet *); void (*flush)(struct camu_frame_queue *); s32 (*count)(struct camu_frame_queue *); u8 (*read)(struct camu_frame_queue *, f64, void *); diff --git a/src/buffer/video.c b/src/buffer/video.c index 2fc5773..34672fb 100644 --- a/src/buffer/video.c +++ b/src/buffer/video.c @@ -12,8 +12,8 @@ #include "../codec/ffmpeg/scaler.h" #endif -#define BUFFER_MARK_LOW ((1.0 / 30.0) * 6) -#define BUFFER_MARK_BUFFERED ((1.0 / 30.0) * 5) // Must be >1. +#define BUFFER_MARK_LOW ((1.0 / 30.0) * 8) +#define BUFFER_MARK_BUFFERED ((1.0 / 30.0) * 4) // Must be >1. #define BUFFER_MARK_HIGH ((1.0 / 30.0) * 12) #define BUFFER_MARK_RESET (BUFFER_MARK_HIGH * 2.0) @@ -117,13 +117,12 @@ bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_code return true; } -#ifdef CAMU_HAVE_FFMPEG -bool camu_video_buffer_configure_subtitles(struct camu_video_buffer *buf, AVCodecParameters *codecpar) +bool camu_video_buffer_configure_subtitles(struct camu_video_buffer *buf, struct camu_codec_stream *stream) { + if (!buf->queue) return false; struct camu_video_format *fmt = &buf->stream->video.fmt; - return buf->queue->configure_subtitles(buf->queue, fmt->width, fmt->height, codecpar); + return buf->queue->configure_subtitles(buf->queue, fmt->width, fmt->height, stream); } -#endif void camu_video_buffer_set_latency(struct camu_video_buffer *buf, s32 frames) { @@ -168,6 +167,7 @@ static bool push_av_frame_internal(struct camu_video_buffer *buf, AVFrame *frame f64 duration = camu_ff_frame_duration(frame) * av_q2d(stream->time_base); f64 base_pts = al_atomic_load(f64)(&buf->pts, AL_ATOMIC_ACQUIRE); if (!buf->single_frame && frame_is_late(buf->clock, base_pts, pts, duration)) { + al_log_info("video_buffer", "Discarding late frame."); return false; } if (base_pts == -1.0) al_atomic_store(f64)(&buf->pts, pts, AL_ATOMIC_RELEASE); @@ -215,12 +215,10 @@ void camu_video_buffer_push(struct camu_video_buffer *buf, struct camu_codec_fra after_push_internal(buf); } -#ifdef CAMU_HAVE_FFMPEG -void camu_video_buffer_push_subtitle(struct camu_video_buffer *buf, AVPacket *pkt) +void camu_video_buffer_push_subtitle(struct camu_video_buffer *buf, struct camu_codec_packet *packet) { - buf->queue->push_subtitle(buf->queue, pkt); + if (buf->queue) buf->queue->push_subtitle(buf->queue, packet); } -#endif // flush() always comes from the same thread as push(). void camu_video_buffer_flush(struct camu_video_buffer *buf) @@ -230,10 +228,9 @@ void camu_video_buffer_flush(struct camu_video_buffer *buf) buf->queue->flush(buf->queue); if (!buf->buffered) { s32 count = buf->queue->count(buf->queue); - f64 have = count * buf->avg_frame_duration; buf->buffered = true; buf->buffered_with_one_frame = count == 1; - al_log_debug("video_buffer", "Buffered (mark: %.2fs).", have); + al_log_debug("video_buffer", "Buffered (flush)."); buf->callback(buf->userdata, CAMU_BUFFER_BUFFERED); } } @@ -286,6 +283,8 @@ bool camu_video_buffer_read(struct camu_video_buffer *buf, void *out, bool *weig if (ret == CAMU_QUEUE_OK) { *weighted = buf->weighted_read; buf->weighted_read = false; + } else if (ret == CAMU_QUEUE_MORE) { + al_log_warn("video_buffer", "Underrun."); } return ret == CAMU_QUEUE_OK || ret == CAMU_QUEUE_MORE; diff --git a/src/buffer/video.h b/src/buffer/video.h index 0814251..c587af1 100644 --- a/src/buffer/video.h +++ b/src/buffer/video.h @@ -49,15 +49,11 @@ struct camu_video_buffer { bool camu_video_buffer_init(struct camu_video_buffer *buf, struct camu_clock *clock); bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_codec_stream *stream, struct camu_renderer *renderer); -#ifdef CAMU_HAVE_FFMPEG -bool camu_video_buffer_configure_subtitles(struct camu_video_buffer *buf, AVCodecParameters *codecpar); -#endif +bool camu_video_buffer_configure_subtitles(struct camu_video_buffer *buf, struct camu_codec_stream *stream); void camu_video_buffer_set_latency(struct camu_video_buffer *buf, s32 frames); bool camu_video_buffer_is_single_frame(struct camu_video_buffer *buf); void camu_video_buffer_push(struct camu_video_buffer *buf, struct camu_codec_frame *frame); -#ifdef CAMU_HAVE_FFMPEG -void camu_video_buffer_push_subtitle(struct camu_video_buffer *buf, AVPacket *pkt); -#endif +void camu_video_buffer_push_subtitle(struct camu_video_buffer *buf, struct camu_codec_packet *packet); void camu_video_buffer_flush(struct camu_video_buffer *buf); void camu_video_buffer_reset(struct camu_video_buffer *buf); bool camu_video_buffer_read(struct camu_video_buffer *buf, void *out, bool *weighted); diff --git a/src/codec/codec.h b/src/codec/codec.h index e8d2b6d..7e66e5d 100644 --- a/src/codec/codec.h +++ b/src/codec/codec.h @@ -9,6 +9,17 @@ #include "../cache/handle.h" +enum { + CAMU_LANG_ENGLISH = 0, + CAMU_LANG_JAPANESE +}; + +enum { + CAMU_MASK_AUDIO = 1, + CAMU_MASK_VIDEO = 1 << 1, + CAMU_MASK_SUBTITLE = 1 << 2 +}; + #ifdef CAMU_HAVE_FFMPEG enum { CAMU_OK = 0, @@ -128,6 +139,7 @@ struct camu_video_format { struct camu_codec_stream { u8 mode; u8 type; + s32 index; u64 duration; struct { struct camu_video_format fmt; @@ -306,8 +318,8 @@ static inline size_t camu_audio_format_sec_to_bytes(struct camu_audio_format *fm static inline u64 camu_audio_format_bytes_to_usec(struct camu_audio_format *fmt, size_t bytes) { - size_t samples = bytes / (camu_audio_format_bytes_per_sample(fmt) * fmt->channel_count); - return samples / (fmt->sample_rate / 1000000.0); + size_t bps = camu_audio_format_bytes_per_sample(fmt) * fmt->channel_count; + return ((bytes / bps) * 1000000 + (fmt->sample_rate >> 1)) / fmt->sample_rate; } static inline f64 camu_audio_format_bytes_to_sec(struct camu_audio_format *fmt, size_t bytes) diff --git a/src/codec/ffmpeg/common.c b/src/codec/ffmpeg/common.c index c5fe7cd..044908c 100644 --- a/src/codec/ffmpeg/common.c +++ b/src/codec/ffmpeg/common.c @@ -13,44 +13,46 @@ s64 camu_ff_frame_duration(AVFrame *frame) #endif } -void camu_ff_set_log_callback(void (*callback)(void *, s32, const char *, va_list)) -{ - av_log_set_callback(callback); -} +static __thread struct { + char buf[AL_LOG_MESSAGE_SIZE]; + size_t pos; +} log_context; -static char *buf = NULL; -static size_t pos = 0; +// If a line takes more than 1 step to print, make sure we stay within AL_LOG_MESSAGE_SIZE. +#define AV_LOG_MAX_CHUNK (AL_LOG_MESSAGE_SIZE / 2) -// If a line takes more than 2 steps to print, make sure we stay within AL_LOG_MESSAGE_SIZE. -#define CHUNK_SIZE (AL_LOG_MESSAGE_SIZE / 2u) - -// @TODO: thread-saftey. static void av_log_callback(void *userdata, s32 level, const char *fmt, va_list args) { (void)userdata; - al_assert(buf); - if (level < AV_LOG_DEBUG) { - s32 ret = al_vsnprintf(&buf[pos], CHUNK_SIZE, fmt, args); - al_assert(ret > 0); - pos += ret; - if ((pos > 0 && buf[pos - 1] == '\n') || pos >= CHUNK_SIZE) { - if (level <= AV_LOG_INFO) { - al_log_info("ff", buf); - } else { - al_log_debug("ff", buf); - } - pos = 0; + + if (level >= AV_LOG_DEBUG) { + return; + } + + char *offset = log_context.buf + log_context.pos; + s32 ret = al_vsnprintf(offset, AV_LOG_MAX_CHUNK, fmt, args); + al_assert(ret > 0); + + offset += ret; + log_context.pos += ret; + + if ((log_context.pos > 0 && *(offset - 1) == '\n') || log_context.pos >= AV_LOG_MAX_CHUNK) { + if (level <= AV_LOG_INFO) { + al_log_info("ff", log_context.buf); + } else { + al_log_debug("ff", log_context.buf); } + log_context.pos = 0; } } -void camu_ff_set_default_log_callback() +void camu_ff_set_log_callback(void (*callback)(void *, s32, const char *, va_list)) { - buf = (char *)al_malloc(AL_LOG_MESSAGE_SIZE); - camu_ff_set_log_callback(av_log_callback); + av_log_set_callback(callback); } -void camu_ff_free_default_log_callback() +void camu_ff_common_init(void) { - al_free(buf); + al_log_info("ff", "Using FFmpeg version "FFMPEG_VERSION"."); + camu_ff_set_log_callback(av_log_callback); } diff --git a/src/codec/ffmpeg/common.h b/src/codec/ffmpeg/common.h index 885dd4d..944f915 100644 --- a/src/codec/ffmpeg/common.h +++ b/src/codec/ffmpeg/common.h @@ -4,12 +4,8 @@ #include <al/log.h> #include <libavutil/frame.h> -void camu_ff_common_init(void); - s64 camu_ff_frame_duration(AVFrame *frame); // userdata, level, fmt, args void camu_ff_set_log_callback(void (*callback)(void *, int, const char *, va_list)); - -void camu_ff_set_default_log_callback(); -void camu_ff_free_default_log_callback(); +void camu_ff_common_init(void); diff --git a/src/codec/ffmpeg/decoder.c b/src/codec/ffmpeg/decoder.c index dd0cc0b..02f077d 100644 --- a/src/codec/ffmpeg/decoder.c +++ b/src/codec/ffmpeg/decoder.c @@ -8,8 +8,19 @@ #include "decoder.h" -#ifndef CAMU_SINK_NO_VIDEO #ifdef CAMU_FF_DECODER_HWACCEL +#if defined CAMU_RENDERER_VULKAN +static const char *hwdevces[] = { "vulkan" }; +#elif defined CAMU_RENDERER_DX11 +static const char *hwdevces[] = { "d3d11va" }; +#elif defined CAMU_RENDERER_OPENGL +#ifdef NAUNET_ON_WINDOWS +static const char *hwdevces[] = { "d3d11va" }; +#else +static const char *hwdevces[] = { "vaapi" }; +#endif +#endif + static s32 get_buffer2(AVCodecContext *context, AVFrame *pic, s32 flags) { struct camu_ff_decoder *av = (struct camu_ff_decoder *)context->opaque; @@ -29,6 +40,7 @@ static s32 init_hwframe_context(struct camu_ff_decoder *av, AVCodecContext *cont AVHWFramesContext *frames_ctx = (AVHWFramesContext *)(hw_frames_ref->data); frames_ctx->format = av->hw_pix_fmt; + // codec_context->sw_pix_fmt is only set in ffmpeg before calling get_hw_format(). frames_ctx->sw_format = av->codec_context->sw_pix_fmt; frames_ctx->width = av->codec_context->width; frames_ctx->height = av->codec_context->height; @@ -56,12 +68,6 @@ static enum AVPixelFormat get_hw_format(AVCodecContext *context, const enum AVPi for (; *fmt != AV_PIX_FMT_NONE; fmt++) { if (*fmt == av->hw_pix_fmt) { if (av->use_frames_context) { - av->hw_context = NULL; - s32 ret = av_hwdevice_ctx_create(&av->hw_context, av->hw_device_type, NULL, NULL, 0); - if (ret < 0 || !av->hw_context) { - al_log_error("ff_decoder", "Failed to initialize hardware device (%s).", av_err2str(ret)); - break; - } init_hwframe_context(av, av->codec_context, av->hw_context); } return *fmt; @@ -75,6 +81,8 @@ static enum AVPixelFormat get_hw_format(AVCodecContext *context, const enum AVPi static s32 init_hwdevice_context(struct camu_ff_decoder *av, AVCodecContext *context) { + av->hw_context = NULL; + s32 ret = av_hwdevice_ctx_create(&av->hw_context, av->hw_device_type, NULL, NULL, 0); if (ret < 0) { al_log_error("ff_decoder", "Failed to create specified HW device."); @@ -82,27 +90,15 @@ static s32 init_hwdevice_context(struct camu_ff_decoder *av, AVCodecContext *con } context->hw_device_ctx = av_buffer_ref(av->hw_context); + context->extra_hw_frames = 40; al_log_info("ff_decoder", "Using %s hardware decoding.", av_hwdevice_get_type_name(av->hw_device_type)); return ret; } -static s32 hw_device_priority(enum AVHWDeviceType type) -{ - switch (type) { - case AV_HWDEVICE_TYPE_MEDIACODEC: - return 4; - case AV_HWDEVICE_TYPE_VAAPI: - return 3; - case AV_HWDEVICE_TYPE_VULKAN: - return 2; - default: - return -1; - }; -} - -static bool hw_device_supported(struct camu_ff_decoder *av, enum AVHWDeviceType type) +/* +static bool hw_device_supported_by_type(struct camu_ff_decoder *av, enum AVHWDeviceType type) { enum AVHWDeviceType supported_type; al_array_foreach(av->supported_hw_devices, i, supported_type) { @@ -110,6 +106,7 @@ static bool hw_device_supported(struct camu_ff_decoder *av, enum AVHWDeviceType } return false; } +*/ static enum AVHWDeviceType hw_device_supported_by_name(struct camu_ff_decoder *av, const char *name) { @@ -124,14 +121,46 @@ static enum AVHWDeviceType hw_device_supported_by_name(struct camu_ff_decoder *a static bool hw_config_needs_frames_ctx(const AVCodecHWConfig *config) { - if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) && - !(config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)) { - return true; - } else { - return false; + return (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) && + !(config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX); +} + +static bool get_hwdevice_config(struct camu_ff_decoder *av, const AVCodec *codec, enum AVHWDeviceType hw_device_type) +{ + s32 iter = 0; + const AVCodecHWConfig *config = NULL; + while ((config = avcodec_get_hw_config(codec, iter++))) { + if (config->device_type == hw_device_type) { + av->hw_pix_fmt = config->pix_fmt; + av->use_frames_context = hw_config_needs_frames_ctx(config); + return true; + } } + return false; +} + +static bool collect_supported_hwaccels(struct camu_ff_decoder *av) +{ + al_array_init(av->supported_hw_codecs); + const AVCodec *codec; + void *iter = NULL; + while ((codec = av_codec_iterate(&iter))) { + if (!av_codec_is_decoder(codec)) continue; + if (codec->capabilities & (AV_CODEC_CAP_HARDWARE | AV_CODEC_CAP_HYBRID)) { + al_array_push(av->supported_hw_codecs, codec); + } + } + + al_array_init(av->supported_hw_devices); + enum AVHWDeviceType hw_device_type = AV_HWDEVICE_TYPE_NONE; + while ((hw_device_type = av_hwdevice_iterate_types(hw_device_type)) != AV_HWDEVICE_TYPE_NONE) { + al_array_push(av->supported_hw_devices, hw_device_type); + } + + av->hw_device_type = AV_HWDEVICE_TYPE_NONE; + + return true; } -#endif #endif static void close_internal(struct camu_ff_decoder *av) @@ -147,47 +176,24 @@ static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *rend av->codec_context = NULL; AVCodecParameters *codecpar = stream->av.stream->codecpar; - const AVCodec *codec; -#ifdef CAMU_FF_DECODER_HWACCEL - void *state = NULL; - al_array_init(av->supported_hw_codecs); - while ((codec = av_codec_iterate(&state))) { - if (codec->capabilities & (AV_CODEC_CAP_HARDWARE | AV_CODEC_CAP_HYBRID)) { - al_array_push(av->supported_hw_codecs, codec); - } - } -#endif - - codec = avcodec_find_decoder(codecpar->codec_id); + const AVCodec *codec = avcodec_find_decoder(codecpar->codec_id); if (!codec) { al_log_error("ff_decoder", "Failed to find decoder."); goto err; } - av->hw_device_type = AV_HWDEVICE_TYPE_NONE; -#ifdef CAMU_FF_DECODER_HWACCEL - if (codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - al_array_init(av->supported_hw_devices); - enum AVHWDeviceType hw_device_type = AV_HWDEVICE_TYPE_NONE; - while ((hw_device_type = av_hwdevice_iterate_types(hw_device_type)) != AV_HWDEVICE_TYPE_NONE) { - al_array_push(av->supported_hw_devices, hw_device_type); - } + bool attempt_hwdec = stream->duration > 0 && codecpar->codec_type == AVMEDIA_TYPE_VIDEO; - const AVCodecHWConfig *config = NULL; - for (s32 i = 0; ; i++) { - config = avcodec_get_hw_config(codec, i); - if (!config) { - break; - } +#ifdef CAMU_FF_DECODER_HWACCEL + if (attempt_hwdec) { + collect_supported_hwaccels(av); - if (!hw_device_supported(av, config->device_type)) { - continue; - } + av->hw_pix_fmt = AV_PIX_FMT_NONE; - if (hw_device_priority(config->device_type) > hw_device_priority(av->hw_device_type)) { - av->hw_device_type = config->device_type; - av->hw_pix_fmt = config->pix_fmt; - av->use_frames_context = hw_config_needs_frames_ctx(config); + for (u32 i = 0; i < ARRAY_SIZE(hwdevces); i++) { + av->hw_device_type = hw_device_supported_by_name(av, hwdevces[i]); + if (get_hwdevice_config(av, codec, av->hw_device_type)) { + break; } } @@ -196,30 +202,17 @@ static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *rend al_array_foreach(av->supported_hw_codecs, i, hw_codec) { if (hw_codec->id == codecpar->codec_id) { av->hw_device_type = hw_device_supported_by_name(av, hw_codec->wrapper_name); - if (av->hw_device_type != AV_HWDEVICE_TYPE_NONE) { - for (u32 j = 0; ; j++) { - config = avcodec_get_hw_config(hw_codec, j); - if (!config) { - al_log_warn("ff_decoder", "Hardware codec has no hw_configs."); - av->hw_device_type = AV_HWDEVICE_TYPE_NONE; - break; - } - codec = hw_codec; - av->hw_pix_fmt = config->pix_fmt; - av->use_frames_context = hw_config_needs_frames_ctx(config); - break; - } - } - if (av->hw_device_type != AV_HWDEVICE_TYPE_NONE) { + if (get_hwdevice_config(av, hw_codec, av->hw_device_type)) { + codec = hw_codec; break; } + av->hw_device_type = AV_HWDEVICE_TYPE_NONE; } } } if (av->hw_device_type == AV_HWDEVICE_TYPE_NONE) { - al_log_warn("ff_decoder", - "Hardware accelerated video decoding of %s not supported.", codec->name); + al_log_warn("ff_decoder", "Hardware accelerated video decoding of %s not supported.", codec->name); } } #endif @@ -236,42 +229,38 @@ static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *rend } #ifdef CAMU_FF_DECODER_HWACCEL - if ((av->hw_device_type != AV_HWDEVICE_TYPE_NONE && !av->use_frames_context) && - (init_hwdevice_context(av, av->codec_context) < 0)) { - av->hw_device_type = AV_HWDEVICE_TYPE_NONE; - } -#endif - -#ifndef CAMU_SINK_NO_VIDEO - if (codecpar->codec_type == AVMEDIA_TYPE_VIDEO && renderer && renderer->get_buffer2) { + if (attempt_hwdec && av->hw_device_type != AV_HWDEVICE_TYPE_NONE) { + //av->codec_context->codec_id = codec->id; + if (init_hwdevice_context(av, av->codec_context) < 0) { + av->hw_device_type = AV_HWDEVICE_TYPE_NONE; + } av->renderer = renderer; - // libplacebo's get_buffer2 take a really long time to discard frames on opengl + no hwaccel. -#ifdef CAMU_FF_DECODER_HWACCEL av->codec_context->opaque = av; av->codec_context->get_buffer2 = get_buffer2; - if (av->hw_device_type != AV_HWDEVICE_TYPE_NONE) { - av->codec_context->get_format = get_hw_format; - } -#endif + av->codec_context->get_format = get_hw_format; } #else (void)renderer; #endif - if (stream->duration > 0 && av->hw_device_type == AV_HWDEVICE_TYPE_NONE) { + if ((!attempt_hwdec || av->hw_device_type == AV_HWDEVICE_TYPE_NONE) && stream->duration > 0) { s32 cpus = 0; if (av->codec_context->codec_type == AVMEDIA_TYPE_VIDEO) { cpus = av_cpu_count(); cpus = MIN(3, MAX(1, cpus / 2)); } - av->codec_context->thread_count = cpus; - // FF_THREAD_FRAME or FF_THREAD_SLICE. - av->codec_context->thread_type = FF_THREAD_FRAME; - al_log_debug("ff_decoder", "Using %i threads for decoder.", cpus); + if (cpus > 0) { + av->codec_context->thread_count = cpus; + // FF_THREAD_FRAME or FF_THREAD_SLICE. + av->codec_context->thread_type = FF_THREAD_FRAME; + al_log_info("ff_decoder", "Using %i threads for decoder.", cpus); + } } - if (avcodec_open2(av->codec_context, codec, NULL) < 0) { - al_log_error("ff_decoder", "Failed to open codec (%s).", codec->name); + AVDictionary *opts = NULL; + s32 ret = avcodec_open2(av->codec_context, codec, &opts); + if (ret < 0) { + al_log_error("ff_decoder", "Failed to open codec %s (%s).", codec->name, av_err2str(ret)); goto err; } diff --git a/src/codec/ffmpeg/decoder.h b/src/codec/ffmpeg/decoder.h index b1cbf19..4aa3d32 100644 --- a/src/codec/ffmpeg/decoder.h +++ b/src/codec/ffmpeg/decoder.h @@ -5,8 +5,7 @@ #include "../codec.h" -// We need EGL for hwaccel on OpenGL. -#if !(defined CAMU_RENDERER_OPENGL && !defined STELA_USE_EGL) +#ifndef CAMU_SINK_NO_VIDEO //#define CAMU_FF_DECODER_HWACCEL #endif diff --git a/src/codec/ffmpeg/meson.build b/src/codec/ffmpeg/meson.build index cba033c..b93e556 100644 --- a/src/codec/ffmpeg/meson.build +++ b/src/codec/ffmpeg/meson.build @@ -27,18 +27,20 @@ ffmpeg_client_deps = [] libavutil = dependency('libavutil', required: false) libavformat = dependency('libavformat', required: false) libavcodec = dependency('libavcodec', required: false) +libavdevice = dependency('libavdevice', required: false) libswresample = dependency('libswresample', required: false) libswscale = dependency('libswscale', required: false) -if not (libavutil.found() and libavformat.found() and libavcodec.found() and libswresample.found() and libswscale.found()) +if not (libavutil.found() and libavformat.found() and libavcodec.found() and libavdevice.found() and libswresample.found() and libswscale.found()) ffmpeg_proj = subproject('ffmpeg', required: false) if ffmpeg_proj.found() libavutil = ffmpeg_proj.get_variable('avutil') libavformat = ffmpeg_proj.get_variable('avformat') libavcodec = ffmpeg_proj.get_variable('avcodec') + libavdevice = ffmpeg_proj.get_variable('avdevice') libswresample = ffmpeg_proj.get_variable('swresample') libswscale = ffmpeg_proj.get_variable('swscale') # Only used to set include dirs for FFmpeg. - ffmpeg_deps += [ffmpeg_proj.get_variable('ffmpeg_inc_dep')] + ffmpeg_deps += [ffmpeg_proj.get_variable('ffmpeg_include')] # We explicitly enable zlib in the FFmpeg build. ffmpeg_deps += [dependency('zlib')] if is_windows @@ -48,18 +50,24 @@ if not (libavutil.found() and libavformat.found() and libavcodec.found() and lib # Android MediaCodec HW decoding. ffmpeg_client_deps += [compiler.find_library('mediandk')] endif + ffmpeg_version_string = ffmpeg_proj.get_variable('ffmpeg_version_string') endif +else + ffmpeg_version_string = run_command('./version.sh', check: true).stdout().strip() endif +# This is probably meaningless compared to libav* versions, it's just something I wanted. +ffmpeg_args += ['-DFFMPEG_VERSION="' + ffmpeg_version_string + '"'] + soxr = compiler.find_library('soxr', required: false) if soxr.found() ffmpeg_client_deps += [soxr] ffmpeg_args += ['-DCAMU_HAVE_SOXR'] endif -if libavutil.found() and libavformat.found() and libavcodec.found() and libswresample.found() and libswscale.found() - ffmpeg_server_deps += [libavutil, libavformat, libavcodec] - ffmpeg_client_deps += [libavutil, libavformat, libavcodec, libswresample, libswscale] +if libavutil.found() and libavformat.found() and libavcodec.found() and libavdevice.found() and libswresample.found() and libswscale.found() + ffmpeg_server_deps += [libavutil, libavformat, libavcodec, libavdevice] + ffmpeg_client_deps += [libavutil, libavformat, libavcodec, libavdevice, libswresample, libswscale] codec_server_deps += [ declare_dependency(sources: ffmpeg_server_src, dependencies: [ffmpeg_deps, ffmpeg_server_deps], compile_args: ffmpeg_args) diff --git a/src/codec/ffmpeg/packet_ext.c b/src/codec/ffmpeg/packet_ext.c index f3704f2..9aa1992 100644 --- a/src/codec/ffmpeg/packet_ext.c +++ b/src/codec/ffmpeg/packet_ext.c @@ -26,7 +26,8 @@ void nn_packet_write_av_codec_parameters(struct nn_packet *packet, AVCodecParame NNWT_PACKET_WRITE_TYPE(packet, enum AVColorSpace, codecpar->color_space); NNWT_PACKET_WRITE_TYPE(packet, enum AVChromaLocation, codecpar->chroma_location); NNWT_PACKET_WRITE_TYPE(packet, s32, codecpar->video_delay); - NNWT_PACKET_WRITE_TYPE(packet, AVChannelLayout, codecpar->ch_layout); + NNWT_PACKET_WRITE_TYPE(packet, u64, codecpar->ch_layout.u.mask); + //NNWT_PACKET_WRITE_TYPE(packet, AVChannelLayout, codecpar->ch_layout); NNWT_PACKET_WRITE_TYPE(packet, s32, codecpar->sample_rate); NNWT_PACKET_WRITE_TYPE(packet, s32, codecpar->block_align); NNWT_PACKET_WRITE_TYPE(packet, s32, codecpar->frame_size); @@ -124,7 +125,10 @@ void nn_packet_read_av_codec_parameters(struct nn_packet *packet, AVCodecParamet NNWT_PACKET_READ_TYPE(packet, enum AVColorSpace, codecpar->color_space); NNWT_PACKET_READ_TYPE(packet, enum AVChromaLocation, codecpar->chroma_location); NNWT_PACKET_READ_TYPE(packet, s32, codecpar->video_delay); - NNWT_PACKET_READ_TYPE(packet, AVChannelLayout, codecpar->ch_layout); + u64 ch_layout_mask; + NNWT_PACKET_READ_TYPE(packet, u64, ch_layout_mask); + av_channel_layout_from_mask(&codecpar->ch_layout, ch_layout_mask); + //NNWT_PACKET_READ_TYPE(packet, AVChannelLayout, codecpar->ch_layout); NNWT_PACKET_READ_TYPE(packet, s32, codecpar->sample_rate); NNWT_PACKET_READ_TYPE(packet, s32, codecpar->block_align); NNWT_PACKET_READ_TYPE(packet, s32, codecpar->frame_size); diff --git a/src/codec/ffmpeg/version.sh b/src/codec/ffmpeg/version.sh new file mode 100755 index 0000000..6afdb2a --- /dev/null +++ b/src/codec/ffmpeg/version.sh @@ -0,0 +1,7 @@ +#! /usr/bin/env sh +if ! command -v ffprobe 2>&1 >/dev/null +then + echo "(unknown)" +else + ffprobe -v 0 -of default=nw=1:nk=1 -show_program_version | head -1 +fi diff --git a/src/fruits/cmc/cmc.c b/src/fruits/cmc/cmc.c index 58bb3b1..1046e41 100644 --- a/src/fruits/cmc/cmc.c +++ b/src/fruits/cmc/cmc.c @@ -18,6 +18,7 @@ enum { CLI_SEARCH }; +/* static struct cmc_search *get_search_by_id(struct cmc *c, s32 id) { struct cmc_search *search; @@ -26,6 +27,7 @@ static struct cmc_search *get_search_by_id(struct cmc *c, s32 id) } return NULL; } +*/ static void parse_list_entry(struct nn_packet *packet, struct cmc_list_entry *entry) { diff --git a/src/fruits/cmsrv/cmsrv.c b/src/fruits/cmsrv/cmsrv.c index be620a7..e830831 100644 --- a/src/fruits/cmsrv/cmsrv.c +++ b/src/fruits/cmsrv/cmsrv.c @@ -54,7 +54,7 @@ static u8 server_line_callback(void *userdata, str *line) } else if (al_str_eq(line, &al_str_c(";REVERSE"))) { lia_list_reverse(list); } else if (al_str_eq(line, &al_str_c(";CLEAR"))) { - lia_list_clear(list); + //lia_list_clear(list); } else { struct nn_packet *packet = nn_packet_create(); #ifdef CAMU_HAVE_PORTAL @@ -155,7 +155,7 @@ s32 main(s32 argc, char *argv[]) signal(SIGINT, sigint_handler); #ifdef CAMU_HAVE_FFMPEG - camu_ff_set_default_log_callback(); + camu_ff_common_init(); #endif nn_event_loop_init(&s.loop); @@ -196,10 +196,6 @@ s32 main(s32 argc, char *argv[]) cmsrv_ui_close(&s.ui); #endif -#ifdef CAMU_HAVE_FFMPEG - camu_ff_free_default_log_callback(); -#endif - nn_common_close(); return EXIT_SUCCESS; diff --git a/src/fruits/cmv/cmv.c b/src/fruits/cmv/cmv.c index ebc2236..f79ffe2 100644 --- a/src/fruits/cmv/cmv.c +++ b/src/fruits/cmv/cmv.c @@ -53,6 +53,66 @@ static void sigint_handler(int signum) goto out; \ } while (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) +{ + u32 index = al_str_rfind(exe_name, '/'); + if (index == al_str_npos) { + index = al_str_rfind(exe_name, '\\'); + if (index == al_str_npos) goto def; + } + + str sub = al_str_substr(exe_name, index + 1, exe_name->length); + if (al_str_cmp(&sub, &al_str_c("sink-"), 0, 5) != 0) { + goto def; + } + sub = al_str_substr(&sub, 5, sub.length); + + // Skip version. + index = al_str_find(&sub, '-'); + if (index == al_str_npos) goto def; + sub = al_str_substr(&sub, index + 1, sub.length); + + index = al_str_find(&sub, '-'); + if (index == al_str_npos) goto def; + *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_npos) { + index = al_str_find(&sub, '.'); + if (index == al_str_npos) { + 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 |= CAMU_MASK_AUDIO; + } else if (al_str_at(&value, 0) == 'v') { + prefs->enabled_mask |= CAMU_MASK_VIDEO; + } else if (al_str_at(&value, 0) == 's') { + prefs->enabled_mask |= CAMU_MASK_SUBTITLE; + } + al_str_free(&value); + + if (index == sub.length) break; + } + + return true; +def: + prefs->enabled_mask = CAMU_MASK_AUDIO | CAMU_MASK_VIDEO | CAMU_MASK_SUBTITLE; + prefs->audio_lang = CAMU_LANG_ENGLISH; + prefs->subtitle_lang = CAMU_LANG_ENGLISH; + + return false; +} + #ifdef NAUNET_ON_WINDOWS s32 wmain(s32 argc, wchar_t **argv) #else @@ -68,7 +128,7 @@ s32 main(s32 argc, char *argv[]) signal(SIGINT, sigint_handler); #ifdef CAMU_HAVE_FFMPEG - camu_ff_set_default_log_callback(); + camu_ff_common_init(); #endif str home = al_str_null(); @@ -84,30 +144,49 @@ s32 main(s32 argc, char *argv[]) nn_event_loop_init(&c.loop); - u8 type; - str *addr; #ifndef CAMU_SINK_ONLY bool local = argc > 1; - if (local) { +#endif + + u8 type; + str addr; + + str exe_name; +#ifdef NAUNET_ON_WINDOWS + al_wstr_to_str(&al_wstr_cr(argv[0]), &exe_name); +#else + exe_name = al_str_cr(argv[0]); +#endif + if (!parse_exe_name_params(&exe_name, &addr, &c.desktop.sink.prefs)) { +#ifndef CAMU_SINK_ONLY + if (local) { #ifdef NAUNET_ON_WINDOWS - type = NNWT_SOCKET_TCP; addr = &CAMU_LOCALHOST; + type = NNWT_SOCKET_TCP; addr = CAMU_LOCALHOST; +#else + type = NNWT_SOCKET_UNIX; addr = CMV_UNIX_PATH; +#endif + } else { + type = CAMU_TEST_TYPE; + addr = CAMU_TEST_ADDR; + } #else - type = NNWT_SOCKET_UNIX; addr = &CMV_UNIX_PATH; + type = NNWT_SOCKET_TCP; addr = CAMU_TEST_ADDR; #endif + } else { + type = NNWT_SOCKET_TCP; + } + +#ifndef CAMU_SINK_ONLY + if (local) { camu_server_init(&c.server, &c.loop); #ifdef CAMU_DIRECT_MODE camu_server_bind_direct(&c.server); #else - if (!camu_server_listen(&c.server, type, addr, CAMU_PORT)) { + if (!camu_server_listen(&c.server, type, &addr, CAMU_PORT)) { failure(); } #endif - } else { - type = CAMU_TEST_TYPE; - addr = &CAMU_TEST_ADDR; } -#else - type = NNWT_SOCKET_TCP; addr = &CAMU_TEST_ADDR; #endif if (!camu_desktop_init(&c.desktop, "cmv")) failure(); @@ -118,7 +197,7 @@ s32 main(s32 argc, char *argv[]) c.desktop.sink.local_server = &c.server.data.server; } #endif - if (!camu_desktop_connect(&c.desktop, type, &c.loop, addr, CAMU_PORT)) failure(); + if (!camu_desktop_connect(&c.desktop, type, &c.loop, &addr, CAMU_PORT)) failure(); #ifndef CAMU_SINK_ONLY if (local) { @@ -177,10 +256,6 @@ out: al_str_free(&home); -#ifdef CAMU_HAVE_FFMPEG - camu_ff_free_default_log_callback(); -#endif - stl_global_close(); nn_common_close(); diff --git a/src/liana/client.c b/src/liana/client.c index 44cba88..d83ef48 100644 --- a/src/liana/client.c +++ b/src/liana/client.c @@ -28,26 +28,20 @@ static void parse_info_packet(struct lia_client *client, struct nn_packet *packe str liana; nn_packet_read_str(packet, &liana); client->duration = nn_packet_read_u64(packet); - // @TODO: This should be made into 2 steps. - // 1. Collect all streams into an array - // 2. Perform selection based on prefrences. - bool have_audio = false; - bool have_video = false; - bool have_subs = false; + u32 count = nn_packet_read_u32(packet); for (u32 i = 0; i < count; i++) { + // Very important zero-initialization. + struct camu_codec_stream stream = { 0 }; u8 mode = nn_packet_read_u8(packet); u8 type = nn_packet_read_u8(packet); u64 duration = nn_packet_read_u64(packet); s32 index = nn_packet_read_s32(packet); al_assert(index < 32); - struct lia_vcr_track *track = NULL; switch (mode) { case CAMU_NORMAL: { - client->mask |= 1 << index; - track = al_alloc_object(struct lia_vcr_track); if (type == CAMU_STREAM_AUDIO) { - struct camu_audio_format *fmt = &track->stream.audio.fmt; + struct camu_audio_format *fmt = &stream.audio.fmt; fmt->format = nn_packet_read_s32(packet); fmt->sample_rate = nn_packet_read_s32(packet); fmt->channel_count = nn_packet_read_s32(packet); @@ -55,7 +49,7 @@ static void parse_info_packet(struct lia_client *client, struct nn_packet *packe av_channel_layout_default(&fmt->channel_layout, fmt->channel_count); #endif } else if (type == CAMU_STREAM_VIDEO) { - struct camu_video_format *fmt = &track->stream.video.fmt; + struct camu_video_format *fmt = &stream.video.fmt; fmt->width = nn_packet_read_u32(packet); fmt->height = nn_packet_read_u32(packet); fmt->format = nn_packet_read_s32(packet); @@ -67,78 +61,81 @@ static void parse_info_packet(struct lia_client *client, struct nn_packet *packe enum AVCodecID codec_id = nn_packet_read_av_codec_id(packet); const AVCodec *codec = avcodec_find_decoder(codec_id); AVFormatContext *format_context = avformat_alloc_context(); - AVStream *stream = nn_packet_read_av_stream(format_context, codec, packet); + stream.av.stream = nn_packet_read_av_stream(format_context, codec, packet); switch (type) { - case CAMU_STREAM_AUDIO: - if (have_audio) { - goto skip; - } - client->mask |= 1 << index; - have_audio = true; - break; - case CAMU_STREAM_VIDEO: - if (have_video) { - goto skip; - } - client->mask |= 1 << index; - have_video = true; - break; - case CAMU_STREAM_SUBTITLE: - if (have_subs || codec_id != AV_CODEC_ID_ASS) { - goto skip; - } - client->mask |= 1 << index; - have_subs = true; - break; - case CAMU_STREAM_ATTACHMENT: { - struct camu_codec_stream attachment; - attachment.type = CAMU_STREAM_ATTACHMENT; - attachment.av.stream = stream; - client->callback(client->userdata, LIANA_CLIENT_CONFIGURE, &attachment, track); + case CAMU_STREAM_ATTACHMENT: // Assume all the data we need is in the AVStream object. - // fallthrough + stream.type = CAMU_STREAM_ATTACHMENT; + client->callback(client->userdata, LIANA_CLIENT_CONFIGURE, &stream, NULL); + avformat_free_context(format_context); + continue; } - default: - goto skip; - } - track = al_alloc_object(struct lia_vcr_track); - track->stream.av.format_context = format_context; - track->stream.av.stream = stream; + stream.av.format_context = format_context; if (type == CAMU_STREAM_AUDIO) { - struct camu_audio_format *fmt = &track->stream.audio.fmt; - fmt->format = stream->codecpar->format; - fmt->sample_rate = stream->codecpar->sample_rate; - av_channel_layout_copy(&fmt->channel_layout, &stream->codecpar->ch_layout); - fmt->channel_count = stream->codecpar->ch_layout.nb_channels; + struct camu_audio_format *fmt = &stream.audio.fmt; + AVCodecParameters *codecpar = stream.av.stream->codecpar; + fmt->format = codecpar->format; + fmt->sample_rate = codecpar->sample_rate; + av_channel_layout_copy(&fmt->channel_layout, &codecpar->ch_layout); + fmt->channel_count = codecpar->ch_layout.nb_channels; } break; -skip: - avformat_free_context(format_context); - continue; } #endif } - al_assert(track); - track->index = index; - track->stream.mode = mode; - track->stream.type = type; - track->stream.duration = duration; + stream.mode = mode; + stream.type = type; + stream.duration = duration; + stream.index = index; + al_array_push(client->streams, stream); + } + + bool have_audio = false; + bool have_video = false; + bool have_subs = false; + + struct camu_codec_stream *stream; + al_array_foreach_ptr(client->streams, i, stream) { + switch (stream->type) { + case CAMU_STREAM_AUDIO: + if (have_audio || !(client->prefs.enabled_mask & CAMU_MASK_AUDIO)) { + continue; + } + have_audio = true; + break; + case CAMU_STREAM_VIDEO: + if (have_video || !(client->prefs.enabled_mask & CAMU_MASK_VIDEO)) { + continue; + } + have_video = true; + break; + case CAMU_STREAM_SUBTITLE: + if (have_subs || !(client->prefs.enabled_mask & CAMU_MASK_SUBTITLE)) { + continue; + } + have_subs = true; + break; + } + + client->mask |= 1 << stream->index; + + struct lia_vcr_track *track = al_alloc_object(struct lia_vcr_track); + track->stream = stream; track->client = lia_handler_by_name(&liana)->create_client_handler(); track->client->callback = client->callback; track->client->userdata = client->userdata; - if (!track->client->init(track->client, client->renderer, &track->stream)) { + + if (!track->client->init(track->client, client->renderer, track->stream)) { track->client->free(&track->client); -#ifdef CAMU_HAVE_FFMPEG - if (track->stream.mode == CAMU_FFMPEG_COMPAT) { - avformat_free_context(track->stream.av.format_context); - } -#endif al_free(track); continue; } - client->callback(client->userdata, LIANA_CLIENT_CONFIGURE, track->client->stream, track); + + client->callback(client->userdata, LIANA_CLIENT_CONFIGURE, stream, track); + lia_vcr_add_track(&client->vcr, track); } + client->callback(client->userdata, LIANA_CLIENT_CONFIGURE_COMPLETE, NULL, NULL); } @@ -229,13 +226,14 @@ static void connection_closed_callback(void *userdata, struct nn_packet_stream * } } -void lia_client_connect(struct lia_client *client, struct nn_event_loop *loop, u8 type, - str *addr, u16 port, u32 node_id, u64 pos, struct camu_renderer *renderer) +void lia_client_connect(struct lia_client *client, struct nn_event_loop *loop, + u8 type, str *addr, u16 port, u32 node_id, u64 pos, struct camu_renderer *renderer) { client->loop = loop; client->node_id = node_id; client->pos = pos; client->mask = 0; + al_array_init(client->streams); client->reconnect = RECONNECT_NONE; lia_vcr_init(&client->vcr, client->loop, &client->data); al_str_clone(&client->addr, addr); @@ -281,5 +279,12 @@ void lia_client_free(struct lia_client *client) { lia_vcr_free(&client->vcr); nn_packet_stream_free(&client->data); + struct camu_codec_stream *stream; + al_array_foreach_ptr(client->streams, i, stream) { + if (stream->mode == CAMU_FFMPEG_COMPAT) { + avformat_free_context(stream->av.format_context); + } + } + al_array_free(client->streams); al_str_free(&client->addr); } diff --git a/src/liana/client.h b/src/liana/client.h index 5d5fc90..9e49d0e 100644 --- a/src/liana/client.h +++ b/src/liana/client.h @@ -18,9 +18,17 @@ enum { LIANA_CLIENT_CLOSED }; +struct lia_prefs { + u8 enabled_mask; + s8 audio_lang; + s8 subtitle_lang; +}; + struct lia_client { struct nn_event_loop *loop; u32 node_id; + struct lia_prefs prefs; + array(struct camu_codec_stream) streams; u32 mask; u64 pos; u64 at; @@ -36,8 +44,8 @@ struct lia_client { void *userdata; }; -void lia_client_connect(struct lia_client *client, struct nn_event_loop *loop, u8 type, - str *addr, u16 port, u32 node_id, u64 pos, struct camu_renderer *renderer); +void lia_client_connect(struct lia_client *client, struct nn_event_loop *loop, + u8 type, str *addr, u16 port, u32 node_id, u64 pos, struct camu_renderer *renderer); void lia_client_seek(struct lia_client *client, u64 pos, u64 at); void lia_client_reseek(struct lia_client *client); void lia_client_disconnect(struct lia_client *client); diff --git a/src/liana/handlers/codec_client.c b/src/liana/handlers/codec_client.c index 36e475e..8a86799 100644 --- a/src/liana/handlers/codec_client.c +++ b/src/liana/handlers/codec_client.c @@ -43,6 +43,13 @@ static bool push_av_packet(struct lia_codec_client *codec, AVPacket *pkt) s32 ret = codec->dec->push_av_packet(codec->dec, pkt); return ret == CAMU_OK; } + +static void passthrough_subtitle(struct lia_codec_client *codec, AVPacket *pkt) +{ + struct camu_codec_packet packet = { .av.pkt = pkt }; + struct camu_codec_stream *stream = codec->handler.stream; + codec->handler.callback(codec->handler.userdata, LIANA_CLIENT_SUBTITLE, stream, &packet); +} #endif static bool push_packet(struct lia_codec_client *codec, struct nn_buffer *buffer) @@ -109,15 +116,16 @@ static bool codec_client_handle_packet(struct lia_client_handler *handler, struc nn_packet_read_av_packet(packet, pkt); packet->opaque = pkt; } - struct camu_codec_stream *stream = codec->handler.stream; if (codec->dec) { success = push_av_packet(codec, pkt); } else { - codec->handler.callback(codec->handler.userdata, LIANA_CLIENT_SUBTITLE, stream, pkt); + passthrough_subtitle(codec, pkt); success = true; } #ifdef VCR_BUFFER_WHOLE_FILE - pkt->pts += av_rescale_q(stream->duration, AV_TIME_BASE_Q, stream->av.stream->time_base); + struct camu_codec_stream *stream = codec->handler.stream; + AVRational time_base = stream->av.stream->time; + pkt->pts += av_rescale_q(stream->duration, AV_TIME_BASE_Q, time_base); #else av_packet_unref(pkt); av_packet_free(&pkt); diff --git a/src/liana/list.c b/src/liana/list.c index 04ed879..ca8aaa5 100644 --- a/src/liana/list.c +++ b/src/liana/list.c @@ -6,28 +6,7 @@ #include "list.h" #include "list_cmp.h" -/* -static void buffer_ahead(struct lia_list *list) -{ - s32 size = (s32)list->entries.count; - if (list->current >= 0 && list->current + 1 < size) { - s32 ahead = list->current + 1; - for (s32 i = ahead; i < MIN(ahead + LIANA_BUFFER_AHEAD, size); i++) { - struct lia_list_entry *entry = al_array_at(list->entries, i); - struct lia_timing time = { - .at = LIANA_TIMESTAMP_INVALID, - .seek_pos = entry->offset, - .pause = LIANA_PAUSE_NONE, - .ended = false - }; - struct lia_list_sink *sink; - al_array_foreach(list->sinks, i, sink) { - sink->callback(sink->userdata, LIANA_SINK_BUFFER, entry, i, &time); - } - } - } -} -*/ +//#define LIANA_LIST_TRACE enum { ADD_SINK = 0, @@ -60,12 +39,10 @@ void lia_list_init(struct lia_list *list, str *name) list->increment = 0; al_array_init(list->entries); al_array_init(list->sinks); - al_array_init(list->queue); - list->cmd = NULL; + al_array_init(list->command_queue); + list->active_cmd = NULL; } -static void pump_queue(struct lia_list *list); - static bool assume_ended(struct lia_list_entry *entry, u64 at) { if (entry->duration == LIANA_TIMESTAMP_INVALID) return false; @@ -82,6 +59,13 @@ static bool assume_ended(struct lia_list_entry *entry, u64 at) return false; } +#define META_OPAQUE(op) ((u8[]){ op }) + +static void signal_meta(struct lia_list *list, struct lia_list_entry *entry, u8 meta) +{ + list->callback(list->userdata, LIANA_LIST_META, entry, META_OPAQUE(meta)); +} + static bool entry_load_and_get_duration(struct lia_list *list, struct lia_list_entry *entry, s32 sequence, bool *error) { u8 status; @@ -96,65 +80,85 @@ static bool entry_load_and_get_duration(struct lia_list *list, struct lia_list_e } } *error = true; - list->callback(list->userdata, LIANA_LIST_META, entry, (u8[]){ LIANA_META_ENTRY_ERRORED }); + signal_meta(list, entry, LIANA_META_ENTRY_ERRORED); return false; } *error = false; if (status == LIANA_ENTRY_LOADED) { - list->callback(list->userdata, LIANA_GET_DURATION, entry, &entry->duration); + list->callback(list->userdata, LIANA_GET_ENTRY_DURATION, entry, &entry->duration); return true; } return false; } -static void set_queued(struct lia_list *list) +static void entry_unload(struct lia_list *list, struct lia_list_entry *entry) { - if (list->queued < 0) - return; - - struct lia_list_entry *queued = al_array_at(list->entries, list->queued); + list->callback(list->userdata, LIANA_UNLOAD_ENTRY, entry, NULL); +} - bool error; - struct lia_list_entry *current = al_array_at(list->entries, list->current); - if (!entry_load_and_get_duration(list, current, list->current, &error)) - return; +static void entry_ref(struct lia_list *list, struct lia_list_entry *entry) +{ + list->callback(list->userdata, LIANA_REF_ENTRY, entry, NULL); +} - queued->start = current->start + (current->duration - current->offset); - u8 pause = queued->paused_at == LIANA_TIMESTAMP_INVALID ? LIANA_PAUSE_RESUME : LIANA_PAUSE_NONE; - struct lia_timing time = { - .at = queued->start, - .seek_pos = queued->offset, - .pause = pause - }; +static void entry_unref(struct lia_list *list, struct lia_list_entry *entry) +{ + list->callback(list->userdata, LIANA_UNREF_ENTRY, entry, NULL); +} - struct lia_list_sink *sink; - al_array_foreach(list->sinks, i, sink) { - if (sink->queued != list->queued) { - sink->queued = list->queued; - sink->callback(sink->userdata, LIANA_SINK_BUFFER_AND_QUEUE, queued, list->queued, &time); +static void unref_all_entries(struct lia_list *list) +{ + struct lia_list_entry *entry; + al_array_foreach(list->entries, i, entry) { + if (list->current >= 0 && i != (u32)list->current) { + entry_unref(list, entry); } } } -static void evaluate_queued(struct lia_list *list) +static void sink_set(struct lia_list_sink *sink, struct lia_list_entry *entry, s32 sequence, struct lia_timing *time) { - s32 size = (s32)list->entries.count; - s32 next = list->current + 1; - if (next >= size || next == list->queued) - return; + sink->set = sequence; + sink->callback(sink->userdata, LIANA_SINK_SET, entry, sequence, time); +} - bool error; - struct lia_list_entry *queued = al_array_at(list->entries, next); - if (!entry_load_and_get_duration(list, queued, next, &error)) - return; +static void sink_seek(struct lia_list_sink *sink, struct lia_list_entry *entry, s32 sequence, struct lia_timing *time) +{ + sink->callback(sink->userdata, LIANA_SINK_SEEK, entry, sequence, time); +} - list->queued = next; +static void sink_toggle_pause(struct lia_list_sink *sink, struct lia_list_entry *entry, s32 sequence, struct lia_timing *time) +{ + sink->callback(sink->userdata, LIANA_SINK_PAUSE, entry, sequence, time); +} + +static void sink_unset(struct lia_list_sink *sink) +{ + sink->callback(sink->userdata, LIANA_SINK_UNSET, NULL, -1, NULL); } +static void set_current(struct lia_list *list, struct lia_list_entry *entry, s32 sequence, struct lia_timing *time) +{ + entry_ref(list, entry); + list->current = sequence; + list->idle = false; + // @TODO: This is really wrong. Switching back and forth between 2 entries + // will unload everything else. + unref_all_entries(list); + struct lia_list_sink *sink; + al_array_foreach(list->sinks, i, sink) { + sink_set(sink, entry, sequence, time); + } + signal_meta(list, entry, LIANA_META_CURRENT_CHANGED); +} + +static void pump_queue(struct lia_list *list); + static bool handle_add_sink(struct lia_list *list, struct lia_list_sink *sink) { struct lia_list_entry *current; - if (list->current >= 0 && !list->idle && !(current = al_array_at(list->entries, list->current))->ended) { + if (list->current >= 0 && !list->idle) { + current = al_array_at(list->entries, list->current); bool error; if (!entry_load_and_get_duration(list, current, list->current, &error)) { if (error) pump_queue(list); @@ -185,12 +189,8 @@ static bool handle_add_sink(struct lia_list *list, struct lia_list_sink *sink) .pause = pause, .ended = ended }; - sink->set = list->current; - sink->callback(sink->userdata, LIANA_SINK_SET, current, list->current, &time); - } else { - sink->set = -1; + sink_set(sink, current, list->current, &time); } - sink->queued = -1; al_array_push(list->sinks, sink); return true; } @@ -209,33 +209,27 @@ static void handle_remove_sink(struct lia_list *list, void *userdata) static bool handle_add(struct lia_list *list, struct lia_list_entry *entry) { - // The list being idle doesn't mean list->current/sink->set isn't set. + // The list being idle doesn't mean list->current or sink->set aren't set. if (list->idle) { bool error; if (!entry_load_and_get_duration(list, entry, -1, &error)) { return error; } - list->current++; - list->idle = false; entry->start = nn_get_timestamp() + LIANA_BASE_DELAY; + } else { + entry->start = LIANA_TIMESTAMP_INVALID; + } + al_array_push(list->entries, entry); + signal_meta(list, entry, LIANA_META_ADDED_ENTRY); + if (list->idle) { struct lia_timing time = { .at = entry->start, .seek_pos = entry->offset, .pause = LIANA_PAUSE_RESUME, .ended = false }; - struct lia_list_sink *sink; - al_array_foreach(list->sinks, i, sink) { - sink->set = list->current; - sink->callback(sink->userdata, LIANA_SINK_SET, entry, list->current, &time); - } - list->callback(list->userdata, LIANA_LIST_META, entry, (u8[]){ LIANA_META_ADDED_ENTRY }); - list->callback(list->userdata, LIANA_LIST_META, entry, (u8[]){ LIANA_META_CURRENT_CHANGED }); - } else { - entry->start = LIANA_TIMESTAMP_INVALID; - list->callback(list->userdata, LIANA_LIST_META, entry, (u8[]){ LIANA_META_ADDED_ENTRY }); + set_current(list, entry, list->current + 1, &time); } - al_array_push(list->entries, entry); return true; } @@ -253,21 +247,35 @@ static void unset_all(struct lia_list *list) static void handle_unset(struct lia_list *list) { unset_all(list); - list->current = list->entries.count - 1; - list->idle = true; struct lia_list_sink *sink; al_array_foreach(list->sinks, i, sink) { - sink->callback(sink->userdata, LIANA_SINK_UNSET, NULL, -1, NULL); + sink_unset(sink); } + list->current = list->entries.count - 1; + list->idle = true; } static struct lia_list_entry *get_entry_from_sequence(struct lia_list *list, s32 sequence) { s32 size = (s32)list->entries.count; - if (sequence < 0 || sequence >= size) return NULL; + if (sequence < 0 || sequence >= size) { + return NULL; + } return al_array_at(list->entries, sequence); } +static s32 get_sequence_from_entry_id(struct lia_list *list, u32 id) +{ + // Not returning the entry pointer here seems wasteful but it's a meaningful simplification. + struct lia_list_entry *entry; + al_array_foreach(list->entries, i, entry) { + if (entry->id == id) { + return (s32)i; + } + } + return -1; +} + static struct lia_list_entry *get_entry_from_id(struct lia_list *list, u32 id, s32 *sequence) { struct lia_list_entry *entry; @@ -284,18 +292,18 @@ static bool handle_skipto(struct lia_list *list, s32 sequence, s32 index) { if (index == list->current) return true; if (sequence == LIANA_SEQUENCE_ANY) sequence = list->current; - if (sequence < 0) return true; + if (sequence < 0) return true; // list->current = -1 if (sequence != list->current) { // Skipping from an entry other than current is not handled and will cause very - // confusing errors. On top of likely resulting in unexpected behavior. + // confusing errors. To handle it wouldn't make sense anyway because the outcome + // would likely be unexpected to the user. return true; } struct lia_list_entry *current = get_entry_from_sequence(list, sequence); struct lia_list_entry *target = get_entry_from_sequence(list, index); - al_assert(current && !current->held); + al_assert(current && !current->held && current != target); if (!target) return true; - al_assert(current != target); bool error; if (!entry_load_and_get_duration(list, target, index, &error)) { // index might point to a different entry after an error. @@ -320,7 +328,7 @@ static bool handle_skipto(struct lia_list *list, s32 sequence, s32 index) // An ended entry may never have been paused, but a non-ended entry that wasn't set // cannot be unpaused. Checking assume_ended(target) should be safe here as long // as it can't go from true to false (consideration for seek?). - //al_assert(assume_ended(target, now) || target->paused_at != LIANA_TIMESTAMP_INVALID); + //al_assert(target->paused_at != LIANA_TIMESTAMP_INVALID); } // These are not equivalent to current/target->ended. @@ -350,6 +358,9 @@ static bool handle_skipto(struct lia_list *list, s32 sequence, s32 index) // Pause current and set the held flag indicating it should be resumed // if it becomes the target of a skip. + // + // @TODO: Why not hold an ended entry? The idea of ended entries being + // unpaused but never held seems like a over-complication. if (!current_ended && current->paused_at == LIANA_TIMESTAMP_INVALID) { current->paused_at = at; if (current->paused_at < current->start) { @@ -360,11 +371,10 @@ static bool handle_skipto(struct lia_list *list, s32 sequence, s32 index) current->held = true; } - al_log_debug("list", "skipto [#%u-#%u]: pause: %hhu, held: %s, current_ended: %s, target_ended: %s.", +#ifdef LIANA_LIST_TRACE + al_log_info("list", "skipto [#%u-#%u]: pause: %hhu, held: %s, current_ended: %s, target_ended: %s.", current->id, target->id, pause, BOOLSTR(current->held), BOOLSTR(current_ended), BOOLSTR(target_ended)); - - list->current = index; - list->idle = false; +#endif struct lia_timing time = { .at = at, @@ -373,13 +383,7 @@ static bool handle_skipto(struct lia_list *list, s32 sequence, s32 index) .ended = target_ended }; - struct lia_list_sink *sink; - al_array_foreach(list->sinks, i, sink) { - sink->set = index; - sink->callback(sink->userdata, LIANA_SINK_SET, target, index, &time); - } - - list->callback(list->userdata, LIANA_LIST_META, target, (u8[]){ LIANA_META_CURRENT_CHANGED }); + set_current(list, target, index, &time); return true; } @@ -387,7 +391,7 @@ static bool handle_skipto(struct lia_list *list, s32 sequence, s32 index) static bool handle_skip(struct lia_list *list, s32 sequence, s32 n) { if (sequence == LIANA_SEQUENCE_ANY) sequence = list->current; - struct lia_list_cmd *cmd = list->cmd; + struct lia_list_cmd *cmd = list->active_cmd; cmd->op = SKIPTO; cmd->sequence = sequence; cmd->arg0.i = sequence + n; @@ -398,11 +402,12 @@ static void handle_toggle_pause(struct lia_list *list, s32 sequence, f64 pts) { // pts should be treated as a hint. if (sequence == LIANA_SEQUENCE_ANY) sequence = list->current; - if (sequence < 0) return; + if (sequence < 0) return; // list->current = -1 if (sequence != list->current) { // A non-current entry should always be paused. return; } + struct lia_list_entry *entry = get_entry_from_sequence(list, sequence); al_assert(entry && !entry->held); @@ -430,7 +435,11 @@ static void handle_toggle_pause(struct lia_list *list, s32 sequence, f64 pts) break; } - al_log_debug("list", "toggle_pause [#%u]: pts: %f, pause: %hhu.", entry->id, pts, pause); +#ifdef LIANA_LIST_TRACE + al_log_info("list", "toggle_pause [#%u]: pts: %f, pause: %hhu.", entry->id, pts, pause); +#else + (void)pts; +#endif struct lia_timing time = { .at = at, @@ -441,7 +450,7 @@ static void handle_toggle_pause(struct lia_list *list, s32 sequence, f64 pts) struct lia_list_sink *sink; al_array_foreach(list->sinks, i, sink) { - sink->callback(sink->userdata, LIANA_SINK_PAUSE, entry, sequence, &time); + sink_toggle_pause(sink, entry, sequence, &time); } } @@ -450,14 +459,15 @@ static void handle_seek(struct lia_list *list, s32 sequence, u32 id, f64 percent struct lia_list_entry *entry; if (sequence == LIANA_SEQUENCE_ANY) { sequence = list->current; - if (sequence < 0) return; - entry = get_entry_from_sequence(list, sequence); - al_assert(entry); } else { - entry = get_entry_from_id(list, id, &sequence); - if (!entry) return; + sequence = get_sequence_from_entry_id(list, id); } + if (sequence < 0) return; + + entry = get_entry_from_sequence(list, sequence); + al_assert(entry); + if (entry->duration == LIANA_TIMESTAMP_INVALID) { al_log_warn("list", "Skipping seek on entry with no duration."); return; @@ -477,7 +487,9 @@ static void handle_seek(struct lia_list *list, s32 sequence, u32 id, f64 percent list->idle = false; - al_log_debug("list", "seek [#%u]: pos: %f.", entry->id, pos / 1000000.0); +#ifdef LIANA_LIST_TRACE + al_log_info("list", "seek [#%u]: pos: %f.", entry->id, pos / 1000000.0); +#endif struct lia_timing time = { .at = at, @@ -489,22 +501,19 @@ static void handle_seek(struct lia_list *list, s32 sequence, u32 id, f64 percent struct lia_list_sink *sink; al_array_foreach(list->sinks, i, sink) { if (sequence == list->current && sink->set != sequence) { - sink->set = sequence; - sink->callback(sink->userdata, LIANA_SINK_SET, entry, sequence, &time); + sink_set(sink, entry, sequence, &time); } - sink->callback(sink->userdata, LIANA_SINK_SEEK, entry, sequence, &time); + sink_seek(sink, entry, sequence, &time); } - list->callback(list->userdata, LIANA_LIST_META, entry, (u8[]){ LIANA_META_ENTRY_SEEKED }); + signal_meta(list, entry, LIANA_META_ENTRY_SEEKED); } static bool handle_end(struct lia_list *list, u32 id, u32 reset_id) { - s32 sequence; - struct lia_list_entry *entry = get_entry_from_id(list, id, &sequence); - if (!entry) { - return true; - } + s32 sequence = get_sequence_from_entry_id(list, id); + if (sequence < 0) return true; + struct lia_list_entry *entry = get_entry_from_sequence(list, sequence); if (reset_id != entry->reset_id) { al_log_warn("list", "Got end() with out of order or incorrect reset id, ignoring."); @@ -516,7 +525,10 @@ static bool handle_end(struct lia_list *list, u32 id, u32 reset_id) return true; } - al_log_debug("list", "end [#%u].", entry->id); +#ifdef LIANA_LIST_TRACE + al_log_info("list", "end [#%u].", entry->id); +#endif + entry->ended = true; entry->offset = entry->duration; @@ -534,17 +546,17 @@ static bool handle_end(struct lia_list *list, u32 id, u32 reset_id) if (sequence == list->current) { s32 next = sequence + 1; if (list->queued >= 0) { - list->current = list->queued; - list->queued = -1; + struct lia_list_entry *queued = al_array_at(list->entries, list->queued); struct lia_list_sink *sink; al_array_foreach(list->sinks, i, sink) { sink->queued = -1; - sink->set = list->current; + sink->set = list->queued; } - struct lia_list_entry *current = al_array_at(list->entries, list->current); - list->callback(list->userdata, LIANA_LIST_META, current, (u8[]){ LIANA_META_CURRENT_CHANGED }); + list->current = list->queued; + list->queued = -1; + signal_meta(list, queued, LIANA_META_CURRENT_CHANGED); } else if (next < size) { - struct lia_list_cmd *cmd = list->cmd; + struct lia_list_cmd *cmd = list->active_cmd; cmd->op = SKIPTO; cmd->sequence = sequence; cmd->arg0.i = next; @@ -561,7 +573,7 @@ static bool handle_end(struct lia_list *list, u32 id, u32 reset_id) static bool adjust_current(struct lia_list *list, struct lia_list_entry *previous) { al_assert(list->current >= 0); - struct lia_list_cmd *cmd = list->cmd; + struct lia_list_cmd *cmd = list->active_cmd; struct lia_list_entry *entry; al_array_foreach(list->entries, i, entry) { if (entry->opaque == previous->opaque) { @@ -582,11 +594,10 @@ static bool adjust_current(struct lia_list *list, struct lia_list_entry *previou static bool handle_reverse(struct lia_list *list) { - if (list->current == -1) { - return true; - } + if (list->current < 0) return true; struct lia_list_entry *previous = al_array_at(list->entries, list->current); + u32 size = list->entries.count; for (u32 i = 0; i < size; i++) { u32 tail = size - (i + 1); @@ -599,11 +610,10 @@ static bool handle_reverse(struct lia_list *list) static bool handle_sort(struct lia_list *list) { - if (list->current == -1) { - return true; - } + if (list->current < 0) return true; struct lia_list_entry *previous = al_array_at(list->entries, list->current); + al_array_sort(list->entries, struct lia_list_entry *, camu_db_compare); return adjust_current(list, previous); @@ -611,14 +621,13 @@ static bool handle_sort(struct lia_list *list) static bool handle_shuffle(struct lia_list *list) { - if (list->current == -1) { - return true; - } + if (list->current < 0) return true; u32 size = list->entries.count; if (size <= 1) return false; struct lia_list_entry *previous = al_array_at(list->entries, list->current); + /* https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle for i from 0 to n−2 do j ← random integer such that i ≤ j ≤ n-1 @@ -634,27 +643,14 @@ static bool handle_shuffle(struct lia_list *list) return adjust_current(list, previous); } -/* -static void handle_clear(struct lia_list *list) -{ - unset_all(list); - list->idle = true; - struct lia_list_entry *entry; - al_array_foreach(list->entries, i, entry) { - al_wstr_free(&entry->name); - al_free(entry); - } - list->entries.count = 0; -} -*/ - static void run_queue(struct lia_list *list) { - if (!list->cmd) { - if (!list->queue.count) return; - al_array_pop_at(list->queue, 0, list->cmd); + // @TODO: What does current = -1/currentless really mean. + if (!list->active_cmd) { + if (!list->command_queue.count) return; + al_array_pop_at(list->command_queue, 0, list->active_cmd); } - struct lia_list_cmd *cmd = list->cmd; + struct lia_list_cmd *cmd = list->active_cmd; switch (cmd->op) { case ADD_SINK: if (!handle_add_sink(list, cmd->sink)) { @@ -680,7 +676,7 @@ static void run_queue(struct lia_list *list) break; case SKIP: if (!handle_skip(list, cmd->sequence, cmd->arg0.i)) { - // Converted to skipto and entry not loaded. + // Converted to skipto and target entry not loaded. return; } break; @@ -712,21 +708,16 @@ static void run_queue(struct lia_list *list) } break; case CLEAR: - /* - handle_clear(list); - */ break; } al_free(cmd); - list->cmd = NULL; + list->active_cmd = NULL; pump_queue(list); } void pump_queue(struct lia_list *list) { run_queue(list); - //evaluate_queued(list); - //set_queued(list); } void lia_list_pump(struct lia_list *list) @@ -737,19 +728,21 @@ void lia_list_pump(struct lia_list *list) void lia_list_add_sink(struct lia_list *list, void (*callback)(void *, u8, struct lia_list_entry *, s32, struct lia_timing *), void *userdata) { struct lia_list_sink *sink = al_alloc_object(struct lia_list_sink); + sink->set = -1; + sink->queued = -1; sink->callback = callback; sink->userdata = userdata; struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd); cmd->op = ADD_SINK; cmd->sink = sink; - al_array_push(list->queue, cmd); + al_array_push(list->command_queue, cmd); pump_queue(list); } void lia_list_remove_sink(struct lia_list *list, void *userdata) { - // Don't queue remove sink because we can't let any currently queued commands - // touch this sink. + // Don't queue remove sink because we can't let any currently queued + // commands touch this sink. handle_remove_sink(list, userdata); } @@ -770,7 +763,7 @@ void lia_list_add(struct lia_list *list, void *opaque, u64 duration, wstr *name) struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd); cmd->op = ADD; cmd->entry = entry; - al_array_push(list->queue, cmd); + al_array_push(list->command_queue, cmd); pump_queue(list); } @@ -778,7 +771,7 @@ void lia_list_unset(struct lia_list *list) { struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd); cmd->op = UNSET; - al_array_push(list->queue, cmd); + al_array_push(list->command_queue, cmd); pump_queue(list); } @@ -788,7 +781,7 @@ void lia_list_skipto(struct lia_list *list, s32 sequence, s32 index) cmd->op = SKIPTO; cmd->sequence = sequence; cmd->arg0.i = index; - al_array_push(list->queue, cmd); + al_array_push(list->command_queue, cmd); pump_queue(list); } @@ -798,7 +791,7 @@ void lia_list_skip(struct lia_list *list, s32 sequence, s32 n) cmd->op = SKIP; cmd->sequence = sequence; cmd->arg0.i = n; - al_array_push(list->queue, cmd); + al_array_push(list->command_queue, cmd); pump_queue(list); } @@ -808,7 +801,7 @@ void lia_list_toggle_pause(struct lia_list *list, s32 sequence, f64 pts) cmd->op = TOGGLE_PAUSE; cmd->sequence = sequence; cmd->argf = pts; - al_array_push(list->queue, cmd); + al_array_push(list->command_queue, cmd); pump_queue(list); } @@ -819,7 +812,7 @@ void lia_list_seek(struct lia_list *list, s32 sequence, u32 id, f64 percent) cmd->sequence = sequence; cmd->arg0.u = id; cmd->argf = percent; - al_array_push(list->queue, cmd); + al_array_push(list->command_queue, cmd); pump_queue(list); } @@ -829,7 +822,7 @@ void lia_list_end(struct lia_list *list, u32 id, u32 reset_id) cmd->op = END; cmd->arg0.u = id; cmd->arg1.u = reset_id; - al_array_push(list->queue, cmd); + al_array_push(list->command_queue, cmd); pump_queue(list); } @@ -837,7 +830,7 @@ void lia_list_reverse(struct lia_list *list) { struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd); cmd->op = REVERSE; - al_array_push(list->queue, cmd); + al_array_push(list->command_queue, cmd); pump_queue(list); } @@ -845,7 +838,7 @@ void lia_list_sort(struct lia_list *list) { struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd); cmd->op = SORT; - al_array_push(list->queue, cmd); + al_array_push(list->command_queue, cmd); pump_queue(list); } @@ -853,35 +846,37 @@ void lia_list_shuffle(struct lia_list *list) { struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd); cmd->op = SHUFFLE; - al_array_push(list->queue, cmd); + al_array_push(list->command_queue, cmd); pump_queue(list); } +/* void lia_list_clear(struct lia_list *list) { struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd); cmd->op = CLEAR; - al_array_push(list->queue, cmd); + al_array_push(list->command_queue, cmd); pump_queue(list); } +*/ void lia_list_close(struct lia_list *list) { // @TODO: Consider sinks being in use. Wait for list->sinks to be empty? struct lia_list_entry *entry; al_array_foreach(list->entries, i, entry) { - list->callback(list->userdata, LIANA_UNLOAD_ENTRY, entry, NULL); + entry_unload(list, entry); } } void lia_list_free(struct lia_list *list) { struct lia_list_cmd *cmd; - al_array_foreach(list->queue, i, cmd) { + al_array_foreach(list->command_queue, i, cmd) { al_free(cmd); } - al_array_free(list->queue); - if (list->cmd) al_free(list->cmd); + al_array_free(list->command_queue); + if (list->active_cmd) al_free(list->active_cmd); struct lia_list_entry *entry; al_array_foreach(list->entries, i, entry) { @@ -898,3 +893,74 @@ void lia_list_free(struct lia_list *list) al_str_free(&list->name); } + +/* +static void buffer_ahead(struct lia_list *list) +{ + s32 size = (s32)list->entries.count; + if (list->current >= 0 && list->current + 1 < size) { + s32 ahead = list->current + 1; + for (s32 i = ahead; i < MIN(ahead + LIANA_BUFFER_AHEAD, size); i++) { + struct lia_list_entry *entry = al_array_at(list->entries, i); + struct lia_timing time = { + .at = LIANA_TIMESTAMP_INVALID, + .seek_pos = entry->offset, + .pause = LIANA_PAUSE_NONE, + .ended = false + }; + struct lia_list_sink *sink; + al_array_foreach(list->sinks, i, sink) { + sink->callback(sink->userdata, LIANA_SINK_BUFFER, entry, i, &time); + } + } + } +} + +static void set_queued(struct lia_list *list) +{ + if (list->queued < 0) { + return; + } + + struct lia_list_entry *queued = al_array_at(list->entries, list->queued); + + bool error; + struct lia_list_entry *current = al_array_at(list->entries, list->current); + if (!entry_load_and_get_duration(list, current, list->current, &error)) { + return; + } + + queued->start = current->start + (current->duration - current->offset); + u8 pause = queued->paused_at == LIANA_TIMESTAMP_INVALID ? LIANA_PAUSE_RESUME : LIANA_PAUSE_NONE; + struct lia_timing time = { + .at = queued->start, + .seek_pos = queued->offset, + .pause = pause + }; + + struct lia_list_sink *sink; + al_array_foreach(list->sinks, i, sink) { + if (sink->queued != list->queued) { + sink->queued = list->queued; + sink->callback(sink->userdata, LIANA_SINK_BUFFER_AND_QUEUE, queued, list->queued, &time); + } + } +} + +static void evaluate_queued(struct lia_list *list) +{ + s32 size = (s32)list->entries.count; + s32 next = list->current + 1; + if (next >= size || next == list->queued) { + return; + } + + bool error; + struct lia_list_entry *queued = al_array_at(list->entries, next); + if (!entry_load_and_get_duration(list, queued, next, &error)) { + return; + } + + list->queued = next; +} +*/ diff --git a/src/liana/list.h b/src/liana/list.h index 0ffe652..4b1ae02 100644 --- a/src/liana/list.h +++ b/src/liana/list.h @@ -7,7 +7,7 @@ #define LIANA_SEQUENCE_ANY -1 #define LIANA_TIMESTAMP_INVALID ((u64)-1) -#define LIANA_BASE_DELAY 450000Lu // 450ms +#define LIANA_BASE_DELAY 750000Lu // 750ms #define LIANA_BASE_PING 150000Lu // 150ms #define LIANA_PAUSE_DELAY LIANA_BASE_PING #define LIANA_DELAY_IGNORE 0Lu @@ -32,13 +32,16 @@ enum { enum { LIANA_LOAD_ENTRY = 0, - LIANA_GET_DURATION, + LIANA_GET_ENTRY_DURATION, + LIANA_REF_ENTRY, + LIANA_UNREF_ENTRY, LIANA_UNLOAD_ENTRY, LIANA_LIST_META }; enum { - LIANA_ENTRY_PREPARING = 0, + LIANA_ENTRY_UNLOADED = 0, + LIANA_ENTRY_PREPARING, LIANA_ENTRY_PREPARED, LIANA_ENTRY_LOADING, LIANA_ENTRY_LOADED, @@ -109,8 +112,8 @@ struct lia_list { u32 increment; array(struct lia_list_entry *) entries; array(struct lia_list_sink *) sinks; - array(struct lia_list_cmd *) queue; - struct lia_list_cmd *cmd; + array(struct lia_list_cmd *) command_queue; + struct lia_list_cmd *active_cmd; void (*callback)(void *, u8, struct lia_list_entry *, void *); void *userdata; }; @@ -133,7 +136,7 @@ void lia_list_end(struct lia_list *list, u32 id, u32 reset_id); void lia_list_reverse(struct lia_list *list); void lia_list_sort(struct lia_list *list); void lia_list_shuffle(struct lia_list *list); -void lia_list_clear(struct lia_list *list); +//void lia_list_clear(struct lia_list *list); void lia_list_close(struct lia_list *list); void lia_list_free(struct lia_list *list); diff --git a/src/liana/server.c b/src/liana/server.c index 24c4bfc..13ca0aa 100644 --- a/src/liana/server.c +++ b/src/liana/server.c @@ -98,6 +98,19 @@ static void discard_packet_callback(void *userdata, struct nn_packet_stream *str al_assert(false); } +static bool should_free_node(struct lia_node *node) +{ + return node->closed && !node->handler && !node->requests.count && !node->connections.count; +} + +static void free_node(struct lia_node *node) +{ + struct lia_server *server = node->server; + cch_entry_free(&node->entry); + al_array_remove(server->nodes, node); + al_free(node); +} + static void free_connection(struct lia_node_connection *conn) { struct lia_node *node = conn->node; @@ -109,8 +122,8 @@ static void free_connection(struct lia_node_connection *conn) al_array_remove_checked(node->connections, conn, removed); al_assert(removed); al_free(conn); - if (node->closed && !node->connections.count) { - cch_entry_free(&node->entry); + if (should_free_node(node)) { + free_node(node); } } @@ -213,6 +226,8 @@ static void handle_connection(struct lia_node_connection *conn, struct nn_packet start_connection_handler(conn, mask); conn->handler->subscribe(conn->handler, mask); } + + nn_packet_stream_return_packet(stream, packet); } static void connection_closed_callback(void *userdata, struct nn_packet_stream *stream) @@ -232,7 +247,7 @@ static void packet_sent_callback(void *userdata, struct nn_packet *packet) static void demote_and_disconnect_stream(struct lia_server *server, struct nn_packet_stream *stream) { // Discard queue based on the currently set packet_sent_callback. - // This should always be the expected behavior but here it's mainly to + // This should always be the expected behavior but here it's important to // not lose packets that belong to the packet pool. nn_packet_stream_discard_queue(stream); stream->userdata = server; @@ -248,34 +263,39 @@ static void signal_callback(void *userdata) struct lia_node_connection *conn = (struct lia_node_connection *)userdata; struct lia_node *node = conn->node; struct lia_server *server = node->server; + nn_thread_join(&conn->thread); nn_signal_stop(&conn->signal); + al_array_remove(node->requests, conn); + + struct nn_packet_stream *stream = conn->stream; struct nn_packet *packet = conn->packet; conn->packet = NULL; + if (!packet || conn->errored) { conn->handler->free(&conn->handler); cch_entry_return_handle(node->entry, &conn->handle); } + if (!packet) { // Connection was closed before init was done. - nn_packet_stream_free(conn->stream); - al_free(conn->stream); + if (should_free_node(node)) { + free_node(node); + } + nn_packet_stream_free(stream); + al_free(stream); al_free(conn); - return; - } - struct nn_packet_stream *stream = conn->stream; - if (!conn->errored) { - conn->id = get_incremental_id(server); - nn_packet_pool_init(&conn->pool, 1024, server->loop, packet_pool_callback, conn); - al_array_push(node->connections, conn); - handle_connection(conn, packet); - } - nn_packet_stream_return_packet(stream, packet); - // conn->errored will not have changed but we want to return the packet before disconnecting. - if (conn->errored) { + } else if (conn->errored) { + // We must return the packet before disconnecting. + nn_packet_stream_return_packet(stream, packet); al_free(conn); conn = NULL; demote_and_disconnect_stream(server, stream); + } else { + conn->id = get_incremental_id(server); + al_array_push(node->connections, conn); + nn_packet_pool_init(&conn->pool, 1024, server->loop, packet_pool_callback, conn); + handle_connection(conn, packet); } } @@ -302,9 +322,7 @@ static struct lia_node_connection *get_connection_from_id(struct lia_node *node, { struct lia_node_connection *conn; al_array_foreach(node->connections, i, conn) { - if (conn->id == id) { - return conn; - } + if (conn->id == id) return conn; } return NULL; } @@ -329,6 +347,10 @@ static void packet_callback(void *userdata, struct nn_packet_stream *stream, str u32 connection_id = nn_packet_read_u32(packet); struct lia_node *node = get_node_from_id(server, node_id); + if (!node) { + goto err; + } + struct lia_node_connection *conn = NULL; if (connection_id == 0) { conn = al_alloc_object(struct lia_node_connection); @@ -346,6 +368,7 @@ static void packet_callback(void *userdata, struct nn_packet_stream *stream, str conn->seek_pos = LIANA_TIMESTAMP_INVALID; stream->packet_callback = discard_packet_callback; stream->connection_closed_callback = pre_init_connection_closed_callback; + al_array_push(node->requests, conn); nn_thread_create(&conn->thread, init_thread, conn); } else { if ((conn = get_connection_from_id(node, connection_id))) { @@ -363,13 +386,16 @@ static void packet_callback(void *userdata, struct nn_packet_stream *stream, str al_assert(conn->node == node); conn->stream = stream; handle_connection(conn, packet); - } - nn_packet_stream_return_packet(stream, packet); - // Return packet before possibly disconnecting. - if (!conn) { - nn_packet_stream_disconnect(stream); + } else { + goto err; } } + + return; +err: + // Return packet before disconnecting. + nn_packet_stream_return_packet(stream, packet); + nn_packet_stream_disconnect(stream); } static bool connection_callback(void *userdata, struct nn_packet_stream *stream) @@ -393,8 +419,10 @@ struct lia_node *lia_server_create_node(struct lia_server *server, struct cch_en struct lia_node *node = al_alloc_object(struct lia_node); node->id = get_incremental_id(server); node->entry = entry; + al_array_init(node->requests); al_array_init(node->connections); node->closed = false; + node->duration = LIANA_TIMESTAMP_INVALID; node->server = server; al_array_push(server->nodes, node); return node; @@ -405,7 +433,6 @@ static nn_thread_result NNWT_THREADCALL init_duration_thread(void *userdata) struct lia_node *node = (struct lia_node *)userdata; if (!node->handler->init(node->handler, &node->handle)) { node->errored = true; - node->duration = LIANA_TIMESTAMP_INVALID; } else { node->duration = node->handler->get_duration(node->handler); } @@ -420,7 +447,11 @@ static void duration_signal_callback(void *userdata) nn_thread_join(&node->thread); node->handler->free(&node->handler); cch_entry_return_handle(node->entry, &node->handle); - node->callback(node->userdata, LIANA_NODE_DURATION, node->duration); + if (should_free_node(node)) { + free_node(node); + } else { + node->callback(node->userdata, LIANA_NODE_DURATION, node->duration); + } } void lia_node_get_duration(struct lia_node *node) @@ -437,10 +468,13 @@ void lia_node_get_duration(struct lia_node *node) void lia_node_close(struct lia_node *node) { node->closed = true; - if (!node->connections.count) { - cch_entry_free(&node->entry); + if (should_free_node(node)) { + free_node(node); } else { struct lia_node_connection *conn; + al_array_foreach_rev(node->requests, i, conn) { + nn_packet_stream_disconnect(conn->stream); + } al_array_foreach_rev(node->connections, i, conn) { if (conn->stream) { conn->disconnected = true; @@ -464,6 +498,8 @@ void lia_server_free(struct lia_server *server) { struct lia_node *node; al_array_foreach(server->nodes, i, node) { + al_assert(!node->requests.count); + al_array_free(node->requests); al_assert(!node->connections.count); al_array_free(node->connections); al_free(node); diff --git a/src/liana/server.h b/src/liana/server.h index 7d0b561..364008e 100644 --- a/src/liana/server.h +++ b/src/liana/server.h @@ -46,6 +46,7 @@ enum { struct lia_node { u32 id; struct cch_entry *entry; + array(struct lia_node_connection *) requests; array(struct lia_node_connection *) connections; bool closed; u64 duration; diff --git a/src/liana/vcr.c b/src/liana/vcr.c index c6f6d93..70fb7b6 100644 --- a/src/liana/vcr.c +++ b/src/liana/vcr.c @@ -3,7 +3,7 @@ #include "vcr.h" #include "handler.h" -#define VCR_BUFFER_BUFFERED MB(6) +#define VCR_BUFFER_BUFFERED MB(24) enum { VCR_EXPAND_UNTOUCHED = 0, @@ -18,7 +18,7 @@ enum { }; #define VCR_TRACK_THREADED(track) \ - (track->stream.type == CAMU_STREAM_AUDIO || track->stream.type == CAMU_STREAM_VIDEO) + (track->stream->type == CAMU_STREAM_AUDIO || track->stream->type == CAMU_STREAM_VIDEO) #ifndef CAMU_DIRECT_MODE static void signal_callback(void *userdata) @@ -30,8 +30,8 @@ static void signal_callback(void *userdata) static void reset_metrics(struct lia_vcr *vcr) { - vcr->metric.current_frame = 0Lu; - vcr->metric.last_report_ts = 0Lu; + vcr->metric.current_frame = 0; + vcr->metric.last_report_ts = 0; } void lia_vcr_init(struct lia_vcr *vcr, struct nn_event_loop *loop, struct nn_packet_stream *data) @@ -198,7 +198,7 @@ static struct lia_vcr_track *get_track_from_index(struct lia_vcr *vcr, s32 index { struct lia_vcr_track *track; al_array_foreach(vcr->tracks, i, track) { - if (track->index == index) return track; + if (track->stream->index == index) return track; } return NULL; } @@ -238,17 +238,17 @@ static void update_metrics(struct lia_vcr *vcr, u32 size) return; } u64 diff; - if ((diff = now - vcr->metric.last_report_ts) > 1000000Lu) { + if ((diff = now - vcr->metric.last_report_ts) > 1000000) { vcr->metric.last_report_ts = now; u64 frame = vcr->metric.current_frame; - vcr->metric.current_frame = 0Lu; - if (diff > 2500000Lu) { - // We are buffering fast enough for it to not matter. - al_log_debug("vcr", "Ignoring %llu bytes in metrics.", frame); + vcr->metric.current_frame = 0; + if (diff > 3000000) { return; } f32 kbps = (frame / 125.f) / (diff / 1000000.f); - al_log_info("vcr", "Receiving packets at %.2fkbps.", kbps); + f32 size = vcr->mark.buffered / (f32)MB(1); + f32 buffered = al_atomic_load(u64)(&vcr->count, AL_ATOMIC_RELAXED) / (f32)MB(1); + al_log_info("vcr", "Receiving packets at %.2fkbps (%.2f/%.2fMB).", kbps, buffered, size); } } @@ -262,7 +262,7 @@ void lia_vcr_push_packet(struct lia_vcr *vcr, struct nn_packet *packet) update_metrics(vcr, size); track = get_track_from_index(vcr, nn_packet_read_s32(packet)); if (!track) { - al_log_warn("liana", "Received data from errored or unknown track."); + al_log_debug("liana", "Received data from errored or unknown track."); break; } if (VCR_TRACK_THREADED(track)) { @@ -395,11 +395,6 @@ void lia_vcr_free(struct lia_vcr *vcr) al_array_foreach(vcr->tracks, i, track) { nn_packet_cache_free(&track->cache); track->client->free(&track->client); -#ifdef CAMU_HAVE_FFMPEG - if (track->stream.mode == CAMU_FFMPEG_COMPAT) { - avformat_free_context(track->stream.av.format_context); - } -#endif al_free(track); } al_array_free(vcr->tracks); diff --git a/src/liana/vcr.h b/src/liana/vcr.h index 8330365..94d0c47 100644 --- a/src/liana/vcr.h +++ b/src/liana/vcr.h @@ -11,8 +11,7 @@ //#define VCR_BUFFER_WHOLE_FILE struct lia_vcr_track { - s32 index; - struct camu_codec_stream stream; + struct camu_codec_stream *stream; struct lia_client_handler *client; atomic(s32) state; atomic(bool) buffered; diff --git a/src/libsink/sink.c b/src/libsink/sink.c index 716990e..0eca9f5 100644 --- a/src/libsink/sink.c +++ b/src/libsink/sink.c @@ -12,12 +12,6 @@ //#define CAMU_SINK_ONESHOT //#define CAMU_SINK_TRACE -#ifndef CAMU_SINK_NO_VIDEO -#ifdef CAMU_RENDERER_LIBPLACEBO -#include "../render/renderer_libplacebo.h" -#endif -#endif - // Requested state of the sinks outputs. enum { SINK_EMPTY = 0, @@ -57,7 +51,7 @@ enum { }; // Number of entries to keep buffered at one time. -#define ENTRY_MAX_AGE 7 +#define ENTRY_MAX_AGE 3 // If a buffer is still INIT or QUEUED after the entry is configured, it's "empty". #define BUFFER_EMPTY(buf) ((buf)->state == BUFFER_INIT || (buf)->state == BUFFER_QUEUED) @@ -149,8 +143,7 @@ static inline void add_entry_video_buffer(struct camu_sink_entry *entry) static void remove_entry_audio_buffer(struct camu_sink *sink, struct camu_sink_entry *entry) { - al_assert(!entry->ended && entry->audio.state != BUFFER_ENDED); - al_assert(entry->audio.state != BUFFER_INIT); + al_assert(entry->audio.state != BUFFER_ENDED && entry->audio.state != BUFFER_INIT); if (entry->audio.state == BUFFER_ADDED) { entry->audio.state = BUFFER_SET_OR_BUFFERED; @@ -174,8 +167,7 @@ static void remove_entry_audio_buffer(struct camu_sink *sink, struct camu_sink_e static void remove_entry_video_buffer(struct camu_sink *sink, struct camu_sink_entry *entry) { // Don't assert !entry->ended here because of single frame handling. - al_assert(entry->video.state != BUFFER_ENDED); - al_assert(entry->video.state != BUFFER_INIT); + al_assert(entry->video.state != BUFFER_ENDED && entry->video.state != BUFFER_INIT); if (entry->video.state == BUFFER_ADDED) { entry->video.state = BUFFER_SET_OR_BUFFERED; @@ -216,7 +208,6 @@ static void add_video_if_set_and_buffered(struct camu_sink_entry *entry); static void add_or_queue_entry(struct camu_sink_entry *entry) { - al_assert(!entry->ended); if (entry->audio.state == BUFFER_INIT) { entry->audio.state = BUFFER_QUEUED; } else { @@ -264,12 +255,21 @@ static void sink_local_pause(struct camu_sink *sink, struct camu_sink_entry *ent static inline s32 get_sequence_for_command(struct camu_sink *sink) { + // SEQUENCE_ANY resolves order on the server. s32 sequence = LIANA_SEQUENCE_ANY; +#if 0 + // Setting an explicit sequence makes skip and pause act on "what you see". + // This is likely not the expected behavior in the common case. The user might + // feel like their input was eaten if skipping after a different skip happens + // on the server but is yet to be reflected on their end. if (sink->target) { sequence = sink->target->sequence; } else if (sink->current) { sequence = sink->current->sequence; } +#else + (void)sink; +#endif return sequence; } @@ -504,6 +504,17 @@ static void maybe_remove_previous(struct camu_sink *sink) sink->previous.count = 0; } +static void remove_previous_if_contains(struct camu_sink *sink, struct camu_sink_entry *key) +{ + struct camu_sink_entry *previous; + al_array_foreach(sink->previous, i, previous) { + if (previous == key) { + maybe_remove_previous(sink); + break; + } + } +} + // Due to the looseness of the previous queue, we may have to explicitly remove an entry // if it becomes incorrect to attempt removing it's buffers. // An obvious example of this is at the point an entry gets freed. @@ -526,15 +537,6 @@ static void maybe_add_to_previous(struct camu_sink *sink, struct camu_sink_entry al_assert(previous != target && !previous->ended); - struct camu_sink_entry *entry; - al_array_foreach_rev(sink->previous, i, entry) { - // If the entry we are about to set is in previous, run the queue. - if (entry == target) { - maybe_remove_previous(sink); - break; - } - } - // If neither of the entries audio or video buffer is ADDED, we don't care about adding it // to previous (waiting for the next added entry to remove it). #ifndef CAMU_SINK_NO_VIDEO @@ -614,13 +616,6 @@ void add_audio_if_set_and_buffered(struct camu_sink_entry *entry) } else if (entry->audio.state == BUFFER_SET_OR_BUFFERED) { entry->audio.state = BUFFER_ADDED; -#ifndef CAMU_SINK_NO_VIDEO - if (VIDEO_EMPTY(entry)) { - struct camu_sink *sink = entry->sink; - sink->callback(sink->userdata, CAMU_SINK_REFRESH_VIDEO, CAMU_SINK_VIDEO, NULL); - } -#endif - if (VIDEO_ADDED_OR_EMPTY(entry)) { #ifndef CAMU_SINK_NO_VIDEO bool single_frame = VIDEO_IS_SINGLE_FRAME(entry); @@ -632,7 +627,9 @@ void add_audio_if_set_and_buffered(struct camu_sink_entry *entry) #endif add_entry_audio_buffer(entry); - // This entry could be in previous. This is well defined but confusing. + // This must be called after setting the buffer's state to ADDED because + // this entry might be in previous. It's confusing but well defined, + // although could probably be avoided by a greater simplification. maybe_remove_previous(entry->sink); #ifndef CAMU_SINK_NO_VIDEO @@ -727,6 +724,7 @@ static void switch_to(struct camu_sink *sink, struct camu_sink_entry *target) } if (!target->ended) { + remove_previous_if_contains(sink, target); add_or_queue_entry(target); } else { #ifndef CAMU_SINK_NO_VIDEO @@ -734,17 +732,12 @@ static void switch_to(struct camu_sink *sink, struct camu_sink_entry *target) add_video_if_set_and_buffered(target); } else { // @TODO: current-less - sink->callback(sink->userdata, CAMU_SINK_REFRESH_VIDEO, CAMU_SINK_VIDEO, NULL); queue_cmd(sink, (struct camu_sink_cmd){ .op = STOP, .value.i = CAMU_SINK_VIDEO }); } #endif - queue_cmd(sink, (struct camu_sink_cmd){ - .op = STOP, - .value.i = CAMU_SINK_AUDIO - }); } sink->current = target; @@ -777,12 +770,12 @@ static bool end_entry_and_advance_queue(struct camu_sink *sink, struct camu_sink return false; #endif entry->ended = true; + maybe_remove_from_previous(sink, entry); queue_cmd(sink, (struct camu_sink_cmd){ .op = END, .value.u = entry->reset_id, .opaque = entry }); - maybe_remove_from_previous(sink, entry); #ifdef LIANA_LIST_SCUFFED_LOOP al_log_info("sink", "Looping."); return true; @@ -818,8 +811,8 @@ static void audio_buffer_callback(void *userdata, u8 op) break; case CAMU_BUFFER_PAUSED: nn_mutex_lock(&sink->mutex); - al_log_info("sink", "Audio buffer paused."); - if (!entry->audio.ignore_paused) { + if (!entry->audio.ignore_paused && entry->buffers_paused) { + al_log_info("sink", "Audio buffer paused."); queue_cmd(sink, (struct camu_sink_cmd){ .op = STOP, .value.i = CAMU_SINK_AUDIO @@ -920,9 +913,18 @@ static void clock_callback(void *userdata, u8 op) al_log_info("sink", "clock_callback(), target: 0x%llx", sink->target); #endif nn_mutex_lock(&sink->mutex); - if (entry == sink->current && sink->target) { - switch_to(sink, sink->target); - sink->target = NULL; + if (entry == sink->current) { + if (sink->target) { + switch_to(sink, sink->target); + sink->target = NULL; + } else if (entry->buffers_paused) { +#ifndef CAMU_SINK_NO_VIDEO + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = STOP, + .value.i = CAMU_SINK_VIDEO + }); +#endif + } } nn_mutex_unlock(&sink->mutex); } @@ -1023,21 +1025,13 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str nn_mutex_unlock(&sink->mutex); break; case CAMU_STREAM_SUBTITLE: -#ifdef CAMU_HAVE_FFMPEG - if (!camu_video_buffer_configure_subtitles(&entry->video.buf, stream->av.stream->codecpar)) { + if (!camu_video_buffer_configure_subtitles(&entry->video.buf, stream)) { al_log_warn("sink", "Video buffer failed to configure subtitles."); } -#endif break; case CAMU_STREAM_ATTACHMENT: { -#ifdef CAMU_HAVE_FFMPEG -#ifdef CAMU_RENDERER_LIBPLACEBO - struct camu_renderer_lp *lp = (struct camu_renderer_lp *)sink->video.renderer; - AVDictionaryEntry *entry = av_dict_get(stream->av.stream->metadata, "filename", NULL, 0); - AVCodecParameters *codecpar = stream->av.stream->codecpar; - ass_add_font(lp->ass, entry->value, (const char *)codecpar->extradata, codecpar->extradata_size); -#endif -#endif + struct camu_renderer *renderer = sink->video.renderer; + renderer->add_font(renderer, stream); break; } #endif @@ -1074,20 +1068,17 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str camu_codec_frame_discard(frame); break; } +#ifndef CAMU_SINK_NO_VIDEO case LIANA_CLIENT_SUBTITLE: { switch (stream->type) { case CAMU_STREAM_SUBTITLE: { -#ifndef CAMU_SINK_NO_VIDEO -#ifdef CAMU_HAVE_FFMPEG - AVPacket *pkt = (AVPacket *)opaque; - camu_video_buffer_push_subtitle(&entry->video.buf, pkt); -#endif -#endif + camu_video_buffer_push_subtitle(&entry->video.buf, (struct camu_codec_packet *)opaque); break; } } break; } +#endif case LIANA_CLIENT_REMOVE_BUFFERS: { bool reconnect = *(bool *)opaque; @@ -1106,14 +1097,7 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str // Entry might be in previous here if it was added to previous then, // - it's being cleaned up after ENTRY_MAX_AGE - 1 entries were added but none buffered. // - it was seeked. - //maybe_remove_from_previous(sink, entry); - struct camu_sink_entry *previous; - al_array_foreach(sink->previous, i, previous) { - if (previous == entry) { - maybe_remove_previous(sink); - break; - } - } + remove_previous_if_contains(sink, entry); bool skip_audio = sink->audio.state == SINK_PAUSED; if (entry->audio.state == BUFFER_ADDED) { @@ -1148,8 +1132,16 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str // some point after unlocking to block above. nn_mutex_lock(&sink->mutex); - if (reconnect) { + if (reconnect && entry->ended) { entry->ended = false; + if (entry->audio.state == BUFFER_QUEUED) { + entry->audio.state = BUFFER_INIT; + } +#ifndef CAMU_SINK_NO_VIDEO + if (entry->video.state == BUFFER_QUEUED) { + entry->video.state = BUFFER_INIT; + } +#endif } // SET_OR_BUFFERED, ADDED, or ENDED. @@ -1202,10 +1194,16 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str al_assert(entry == sink->current); if (entry->audio.state > BUFFER_QUEUED) { add_audio_if_set_and_buffered(entry); + } else { + entry->audio.state = BUFFER_QUEUED; } #ifndef CAMU_SINK_NO_VIDEO - if (entry->video.state > BUFFER_QUEUED && !VIDEO_IS_SINGLE_FRAME(entry)) { - add_video_if_set_and_buffered(entry); + if (entry->video.state > BUFFER_QUEUED) { + if (!VIDEO_IS_SINGLE_FRAME(entry)) { + add_video_if_set_and_buffered(entry); + } + } else { + entry->video.state = BUFFER_QUEUED; } #endif sink->reconnecting = NULL; @@ -1237,6 +1235,9 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str // LIANA_CLIENT_REMOVE_BUFFERS has been called on this entry before we're here. nn_mutex_lock(&sink->mutex); + bool removed; + al_array_remove_checked(sink->entries, entry, removed); + if (entry == sink->target) { // @TODO: current-less // current could be paused and targeting this entry. @@ -1254,15 +1255,14 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str } else { // @TODO: current-less sink->current = NULL; - sink->callback(sink->userdata, CAMU_SINK_REFRESH_VIDEO, CAMU_SINK_VIDEO, NULL); - queue_cmd(entry->sink, (struct camu_sink_cmd){ - .op = STOP, - .value.i = CAMU_SINK_VIDEO - }); - queue_cmd(entry->sink, (struct camu_sink_cmd){ - .op = STOP, - .value.i = CAMU_SINK_AUDIO - }); +#ifndef CAMU_SINK_NO_VIDEO + if (removed) { // Don't stop video on exit. + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = STOP, + .value.i = CAMU_SINK_VIDEO + }); + } +#endif } // If current was never fully added we need to call this here. maybe_remove_previous(sink); @@ -1270,9 +1270,6 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str sink->queued = NULL; } - bool removed; - al_array_remove_checked(sink->entries, entry, removed); - nn_mutex_unlock(&sink->mutex); lia_client_free(&entry->client); @@ -1280,7 +1277,7 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str #ifndef CAMU_SINK_NO_VIDEO camu_video_buffer_free(&entry->video.buf); #endif - al_log_info("sink", "Entry (0x%llx) closed by %s.", entry, removed ? "disconnect" : "cleanup"); + al_log_info("sink", "Entry (0x%llx) closed by %s.", entry, removed ? "cleanup" : "exit"); al_free(entry); break; @@ -1313,6 +1310,7 @@ static struct camu_sink_entry *create_entry(struct camu_sink *sink, u32 id) entry->client.callback = client_callback; entry->client.userdata = entry; + entry->client.prefs = sink->prefs; al_array_push(sink->entries, entry); @@ -1523,12 +1521,8 @@ static bool pause_command_callback(void *userdata, struct nn_rpc_connection *con case LIANA_PAUSE_PAUSE: entry->buffers_paused = true; camu_clock_pause(&entry->clock, at); - if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry)) { - queue_cmd(entry->sink, (struct camu_sink_cmd){ - .op = STOP, - .value.i = CAMU_SINK_VIDEO - }); - } + // Audio will be stopped in a BUFFER_PAUSED callback and video + // will be stopped in clock_callback(). break; case LIANA_PAUSE_RESUME: entry->buffers_paused = false; @@ -1540,12 +1534,14 @@ static bool pause_command_callback(void *userdata, struct nn_rpc_connection *con .value.i = CAMU_SINK_AUDIO }); } +#ifndef CAMU_SINK_NO_VIDEO if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry)) { queue_cmd(entry->sink, (struct camu_sink_cmd){ .op = START, .value.i = CAMU_SINK_VIDEO }); } +#endif break; } #endif @@ -1632,6 +1628,8 @@ bool camu_sink_connect(struct camu_sink *sink, u8 type, str *addr, u16 port, str { al_str_clone(&sink->name, name); sink->type = type; + al_str_clone(&sink->addr, addr); + sink->port = port; nn_timer_init(&sink->periodic_timer, sink->loop, periodic_timer_callback, sink); nn_timer_set_repeat(&sink->periodic_timer, NNWT_TS_FROM_USEC(1000000)); nn_rpc_init(&sink->client, sink->loop, connection_callback, connection_closed_callback, sink); @@ -1641,8 +1639,8 @@ bool camu_sink_connect(struct camu_sink *sink, u8 type, str *addr, u16 port, str nn_rpc_add_command(&sink->client, &commands[i]); } nn_rpc_prepare_client(&sink->client); - al_str_clone(&sink->addr, addr); - sink->port = port; + // Directly set conn here to handle never connected case. + sink->conn = sink->client.conn; #ifdef CAMU_DIRECT_MODE struct nn_rpc_connection *conn = sink->client.conn; nn_multiplex_direct_connect(conn->stream, CAMU_MULTIPLEX_RPC); @@ -1722,11 +1720,57 @@ void camu_sink_unset(struct camu_sink *sink) }); } -void camu_sink_offset_volume(struct camu_sink *sink, f64 amount) +void camu_sink_set_volume(struct camu_sink *sink, f32 volume) +{ + camu_mixer_set_volume(sink->audio.mixer, volume); +} + +void camu_sink_offset_volume(struct camu_sink *sink, f32 amount) { camu_mixer_offset_volume(sink->audio.mixer, amount); } +static char status[128]; + +void camu_sink_status(struct camu_sink *sink) +{ + nn_mutex_lock(&sink->mutex); + struct camu_sink_entry *current = sink->current; + nn_mutex_unlock(&sink->mutex); + if (!current) { + al_log_info("sink", "Nothing playing."); + return; + } + f64 pts = camu_clock_get_pts(¤t->clock, 0.0, false); + if (pts == -1.0) { +#ifndef CAMU_SINK_NO_VIDEO + pts = al_atomic_load(f64)(¤t->video.buf.pts, AL_ATOMIC_RELAXED); +#endif + } + f64 duration = current->client.duration / 1000000.0; + s32 text = 0; + u32 minute = (u32)(pts / 60); + u32 hour = minute / 60; + minute -= hour * 60; + if (camu_clock_is_paused(¤t->clock)) { + text += al_snprintf(status + text, sizeof(status) - text, "⏸ "); + } else { + text += al_snprintf(status + text, sizeof(status) - text, "⏵ "); + } + text += al_snprintf(status + text, sizeof(status) - text, "["); + bool show_hour = duration >= 60.0 * 60.0; + if (show_hour) text += al_snprintf(status + text, sizeof(status) - text, "%.2d:", hour); + text += al_snprintf(status + text, sizeof(status) - text, "%.2d:%.2d/", minute, (u32)pts % 60); + minute = (u32)(duration / 60); + hour = minute / 60; + minute -= hour * 60; + if (show_hour) text += al_snprintf(status + text, sizeof(status) - text, "%.2d:", hour); + text += al_snprintf(status + text, sizeof(status) - text, "%.2d:%.2d", minute, (u32)duration % 60); + text += al_snprintf(status + text, sizeof(status) - text, "]"); + status[text] = '\0'; + al_log_info("sink", "%s", status); +} + void camu_sink_stop(struct camu_sink *sink) { queue_cmd(sink, (struct camu_sink_cmd){ diff --git a/src/libsink/sink.h b/src/libsink/sink.h index 9bb408d..86c6a04 100644 --- a/src/libsink/sink.h +++ b/src/libsink/sink.h @@ -100,6 +100,7 @@ struct camu_sink { struct camu_renderer *renderer; } video; #endif + struct lia_prefs prefs; struct lia_server *local_server; u8 (*callback)(void *, u8, u8, void *); void *userdata; @@ -120,7 +121,9 @@ void camu_sink_toggle_pause(struct camu_sink *sink); void camu_sink_seek(struct camu_sink *sink, f64 pos); void camu_sink_reseek(struct camu_sink *sink); void camu_sink_unset(struct camu_sink *sink); -void camu_sink_offset_volume(struct camu_sink *sink, f64 amount); +void camu_sink_set_volume(struct camu_sink *sink, f32 volume); +void camu_sink_offset_volume(struct camu_sink *sink, f32 amount); +void camu_sink_status(struct camu_sink *sink); void camu_sink_stop(struct camu_sink *sink); void camu_sink_close(struct camu_sink *sink); void camu_sink_free(struct camu_sink *sink); diff --git a/src/mixer/audio_miniaudio.c b/src/mixer/audio_miniaudio.c index 13f4b8f..30a5b6b 100644 --- a/src/mixer/audio_miniaudio.c +++ b/src/mixer/audio_miniaudio.c @@ -91,10 +91,10 @@ static bool audio_miniaudio_init(struct camu_audio *audio, str *name) return false; } ma->selected_device = NULL; + /* if (ma_context_get_devices(&ma->context, &ma->infos, &ma->info_count, NULL, NULL) != MA_SUCCESS) { return false; } - /* for (u32 i = 0; i < ma->info_count; i++) { ma_device_info *info = &ma->infos[i]; if (strcmp(info->id.alsa, "front:CARD=D10,DEV=0") == 0) { @@ -154,8 +154,15 @@ static void data_callback(ma_device *device, void *output, const void *input, u3 ma->a.data_callback(ma->a.userdata, output, frame_count, &silence); } +//#define MINIAUDIO_LOW_LATENCY + +#ifdef MINIAUDIO_LOW_LATENCY +#define DEFAULT_PERIODS 2 +#define DEFAULT_PERIOD_SIZE_IN_MILLISECONDS 8 +#else #define DEFAULT_PERIODS 3 #define DEFAULT_PERIOD_SIZE_IN_MILLISECONDS 48 +#endif static bool audio_miniaudio_configure_stream(struct camu_audio *audio, void *opaque) { @@ -166,9 +173,14 @@ static bool audio_miniaudio_configure_stream(struct camu_audio *audio, void *opa if (ma->selected_device) { ma->config.playback.pDeviceID = &ma->selected_device->id; } +#ifdef MINIAUDIO_LOW_LATENCY + ma->config.performanceProfile = ma_performance_profile_low_latency; +#else ma->config.performanceProfile = ma_performance_profile_conservative; +#endif ma->config.periods = DEFAULT_PERIODS; ma->config.periodSizeInMilliseconds = DEFAULT_PERIOD_SIZE_IN_MILLISECONDS; + ma->config.periodSizeInFrames = 0; // Derive from periodSizeInMilliseconds. ma->config.playback.format = ma_format_unknown; ma->config.playback.channels = 2; ma->config.sampleRate = 0; diff --git a/src/mixer/mixer.c b/src/mixer/mixer.c index aad0877..079b582 100644 --- a/src/mixer/mixer.c +++ b/src/mixer/mixer.c @@ -3,7 +3,7 @@ #include "mixer.h" #include "audio.h" -// Should an initial read inside of start() be marked as silence or not. +// Should an initial read from within start() be marked as silence or not. #define MIXER_WANT_INITIAL_SILENCE 1 // Number of silent frames to append before signaling MIXER_EMPTY. @@ -14,7 +14,7 @@ static s32 data_callback(void *userdata, u8 *data, s32 frame_count, bool *silenc struct camu_mixer *mixer = (struct camu_mixer *)userdata; ptrdiff_t req = (ptrdiff_t)camu_audio_format_samples_to_bytes(&mixer->fmt.req, (size_t)frame_count); #ifdef CAMU_MIXER_THREADED_START_STOP - if (UNLIKELY(mixer->paused)) { + if (UNLIKELY(al_atomic_load(bool)(&mixer->paused, AL_ATOMIC_SEQ_CST))) { al_memset(data, 0, req); *silence = MIXER_WANT_INITIAL_SILENCE; } else { @@ -73,7 +73,7 @@ bool camu_mixer_init(struct camu_mixer *mixer, struct camu_audio *audio) mixer->audio->userdata = mixer; mixer->audio->init(mixer->audio, &al_str_c("cmv")); mixer->volume = 1.f; - mixer->paused = true; + al_atomic_store(bool)(&mixer->paused, true, AL_ATOMIC_RELAXED); mixer->empty_after = 0; al_array_init(mixer->buffers); #ifdef CAMU_MIXER_THREADED @@ -233,7 +233,7 @@ void camu_mixer_remove_buffer(struct camu_mixer *mixer, struct camu_audio_buffer } al_array_push(mixer->rem_queue, buf); al_atomic_store(u8)(&mixer->queued, 1, AL_ATOMIC_RELAXED); - if (mixer->paused) { + if (al_atomic_load(bool)(&mixer->paused, AL_ATOMIC_RELAXED)) { run_queue_internal(mixer); } nn_mutex_unlock(&mixer->mutex); @@ -267,9 +267,9 @@ void camu_mixer_pause(struct camu_mixer *mixer) #ifdef CAMU_MIXER_THREADED_START_STOP nn_mutex_lock(&mixer->mutex); #endif - if (!mixer->paused) { + if (!al_atomic_load(bool)(&mixer->paused, AL_ATOMIC_ACQUIRE)) { mixer->audio->stop(mixer->audio); - mixer->paused = true; + al_atomic_store(bool)(&mixer->paused, true, AL_ATOMIC_RELEASE); } #ifdef CAMU_MIXER_THREADED // This assumes stop() blocks until the output actually stops. @@ -286,13 +286,13 @@ void camu_mixer_resume(struct camu_mixer *mixer) #ifdef CAMU_MIXER_THREADED_START_STOP nn_mutex_lock(&mixer->mutex); #endif - if (mixer->paused) { - // start() can internally call data_callback once before returning. - // In that call mixer->paused will still be true. So, if MIXER_THREADED_START_STOP - // is defined, we have a special case to immediately return silence to avoid a deadlock. - // Outputs can treat that silence as part of the stream with MIXER_WANT_INITIAL_SILENCE. + if (al_atomic_load(bool)(&mixer->paused, AL_ATOMIC_ACQUIRE)) { + // audio->start() can internally call data_callback() or possibly start a thread which + // can call data_callback() before we have a chance to set paused to false. + // MIXER_THREADED_START_STOP: If paused is true in data_callback(), immediately return silence. + // This will avoid a deadlock in the first case described above. mixer->audio->start(mixer->audio); - mixer->paused = false; + al_atomic_store(bool)(&mixer->paused, false, AL_ATOMIC_RELEASE); } #ifdef CAMU_MIXER_THREADED_START_STOP nn_mutex_unlock(&mixer->mutex); diff --git a/src/mixer/mixer.h b/src/mixer/mixer.h index 802d1b5..8c14ea1 100644 --- a/src/mixer/mixer.h +++ b/src/mixer/mixer.h @@ -2,8 +2,8 @@ #include <al/types.h> #include <al/array.h> -#ifdef CAMU_MIXER_THREADED #include <al/atomic.h> +#ifdef CAMU_MIXER_THREADED #include <nnwt/thread.h> #endif @@ -25,7 +25,7 @@ struct camu_mixer { struct camu_audio *audio; f32 volume; struct camu_resampler_format fmt; - bool paused; + atomic(bool) paused; u8 empty_after; array(struct camu_audio_buffer *) buffers; #ifdef CAMU_MIXER_THREADED diff --git a/src/portal/src/search.c b/src/portal/src/search.c index 2584878..ab14d0e 100644 --- a/src/portal/src/search.c +++ b/src/portal/src/search.c @@ -61,8 +61,10 @@ static nn_thread_result NNWT_THREADCALL queue_thread(void *userdata) { nn_thread_setcanceltype(NNWT_THREAD_CANCEL_ASYNCHRONOUS); struct camu_portal_bridge *bridge = (struct camu_portal_bridge *)userdata; + bool have_python = false; nn_mutex_lock(&bridge->mutex); + for (;;) { nn_cond_wait(&bridge->cond, &bridge->mutex); if (bridge->quit) break; @@ -83,19 +85,19 @@ static nn_thread_result NNWT_THREADCALL queue_thread(void *userdata) result.userdata = cmd->userdata; switch (cmd->op) { case CAMU_CLIENT_CREATE_SEARCH: { - s32 id = portal_bridge_search(&cmd->module, &cmd->query); - if (id >= 0) { + result.id = portal_bridge_search(&cmd->module, &cmd->query); + if (result.id >= 0) { struct camu_search *search = al_alloc_object(struct camu_search); - search->id = id; - result.id = search->id; + search->id = result.id; al_str_clone(&search->module, &cmd->module); al_str_clone(&search->query, &cmd->query); search->page = 0; al_array_init(search->pages); search->bridge = bridge; al_array_push(bridge->searches, search); - al_log_info("portal", "New search %x (%.*s).", id, al_str_fmt(&cmd->query)); + al_log_info("portal", "New search %x (%.*s).", result.id, al_str_fmt(&cmd->query)); } else { + al_log_info("portal", "Failed to create search (%.*s).", al_str_fmt(&cmd->query)); } al_str_free(&cmd->module); al_str_free(&cmd->query); @@ -123,6 +125,9 @@ static nn_thread_result NNWT_THREADCALL queue_thread(void *userdata) camu_post_cache_push(bridge->cache, post, false); } } + } else { + al_log_info("portal", "Requested search doesn't exist (id: %d).", search->id); + result.id = -1; } break; } @@ -132,8 +137,23 @@ static nn_thread_result NNWT_THREADCALL queue_thread(void *userdata) bridge->queue.count = 0; nn_signal_send(&bridge->results_signal); } + + // Return error for all remaining requests. + struct camu_portal_cmd *cmd; + al_array_foreach_ptr(bridge->queue, i, cmd) { + struct camu_portal_result result = { 0 }; + result.op = cmd->op; + result.callback = cmd->callback; + result.userdata = cmd->userdata; + result.id = -1; + camu_queue_push(bridge->results, result); + } + bridge->queue.count = 0; + nn_signal_send(&bridge->results_signal); + nn_mutex_unlock(&bridge->mutex); if (have_python) camu_python_close(); + return 0; } diff --git a/src/render/meson.build b/src/render/meson.build index 75cde7c..f0fd2fd 100644 --- a/src/render/meson.build +++ b/src/render/meson.build @@ -16,13 +16,12 @@ elif get_option('renderer') == 'libplacebo' 'glslang=disabled', 'gl-proc-addr=disabled', 'vk-proc-addr=disabled', - 'd3d11=disabled', 'lcms=disabled', 'dovi=disabled', 'libdovi=disabled' ] - if get_option('sink-use-vulkan') + if get_option('sink-use-vulkan') or get_option('sink-use-dx11') shaderc = dependency('shaderc', required: false, allow_fallback: false) if shaderc.found() render_deps += [shaderc] @@ -48,9 +47,57 @@ elif get_option('renderer') == 'libplacebo' shaderc_proj.dependency('SPIRV-Tools-static') ] endif - libplacebo_opts += ['shaderc=enabled', 'vulkan=enabled', 'opengl=disabled'] + #glslang = dependency('glslang', required: false, allow_fallback: false) + #if glslang.found() + # render_deps += [glslang] + #else + # glslang_opts = cmake.subproject_options() + # reltype = is_minsize ? 'MinSizeRel' : 'Release' + # glslang_opts.add_cmake_defines({ 'CMAKE_BUILD_TYPE': is_debug ? 'Debug' : reltype }) + # glslang_opts.add_cmake_defines({ 'ENABLE_OPT': '0' }) + # glslang_proj = cmake.subproject('glslang', options: glslang_opts) + # message('CMake targets:\n - ' + '\n - '.join(glslang_proj.target_list())) + # glslang = glslang_proj.dependency('glslang') + # spirv = glslang_proj.dependency('SPIRV') + # glslang_default_resource_limits = glslang_proj.dependency('glslang-default-resource-limits') + # meson.override_dependency('glslang', glslang) + # meson.override_dependency('SPIRV', spirv) + # meson.override_dependency('glslang-default-resource-limits', glslang_default_resource_limits) + # render_deps += [ + # glslang_proj.dependency('MachineIndependent'), + # glslang_proj.dependency('OSDependent'), + # glslang_proj.dependency('GenericCodeGen'), + # glslang, + # spirv, + # glslang_default_resource_limits + # ] + #endif + endif + + if get_option('sink-use-vulkan') + libplacebo_opts += ['shaderc=enabled', 'vulkan=enabled', 'd3d11=disabled', 'opengl=disabled'] + elif get_option('sink-use-dx11') + spirv_cross_opts = cmake.subproject_options() + reltype = is_minsize ? 'MinSizeRel' : 'Release' + spirv_cross_opts.add_cmake_defines({ 'CMAKE_BUILD_TYPE': is_debug ? 'Debug' : reltype }) + spirv_cross_opts.add_cmake_defines({ 'SPIRV_CROSS_CLI': false }) + spirv_cross_opts.add_cmake_defines({ 'SPIRV_CROSS_ENABLE_TESTS': false }) + spirv_cross_opts.add_cmake_defines({ 'SPIRV_CROSS_ENABLE_MSL': false }) + spirv_cross_opts.add_cmake_defines({ 'SPIRV_CROSS_ENABLE_CPP': false }) + spirv_cross_opts.add_cmake_defines({ 'SPIRV_CROSS_ENABLE_REFLECT': false }) + spirv_cross_opts.add_cmake_defines({ 'SPIRV_CROSS_ENABLE_UTIL': false }) + spirv_cross_proj = cmake.subproject('SPIRV-Cross', options: spirv_cross_opts) + spirv_cross_c_shared = spirv_cross_proj.dependency('spirv-cross-c') + render_deps += [ + spirv_cross_proj.dependency('spirv-cross-glsl'), + spirv_cross_proj.dependency('spirv-cross-hlsl'), + spirv_cross_proj.dependency('spirv-cross-core'), + spirv_cross_c_shared + ] + meson.override_dependency('spirv-cross-c-shared', spirv_cross_c_shared) + libplacebo_opts += ['shaderc=enabled', 'd3d11=enabled', 'vulkan=enabled', 'opengl=disabled'] else - libplacebo_opts += ['shaderc=disabled', 'opengl=enabled', 'vulkan=disabled'] + libplacebo_opts += ['shaderc=disabled', 'opengl=enabled', 'vulkan=disabled', 'd3d11=disabled'] endif libplacebo = dependency('libplacebo', default_options: libplacebo_opts) diff --git a/src/render/queue_libplacebo.c b/src/render/queue_libplacebo.c index 165fc4b..b6a7477 100644 --- a/src/render/queue_libplacebo.c +++ b/src/render/queue_libplacebo.c @@ -10,8 +10,8 @@ #include "queue_libplacebo.h" -#ifdef CAMU_HAVE_FFMPEG -static bool queue_lp_configure_subtitiles(struct camu_frame_queue *queue, u32 width, u32 height, AVCodecParameters *codecpar) +static bool queue_lp_configure_subtitiles(struct camu_frame_queue *queue, u32 width, u32 height, + struct camu_codec_stream *stream) { struct camu_frame_queue_lp *lq = (struct camu_frame_queue_lp *)queue; if (!lq->ass) return false; @@ -28,12 +28,12 @@ static bool queue_lp_configure_subtitiles(struct camu_frame_queue *queue, u32 wi ass_set_frame_size(lq->ass_renderer, width, height); ass_set_storage_size(lq->ass_renderer, width, height); ass_set_fonts(lq->ass_renderer, NULL, NULL, ASS_FONTPROVIDER_AUTODETECT, NULL, 0); + AVCodecParameters *codecpar = stream->av.stream->codecpar; ass_process_codec_private(lq->ass_track, (const char *)codecpar->extradata, codecpar->extradata_size); lq->have_subtitles = true; return true; } -#endif static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src, struct pl_frame *out_frame) @@ -206,15 +206,38 @@ static bool map_av_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame * struct camu_frame_queue_lp *lq = (struct camu_frame_queue_lp *)frame->opaque; AVStream *stream = lq->q.buf->stream->av.stream; - bool ok = pl_map_avframe_ex(gpu, out_frame, pl_avframe_params( - .frame = frame, - .tex = tex, - .map_dovi = false - )); + bool ok = false; + + if (!lq->copy_frame_fallback) { + ok = pl_map_avframe_ex(gpu, out_frame, pl_avframe_params( + .frame = frame, + .tex = tex + )); + } + + if (!ok) { + if (!lq->copy_frame_fallback) { + al_log_warn("frame_queue_libplacebo", "Falling back to software copy."); + lq->copy_frame_fallback = true; + lq->swframe = av_frame_alloc(); + } + ok = av_hwframe_transfer_data(lq->swframe, frame, 0) == 0; + if (ok) { + av_frame_copy_props(lq->swframe, frame); + } + } + + if (lq->copy_frame_fallback) { + ok = pl_map_avframe_ex(gpu, out_frame, pl_avframe_params( + .frame = lq->swframe, + .tex = tex + )); + av_frame_unref(lq->swframe); + } ((struct pl_source_frame *)src)->frame_data = NULL; - if (lq->have_subtitles) { + if (ok && lq->have_subtitles) { s64 now = av_rescale_q(frame->best_effort_timestamp, stream->time_base, (AVRational){ 1, 1000 }); s32 change; ASS_Image *ass_frame = ass_render_frame(lq->ass_renderer, lq->ass_track, now, &change); @@ -289,14 +312,15 @@ static void queue_lp_push_av_frame(struct camu_frame_queue *queue, AVFrame *fram .first_field = pl_field_from_avframe(frame) }); } +#endif -static void queue_lp_push_subtitle(struct camu_frame_queue *queue, AVPacket *pkt) +static void queue_lp_push_subtitle(struct camu_frame_queue *queue, struct camu_codec_packet *packet) { struct camu_frame_queue_lp *lq = (struct camu_frame_queue_lp *)queue; al_assert(lq->ass_track && lq->ass_renderer); + AVPacket *pkt = packet->av.pkt; ass_process_chunk(lq->ass_track, (const char *)pkt->data, pkt->size, pkt->pts, pkt->duration); } -#endif static void queue_lp_flush(struct camu_frame_queue *queue) { @@ -343,6 +367,9 @@ static void queue_lp_free(struct camu_frame_queue **queue) ass_free_track(lq->ass_track); ass_renderer_done(lq->ass_renderer); } + if (lq->copy_frame_fallback) { + av_frame_free(&lq->swframe); + } pl_queue_destroy(&lq->queue); al_free(lq); *queue = NULL; @@ -353,9 +380,7 @@ struct camu_frame_queue *camu_frame_queue_lp_create(void) struct camu_frame_queue_lp *lq = al_alloc_object(struct camu_frame_queue_lp); al_array_init(lq->overlays); lq->have_subtitles = false; -#ifdef CAMU_HAVE_FFMPEG lq->q.configure_subtitles = queue_lp_configure_subtitiles; -#endif lq->q.push = queue_lp_push; #ifdef CAMU_HAVE_FFMPEG lq->q.push_av_frame = queue_lp_push_av_frame; diff --git a/src/render/queue_libplacebo.h b/src/render/queue_libplacebo.h index 4f5f644..f87a34d 100644 --- a/src/render/queue_libplacebo.h +++ b/src/render/queue_libplacebo.h @@ -18,6 +18,8 @@ struct camu_frame_queue_lp { struct camu_frame_queue q; pl_queue queue; struct pl_queue_params params; + bool copy_frame_fallback; + AVFrame *swframe; array(struct camu_overlay_lp *) overlays; ASS_Library *ass; ASS_Renderer *ass_renderer; diff --git a/src/render/renderer.h b/src/render/renderer.h index 101dda0..a4d3a53 100644 --- a/src/render/renderer.h +++ b/src/render/renderer.h @@ -13,8 +13,12 @@ #endif #elif defined STELA_API_VULKAN #define CAMU_RENDERER_VULKAN +#elif defined STELA_API_DX11 +#define CAMU_RENDERER_DX11 #endif +#include "../codec/codec.h" + struct camu_frame_queue; struct camu_screen; struct camu_renderer { @@ -24,6 +28,8 @@ struct camu_renderer { VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL (*get_vk_proc_address)(VkInstance, const char *), VkResult (*vk_create_surface)(void *, VkInstance, VkSurfaceKHR *), const char *const *(*vk_get_extensions)(u32 *), +#elif defined CAMU_RENDERER_DX11 + HWND win32_window, #elif defined CAMU_RENDERER_OPENGL #ifdef STELA_USE_EGL EGLDisplay display, @@ -38,6 +44,7 @@ struct camu_renderer { #endif void *priv); struct camu_frame_queue *(*create_queue)(struct camu_renderer *); + void (*add_font)(struct camu_renderer *, struct camu_codec_stream *); u32 (*get_latency)(struct camu_renderer *); void (*resize)(struct camu_renderer *, u32 *, u32 *); void (*render)(struct camu_renderer *, struct camu_screen *, bool); diff --git a/src/render/renderer_libplacebo.c b/src/render/renderer_libplacebo.c index 2c632ee..0dcff45 100644 --- a/src/render/renderer_libplacebo.c +++ b/src/render/renderer_libplacebo.c @@ -50,6 +50,8 @@ static bool renderer_lp_create_renderer(struct camu_renderer *renderer, u32 *wid VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL (*get_vk_proc_address)(VkInstance, const char *), VkResult (*vk_create_surface)(void *, VkInstance, VkSurfaceKHR *), const char *const *(*vk_get_extensions)(u32 *), +#elif defined CAMU_RENDERER_DX11 + HWND win32_window, #elif defined CAMU_RENDERER_OPENGL #ifdef STELA_USE_EGL EGLDisplay display, @@ -118,6 +120,28 @@ static bool renderer_lp_create_renderer(struct camu_renderer *renderer, u32 *wid } lr->gpu = lr->vk->gpu; + + al_log_info("render_libplacebo", "Using Vulkan."); +#elif defined CAMU_RENDERER_DX11 + lr->d3d11 = pl_d3d11_create(lr->logger, pl_d3d11_params( + .debug = false + )); + if (!lr->d3d11) { + al_log_error("render_libplacebo", "Failed to create D3D11 device."); + goto err; + } + + lr->swapchain = pl_d3d11_create_swapchain(lr->d3d11, pl_d3d11_swapchain_params( + .window = win32_window, + )); + if (!lr->swapchain) { + al_log_error("render_libplacebo", "Failed to create D3D11 swapchain."); + goto err; + } + + lr->gpu = lr->d3d11->gpu; + + al_log_info("render_libplacebo", "Using DirectX 11."); #elif defined CAMU_RENDERER_OPENGL (void)gl_load_loader; lr->gl = pl_opengl_create(lr->logger, pl_opengl_params( @@ -139,7 +163,7 @@ static bool renderer_lp_create_renderer(struct camu_renderer *renderer, u32 *wid } lr->swapchain = pl_opengl_create_swapchain(lr->gl, pl_opengl_swapchain_params( - .max_swapchain_depth = 2, + //.max_swapchain_depth = 2, .swap_buffers = gl_swap_buffers, .priv = priv )); @@ -149,6 +173,8 @@ static bool renderer_lp_create_renderer(struct camu_renderer *renderer, u32 *wid } lr->gpu = lr->gl->gpu; + + al_log_info("render_libplacebo", "Using OpenGL."); #endif lr->renderer = pl_renderer_create(lr->logger, lr->gpu); @@ -211,6 +237,15 @@ static struct camu_frame_queue *renderer_lp_create_queue(struct camu_renderer *r return queue; } +static void renderer_lp_add_font(struct camu_renderer *renderer, struct camu_codec_stream *attachment) +{ + struct camu_renderer_lp *lr = (struct camu_renderer_lp *)renderer; + AVStream *stream = attachment->av.stream; + AVDictionaryEntry *entry = av_dict_get(stream->metadata, "filename", NULL, 0); + AVCodecParameters *codecpar = stream->codecpar; + ass_add_font(lr->ass, entry->value, (const char *)codecpar->extradata, codecpar->extradata_size); +} + static u32 renderer_lp_get_latency(struct camu_renderer *renderer) { struct camu_renderer_lp *lr = (struct camu_renderer_lp *)renderer; @@ -229,7 +264,12 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree if (!lr->have_frame) { struct pl_swapchain_frame frame; - pl_swapchain_start_frame(lr->swapchain, &frame); + if (!pl_swapchain_start_frame(lr->swapchain, &frame)) { + if (force) { + pl_swapchain_swap_buffers(lr->swapchain); + } + return; + } pl_frame_from_swapchain(&lr->target, &frame); pl_frame_clear_rgba(lr->gpu, &lr->target, clear_color); lr->have_frame = true; @@ -286,12 +326,13 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree if (!scr->videos.count && !force) { // Don't spin too hard on a potential error state. - //al_log_warn("render_libplacebo", "Spinning in renderer."); - nn_thread_sleep(NNWT_TS_FROM_USEC(128)); + nn_thread_sleep(NNWT_TS_FROM_USEC(192)); return; } - pl_swapchain_submit_frame(lr->swapchain); + if (!pl_swapchain_submit_frame(lr->swapchain)) { + al_log_error("render_libplacebo", "Failed to submit frame, likely fatal."); + } lr->have_frame = false; pl_swapchain_swap_buffers(lr->swapchain); @@ -301,14 +342,14 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree pl_gpu_finish(lr->gpu); } - if (!force) { - f64 tick = nn_get_tick(); - f64 frame_time = tick - lr->last_render_tick; - if (frame_time > (1.0 / 24.0)) { - al_log_info("render_libplacebo", "FRAME_TIME: %fs", frame_time); - } - lr->last_render_tick = tick; +#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. + al_log_info("render_libplacebo", "FRAME_TIME: %fs", frame_time); } + lr->last_render_tick = tick; +#endif } void renderer_lp_free(struct camu_renderer **renderer) @@ -331,6 +372,7 @@ struct camu_renderer *camu_renderer_lp_create(void) struct camu_renderer_lp *lr = al_alloc_object(struct camu_renderer_lp); lr->r.create_renderer = renderer_lp_create_renderer; lr->r.create_queue = renderer_lp_create_queue; + lr->r.add_font = renderer_lp_add_font; lr->r.get_latency = renderer_lp_get_latency; lr->r.resize = renderer_lp_resize; lr->r.render = renderer_lp_render; diff --git a/src/render/renderer_libplacebo.h b/src/render/renderer_libplacebo.h index 38407bb..8a284d5 100644 --- a/src/render/renderer_libplacebo.h +++ b/src/render/renderer_libplacebo.h @@ -10,6 +10,8 @@ #if defined CAMU_RENDERER_VULKAN #include <libplacebo/vulkan.h> +#elif defined CAMU_RENDERER_DX11 +#include <libplacebo/d3d11.h> #elif defined CAMU_RENDERER_OPENGL #include <libplacebo/opengl.h> #endif @@ -20,6 +22,8 @@ struct camu_renderer_lp { VkSurfaceKHR surface; pl_vulkan vk; pl_vk_inst vk_inst; +#elif defined CAMU_RENDERER_DX11 + pl_d3d11 d3d11; #elif defined CAMU_RENDERER_OPENGL pl_opengl gl; #endif diff --git a/src/screen/screen.c b/src/screen/screen.c index cb49188..3312d77 100644 --- a/src/screen/screen.c +++ b/src/screen/screen.c @@ -42,7 +42,6 @@ static void resize_callback(void *userdata, u32 width, u32 height) al_array_foreach_ptr(scr->videos, i, video) { camu_view_calculate(&video->view, scr->width, scr->height); } - al_atomic_store(u32)(&scr->force_refresh, 1, AL_ATOMIC_RELAXED); } static void refresh_callback(void *userdata) @@ -67,16 +66,7 @@ static bool pointer_pos_callback(void *userdata, f64 x, f64 y) struct camu_view *view = get_view_from_mouse_pos(scr); bool queue_refresh = false; if (scr->flags & CAMU_SCREEN_DRAGGING) { - if (scr->flags & CAMU_SCREEN_MOD_CONTROL) { - u64 now = nn_get_timestamp(); - if (scr->last_seek_ts == SCREEN_INVALID_TS) { - scr->last_seek_ts = now; - } - if (now - scr->last_seek_ts > 100000) { - scr->last_seek_ts = now; - seek_to_percent_at_pointer(scr); - } - } else if (view && scr->flags & CAMU_SCREEN_ZOOM_PAN_SIMPLE) { + if (view && scr->flags & CAMU_SCREEN_ZOOM_PAN_SIMPLE) { f64 dx = x - scr->last_mouse_x; f64 dy = y - scr->last_mouse_y; if (camu_view_pan_simple(view, scr->width, scr->height, dx, dy)) { @@ -102,13 +92,10 @@ static bool mouse_button_callback(void *userdata, u8 state, u8 button) case STELA_BUTTON_PRESSED: scr->flags |= CAMU_SCREEN_DRAGGING; scr->last_click_ts = nn_get_timestamp(); - //scr->callback(scr->userdata, CAMU_SCREEN_TOGGLE_PAUSE, NULL); break; case STELA_BUTTON_RELEASED: scr->flags &= ~CAMU_SCREEN_DRAGGING; - //scr->callback(scr->userdata, CAMU_SCREEN_TOGGLE_PAUSE, NULL); - if (scr->last_click_ts != SCREEN_INVALID_TS && - (nn_get_timestamp() - scr->last_click_ts <= 300000)) { + if (scr->last_click_ts != SCREEN_INVALID_TS && nn_get_timestamp() - scr->last_click_ts <= 300000) { if (scr->flags & CAMU_SCREEN_MOD_CONTROL) { seek_to_percent_at_pointer(scr); } else { @@ -148,9 +135,9 @@ static bool mouse_button_callback(void *userdata, u8 state, u8 button) } #ifdef STELA_WINDOW_WAYLAND -#define SCROLL_MULTIPLIER 155.0 +#define SCROLL_MULTIPLIER 160.0 #else -#define SCROLL_MULTIPLIER 13.0 +#define SCROLL_MULTIPLIER 20.0 #endif static bool scroll_callback(void *userdata, f64 y) @@ -168,7 +155,7 @@ static bool scroll_callback(void *userdata, f64 y) } } } else { - scr->callback(scr->userdata, CAMU_SCREEN_VOLUME, &y); + scr->callback(scr->userdata, CAMU_SCREEN_OFFSET_VOLUME, &y); } return false; } @@ -187,6 +174,7 @@ static bool key_callback(void *userdata, u8 state, u8 button) scr->flags |= CAMU_SCREEN_MOD_CONTROL; break; case 0x10: // q + case 0x18: // q (spice?) scr->callback(scr->userdata, CAMU_SCREEN_CLOSE, NULL); break; case 0x31: // n @@ -202,6 +190,7 @@ static bool key_callback(void *userdata, u8 state, u8 button) scr->callback(scr->userdata, CAMU_SCREEN_PREVIOUS, NULL); break; case 0x39: // spacebar + case 0x41: // spacebar (spice?) scr->callback(scr->userdata, CAMU_SCREEN_TOGGLE_PAUSE, NULL); break; case 0x1f: // s @@ -210,6 +199,15 @@ static bool key_callback(void *userdata, u8 state, u8 button) case 0x12: // e scr->callback(scr->userdata, CAMU_SCREEN_RESEEK, NULL); break; + case 0x2f: { // v + f32 volume = 1.0; + scr->callback(scr->userdata, CAMU_SCREEN_SET_VOLUME, &volume); + break; + case 0xb: { + scr->callback(scr->userdata, CAMU_SCREEN_STATUS, NULL); + break; + } + } #ifdef CAMU_SCREEN_DEBUG_KEY case 0x19: // p scr->callback(scr->userdata, CAMU_SCREEN_DEBUG, NULL); @@ -299,14 +297,11 @@ static void key_immediate_callback(void *userdata, u8 state, u8 button) switch (state) { case STELA_BUTTON_PRESSED: switch (button) { - case 0x21: // f + case 0x21: { // f scr->window->toggle_fullscreen(scr->window); - // Least intrusive workaround I can think of for the egl resizing issue. - // Will only work for fullscreening, things like toggling floating state - // on sway are still broken. - al_atomic_store(u32)(&scr->force_refresh, 1, AL_ATOMIC_RELAXED); break; } + } break; default: break; @@ -327,7 +322,7 @@ bool camu_screen_init(struct camu_screen *scr, void *context) scr->window->should_close_callback = should_close_callback; scr->window->userdata = scr; scr->renderer = NULL; - al_atomic_store(u32)(&scr->force_refresh, 0, AL_ATOMIC_RELAXED); + al_atomic_store(u32)(&scr->force_refresh, 1, AL_ATOMIC_RELAXED); scr->flags = CAMU_SCREEN_ZOOM_PAN_SIMPLE; scr->last_click_ts = SCREEN_INVALID_TS; scr->last_mouse_x = 0.0; @@ -376,6 +371,8 @@ bool camu_screen_create_renderer(struct camu_screen *scr, struct camu_renderer * scr->window->get_vk_proc_address, scr->window->vk_create_surface, scr->window->vk_get_extensions, +#elif defined CAMU_RENDERER_DX11 + scr->window->win32_window, #elif defined CAMU_RENDERER_OPENGL #ifdef STELA_USE_EGL scr->window->egl_display, @@ -420,7 +417,6 @@ static void add_buffer_internal(struct camu_screen *scr, struct camu_video_buffe #ifdef CAMU_SCREEN_THREADED al_atomic_store(u8)(&buf->ref, 1, AL_ATOMIC_RELAXED); #endif - //al_atomic_store(u32)(&scr->force_refresh, 1, AL_ATOMIC_RELAXED); al_array_push(scr->videos, video); } @@ -447,7 +443,7 @@ static void run_queue_internal(struct camu_screen *scr) remove_buffer_internal(scr, buf); } scr->rem_queue.count = 0; - // src->videos cannot be touched outside of the add/remove queue context. + // src->videos cannot be touched from outside of the render context. // For example, reserving space inside of screen_add_buffer() instead // of here would be very wrong. al_array_reserve(scr->videos, scr->videos.count + scr->add_queue.count); @@ -544,7 +540,12 @@ void camu_screen_clear(struct camu_screen *scr) void camu_screen_set_state(struct camu_screen *scr, s32 state) { // @TODO: What to do if a buffer is queued for removal and we pause here. - al_atomic_store(s32)(&scr->state, state, AL_ATOMIC_RELAXED); + if (al_atomic_load(s32)(&scr->state, AL_ATOMIC_ACQUIRE) != CAMU_SCREEN_STOPPED) { + al_atomic_store(s32)(&scr->state, state, AL_ATOMIC_RELEASE); + if (state == CAMU_SCREEN_PAUSED) { + al_atomic_store(u32)(&scr->force_refresh, 1, AL_ATOMIC_RELAXED); + } + } } void camu_screen_force_refresh(struct camu_screen *scr) diff --git a/src/screen/screen.h b/src/screen/screen.h index 0b8ed33..2050d78 100644 --- a/src/screen/screen.h +++ b/src/screen/screen.h @@ -12,7 +12,7 @@ #include "view.h" -#define CAMU_SCREEN_DEBUG_KEY +//#define CAMU_SCREEN_DEBUG_KEY #define CAMU_SCREEN_WIDTH 700 #define CAMU_SCREEN_HEIGHT 700 @@ -37,7 +37,9 @@ enum { CAMU_SCREEN_TOGGLE_PAUSE, CAMU_SCREEN_SEEK, CAMU_SCREEN_RESEEK, - CAMU_SCREEN_VOLUME, + CAMU_SCREEN_SET_VOLUME, + CAMU_SCREEN_OFFSET_VOLUME, + CAMU_SCREEN_STATUS, #ifdef CAMU_SCREEN_DEBUG_KEY CAMU_SCREEN_DEBUG, #endif diff --git a/src/server/resource.h b/src/server/resource.h index 1cd00fc..2999523 100644 --- a/src/server/resource.h +++ b/src/server/resource.h @@ -9,6 +9,7 @@ struct camu_resource { struct cch_entry *entry; struct lia_node *node; u64 duration; + u32 ref; array(struct lia_list_entry *) pending; }; @@ -25,6 +26,7 @@ struct camu_resource_http { struct camu_resource_portal { struct camu_resource r; struct camu_post *post; + u32 index; }; struct camu_resource_cdio { diff --git a/src/server/server.c b/src/server/server.c index e22cf88..d85d82c 100644 --- a/src/server/server.c +++ b/src/server/server.c @@ -16,6 +16,8 @@ #include "common.h" #include "db.h" +#define RESOURCE_MAX_AGE 7 + static struct camu_user *get_user_by_username(struct camu_server *server, str *username) { struct camu_user *user; @@ -265,6 +267,91 @@ static void send_clients_entry_seeked(struct camu_server *server, struct lia_lis } } +#ifdef CAMU_HAVE_PORTAL +static struct cch_entry *entry_from_post(struct camu_server *server, struct camu_post *post, u32 index) +{ + struct cch_entry *entry = NULL; + if (index <= post->media.count) { + struct camu_post_media *media = &al_array_at(post->media, index); + if (!al_str_is_empty(&media->url)) { + entry = cch_handler_http_create(&media->url, server->loop); + } + } + if (!entry) { + al_log_warn("server", "Failed to load resource %.*s %u.", al_str_fmt(&post->unique_id), index); + return NULL; + } + entry->handler->maybe_spawn_worker(entry->handler, 0); + return entry; +} +#endif + +static bool prepare_server_resource(struct camu_server *server, struct camu_resource *resource) +{ + struct cch_entry *entry = NULL; + switch (resource->type) { + case CAMU_RESOURCE_FILE: { + struct camu_resource_file *file = (struct camu_resource_file *)resource; + entry = cch_handler_file_create(&file->path, &al_str_c("codec")); + if (!entry) { + resource->load = LIANA_ENTRY_ERRORED; + return false; + } + resource->load = LIANA_ENTRY_PREPARED; + break; + } +#ifdef NAUNET_HAS_CURL + case CAMU_RESOURCE_HTTP: { + struct camu_resource_http *http = (struct camu_resource_http *)resource; + entry = cch_handler_http_create(&http->url, server->loop); + if (!entry) { + resource->load = LIANA_ENTRY_ERRORED; + return false; + } + entry->handler->maybe_spawn_worker(entry->handler, 0); + resource->load = LIANA_ENTRY_PREPARED; + break; + } +#endif +#ifdef CAMU_HAVE_PORTAL + case CAMU_RESOURCE_PORTAL: { + struct camu_resource_portal *portal = (struct camu_resource_portal *)resource; + entry = entry_from_post(server, portal->post, portal->index); + if (!entry) { + resource->load = LIANA_ENTRY_ERRORED; + return false; + } + resource->load = LIANA_ENTRY_PREPARED; + break; + } +#endif +#ifdef CACHE_HAVE_CDIO + case CAMU_RESOURCE_CDIO: { + struct camu_resource_cdio *cdio = (struct camu_resource_cdio *)resource; + entry = cch_handler_cdio_create(); + if (!entry) { + resource->load = LIANA_ENTRY_ERRORED; + return false; + } + cdio->track = MIN(cdio->track, entry->chapters.count); + entry->chapter = &al_array_at(entry->chapters, cdio->track); + entry->handler->maybe_spawn_worker(entry->handler, entry->chapter->start); + resource->load = LIANA_ENTRY_PREPARED; + break; + } +#endif + } + + if (entry) { + resource->entry = entry; + resource->node = lia_server_create_node(&server->data.server, resource->entry); + resource->node->callback = node_callback; + resource->node->userdata = resource; + } + + return true; +} + static void list_callback(void *userdata, u8 op, struct lia_list_entry *entry, void *opaque) { struct camu_server *server = (struct camu_server *)userdata; @@ -272,12 +359,18 @@ static void list_callback(void *userdata, u8 op, struct lia_list_entry *entry, v switch (op) { case LIANA_LOAD_ENTRY: switch (resource->load) { - case LIANA_ENTRY_PREPARED: - resource->load = LIANA_ENTRY_LOADING; - lia_node_get_duration(resource->node); + case LIANA_ENTRY_UNLOADED: + if (!prepare_server_resource(server, resource)) { + break; + } // fallthrough case LIANA_ENTRY_PREPARING: + case LIANA_ENTRY_PREPARED: case LIANA_ENTRY_LOADING: + if (resource->load == LIANA_ENTRY_PREPARED) { + resource->load = LIANA_ENTRY_LOADING; + lia_node_get_duration(resource->node); + } al_array_push(resource->pending, entry); break; case LIANA_ENTRY_LOADED: @@ -286,12 +379,24 @@ static void list_callback(void *userdata, u8 op, struct lia_list_entry *entry, v } *(u8 *)opaque = resource->load; break; - case LIANA_GET_DURATION: { + case LIANA_GET_ENTRY_DURATION: *(u64 *)opaque = resource->duration; break; - } + case LIANA_REF_ENTRY: + resource->ref = RESOURCE_MAX_AGE; + break; + case LIANA_UNREF_ENTRY: + if (!resource->ref || --resource->ref) { + break; + } + // fallthrough case LIANA_UNLOAD_ENTRY: - lia_node_close(resource->node); + if (resource->node) { + lia_node_close(resource->node); + resource->node = NULL; + resource->entry = NULL; + resource->load = LIANA_ENTRY_UNLOADED; + } break; case LIANA_LIST_META: if (server->meta_callback) { @@ -338,6 +443,7 @@ static void client_portal_callback(void *userdata0, void *userdata1, struct camu struct camu_server *server = (struct camu_server *)userdata0; struct nn_rpc_connection *conn = (struct nn_rpc_connection *)userdata1; if (!client_still_connected(server, conn)) return; + // @TODO: result->id = -1 case. struct nn_packet *packet = nn_rpc_get_packet(conn->rpc, CAMU_CLIENT_RESULTS); nn_packet_write_u8(packet, result->op); nn_packet_write_s32(packet, result->id); @@ -421,52 +527,30 @@ out: } #ifdef CAMU_HAVE_PORTAL -static struct cch_entry *entry_from_post(struct camu_server *server, struct camu_post *post, u32 index) -{ - struct cch_entry *entry = NULL; - if (index <= post->media.count) { - struct camu_post_media *media = &al_array_at(post->media, index); - if (!al_str_is_empty(&media->url)) { - entry = cch_handler_http_create(&media->url, server->loop); - } - } - if (!entry) { - al_log_warn("server", "Failed to load resource %.*s %u.", al_str_fmt(&post->unique_id), index); - return NULL; - } - entry->handler->maybe_spawn_worker(entry->handler, 0); - return entry; -} - 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_portal *portal = (struct camu_resource_portal *)userdata1; struct camu_resource *resource = (struct camu_resource *)portal; switch (result->op) { - case CAMU_CLIENT_CREATE_SEARCH: { + case CAMU_CLIENT_CREATE_SEARCH: + if (result->id == -1) { + break; + } camu_portal_get_page(&server->bridge, result->id, 0, simple_search_portal_callback, portal); return; - } - case CAMU_CLIENT_GET_PAGE: { - if (!result->page || !result->page->list.count) break; - struct camu_post *post = camu_post_cache_get(&server->cache, &al_array_at(result->page->list, 0)); - if (!post) break; - struct cch_entry *entry = entry_from_post(server, post, 0); - if (!entry) break; - portal->post = post; - resource->entry = entry; - resource->node = lia_server_create_node(&server->data.server, resource->entry); - resource->node->callback = node_callback; - resource->node->userdata = resource; + case CAMU_CLIENT_GET_PAGE: + if (result->id == -1 || !result->page || !result->page->list.count) { + break; + } + portal->post = camu_post_cache_get(&server->cache, &al_array_at(result->page->list, 0)); resource->type = CAMU_RESOURCE_PORTAL; - resource->load = LIANA_ENTRY_PREPARED; + prepare_server_resource(server, resource); process_pending(resource); return; } - } // No return is the error case. - al_log_warn("server", "Server resource failed to load."); + al_log_warn("server", "Failed to process simple search request."); resource->load = LIANA_ENTRY_ERRORED; } #endif @@ -474,79 +558,55 @@ static void simple_search_portal_callback(void *userdata0, void *userdata1, stru static void handle_add_command(struct camu_server *server, struct lia_list *list, struct nn_packet *packet) { u8 op = nn_packet_read_u8(packet); - struct cch_entry *entry = NULL; struct camu_resource *resource = NULL; wstr name; switch (op) { case CAMU_RESOURCE_FILE: { str path; nn_packet_read_str(packet, &path); - entry = cch_handler_file_create(&path, &al_str_c("codec")); - if (!entry) return; struct camu_resource_file *file = al_alloc_object(struct camu_resource_file); al_str_clone(&file->path, &path); al_wstr_from_str(&name, &path); resource = (struct camu_resource *)file; resource->type = CAMU_RESOURCE_FILE; - resource->load = LIANA_ENTRY_PREPARED; + resource->load = LIANA_ENTRY_UNLOADED; break; } #ifdef NAUNET_HAS_CURL case CAMU_RESOURCE_HTTP: { str url; nn_packet_read_str(packet, &url); - entry = cch_handler_http_create(&url, server->loop); - if (!entry) return; - entry->handler->maybe_spawn_worker(entry->handler, 0); struct camu_resource_http *http = al_alloc_object(struct camu_resource_http); al_str_clone(&http->url, &url); al_wstr_from_str(&name, &url); resource = (struct camu_resource *)http; resource->type = CAMU_RESOURCE_HTTP; - resource->load = LIANA_ENTRY_PREPARED; + resource->load = LIANA_ENTRY_UNLOADED; break; } #endif #ifdef CACHE_HAVE_CDIO case CAMU_RESOURCE_CDIO: { - entry = cch_handler_cdio_create(); - if (!entry) return; str url; nn_packet_read_str(packet, &url); u32 track = 0; if (url.length > 7) { bool error; s64 index = al_str_to_long(&al_str_substr(&url, 7, url.length), 10, &error); - if (!error && index <= entry->chapters.count) + if (!error && index > 0) { track = (u32)index - 1; + } } - entry->chapter = &al_array_at(entry->chapters, track); - entry->handler->maybe_spawn_worker(entry->handler, entry->chapter->start); struct camu_resource_cdio *cdio = al_alloc_object(struct camu_resource_cdio); cdio->track = track; al_wstr_from_cstr(&name, "cdio"); resource = (struct camu_resource *)cdio; resource->type = CAMU_RESOURCE_CDIO; - resource->load = LIANA_ENTRY_PREPARED; + resource->load = LIANA_ENTRY_UNLOADED; break; } #endif #ifdef CAMU_HAVE_PORTAL - case CAMU_RESOURCE_PORTAL: { - str unique_id; - nn_packet_read_str(packet, &unique_id); - u32 index = nn_packet_read_u32(packet); - struct camu_post *post = camu_post_cache_get(&server->cache, &unique_id); - entry = entry_from_post(server, post, index); - if (!entry) return; - struct camu_resource_portal *portal = al_alloc_object(struct camu_resource_portal); - portal->post = post; - al_wstr_clone(&name, &post->title); - resource = (struct camu_resource *)portal; - resource->type = CAMU_RESOURCE_PORTAL; - resource->load = LIANA_ENTRY_PREPARED; - break; - } case CAMU_RESOURCE_SIMPLE_SEARCH: { str search; nn_packet_read_str(packet, &search); @@ -563,7 +623,8 @@ static void handle_add_command(struct camu_server *server, struct lia_list *list al_str_from(&query, "link:"); al_str_cat(&query, &search); } - camu_portal_create_search(&server->bridge, &al_str_c("youtube"), &query, simple_search_portal_callback, portal); + camu_portal_create_search(&server->bridge, &al_str_c("youtube"), &query, + simple_search_portal_callback, portal); al_str_free(&query); break; } @@ -571,12 +632,7 @@ static void handle_add_command(struct camu_server *server, struct lia_list *list } al_assert(resource); al_array_push(server->data.resources, resource); - if (entry) { - resource->entry = entry; - resource->node = lia_server_create_node(&server->data.server, resource->entry); - resource->node->callback = node_callback; - resource->node->userdata = resource; - } + resource->node = NULL; resource->duration = LIANA_TIMESTAMP_INVALID; al_array_init(resource->pending); lia_list_add(list, resource, resource->duration, &name); diff --git a/src/sink/desktop.c b/src/sink/desktop.c index 6fe6001..61cad77 100644 --- a/src/sink/desktop.c +++ b/src/sink/desktop.c @@ -68,11 +68,20 @@ static void screen_callback(void *userdata, u8 op, void *opaque) //camu_sink_unset(&c->sink); break; #endif - case CAMU_SCREEN_VOLUME: { + case CAMU_SCREEN_SET_VOLUME: { + f32 volume = *(f32 *)opaque; + camu_sink_set_volume(&c->sink, volume); + break; + } + case CAMU_SCREEN_OFFSET_VOLUME: { f64 amount = *(f64 *)opaque; camu_sink_offset_volume(&c->sink, (f32)amount); break; } + case CAMU_SCREEN_STATUS: { + camu_sink_status(&c->sink); + break; + } case CAMU_SCREEN_CLOSE: c->should_quit = 1; break; diff --git a/subprojects/SPIRV-Cross.wrap b/subprojects/SPIRV-Cross.wrap new file mode 100644 index 0000000..c6ff02b --- /dev/null +++ b/subprojects/SPIRV-Cross.wrap @@ -0,0 +1,6 @@ +[wrap-git] +directory = SPIRV-Cross-2c32b6b +url = https://github.com/KhronosGroup/SPIRV-Cross.git +revision = 2c32b6bf86f3c4a5539aa1f0bacbd59fe61759cf +depth = 1 +method = cmake diff --git a/subprojects/glslang.wrap b/subprojects/glslang.wrap new file mode 100644 index 0000000..275f439 --- /dev/null +++ b/subprojects/glslang.wrap @@ -0,0 +1,6 @@ +[wrap-git] +directory = glslang-ef5207f +url = https://github.com/KhronosGroup/glslang.git +revision = ef5207f9f8cce5097568efb15d3297d8b5d13014 +depth = 1 +method = cmake diff --git a/subprojects/libplacebo.wrap b/subprojects/libplacebo.wrap index cc66e64..23885d4 100644 --- a/subprojects/libplacebo.wrap +++ b/subprojects/libplacebo.wrap @@ -1,7 +1,7 @@ [wrap-git] -directory = libplacebo-118d810 +directory = libplacebo-056b852 url = https://code.videolan.org/videolan/libplacebo.git -revision = 118d8106640796d3f2ceb55f8634a32a58a47aa2 +revision = 056b852018db04aa2ebc0982e27713afcea8106b clone-recursive = true depth = 1 diff --git a/subprojects/miniaudio.wrap b/subprojects/miniaudio.wrap index e4e8862..2483267 100644 --- a/subprojects/miniaudio.wrap +++ b/subprojects/miniaudio.wrap @@ -1,7 +1,7 @@ [wrap-git] -directory = miniaudio-6d5efde +directory = miniaudio-445cdcb url = https://github.com/mackron/miniaudio.git -revision = 6d5efde254a81e42294f3baf4a4376a76492c192 +revision = 445cdcb82bb948e0d78e8a7c11aa28a2b4d1b198 depth = 1 patch_directory = miniaudio diff_files = miniaudio/miniaudio_pulse_flush.diff, miniaudio/miniaudio_alsa_compile.diff diff --git a/subprojects/packagefiles/ffmpeg/meson.build b/subprojects/packagefiles/ffmpeg/meson.build index 33dbd30..05940c4 100644 --- a/subprojects/packagefiles/ffmpeg/meson.build +++ b/subprojects/packagefiles/ffmpeg/meson.build @@ -1,11 +1,14 @@ project('ffmpeg', 'c', version: '7.1', meson_version: '>=0.57.0') +ffmpeg_version_string = meson.project_version() + compiler = meson.get_compiler('c') cpu = target_machine.cpu_family() is_64bit = (cpu == 'x86_64' or cpu == 'aarch64') is_debug = get_option('buildtype').startswith('debug') is_minsize = get_option('buildtype') == 'minsize' +is_linux = host_machine.system() == 'linux' is_windows = host_machine.system() == 'windows' is_msvc = compiler.get_id() == 'msvc' or compiler.get_id() == 'clang-cl' is_mingw = is_windows and not is_msvc @@ -45,6 +48,7 @@ endif decoders = 'flac,mp3,mp3float,aac,opus,vorbis,alac,mjpeg,jpeg2000,gif,h264,hevc,av1,vp9,vp8' decoders += ',pcm_f32be,pcm_s32be,pcm_s32le,pcm_s32le_planar,pcm_f32le,pcm_s24be,pcm_s24le,pcm_s16be,pcm_s16be_planar,pcm_s16le,pcm_s16le_planar' +decoders += ',dca' #decoders += ',pcm_dvd,mpegvideo,mpeg2video' #decoders += ',ass,srt' @@ -53,6 +57,7 @@ demuxers = 'flac,mp3,aac,wav,image2,mjpeg,image2pipe,image_jpeg_pipe,gif,matrosk #demuxers += ',ass,srt' parsers = 'aac,opus,mjpeg,jpeg2000,gif,h264,hevc,av1,vp9,vp8' +parsers += ',dca' #parsers += ',mpegaudio,mpegvideo,dvd_nav' extra_options += ['--enable-zlib'] @@ -63,16 +68,24 @@ parsers += ',png' bsfs = 'extract_extradata,mp3_header_decompress' hwaccels = '' -if is_windows and get_option('sink-use-vulkan') +if get_option('sink-use-vulkan') extra_options += ['--enable-vulkan'] hwaccels += 'h264_vulkan,hevc_vulkan,av1_vulkan' - #extra_options += ['--enable-d3d11va', '--enable-d3d12va', '--enable-dxva2'] - #hwaccels += 'h264_dxva2,' +elif is_windows + extra_options += ['--enable-d3d11va'] + #extra_options += ['--enable-d3d11va', '--enable-dxva2'] + hwaccels += 'h264_d3d11va,hevc_d3d11va,av1_d3d11va,vp9_d3d11va' + hwaccels += ',h264_d3d11va2,hevc_d3d11va2,av1_d3d11va2,vp9_d3d11va2' + #hwaccels += ',h264_dxva2,hevc_dxva2,av1_dxva2,vp9_dxva2' elif is_android extra_options += ['--enable-jni', '--enable-mediacodec'] decoders += ',h264_mediacodec,hevc_mediacodec,av1_mediacodec' -else - extra_options += ['--disable-vulkan', '--disable-vdpau', '--disable-vaapi'] +elif is_linux + extra_options += ['--enable-vaapi'] +endif + +if hwaccels != '' + extra_options += ['--enable-hwaccel=' + hwaccels] endif protocols = 'file,cache' @@ -121,6 +134,7 @@ proj = ext_proj.add_project('configure', '--enable-avutil', '--enable-avformat', '--enable-avcodec', + '--enable-avdevice', '--enable-swresample', '--enable-swscale', '--disable-indevs', @@ -129,9 +143,8 @@ proj = ext_proj.add_project('configure', '--enable-decoder=' + decoders, '--enable-parser=' + parsers, '--enable-bsf=' + bsfs, - '--enable-hwaccel=' + hwaccels, '--enable-protocol=' + protocols, - extra_options, + extra_options ], cross_configure_options: ['--enable-cross-compile'], verbose: true) @@ -139,6 +152,7 @@ proj = ext_proj.add_project('configure', avutil = proj.dependency('avutil') avformat = proj.dependency('avformat') avcodec = proj.dependency('avcodec') +avdevice = proj.dependency('avdevice') swresample = proj.dependency('swresample') swscale = proj.dependency('swscale') -ffmpeg_inc_dep = declare_dependency(include_directories: include_directories('.', 'build/')) +ffmpeg_include = declare_dependency(include_directories: include_directories('.', 'build/')) diff --git a/subprojects/packagefiles/miniaudio/miniaudio_pulse_flush.diff b/subprojects/packagefiles/miniaudio/miniaudio_pulse_flush.diff index 41ea436..293ba94 100644 --- a/subprojects/packagefiles/miniaudio/miniaudio_pulse_flush.diff +++ b/subprojects/packagefiles/miniaudio/miniaudio_pulse_flush.diff @@ -1,26 +1,3 @@ -diff --git a/extras/miniaudio_split/miniaudio.c b/extras/miniaudio_split/miniaudio.c -index bb97e1f..43bf4f1 100644 ---- a/extras/miniaudio_split/miniaudio.c -+++ b/extras/miniaudio_split/miniaudio.c -@@ -4763,7 +4763,7 @@ static void ma_thread_wait__posix(ma_thread* pThread) - static ma_result ma_mutex_init__posix(ma_mutex* pMutex) - { - int result; -- -+ - if (pMutex == NULL) { - return MA_INVALID_ARGS; - } -@@ -19374,7 +19374,8 @@ static ma_result ma_device_stop__pulse(ma_device* pDevice) - happens, draining never completes and we get stuck here. For now I'm disabling draining of - the device so we don't just freeze the application. - */ -- #if 0 -+ #if 1 -+ ma_bool32 wasSuccessful = MA_FALSE; - ma_pa_operation* pOP = ((ma_pa_stream_drain_proc)pDevice->pContext->pulse.pa_stream_drain)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_pulse_operation_complete_callback, &wasSuccessful); - ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pDevice->pulse.pMainLoop, pOP); - #endif diff --git a/miniaudio.h b/miniaudio.h index a5673ac..cf73a54 100644 --- a/miniaudio.h @@ -29,7 +6,7 @@ index a5673ac..cf73a54 100644 static ma_result ma_mutex_init__posix(ma_mutex* pMutex) { int result; -- +- + if (pMutex == NULL) { return MA_INVALID_ARGS; diff --git a/subprojects/packagefiles/miniaudio/miniaudio_wasapi_clock_service.diff b/subprojects/packagefiles/miniaudio/miniaudio_wasapi_clock_service.diff new file mode 100644 index 0000000..4398e31 --- /dev/null +++ b/subprojects/packagefiles/miniaudio/miniaudio_wasapi_clock_service.diff @@ -0,0 +1,151 @@ +diff --git a/miniaudio.h b/miniaudio.h +index 3879cd3..58a16a6 100644 +--- a/miniaudio.h ++++ b/miniaudio.h +@@ -7312,6 +7312,7 @@ typedef struct + ma_device_type deviceType; + void* pAudioClient; + void** ppAudioClientService; ++ void** ppAudioClock; + ma_result* pResult; /* The result from creating the audio client service. */ + } createAudioClient; + struct +@@ -7821,6 +7822,7 @@ struct ma_device + /*IAudioClient**/ ma_ptr pAudioClientCapture; + /*IAudioRenderClient**/ ma_ptr pRenderClient; + /*IAudioCaptureClient**/ ma_ptr pCaptureClient; ++ /*IAudioClock**/ ma_ptr pAudioClock; + /*IMMDeviceEnumerator**/ ma_ptr pDeviceEnumerator; /* Used for IMMNotificationClient notifications. Required for detecting default device changes. */ + ma_IMMNotificationClient notificationClient; + /*HANDLE*/ ma_handle hEventPlayback; /* Auto reset. Initialized to signaled. */ +@@ -20305,6 +20307,8 @@ static const IID MA_IID_IAudioClient2 = {0x726778CD, + static const IID MA_IID_IAudioClient3 = {0x7ED4EE07, 0x8E67, 0x4CD4, {0x8C, 0x1A, 0x2B, 0x7A, 0x59, 0x87, 0xAD, 0x42}}; /* 7ED4EE07-8E67-4CD4-8C1A-2B7A5987AD42 = __uuidof(IAudioClient3) */ + static const IID MA_IID_IAudioRenderClient = {0xF294ACFC, 0x3146, 0x4483, {0xA7, 0xBF, 0xAD, 0xDC, 0xA7, 0xC2, 0x60, 0xE2}}; /* F294ACFC-3146-4483-A7BF-ADDCA7C260E2 = __uuidof(IAudioRenderClient) */ + static const IID MA_IID_IAudioCaptureClient = {0xC8ADBD64, 0xE71E, 0x48A0, {0xA4, 0xDE, 0x18, 0x5C, 0x39, 0x5C, 0xD3, 0x17}}; /* C8ADBD64-E71E-48A0-A4DE-185C395CD317 = __uuidof(IAudioCaptureClient) */ ++static const IID MA_IID_IAudioClock = {0xCD63314F, 0x3FBA, 0x4a1b, {0x81, 0x2C, 0xEF, 0x96, 0x35, 0x87, 0x28, 0xE7}}; /* CD63314F-3FBA-4a1b-812C-EF96358728E7 = _uuidof(IAudioClock) */ ++static const IID MA_IID_IAudioClock2 = {0x6f49ff73, 0x6727, 0x49ac, {0xa0, 0x08, 0xd9, 0x8c, 0xf5, 0xe7, 0x00, 0x48}}; /* 6f49ff73-6727-49ac-a008-d98cf5e70048 = __uuidof(IAudioClock2) */ + static const IID MA_IID_IMMNotificationClient = {0x7991EEC9, 0x7E89, 0x4D85, {0x83, 0x90, 0x6C, 0x70, 0x3C, 0xEC, 0x60, 0xC0}}; /* 7991EEC9-7E89-4D85-8390-6C703CEC60C0 = __uuidof(IMMNotificationClient) */ + #if !defined(MA_WIN32_DESKTOP) && !defined(MA_WIN32_GDK) + static const IID MA_IID_DEVINTERFACE_AUDIO_RENDER = {0xE6327CAD, 0xDCEC, 0x4949, {0xAE, 0x8A, 0x99, 0x1E, 0x97, 0x6A, 0x79, 0xD2}}; /* E6327CAD-DCEC-4949-AE8A-991E976A79D2 */ +@@ -20334,6 +20338,7 @@ typedef struct ma_IAudioClient2 ma_IAudioClient2; + typedef struct ma_IAudioClient3 ma_IAudioClient3; + typedef struct ma_IAudioRenderClient ma_IAudioRenderClient; + typedef struct ma_IAudioCaptureClient ma_IAudioCaptureClient; ++typedef struct ma_IAudioClock ma_IAudioClock; + + typedef ma_int64 MA_REFERENCE_TIME; + +@@ -20745,6 +20750,28 @@ static MA_INLINE HRESULT ma_IAudioCaptureClient_GetBuffer(ma_IAudioCaptureClient + static MA_INLINE HRESULT ma_IAudioCaptureClient_ReleaseBuffer(ma_IAudioCaptureClient* pThis, ma_uint32 numFramesRead) { return pThis->lpVtbl->ReleaseBuffer(pThis, numFramesRead); } + static MA_INLINE HRESULT ma_IAudioCaptureClient_GetNextPacketSize(ma_IAudioCaptureClient* pThis, ma_uint32* pNumFramesInNextPacket) { return pThis->lpVtbl->GetNextPacketSize(pThis, pNumFramesInNextPacket); } + ++ ++/* IAudioClock */ ++typedef struct ++{ ++ /* IUnknown */ ++ HRESULT (STDMETHODCALLTYPE * QueryInterface)(ma_IAudioCaptureClient* pThis, const IID* const riid, void** ppObject); ++ ULONG (STDMETHODCALLTYPE * AddRef) (ma_IAudioCaptureClient* pThis); ++ ULONG (STDMETHODCALLTYPE * Release) (ma_IAudioCaptureClient* pThis); ++ ++ /* IAudioClock */ ++ HRESULT (STDMETHODCALLTYPE * GetFrequency)(ma_IAudioClock* pThis, ma_uint64* pu64Frequency); ++ HRESULT (STDMETHODCALLTYPE * GetPosition)(ma_IAudioClock* pThis, ma_uint64* pu64Position, ma_uint64* pu64QPCPosition); ++ HRESULT (STDMETHODCALLTYPE * GetCharacteristics)(ma_IAudioClock* pThis, DWORD* pdwCharacteristics); ++} ma_IAudioClockVtbl; ++struct ma_IAudioClock ++{ ++ ma_IAudioClockVtbl* lpVtbl; ++}; ++static MA_INLINE HRESULT ma_IAudioClock_GetFrequency(ma_IAudioClock* pThis, ma_uint64* pu64Frequency) { return pThis->lpVtbl->GetFrequency(pThis, pu64Frequency); } ++static MA_INLINE HRESULT ma_IAudioClock_GetPosition(ma_IAudioClock* pThis, ma_uint64* pu64Position, ma_uint64* pu64QPCPosition) { return pThis->lpVtbl->GetPosition(pThis, pu64Position, pu64QPCPosition); } ++static MA_INLINE HRESULT ma_IAudioClock_GetCharacteristics(ma_IAudioClock* pThis, DWORD* pdwCharacteristics) { return pThis->lpVtbl->GetCharacteristics(pThis, pdwCharacteristics); } ++ + #if defined(MA_WIN32_UWP) + /* mmdevapi Functions */ + typedef HRESULT (WINAPI * MA_PFN_ActivateAudioInterfaceAsync)(const wchar_t* deviceInterfacePath, const IID* riid, MA_PROPVARIANT* activationParams, ma_IActivateAudioInterfaceCompletionHandler* completionHandler, ma_IActivateAudioInterfaceAsyncOperation** activationOperation); +@@ -21248,6 +21275,7 @@ static ma_thread_result MA_THREADCALL ma_context_command_thread__wasapi(void* pU + } else { + *cmd.data.createAudioClient.pResult = ma_result_from_HRESULT(ma_IAudioClient_GetService((ma_IAudioClient*)cmd.data.createAudioClient.pAudioClient, &MA_IID_IAudioCaptureClient, cmd.data.createAudioClient.ppAudioClientService)); + } ++ ma_IAudioClient_GetService((ma_IAudioClient*)cmd.data.createAudioClient.pAudioClient, &MA_IID_IAudioClock, cmd.data.createAudioClient.ppAudioClock); + } break; + + case MA_CONTEXT_COMMAND_RELEASE_IAUDIOCLIENT__WASAPI: +@@ -21286,7 +21314,7 @@ static ma_thread_result MA_THREADCALL ma_context_command_thread__wasapi(void* pU + return (ma_thread_result)0; + } + +-static ma_result ma_device_create_IAudioClient_service__wasapi(ma_context* pContext, ma_device_type deviceType, ma_IAudioClient* pAudioClient, void** ppAudioClientService) ++static ma_result ma_device_create_IAudioClient_service__wasapi(ma_context* pContext, ma_device_type deviceType, ma_IAudioClient* pAudioClient, void** ppAudioClientService, void **ppAudioClock) + { + ma_result result; + ma_result cmdResult; +@@ -21294,6 +21322,7 @@ static ma_result ma_device_create_IAudioClient_service__wasapi(ma_context* pCont + cmd.data.createAudioClient.deviceType = deviceType; + cmd.data.createAudioClient.pAudioClient = (void*)pAudioClient; + cmd.data.createAudioClient.ppAudioClientService = ppAudioClientService; ++ cmd.data.createAudioClient.ppAudioClock = ppAudioClock; + cmd.data.createAudioClient.pResult = &cmdResult; /* Declared locally, but won't be dereferenced after this function returns since execution of the command will wait here. */ + + result = ma_context_post_command__wasapi(pContext, &cmd); /* This will not return until the command has actually been run. */ +@@ -22121,6 +22150,7 @@ typedef struct + ma_IAudioClient* pAudioClient; + ma_IAudioRenderClient* pRenderClient; + ma_IAudioCaptureClient* pCaptureClient; ++ ma_IAudioClock* pAudioClock; + ma_format formatOut; + ma_uint32 channelsOut; + ma_uint32 sampleRateOut; +@@ -22160,6 +22190,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device + pData->pAudioClient = NULL; + pData->pRenderClient = NULL; + pData->pCaptureClient = NULL; ++ pData->pAudioClock = NULL; + + streamFlags = MA_AUDCLNT_STREAMFLAGS_EVENTCALLBACK; + if (!pData->noAutoConvertSRC && pData->sampleRateIn != 0 && pData->shareMode != ma_share_mode_exclusive) { /* <-- Exclusive streams must use the native sample rate. */ +@@ -22530,9 +22561,9 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device + + + if (deviceType == ma_device_type_playback) { +- result = ma_device_create_IAudioClient_service__wasapi(pContext, deviceType, (ma_IAudioClient*)pData->pAudioClient, (void**)&pData->pRenderClient); ++ result = ma_device_create_IAudioClient_service__wasapi(pContext, deviceType, (ma_IAudioClient*)pData->pAudioClient, (void**)&pData->pRenderClient, (void**)&pData->pAudioClock); + } else { +- result = ma_device_create_IAudioClient_service__wasapi(pContext, deviceType, (ma_IAudioClient*)pData->pAudioClient, (void**)&pData->pCaptureClient); ++ result = ma_device_create_IAudioClient_service__wasapi(pContext, deviceType, (ma_IAudioClient*)pData->pAudioClient, (void**)&pData->pCaptureClient, (void**)&pData->pAudioClock); + } + + /*if (FAILED(hr)) {*/ +@@ -22693,6 +22724,7 @@ static ma_result ma_device_reinit__wasapi(ma_device* pDevice, ma_device_type dev + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_loopback) { + pDevice->wasapi.pAudioClientCapture = data.pAudioClient; + pDevice->wasapi.pCaptureClient = data.pCaptureClient; ++ pDevice->wasapi.pAudioClock = data.pAudioClock; + + pDevice->capture.internalFormat = data.formatOut; + pDevice->capture.internalChannels = data.channelsOut; +@@ -22714,6 +22746,7 @@ static ma_result ma_device_reinit__wasapi(ma_device* pDevice, ma_device_type dev + if (deviceType == ma_device_type_playback) { + pDevice->wasapi.pAudioClientPlayback = data.pAudioClient; + pDevice->wasapi.pRenderClient = data.pRenderClient; ++ pDevice->wasapi.pAudioClock = data.pAudioClock; + + pDevice->playback.internalFormat = data.formatOut; + pDevice->playback.internalChannels = data.channelsOut; +@@ -22783,6 +22816,7 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf + + pDevice->wasapi.pAudioClientCapture = data.pAudioClient; + pDevice->wasapi.pCaptureClient = data.pCaptureClient; ++ pDevice->wasapi.pAudioClock = data.pAudioClock; + pDevice->wasapi.originalPeriodSizeInMilliseconds = pDescriptorCapture->periodSizeInMilliseconds; + pDevice->wasapi.originalPeriodSizeInFrames = pDescriptorCapture->periodSizeInFrames; + pDevice->wasapi.originalPeriods = pDescriptorCapture->periodCount; +@@ -22862,6 +22896,7 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf + + pDevice->wasapi.pAudioClientPlayback = data.pAudioClient; + pDevice->wasapi.pRenderClient = data.pRenderClient; ++ pDevice->wasapi.pAudioClock = data.pAudioClock; + pDevice->wasapi.originalPeriodSizeInMilliseconds = pDescriptorPlayback->periodSizeInMilliseconds; + pDevice->wasapi.originalPeriodSizeInFrames = pDescriptorPlayback->periodSizeInFrames; + pDevice->wasapi.originalPeriods = pDescriptorPlayback->periodCount; diff --git a/subprojects/packagefiles/miniaudio/miniaudio_wasapi_drain.diff b/subprojects/packagefiles/miniaudio/miniaudio_wasapi_drain.diff new file mode 100644 index 0000000..eb6bd24 --- /dev/null +++ b/subprojects/packagefiles/miniaudio/miniaudio_wasapi_drain.diff @@ -0,0 +1,44 @@ +commit 7ead713f6b67c0247e248de3709cc383da19bdea +Author: Andrew Opalach <andrew@akon.city> +Date: Sun Feb 2 15:24:33 2025 -0500 + + WASAPI: Fix drain on device stop + +diff --git a/miniaudio.h b/miniaudio.h +index fced0d2..22b0e4f 100644 +--- a/miniaudio.h ++++ b/miniaudio.h +@@ -23133,13 +23133,12 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice) + */ + if (ma_atomic_bool32_get(&pDevice->wasapi.isStartedPlayback)) { + /* We need to make sure we put a timeout here or else we'll risk getting stuck in a deadlock in some cases. */ +- DWORD waitTime = pDevice->wasapi.actualBufferSizeInFramesPlayback / pDevice->playback.internalSampleRate; ++ DWORD waitTime = pDevice->wasapi.actualBufferSizeInFramesPlayback / (pDevice->playback.internalSampleRate / 1000); + + if (pDevice->playback.shareMode == ma_share_mode_exclusive) { + WaitForSingleObject((HANDLE)pDevice->wasapi.hEventPlayback, waitTime); +- } +- else { +- ma_uint32 prevFramesAvaialablePlayback = (ma_uint32)-1; ++ } else { ++ ma_uint32 prevFramesAvailablePlayback = (ma_uint32)-1; + ma_uint32 framesAvailablePlayback; + for (;;) { + result = ma_device__get_available_frames__wasapi(pDevice, (ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &framesAvailablePlayback); +@@ -23155,13 +23154,13 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice) + Just a safety check to avoid an infinite loop. If this iteration results in a situation where the number of available frames + has not changed, get out of the loop. I don't think this should ever happen, but I think it's nice to have just in case. + */ +- if (framesAvailablePlayback == prevFramesAvaialablePlayback) { ++ if (framesAvailablePlayback == prevFramesAvailablePlayback) { + break; + } +- prevFramesAvaialablePlayback = framesAvailablePlayback; ++ prevFramesAvailablePlayback = framesAvailablePlayback; + +- WaitForSingleObject((HANDLE)pDevice->wasapi.hEventPlayback, waitTime * 1000); + ResetEvent((HANDLE)pDevice->wasapi.hEventPlayback); /* Manual reset. */ ++ WaitForSingleObject((HANDLE)pDevice->wasapi.hEventPlayback, waitTime); + } + } + } diff --git a/subprojects/packagefiles/miniaudio/miniaudio_wasapi_exclusive_testing.diff b/subprojects/packagefiles/miniaudio/miniaudio_wasapi_exclusive_testing.diff new file mode 100644 index 0000000..994a750 --- /dev/null +++ b/subprojects/packagefiles/miniaudio/miniaudio_wasapi_exclusive_testing.diff @@ -0,0 +1,83 @@ +diff --git a/miniaudio.h b/miniaudio.h +index 22b0e4f..49ecd9a 100644 +--- a/miniaudio.h ++++ b/miniaudio.h +@@ -23061,6 +23061,26 @@ static ma_result ma_device_start__wasapi_nolock(ma_device* pDevice) + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { ++ ma_uint32 bufferSizeInFrames; ++ if (pDevice->playback.shareMode == ma_share_mode_exclusive) { ++ bufferSizeInFrames = pDevice->wasapi.actualBufferSizeInFramesPlayback; ++ } else { ++ bufferSizeInFrames = pDevice->wasapi.periodSizeInFramesPlayback; ++ } ++ hr = ma_IAudioRenderClient_GetBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, bufferSizeInFrames, (BYTE**)&pDevice->wasapi.pMappedBufferPlayback); ++ if (FAILED(hr)) { ++ return ma_result_from_HRESULT(hr); ++ } ++ pDevice->wasapi.mappedBufferPlaybackCap = bufferSizeInFrames; ++ pDevice->wasapi.mappedBufferPlaybackLen = 0; ++ ++ ma_device__read_frames_from_client(pDevice, bufferSizeInFrames, pDevice->wasapi.pMappedBufferPlayback); ++ ++ ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->wasapi.mappedBufferPlaybackCap, 0); ++ pDevice->wasapi.pMappedBufferPlayback = NULL; ++ pDevice->wasapi.mappedBufferPlaybackCap = 0; ++ pDevice->wasapi.mappedBufferPlaybackLen = 0; ++ + hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); + if (FAILED(hr)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal playback device. HRESULT = %d.", (int)hr); +@@ -23136,6 +23156,7 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice) + DWORD waitTime = pDevice->wasapi.actualBufferSizeInFramesPlayback / (pDevice->playback.internalSampleRate / 1000); + + if (pDevice->playback.shareMode == ma_share_mode_exclusive) { ++ ResetEvent((HANDLE)pDevice->wasapi.hEventPlayback); + WaitForSingleObject((HANDLE)pDevice->wasapi.hEventPlayback, waitTime); + } else { + ma_uint32 prevFramesAvailablePlayback = (ma_uint32)-1; +@@ -23415,7 +23436,23 @@ static ma_result ma_device_write__wasapi(ma_device* pDevice, const void* pFrames + ma_uint32 totalFramesProcessed = 0; + + /* Keep writing to the device until it's stopped or we've consumed all of our input. */ +- while (ma_device_get_state(pDevice) == ma_device_state_started && totalFramesProcessed < frameCount) { ++ while (totalFramesProcessed < frameCount) { ++ if (pDevice->playback.shareMode == ma_share_mode_exclusive && pDevice->wasapi.pMappedBufferPlayback == NULL) { ++ /* ++ In exclusive mode we need to wait here. Exclusive mode is weird because GetBuffer() never ++ seems to return AUDCLNT_E_BUFFER_TOO_LARGE, which is what we normally use to determine ++ whether or not we need to wait for more data. ++ */ ++ if (WaitForSingleObject((HANDLE)pDevice->wasapi.hEventPlayback, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { ++ result = MA_ERROR; ++ break; /* Wait failed. Probably timed out. */ ++ } ++ } ++ ++ if (ma_device_get_state(pDevice) != ma_device_state_started) { ++ break; ++ } ++ + ma_uint32 framesRemaining = frameCount - totalFramesProcessed; + + /* +@@ -23448,18 +23485,6 @@ static ma_result ma_device_write__wasapi(ma_device* pDevice, const void* pFrames + pDevice->wasapi.pMappedBufferPlayback = NULL; + pDevice->wasapi.mappedBufferPlaybackCap = 0; + pDevice->wasapi.mappedBufferPlaybackLen = 0; +- +- /* +- In exclusive mode we need to wait here. Exclusive mode is weird because GetBuffer() never +- seems to return AUDCLNT_E_BUFFER_TOO_LARGE, which is what we normally use to determine +- whether or not we need to wait for more data. +- */ +- if (pDevice->playback.shareMode == ma_share_mode_exclusive) { +- if (WaitForSingleObject((HANDLE)pDevice->wasapi.hEventPlayback, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { +- result = MA_ERROR; +- break; /* Wait failed. Probably timed out. */ +- } +- } + } + } else { + /* We don't have a mapped data buffer so we'll need to get one. */ diff --git a/subprojects/packagefiles/miniaudio/miniaudio_wasapi_release.diff b/subprojects/packagefiles/miniaudio/miniaudio_wasapi_release.diff new file mode 100644 index 0000000..15bc2e0 --- /dev/null +++ b/subprojects/packagefiles/miniaudio/miniaudio_wasapi_release.diff @@ -0,0 +1,70 @@ +commit 90f8315432c85b3efdde3a808c3fd890fe4a0064 +Author: Andrew Opalach <andrew@akon.city> +Date: Mon Feb 3 13:58:49 2025 -0500 + + WASAPI: Release mapped buffer before stopping device + +diff --git a/miniaudio.h b/miniaudio.h +index 22b0e4f..2d94253 100644 +--- a/miniaudio.h ++++ b/miniaudio.h +@@ -23102,6 +23102,14 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice) + } + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { ++ /* If we have a mapped buffer we need to release it. */ ++ if (pDevice->wasapi.pMappedBufferCapture != NULL) { ++ ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap); ++ pDevice->wasapi.pMappedBufferCapture = NULL; ++ pDevice->wasapi.mappedBufferCaptureCap = 0; ++ pDevice->wasapi.mappedBufferCaptureLen = 0; ++ } ++ + hr = ma_IAudioClient_Stop((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); + if (FAILED(hr)) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal capture device."); +@@ -23115,18 +23123,22 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice) + return ma_result_from_HRESULT(hr); + } + +- /* If we have a mapped buffer we need to release it. */ +- if (pDevice->wasapi.pMappedBufferCapture != NULL) { +- ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap); +- pDevice->wasapi.pMappedBufferCapture = NULL; +- pDevice->wasapi.mappedBufferCaptureCap = 0; +- pDevice->wasapi.mappedBufferCaptureLen = 0; +- } +- + ma_atomic_bool32_set(&pDevice->wasapi.isStartedCapture, MA_FALSE); + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { ++ if (pDevice->wasapi.pMappedBufferPlayback != NULL) { ++ ma_silence_pcm_frames( ++ ma_offset_pcm_frames_ptr(pDevice->wasapi.pMappedBufferPlayback, pDevice->wasapi.mappedBufferPlaybackLen, pDevice->playback.internalFormat, pDevice->playback.internalChannels), ++ pDevice->wasapi.mappedBufferPlaybackCap - pDevice->wasapi.mappedBufferPlaybackLen, ++ pDevice->playback.internalFormat, pDevice->playback.internalChannels ++ ); ++ ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->wasapi.mappedBufferPlaybackCap, 0); ++ pDevice->wasapi.pMappedBufferPlayback = NULL; ++ pDevice->wasapi.mappedBufferPlaybackCap = 0; ++ pDevice->wasapi.mappedBufferPlaybackLen = 0; ++ } ++ + /* + The buffer needs to be drained before stopping the device. Not doing this will result in the last few frames not getting output to + the speakers. This is a problem for very short sounds because it'll result in a significant portion of it not getting played. +@@ -23178,13 +23190,6 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice) + return ma_result_from_HRESULT(hr); + } + +- if (pDevice->wasapi.pMappedBufferPlayback != NULL) { +- ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->wasapi.mappedBufferPlaybackCap, 0); +- pDevice->wasapi.pMappedBufferPlayback = NULL; +- pDevice->wasapi.mappedBufferPlaybackCap = 0; +- pDevice->wasapi.mappedBufferPlaybackLen = 0; +- } +- + ma_atomic_bool32_set(&pDevice->wasapi.isStartedPlayback, MA_FALSE); + } + |