summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Opalach <andrew@akon.city> 2025-02-19 13:40:38 -0500
committerAndrew Opalach <andrew@akon.city> 2025-02-19 13:40:38 -0500
commit2bee71a7e032c0972418e324bb1d7e6b02330b18 (patch)
treefa5819e9d9efe75cbc6f50d462dd0eb6bd19b6a2
parentb36f022defd8d4ec5a8c29578bb583bea05dfbb6 (diff)
downloadcamu-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>
-rw-r--r--.gitignore6
-rw-r--r--README.txt4
-rw-r--r--docs/references/references.txt5
-rw-r--r--flake.lock32
-rw-r--r--flake.nix15
-rw-r--r--meson.build2
-rw-r--r--meson_options.txt1
-rwxr-xr-xscripts/run_valgrind.sh4
-rw-r--r--src/buffer/audio.c40
-rw-r--r--src/buffer/audio.h2
-rw-r--r--src/buffer/frame_queue.h6
-rw-r--r--src/buffer/video.c23
-rw-r--r--src/buffer/video.h8
-rw-r--r--src/codec/codec.h16
-rw-r--r--src/codec/ffmpeg/common.c56
-rw-r--r--src/codec/ffmpeg/common.h6
-rw-r--r--src/codec/ffmpeg/decoder.c187
-rw-r--r--src/codec/ffmpeg/decoder.h3
-rw-r--r--src/codec/ffmpeg/meson.build18
-rw-r--r--src/codec/ffmpeg/packet_ext.c8
-rwxr-xr-xsrc/codec/ffmpeg/version.sh7
-rw-r--r--src/fruits/cmc/cmc.c2
-rw-r--r--src/fruits/cmsrv/cmsrv.c8
-rw-r--r--src/fruits/cmv/cmv.c109
-rw-r--r--src/liana/client.c139
-rw-r--r--src/liana/client.h12
-rw-r--r--src/liana/handlers/codec_client.c14
-rw-r--r--src/liana/list.c422
-rw-r--r--src/liana/list.h15
-rw-r--r--src/liana/server.c94
-rw-r--r--src/liana/server.h1
-rw-r--r--src/liana/vcr.c29
-rw-r--r--src/liana/vcr.h3
-rw-r--r--src/libsink/sink.c224
-rw-r--r--src/libsink/sink.h5
-rw-r--r--src/mixer/audio_miniaudio.c14
-rw-r--r--src/mixer/mixer.c24
-rw-r--r--src/mixer/mixer.h4
-rw-r--r--src/portal/src/search.c30
-rw-r--r--src/render/meson.build55
-rw-r--r--src/render/queue_libplacebo.c51
-rw-r--r--src/render/queue_libplacebo.h2
-rw-r--r--src/render/renderer.h7
-rw-r--r--src/render/renderer_libplacebo.c66
-rw-r--r--src/render/renderer_libplacebo.h4
-rw-r--r--src/screen/screen.c55
-rw-r--r--src/screen/screen.h6
-rw-r--r--src/server/resource.h2
-rw-r--r--src/server/server.c206
-rw-r--r--src/sink/desktop.c11
-rw-r--r--subprojects/SPIRV-Cross.wrap6
-rw-r--r--subprojects/glslang.wrap6
-rw-r--r--subprojects/libplacebo.wrap4
-rw-r--r--subprojects/miniaudio.wrap4
-rw-r--r--subprojects/packagefiles/ffmpeg/meson.build30
-rw-r--r--subprojects/packagefiles/miniaudio/miniaudio_pulse_flush.diff25
-rw-r--r--subprojects/packagefiles/miniaudio/miniaudio_wasapi_clock_service.diff151
-rw-r--r--subprojects/packagefiles/miniaudio/miniaudio_wasapi_drain.diff44
-rw-r--r--subprojects/packagefiles/miniaudio/miniaudio_wasapi_exclusive_testing.diff83
-rw-r--r--subprojects/packagefiles/miniaudio/miniaudio_wasapi_release.diff70
60 files changed, 1675 insertions, 811 deletions
diff --git a/.gitignore b/.gitignore
index 0d17fc6..e4bf260 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/README.txt b/README.txt
index d4563b9..e2fa8cb 100644
--- a/README.txt
+++ b/README.txt
@@ -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
diff --git a/flake.lock b/flake.lock
index a6b8fac..1be7340 100644
--- a/flake.lock
+++ b/flake.lock
@@ -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": {
diff --git a/flake.nix b/flake.nix
index 816ef59..afb2058 100644
--- a/flake.nix
+++ b/flake.nix
@@ -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(&current->clock, 0.0, false);
+ if (pts == -1.0) {
+#ifndef CAMU_SINK_NO_VIDEO
+ pts = al_atomic_load(f64)(&current->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(&current->clock)) {
+ text += al_snprintf(status + text, sizeof(status) - text, "⏸ ");
+ } else {
+ text += al_snprintf(status + text, sizeof(status) - text, "⏵ ");
+ }
+ text += al_snprintf(status + text, sizeof(status) - text, "[");
+ bool show_hour = duration >= 60.0 * 60.0;
+ if (show_hour) text += al_snprintf(status + text, sizeof(status) - text, "%.2d:", hour);
+ text += al_snprintf(status + text, sizeof(status) - text, "%.2d:%.2d/", minute, (u32)pts % 60);
+ minute = (u32)(duration / 60);
+ hour = minute / 60;
+ minute -= hour * 60;
+ if (show_hour) text += al_snprintf(status + text, sizeof(status) - text, "%.2d:", hour);
+ text += al_snprintf(status + text, sizeof(status) - text, "%.2d:%.2d", minute, (u32)duration % 60);
+ text += al_snprintf(status + text, sizeof(status) - text, "]");
+ status[text] = '\0';
+ al_log_info("sink", "%s", status);
+}
+
void camu_sink_stop(struct camu_sink *sink)
{
queue_cmd(sink, (struct camu_sink_cmd){
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);
+ }
+