summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Opalach <andrew@akon.city> 2025-11-30 14:32:51 -0500
committerAndrew Opalach <andrew@akon.city> 2025-11-30 14:32:51 -0500
commitc8412bbedae0fce38db96833732e8ce904721e4c (patch)
tree4611046186c714513d042966ce97825df0fecf9e
parent0d6d13425015d78606232874498327cabcb0e4e2 (diff)
downloadcamu-c8412bbedae0fce38db96833732e8ce904721e4c.tar.gz
camu-c8412bbedae0fce38db96833732e8ce904721e4c.tar.bz2
camu-c8412bbedae0fce38db96833732e8ce904721e4c.zip
Build cleanup and fixes from sink testing
Signed-off-by: Andrew Opalach <andrew@akon.city>
-rw-r--r--.gitignore13
-rw-r--r--TODO11
-rw-r--r--cross/x86_64-w64-msvc-clang.txt6
-rw-r--r--env/msvc_build.sh22
-rw-r--r--flake.lock30
-rw-r--r--flake.nix19
-rw-r--r--meson_options.txt1
-rwxr-xr-xscripts/cmv_add.sh2
-rwxr-xr-xscripts/encode_ip.py38
-rwxr-xr-xscripts/run_server.sh1
-rwxr-xr-xscripts/run_valgrind.sh4
-rw-r--r--src/buffer/audio.c11
-rw-r--r--src/buffer/clock.c77
-rw-r--r--src/buffer/clock.h5
-rw-r--r--src/buffer/video.c14
-rw-r--r--src/buffer/video.h2
-rw-r--r--src/cache/handlers/cdio.c24
-rw-r--r--src/cache/meson.build30
-rw-r--r--src/codec/codec.h2
-rw-r--r--src/codec/codecs.c3
-rw-r--r--src/codec/ffmpeg/decoder.c3
-rw-r--r--src/codec/ffmpeg/meson.build2
-rw-r--r--src/fruits/cmc/cmc.c8
-rw-r--r--src/fruits/cmc/meson.build4
-rw-r--r--src/fruits/cmc/ui/util/waveform.c4
-rw-r--r--src/fruits/cmsrv/cmsrv.c18
-rw-r--r--src/fruits/cmsrv/meson.build8
-rw-r--r--src/fruits/cmsrv/ui.c6
-rw-r--r--src/fruits/cmsrv/ui.h1
-rw-r--r--src/fruits/cmv/cmv.c97
-rw-r--r--src/fruits/cmv/meson.build2
-rw-r--r--src/fruits/common.h24
-rw-r--r--src/fruits/ctv/ctv.c8
-rw-r--r--src/fruits/meson.build7
-rw-r--r--src/liana/client.c49
-rw-r--r--src/liana/client.h8
-rw-r--r--src/liana/handlers/codec_client.c2
-rw-r--r--src/liana/list.c28
-rw-r--r--src/liana/list.h4
-rw-r--r--src/liana/meson.build11
-rw-r--r--src/liana/vcr.c73
-rw-r--r--src/libsink/desktop.c9
-rw-r--r--src/libsink/input_simulator.c10
-rw-r--r--src/libsink/sink.c342
-rw-r--r--src/libsink/sink.h6
-rw-r--r--src/mixer/audio_miniaudio.c62
-rw-r--r--src/mixer/audio_miniaudio.h2
-rw-r--r--src/portal/src/search.c21
-rw-r--r--src/render/meson.build56
-rw-r--r--src/render/queue_libplacebo.c15
-rw-r--r--src/render/renderer.h5
-rw-r--r--src/render/renderer_libplacebo.c100
-rw-r--r--src/render/renderer_libplacebo.h2
-rw-r--r--src/render/renderer_momo.c3
-rw-r--r--src/screen/screen.c12
-rw-r--r--src/server/server.c36
-rw-r--r--src/util/queue.h16
-rw-r--r--subprojects/SPIRV-Cross.wrap4
-rw-r--r--subprojects/SPIRV-Headers.wrap9
-rw-r--r--subprojects/dav1d.wrap8
-rw-r--r--subprojects/ffmpeg3.wrap20
-rw-r--r--subprojects/ffmpeg4.wrap2
-rw-r--r--subprojects/ffmpeg5.wrap10
-rw-r--r--subprojects/ffmpeg6.wrap10
-rw-r--r--subprojects/ffmpeg7.wrap10
-rw-r--r--subprojects/ffmpeg8.wrap2
-rw-r--r--subprojects/glslang.wrap4
-rw-r--r--subprojects/libalabaster.wrap4
-rw-r--r--subprojects/libnaunet.wrap4
-rw-r--r--subprojects/libplacebo.wrap4
-rw-r--r--subprojects/msvc-wine.wrap6
-rw-r--r--subprojects/packagefiles/ffmpeg/meson.build42
-rw-r--r--subprojects/packagefiles/ffmpeg/msvc_build_conform.diff33
-rw-r--r--subprojects/packagefiles/ffmpeg/msvc_build_conform_old.diff22
-rw-r--r--subprojects/packagefiles/ffmpeg/version_3.3.9.diff45
-rw-r--r--subprojects/packagefiles/ffmpeg/version_4.2.11.diff26
-rw-r--r--subprojects/packagefiles/ffmpeg/version_5.1.8.diff29
-rw-r--r--subprojects/packagefiles/ffmpeg/version_6.1.4.diff29
-rw-r--r--subprojects/packagefiles/ffmpeg/version_7.1.3.diff29
-rw-r--r--subprojects/packagefiles/miniaudio/meson.build2
-rw-r--r--subprojects/packagefiles/msvc-wine/msvc-wine-shebang-nixos.diff405
-rw-r--r--subprojects/packagefiles/wuffs/meson.build2
-rw-r--r--subprojects/shaderc.wrap4
-rw-r--r--subprojects/stela.wrap4
84 files changed, 1026 insertions, 1122 deletions
diff --git a/.gitignore b/.gitignore
index f1b2c2e..3014b50 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,17 +7,20 @@ data/
docs/**/*.html
env/msvc-root/
result
+scripts/host_ip
src/portal/cpy/portal.c
src/portal/cpy/portal.h
src/portal/py/config.py
src/portal/vendor/*
subprojects/BLAKE3-*/
subprojects/SPIRV-Cross-*/
+subprojects/SPIRV-Headers-*/
subprojects/SPIRV-Tools-*/
subprojects/c89atomic-*/
subprojects/c89atomic.wrap
+subprojects/cairo-*/
subprojects/cairo.wrap
-subprojects/cairo/
+subprojects/dav1d-*/
subprojects/expat-*/
subprojects/expat.wrap
subprojects/ffmpeg-*/
@@ -35,6 +38,8 @@ subprojects/gperf.wrap
subprojects/gperf/
subprojects/gvdb.wrap
subprojects/harfbuzz-*/
+subprojects/icu.wrap
+subprojects/icu/
subprojects/jansson-*/
subprojects/jansson.wrap
subprojects/jsmn-*/
@@ -50,6 +55,7 @@ subprojects/libdvdnav-*/
subprojects/libdvdread-*/
subprojects/libev-4.33/
subprojects/libev.wrap
+subprojects/libffi-*/
subprojects/libffi.wrap
subprojects/libffi/
subprojects/libnaunet
@@ -61,15 +67,20 @@ subprojects/libxml2-*/
subprojects/libxml2.wrap
subprojects/linmath.h-*/
subprojects/miniaudio-*/
+subprojects/msvc-wine-*/
subprojects/msvc-wine/
subprojects/notcurses-*/
subprojects/packagecache/
subprojects/pcre2-*/
subprojects/pcre2.wrap
+subprojects/proxy-libintl.wrap
+subprojects/proxy-libintl/
subprojects/shaderc-*/
subprojects/stb-*/
subprojects/stela
subprojects/stela-*/
+subprojects/sysprof.wrap
+subprojects/sysprof/
subprojects/vulkan-headers-*/
subprojects/vulkan.wrap
subprojects/wayland-protocols-*/
diff --git a/TODO b/TODO
index d139dd5..533ab82 100644
--- a/TODO
+++ b/TODO
@@ -37,11 +37,17 @@
- [x] Checking buffer states needs a lock, check asserts.
- [x] Commit fixes.
- [x] Commit changes.
+ - [x] cache selection in meson_options
+ - [x] al_atomic_* -> atomic_*
+ - [ ] Remove entry->ended from sink.
+ - [ ] libnaunet zlib wrap
+ - [ ] Pass over logging.
+ - [ ] list_remove().
+ - [ ] Do ntp like latency evaluation.
+ - [ ] packet_cache_v2.
- [ ] Implement queued.
- [ ] Command line argument test in libnaunet.
- [ ] Header cleanup.
- - [ ] Remove entry->ended from sink.
- - [ ] packet_cache_v2.
- [ ] https://github.com/JuliaStrings/utf8proc/blob/master/utf8proc.h
- [ ] ustr for ui elements.
- [ ] remove wstr.
@@ -97,6 +103,7 @@
=== Cache
- See cache/entry.h.
+ - [ ] Don't use file_mapped for large files.
- [ ] EOF when seeking forward in slow http source.
- [ ] HTTP timeout.
diff --git a/cross/x86_64-w64-msvc-clang.txt b/cross/x86_64-w64-msvc-clang.txt
index f269f63..01188f3 100644
--- a/cross/x86_64-w64-msvc-clang.txt
+++ b/cross/x86_64-w64-msvc-clang.txt
@@ -11,12 +11,14 @@ pkg-config = ''
exe_wrapper = 'wine64'
[properties]
-c_args = ['--target=x86_64-windows-msvc', '-fuse-ld=lld']
-cpp_args = ['--target=x86_64-windows-msvc', '-fuse-ld=lld']
ar = 'llvm-lib'
nm = 'llvm-objdump'
needs_exe_wrapper = true
+[built-in options]
+c_args = ['--target=x86_64-windows-msvc', '-fuse-ld=lld']
+cpp_args = ['--target=x86_64-windows-msvc', '-fuse-ld=lld']
+
[host_machine]
system = 'windows'
cpu_family = 'x86_64'
diff --git a/env/msvc_build.sh b/env/msvc_build.sh
index ccc5625..ba73416 100644
--- a/env/msvc_build.sh
+++ b/env/msvc_build.sh
@@ -1,13 +1,17 @@
#! /usr/bin/env sh
+# nix develop .#mingw64
# meson subprojects download msvc-wine
-# ./vsdownload.py --dest env/msvc-root
-# ./install.sh env/msvc-root
+# cd subprojects/msvc-wine-32b504c/
+# ./vsdownload.py --dest ../../env/msvc-root
+# ./install.sh ../../env/msvc-root
-BIN=env/msvc-root/bin/x64 . subprojects/msvc-wine/msvcenv-native.sh
-export INCLUDE="$HOME/c/camu/subprojects/vulkan-headers-1.4.313/include;/nix/store/jx7aqhh225lhxdxdbiw42mrf2awvpdsq-clang-19.1.7-lib/lib/clang/19/include;$INCLUDE"
-CC=clang-cl CXX=clang-cl meson setup build-clang-cl -Drenderer-api=dx11 \
- -Dsink-only=false -Dserver=enabled -Dportal=disabled -Dclient=disabled \
- -Dcodecs=ffmpeg -Dcodec-hwaccel=enabled -Dsubtitles=enabled \
- -Dtests=disabled --cross-file ./cross/x86_64-w64-msvc-clang.txt --default-library=static --buildtype=release \
- --optimization=2 -Ddebug=false --strip -Db_vscrt=static_from_buildtype
+export INCLUDE="$INCLUDE:$HOME/c/camu/subprojects/vulkan-headers-1.4.325/include"
+
+BIN=env/msvc-root/bin/x64 . subprojects/msvc-wine-32b504c/msvcenv-native.sh
+
+CC=clang-cl CXX=clang-cl meson setup build-clang-cl -Dwindow=win32 -Drenderer-api=dx11 \
+ -Dsink-only=false -Dserver=enabled -Dportal=disabled -Dclient=disabled -Dsources=file \
+ -Dcodecs=ffmpeg -Dcodec-ffmpeg-version=8 -Dcodec-hwaccels=d3d11va -Dsubtitles=enabled -Dtests=disabled \
+ --cross-file ./cross/x86_64-w64-msvc-clang.txt --default-library=static -Db_vscrt=static_from_buildtype \
+ --buildtype=release --optimization=s -Ddebug=false --strip
diff --git a/flake.lock b/flake.lock
index 87d6900..e745807 100644
--- a/flake.lock
+++ b/flake.lock
@@ -41,11 +41,11 @@
]
},
"locked": {
- "lastModified": 1763748372,
- "narHash": "sha256-AUc78Qv3sWir0hvbmfXoZ7Jzq9VVL97l+sP9Jgms+JU=",
+ "lastModified": 1764510518,
+ "narHash": "sha256-hDFkciyjhjMLRC/qXCREWX1dgq8kopiuJdwXdfVlJuQ=",
"owner": "nix-community",
"repo": "home-manager",
- "rev": "d10a9b16b2a3ee28433f3d1c603f4e9f1fecb8e1",
+ "rev": "83053e1d337f33e0b48250588006e4b9df2f0d9d",
"type": "github"
},
"original": {
@@ -76,11 +76,11 @@
},
"nixos-hardware": {
"locked": {
- "lastModified": 1762847253,
- "narHash": "sha256-BWWnUUT01lPwCWUvS0p6Px5UOBFeXJ8jR+ZdLX8IbrU=",
+ "lastModified": 1764440730,
+ "narHash": "sha256-ZlJTNLUKQRANlLDomuRWLBCH5792x+6XUJ4YdFRjtO4=",
"owner": "NixOS",
"repo": "nixos-hardware",
- "rev": "899dc449bc6428b9ee6b3b8f771ca2b0ef945ab9",
+ "rev": "9154f4569b6cdfd3c595851a6ba51bfaa472d9f3",
"type": "github"
},
"original": {
@@ -96,11 +96,11 @@
"nixpkgs": "nixpkgs_2"
},
"locked": {
- "lastModified": 1763738061,
- "narHash": "sha256-VpNRcInaj1MOya8NmcqhFmdO7KGO7SSZelJQmPl6HoQ=",
+ "lastModified": 1764072830,
+ "narHash": "sha256-ezkjlUCohD9o9c47Ey0/I4CamSS0QEORTqGvyGqMud0=",
"owner": "nix-community",
"repo": "NixOS-WSL",
- "rev": "3bcc267c4e0efa023b98b9c5cfbe11b88ec2dc8f",
+ "rev": "c7832dd786175e20f2697179e0e03efadffe4201",
"type": "github"
},
"original": {
@@ -127,11 +127,11 @@
},
"nixpkgs_2": {
"locked": {
- "lastModified": 1762977756,
- "narHash": "sha256-4PqRErxfe+2toFJFgcRKZ0UI9NSIOJa+7RXVtBhy4KE=",
+ "lastModified": 1763678758,
+ "narHash": "sha256-+hBiJ+kG5IoffUOdlANKFflTT5nO3FrrR2CA3178Y5s=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "c5ae371f1a6a7fd27823bc500d9390b38c05fa55",
+ "rev": "117cc7f94e8072499b0a7aa4c52084fa4e11cc9b",
"type": "github"
},
"original": {
@@ -143,11 +143,11 @@
},
"nixpkgs_3": {
"locked": {
- "lastModified": 1763421233,
- "narHash": "sha256-Stk9ZYRkGrnnpyJ4eqt9eQtdFWRRIvMxpNRf4sIegnw=",
+ "lastModified": 1764242076,
+ "narHash": "sha256-sKoIWfnijJ0+9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "89c2b2330e733d6cdb5eae7b899326930c2c0648",
+ "rev": "2fad6eac6077f03fe109c4d4eb171cf96791faa4",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index 9d10483..3be0bab 100644
--- a/flake.nix
+++ b/flake.nix
@@ -151,9 +151,11 @@
pipewire
libpulseaudio
libva
+ dav1d
libplacebo
xxHash
lcms
+ libdovi
shaderc
glslang.dev
libGL
@@ -308,12 +310,12 @@
meson
cmake
gdb
- wineWowPackages.staging
+ #wineWowPackages.staging
# Use nixGLIntel to run wine
- pkgs.nixgl.nixGLIntel
- pkgs.nixgl.nixVulkanIntel
- # clang-cl
- clang
+ #pkgs.nixgl.nixGLIntel
+ #pkgs.nixgl.nixVulkanIntel
+ # clang-cl, .cc is the 'unwrapped' compiler.
+ clang.cc
msitools
lld
libllvm
@@ -340,6 +342,9 @@
(soxr.overrideAttrs (oldAttrs: {
cmakeFlags = oldAttrs.cmakeFlags ++ [ "-DBUILD_STATIC_LIBS=ON" "-DBUILD_SHARED_LIBS=OFF" ];
}))
+ (dav1d.overrideAttrs (oldAttrs: {
+ mesonFlags = oldAttrs.mesonFlags ++ [ "--default-library=static" ];
+ }))
(openssl.override { static = true; })
xxHash
(libiconv.override { enableStatic = true; enableShared = false; })
@@ -353,7 +358,7 @@
export SHELL="${pkgs.bashInteractive}/bin/bash"
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${pkgs.stdenv.cc.cc.lib}/lib"
export XDG_DATA_DIRS="${pkgs.mesa}/share:$XDG_DATA_DIRS"
- alias wine="${pkgs.nixgl.nixGLIntel}/bin/nixGLIntel wine"
+ #alias wine="${pkgs.nixgl.nixGLIntel}/bin/nixGLIntel wine"
# Silence Wine logging and enable Esync by default.
export WINEDEBUG=-all
export WINEESYNC=1
@@ -401,7 +406,7 @@
androidComposition = androidenv.composeAndroidPackages {
cmdLineToolsVersion = "13.0";
toolsVersion = "26.1.1";
- platformToolsVersion = "34.0.5";
+ platformToolsVersion = "36.0.2";
buildToolsVersions = [ "34.0.0" ];
includeEmulator = false;
emulatorVersion = "35.2.5";
diff --git a/meson_options.txt b/meson_options.txt
index fb06ae9..70fc940 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,4 +1,5 @@
# Options
+option('sources', type: 'array', choices: ['file', 'http', 'cdio', 'libdvd'], value: ['file', 'http'])
option('codecs', type: 'array', choices: ['ffmpeg', 'stb_image', 'wuffs', 'spng'], value: ['ffmpeg', 'stb_image'])
option('codec-hwaccels', type: 'array', choices: ['vulkan', 'vaapi', 'mediacodec', 'd3d11va', 'dxva2'], value: [], yield: true)
option('codec-ffmpeg-version', type: 'combo', choices: ['system', '8', '7', '6', '5', '4', '3', '2'], value: 'system')
diff --git a/scripts/cmv_add.sh b/scripts/cmv_add.sh
index 92de535..4572b51 100755
--- a/scripts/cmv_add.sh
+++ b/scripts/cmv_add.sh
@@ -12,4 +12,4 @@ do
fi
CMD="$CMD""$EXP\n"
done
-echo -e "$CMD""X" | socat - UNIX-CONNECT:/tmp/camu_control_sock
+echo -e "$CMD""X" | socat - UNIX-CONNECT:/tmp/camu_control
diff --git a/scripts/encode_ip.py b/scripts/encode_ip.py
new file mode 100755
index 0000000..9f24fd6
--- /dev/null
+++ b/scripts/encode_ip.py
@@ -0,0 +1,38 @@
+#! /usr/bin/env python3
+
+import sys
+
+arg = sys.argv[1]
+
+# Invalid characters on Windows: \/:*?"<>|
+
+#edge_chars = ['¥','£','€','¢']
+#chars = ['¡', '"', '#', '$', '%', '&', "'", '(', ')', ',', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '—', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '}', '~']
+#chars.extend(edge_chars)
+#shift_up = '+'
+#shift_down = '-'
+#
+#c_struct = 'static char addr_char_map[] = { '
+#for i in range(0, 87):
+# c_struct += f'[\'{chars[i] if chars[i] != '\'' else '\\\''}\'] = {str(i)}'
+# if i != 86:
+# c_struct += ', '
+#c_struct += ' };'
+#
+#s = ''
+#for n in arg.split('.'):
+# i = int(n, 10)
+# if i >= 174:
+# i -= 174
+# s += shift_down
+# if i >= 87:
+# i -= 87
+# else:
+# s += shift_up
+# s += chars[i]
+
+s = ''
+for n in arg.split('.'):
+ s += '%02x' % int(n, 10)
+
+print(s)
diff --git a/scripts/run_server.sh b/scripts/run_server.sh
index 22945ed..f3c520b 100755
--- a/scripts/run_server.sh
+++ b/scripts/run_server.sh
@@ -1,5 +1,6 @@
#! /usr/bin/env sh
source ../scripts/python_env
+export CMSRV_IP=$(<../scripts/host_ip)
# screen will use $SHELL.
screen -c ../scripts/screenrc
#valgrind --leak-check=no --show-error-list=yes --log-file=./server-valgrind.log ./src/fruits/cmsrv/cmsrv $@
diff --git a/scripts/run_valgrind.sh b/scripts/run_valgrind.sh
index c49d561..7982184 100755
--- a/scripts/run_valgrind.sh
+++ b/scripts/run_valgrind.sh
@@ -1,6 +1,6 @@
#! /usr/bin/env sh
source ../scripts/python_env
#valgrind --leak-check=full --show-leak-kinds=definite ./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 "$@"
#valgrind --tool=callgrind ./src/fruits/cmv/cmv "$@"
diff --git a/src/buffer/audio.c b/src/buffer/audio.c
index a740111..998662b 100644
--- a/src/buffer/audio.c
+++ b/src/buffer/audio.c
@@ -10,12 +10,12 @@
#include "common.h"
#include "common_internal.h"
-#define BUFFER_SIZE 6.0
+#define BUFFER_SIZE 7.0
#define BUFFER_MARK_MIN 3.25 // Must be a most half of the buffer size.
#define BUFFER_MARK_BUFFERED 0.35
#ifdef CAMU_AUDIO_BUFFER_FADE
-#define FADE_STEP(fmt) (2.50f / (fmt)->sample_rate)
+#define FADE_STEP(fmt, down) ((down ? -2.5f : 2.5f) / (fmt)->sample_rate)
#define FADE_MIN 0.175f
#endif
@@ -436,10 +436,9 @@ ptrdiff_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, ptrdif
f32 step = 0.f;
if (buf->pause == PAUSE_FADING || buf->fade.volume != buf->volume.user) {
- step = FADE_STEP(fmt);
- step *= (buf->pause == PAUSE_FADING || buf->fade.volume > buf->volume.user) ? -1.f : 1.f;
- if (buf->volume.user > 1.f) step *= buf->volume.user;
- step *= MAX(buf->fade.volume, FADE_MIN);
+ bool down = (buf->pause == PAUSE_FADING || buf->volume.user < buf->fade.volume);
+ step = FADE_STEP(fmt, down) * MAX(buf->fade.volume, FADE_MIN) *
+ ((buf->volume.user > 1.f) ? buf->volume.user : 1.f); // Move faster at >100%.
}
if (step != 0.f || buf->fade.volume != 1.f) {
buf->fade.volume = apply_volume(data, req, fmt, buf->fade.volume, buf->volume.user, step);
diff --git a/src/buffer/clock.c b/src/buffer/clock.c
index 732185e..356ac56 100644
--- a/src/buffer/clock.c
+++ b/src/buffer/clock.c
@@ -7,6 +7,11 @@
#define RUNNING 0.0
#define PAUSED -DBL_MAX
+// @TODO:
+// Stop throwing away precision before we need to.
+// Assume calc_tick_offset() could give a negative result at any point.
+// Use a tick offset to set local buffer latency in a way that makes more sense.
+
void camu_clock_init(struct camu_clock *clock, void (*callback)(void *, u8), void *userdata)
{
clock->callback = callback;
@@ -22,28 +27,33 @@ static f64 calc_tick_offset(f64 tick, u64 now, u64 target)
}
}
+static void offset_tick(struct camu_clock *clock, f64 diff)
+{
+ diff += atomic_load(f64)(&clock->tick, AL_ATOMIC_ACQUIRE);
+ atomic_store(f64)(&clock->tick, diff, AL_ATOMIC_RELEASE);
+}
+
void camu_clock_set(struct camu_clock *clock, f64 base)
{
clock->base = base;
- clock->offset = 0.0;
- al_atomic_store(f64)(&clock->tick, -1.0, AL_ATOMIC_RELAXED);
- al_atomic_store(f64)(&clock->pause, PAUSED, AL_ATOMIC_RELAXED);
+ atomic_store(f64)(&clock->tick, -1.0, AL_ATOMIC_RELAXED);
+ atomic_store(f64)(&clock->pause, PAUSED, AL_ATOMIC_RELAXED);
clock->paused_at = 0.0;
- al_atomic_store(f64)(&clock->last_pts, base, AL_ATOMIC_RELAXED);
+ atomic_store(f64)(&clock->last_pts, base, AL_ATOMIC_RELAXED);
}
void camu_clock_seek(struct camu_clock *clock, f64 base, u64 target)
{
clock->base = base;
- clock->offset = 0.0;
- al_atomic_store(f64)(&clock->last_pts, base, AL_ATOMIC_RELAXED);
+ atomic_store(f64)(&clock->last_pts, base, AL_ATOMIC_RELAXED);
if (clock->paused_at == -1.0) {
// We are safe to directly edit the tick here.
if (target == 0) {
- al_atomic_store(f64)(&clock->tick, -1.0, AL_ATOMIC_RELAXED);
+ atomic_store(f64)(&clock->tick, -1.0, AL_ATOMIC_RELAXED);
} else {
- f64 tick = calc_tick_offset(nn_get_tick(), nn_get_timestamp(), target);
- al_atomic_store(f64)(&clock->tick, tick, AL_ATOMIC_RELAXED);
+ f64 tick = nn_get_tick();
+ tick = calc_tick_offset(tick, nn_get_timestamp(), target);
+ atomic_store(f64)(&clock->tick, tick, AL_ATOMIC_RELAXED);
}
} else {
// Don't touch clock->pause here for the sake of sync. This means,
@@ -57,28 +67,29 @@ void camu_clock_seek(struct camu_clock *clock, f64 base, u64 target)
}
}
+// Seek to 0 but include the time it took to perform the seek.
void camu_clock_loop(struct camu_clock *clock, f64 last_pts)
{
- // Seek to 0 but include the time it took to perform the seek.
- clock->offset += last_pts - clock->base;
clock->base = 0.0;
+ offset_tick(clock, last_pts - clock->base);
}
+// If late, pause immediately and on resume() there will be a long delay.
void camu_clock_pause(struct camu_clock *clock, u64 target)
{
al_assert(clock->paused_at == -1.0);
- f64 tick = nn_get_tick();
+ f64 now = nn_get_tick(), tick = now;
if (target > 0) {
tick = calc_tick_offset(tick, nn_get_timestamp(), target);
- al_atomic_store(f64)(&clock->pause, tick, AL_ATOMIC_RELAXED);
- } else {
- al_atomic_store(f64)(&clock->pause, PAUSED, AL_ATOMIC_RELAXED);
}
+ atomic_store(f64)(&clock->pause, (tick > now) ? tick : -tick, AL_ATOMIC_RELAXED);
+
clock->paused_at = tick;
}
+// If late, this will be a catchup.
void camu_clock_resume(struct camu_clock *clock, u64 target)
{
al_assert(clock->paused_at != -1.0);
@@ -91,22 +102,23 @@ void camu_clock_resume(struct camu_clock *clock, u64 target)
if (clock->paused_at == 0.0) {
if (target == 0) {
// target = 0 can never be synced.
- al_atomic_store(f64)(&clock->tick, -1.0, AL_ATOMIC_RELAXED);
+ atomic_store(f64)(&clock->tick, -1.0, AL_ATOMIC_RELAXED);
} else {
- al_atomic_store(f64)(&clock->tick, tick, AL_ATOMIC_RELAXED);
+ atomic_store(f64)(&clock->tick, tick, AL_ATOMIC_RELAXED);
}
} else {
- clock->offset += tick - clock->paused_at;
+ f64 pause = atomic_load(f64)(&clock->pause, AL_ATOMIC_ACQUIRE);
+ offset_tick(clock, tick - ((pause != PAUSED) ? -pause : clock->paused_at));
}
- al_atomic_store(f64)(&clock->pause, RUNNING, AL_ATOMIC_RELAXED);
+ atomic_store(f64)(&clock->pause, RUNNING, AL_ATOMIC_RELEASE);
clock->paused_at = -1.0;
}
bool camu_clock_is_paused(struct camu_clock *clock)
{
- return al_atomic_load(f64)(&clock->pause, AL_ATOMIC_RELAXED) == PAUSED;
+ return atomic_load(f64)(&clock->pause, AL_ATOMIC_RELAXED) < 0.0;
}
f64 camu_clock_get_base_pts(struct camu_clock *clock)
@@ -116,35 +128,34 @@ f64 camu_clock_get_base_pts(struct camu_clock *clock)
f64 camu_clock_get_pts(struct camu_clock *clock, f64 latency, bool allow_set)
{
- f64 pause = al_atomic_load(f64)(&clock->pause, AL_ATOMIC_ACQUIRE);
- if (pause == PAUSED) return CAMU_PTS_PAUSED;
+ f64 pause = atomic_load(f64)(&clock->pause, AL_ATOMIC_RELAXED);
+ if (pause < 0.0) return CAMU_PTS_PAUSED;
f64 current = nn_get_tick();
-
- f64 tick = al_atomic_load(f64)(&clock->tick, AL_ATOMIC_RELAXED);
+ f64 tick = atomic_load(f64)(&clock->tick, AL_ATOMIC_RELAXED);
if (tick == -1.0) {
if (allow_set) {
- tick = al_atomic_compare_and_swap(f64)(&clock->tick, -1.0, current);
+ tick = atomic_compare_and_swap(f64)(&clock->tick, -1.0, current);
if (tick == -1.0) tick = current;
} else {
return CAMU_PTS_PAUSED;
}
}
+ f64 pts = clock->base + (current - tick);
+
bool signal_pause = false;
- if (pause > 0.0 && current > pause) { // pause > 0.0 means running or pause armed.
- current = pause;
- pause = al_atomic_compare_and_swap(f64)(&clock->pause, pause, PAUSED);
- if (pause != PAUSED) {
+ if (pause > 0.0 && current > pause) { // pause > 0.0 = RUNNING or armed for pause.
+ f64 paused_at = -current;
+ pause = atomic_compare_and_swap(f64)(&clock->pause, pause, paused_at);
+ if (pause != paused_at) {
clock->callback(clock->userdata, CAMU_CLOCK_PAUSED);
signal_pause = true;
}
}
- f64 pts = (clock->base - clock->offset) + (current - tick);
-
if (allow_set) {
- al_atomic_store(f64)(&clock->last_pts, pts, AL_ATOMIC_RELAXED);
+ atomic_store(f64)(&clock->last_pts, pts, AL_ATOMIC_RELAXED);
}
return signal_pause ? CAMU_PTS_SIGNAL_PAUSE : pts + latency;
@@ -152,5 +163,5 @@ f64 camu_clock_get_pts(struct camu_clock *clock, f64 latency, bool allow_set)
f64 camu_clock_get_last_pts(struct camu_clock *clock)
{
- return al_atomic_load(f64)(&clock->last_pts, AL_ATOMIC_RELAXED);
+ return atomic_load(f64)(&clock->last_pts, AL_ATOMIC_RELAXED);
}
diff --git a/src/buffer/clock.h b/src/buffer/clock.h
index 3209119..8324b6a 100644
--- a/src/buffer/clock.h
+++ b/src/buffer/clock.h
@@ -23,8 +23,8 @@
// Next/Prev:
// - User input -> swap
-#define CAMU_PTS_PAUSED 0xffffffffffffffff
-#define CAMU_PTS_SIGNAL_PAUSE 0x7fffffffffffffff
+#define CAMU_PTS_PAUSED ((f64)0xffffffffffffffff)
+#define CAMU_PTS_SIGNAL_PAUSE ((f64)0x7fffffffffffffff)
#define CAMU_PTS_CONSIDER_PAUSED(pts) (pts == CAMU_PTS_PAUSED || pts == CAMU_PTS_SIGNAL_PAUSE)
enum {
@@ -33,7 +33,6 @@ enum {
struct camu_clock {
f64 base;
- f64 offset;
atomic(f64) tick;
atomic(f64) pause;
f64 paused_at;
diff --git a/src/buffer/video.c b/src/buffer/video.c
index e5c38a6..5011e9d 100644
--- a/src/buffer/video.c
+++ b/src/buffer/video.c
@@ -30,6 +30,7 @@ bool camu_video_buffer_init(struct camu_video_buffer *buf, struct camu_clock *cl
buf->single_frame = true;
buf->queue = NULL;
buf->buffered = false;
+ buf->weighted_first_read = false;
al_atomic_store(u8)(&buf->flow, FLOWING, AL_ATOMIC_RELAXED);
#ifdef CAMU_SCREEN_THREADED
al_atomic_store(u8)(&buf->ref, 0, AL_ATOMIC_RELAXED);
@@ -63,13 +64,13 @@ bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_code
} else {
al_assert(frame_rate.den > 0);
buf->avg_frame_duration = av_q2d(av_inv_q(frame_rate));
+ buf->weighted_first_read = true;
log_info("Stream: %s (%ux%u) VIDEO %.3ffps.", format_name, fmt->width, fmt->height, av_q2d(frame_rate));
}
break;
}
#endif
}
- buf->weighted_read = !buf->single_frame;
struct camu_video_format *in = &buf->fmt.in;
struct camu_video_format *req = &buf->fmt.req;
camu_video_format_copy(in, fmt);
@@ -233,7 +234,8 @@ bool camu_video_buffer_read(struct camu_video_buffer *buf, void *out, bool *weig
f64 base_pts = al_atomic_load(f64)(&buf->pts, AL_ATOMIC_ACQUIRE);
if (!buf->single_frame) {
- f64 pts = camu_clock_get_pts(buf->clock, buf->latency, !buf->weighted_read);
+ bool set_clock = !buf->weighted_first_read;
+ f64 pts = camu_clock_get_pts(buf->clock, buf->latency, set_clock);
if (!CAMU_PTS_CONSIDER_PAUSED(pts) && pts > base_pts) {
base_pts = pts;
}
@@ -268,15 +270,15 @@ bool camu_video_buffer_read(struct camu_video_buffer *buf, void *out, bool *weig
}
if (ret == CAMU_QUEUE_OK) {
- if (UNLIKELY(buf->weighted_read)) {
- *weighted = buf->weighted_read;
- buf->weighted_read = false;
- }
// Only updating buf->pts on QUEUE_OK affects the behavior when we fall completely behind.
// Setting it here will result in laggily displaying out of date frames as they come in.
// Setting it on every read() will cause the video to freeze.
// This option ignores sync but is easier for the user to understand what's happening.
al_atomic_store(f64)(&buf->pts, base_pts, AL_ATOMIC_RELEASE);
+ if (UNLIKELY(buf->weighted_first_read)) {
+ *weighted = true;
+ buf->weighted_first_read = false;
+ }
} else if (ret == CAMU_QUEUE_MORE) {
log_trace("Underrun.");
}
diff --git a/src/buffer/video.h b/src/buffer/video.h
index fdd7d0b..20644bc 100644
--- a/src/buffer/video.h
+++ b/src/buffer/video.h
@@ -30,7 +30,7 @@ struct camu_video_buffer {
// Need to manually trigger EOF in read().
bool buffered_with_one_frame;
// Flush the renderer on this read and don't allow it to set the clock.
- bool weighted_read;
+ bool weighted_first_read;
atomic(u8) flow;
diff --git a/src/cache/handlers/cdio.c b/src/cache/handlers/cdio.c
index 29126c0..a9e3747 100644
--- a/src/cache/handlers/cdio.c
+++ b/src/cache/handlers/cdio.c
@@ -1,4 +1,5 @@
#define AL_LOG_SECTION "cache_cdio"
+//#define AL_LOG_ENABLE_TRACE
#include <al/log.h>
#include "../../codec/codec.h"
@@ -10,7 +11,7 @@
#include "cdio.h"
-// 1 sector at a time to minimize skips
+// 1 sector at a time to minimize skips.
#define SECTORS_PER_STEP 1
#define BYTES_PER_STEP (SECTORS_PER_STEP * CDIO_CD_FRAMESIZE_RAW)
@@ -18,13 +19,13 @@ static void cdio_log_messages(struct cch_handler_cdio *cdio)
{
char *error_messages = cdio_cddap_errors(cdio->drive);
if (error_messages) {
- log_error("\n%s", error_messages);
+ log_error("%s", error_messages);
cdio_cddap_free_messages(error_messages);
}
char *messages = cdio_cddap_messages(cdio->drive);
if (messages) {
- log_info("\n%s", messages);
+ log_info("%s", messages);
cdio_cddap_free_messages(messages);
}
}
@@ -42,26 +43,21 @@ static nn_thread_result NNWT_THREADCALL cd_read_thread(void *userdata)
for (;;) {
if (!al_atomic_load(s32)(&cdio->running, AL_ATOMIC_RELAXED)) {
- // We don't care about how complete the disc read was.
+ // We don't care about how complete the read was.
return 0;
}
- // cdio_read() here can block for a very long time so we buffer each read
- // before writing it to the backing.
+ // cdio_read() can block for a long time so we buffer each read before writing it to the backing.
long ret = cdio_cddap_read(cdio->drive, nn_buffer_get_ptr(&cdio->buffer, 0), cdio->sector, SECTORS_PER_STEP);
cdio_log_messages(cdio);
al_assert(ret <= SECTORS_PER_STEP);
off_t pointer = cdio->sector * CDIO_CD_FRAMESIZE_RAW;
size_t size;
u8 *ptr = cdio->backing->get_ptr(cdio->backing, pointer, &size);
- if (size < BYTES_PER_STEP) {
- log_error("Tried to write over expected size during normal operation.");
- cdio->backing->unlock(cdio->backing);
- break;
- }
+ al_assert(size >= BYTES_PER_STEP);
if (ret == SECTORS_PER_STEP) {
al_memcpy(ptr, nn_buffer_get_ptr(&cdio->buffer, 0), BYTES_PER_STEP);
} else if (ret == -10) {
- log_error("I/O error, skipping sector.");
+ log_error("Disc read error, skipping sector.");
al_memset(ptr, 0, BYTES_PER_STEP);
ret = SECTORS_PER_STEP;
} else if (ret < 0) {
@@ -170,7 +166,8 @@ static bool open_cd_drive(struct cch_handler_cdio *cdio)
lsn_t start = cdio_cddap_disc_firstsector(drive);
lsn_t end = cdio_cddap_disc_lastsector(drive);
- lsn_t offset = start;
+ lsn_t first_track_start = cdio_cddap_track_firstsector(drive, first_track);
+ lsn_t offset = first_track_start;
s32 track = CDIO_INVALID_TRACK;
while (offset < end) {
track = cdio_cddap_sector_gettrack(drive, offset);
@@ -178,6 +175,7 @@ static bool open_cd_drive(struct cch_handler_cdio *cdio)
lsn_t track_start = cdio_cddap_track_firstsector(drive, track);
lsn_t track_end = cdio_cddap_track_lastsector(drive, track);
al_array_push(cdio->handler.entry->chapters, ((struct cch_chapter){ track_start, track_end }));
+ log_trace("Track #%u: %zd-%zd.", track, track_start, track_end);
offset = track_end + 1;
}
// end is a valid index so add one framesize.
diff --git a/src/cache/meson.build b/src/cache/meson.build
index 1a198eb..61be949 100644
--- a/src/cache/meson.build
+++ b/src/cache/meson.build
@@ -8,37 +8,35 @@ cache_src = [
cache_deps = []
cache_args = []
-if naunet_has_mmap
- cache_src += ['backings/file_mapped.c']
-else
- cache_src += ['backings/file.c']
+if 'file' in get_option('sources')
+ if naunet_has_mmap
+ cache_src += ['backings/file_mapped.c']
+ else
+ cache_src += ['backings/file.c']
+ endif
endif
-if naunet_has_curl
+if 'http' in get_option('sources') and naunet_has_curl
cache_src += ['handlers/http.c']
endif
-cache_have_cdio = false
-if is_linux
- libcdio_paranoia = dependency('libcdio_paranoia', required: false, allow_fallback: true)
- libcdio_cdda = dependency('libcdio_cdda', required: false, allow_fallback: true)
+if 'cdio' in get_option('sources')
+ libcdio_paranoia = dependency('libcdio_paranoia', allow_fallback: true)
+ libcdio_cdda = dependency('libcdio_cdda', allow_fallback: true)
if libcdio_paranoia.found() and libcdio_cdda.found()
cache_src += ['handlers/cdio.c']
cache_deps += [libcdio_paranoia, libcdio_cdda]
cache_args += ['-DCACHE_HAVE_CDIO']
- cache_have_cdio = true
endif
endif
-cache_have_libdvd = false
-if is_linux
- libdvdcss = dependency('libdvdcss', required: false, allow_fallback: true)
- libdvdread = dependency('dvdread', required: false, allow_fallback: true)
- libdvdnav = dependency('dvdnav', required: false, allow_fallback: true)
+if 'libdvd' in get_option('sources')
+ libdvdcss = dependency('libdvdcss', allow_fallback: true)
+ libdvdread = dependency('dvdread', allow_fallback: true)
+ libdvdnav = dependency('dvdnav', allow_fallback: true)
if libdvdcss.found() and libdvdread.found() and libdvdnav.found()
cache_deps += [libdvdcss, libdvdread, libdvdnav]
cache_args += ['-DCACHE_HAVE_LIBDVD']
- cache_have_libdvd = true
endif
endif
diff --git a/src/codec/codec.h b/src/codec/codec.h
index f32283e..6a9b654 100644
--- a/src/codec/codec.h
+++ b/src/codec/codec.h
@@ -428,7 +428,7 @@ static inline s32 camu_guess_format(str *short_name, str *ext, str *mime_type)
(short_name && al_str_eq(short_name, &al_str_c("png"))) ||
(mime_type && al_str_eq(mime_type, &al_str_c("image/png")))) {
return CAMU_CODEC_PNG;
- } else if ((ext && (al_str_eq(ext, &al_str_c(".jpeg")) || al_str_eq(ext, &al_str_c(".jpg")))) ||
+ } else if ((ext && (al_str_eq(ext, &al_str_c(".jpeg")) || al_str_eq(ext, &al_str_c(".jpg")) || al_str_eq(ext, &al_str_c(".jpe")) || al_str_eq(ext, &al_str_c(".jfif")))) ||
(short_name && al_str_eq(short_name, &al_str_c("jpeg"))) ||
(mime_type && al_str_eq(mime_type, &al_str_c("image/jpeg")))) {
return CAMU_CODEC_JPEG;
diff --git a/src/codec/codecs.c b/src/codec/codecs.c
index dbec693..a373daf 100644
--- a/src/codec/codecs.c
+++ b/src/codec/codecs.c
@@ -83,6 +83,9 @@ struct camu_codec_info *camu_codec_info_by_type(s32 type)
struct camu_codec_info *camu_codec_info_by_name(str *name)
{
+ if (al_str_eq(name, &al_str_c("pcm_s16le"))) {
+ return (struct camu_codec_info *)0xcdda;
+ }
for (u32 i = 0; i < ARRAY_SIZE(codec_information); i++) {
if (al_str_eq(&codec_information[i].name, name)) {
return &codec_information[i];
diff --git a/src/codec/ffmpeg/decoder.c b/src/codec/ffmpeg/decoder.c
index 645a88a..7d50bca 100644
--- a/src/codec/ffmpeg/decoder.c
+++ b/src/codec/ffmpeg/decoder.c
@@ -114,9 +114,6 @@ static enum AVPixelFormat get_hw_format(AVCodecContext *context, const enum AVPi
log_error("Failed to get %s HW surface format.", hwdevice_name);
// Remake AVCodecContext as a software decoder.
- // This might log an error like "Your platform doesn't support hardware accelerated AV1 decoding.".
- // Even though the problem is really a lack of any decoder hardware OR software.
- // This happens with wine + d3d11va. Maybe a build configuration issue or FFmpeg bug?
AVCodecParameters *codecpar = av->codecpar;
av->errored_hw_context = av->codec_context;
alloc_codec_context_internal(av, codec, codecpar);
diff --git a/src/codec/ffmpeg/meson.build b/src/codec/ffmpeg/meson.build
index 6482426..e34cc92 100644
--- a/src/codec/ffmpeg/meson.build
+++ b/src/codec/ffmpeg/meson.build
@@ -11,7 +11,7 @@ ffmpeg_server_deps = []
ffmpeg_client_src = [ffmpeg_src, 'decoder.c', 'resampler.c', 'scaler.c']
ffmpeg_client_deps = []
-ffmpeg_force_fallback = false
+ffmpeg_force_fallback = get_option('wrap_mode') == 'forcefallback'
foreach dep: ['ffmpeg', 'libavutil', 'libavformat', 'libavcodec', 'libavdevice', 'libswresample', 'libswscale']
if dep in get_option('force_fallback_for')
ffmpeg_force_fallback = true
diff --git a/src/fruits/cmc/cmc.c b/src/fruits/cmc/cmc.c
index 3d91ba6..f44f82f 100644
--- a/src/fruits/cmc/cmc.c
+++ b/src/fruits/cmc/cmc.c
@@ -6,8 +6,6 @@
#include "../../liana/list.h"
#include "../../codec/packet_ext.h"
-#include "../common.h"
-
#include "cmc.h"
#include "ui/ui.h"
@@ -281,9 +279,13 @@ s32 main(s32 argc, char *argv[])
camu_post_cache_init(&c.cache);
al_array_init(c.searches);
+ str user = al_str_c("guest");
+ char *CMC_USER = getenv("CMC_USER");
+ if (CMC_USER) user = al_str_cr(CMC_USER);
+
c.client.callback = client_callback;
c.client.userdata = &c;
- if (!camu_client_login(&c.client, &al_str_c("andrew"), &c.loop, CAMU_TEST_TYPE, &CAMU_TEST_ADDR, CAMU_PORT)) {
+ if (!camu_client_login(&c.client, &user, &c.loop, NNWT_SOCKET_TCP, &al_str_c("127.0.0.1"), CAMU_PORT)) {
goto err;
}
diff --git a/src/fruits/cmc/meson.build b/src/fruits/cmc/meson.build
index de6d335..bb9d38d 100644
--- a/src/fruits/cmc/meson.build
+++ b/src/fruits/cmc/meson.build
@@ -20,7 +20,7 @@ if use_tui
endif
if is_windows and not is_msvc
- cmc_args += ['-static', '-static-libgcc', '-static-libstdc++', '-municode', '-mwindows']
+ cmc_args += ['-static', '-static-libgcc', '-static-libstdc++', '-mwindows', '-municode']
endif
-executable('cmc', cmc_src, dependencies: cmc_deps, link_args: cmc_args, install: true)
+executable('cmc', cmc_src, dependencies: cmc_deps, c_args: cmc_args, link_args: cmc_args, install: true)
diff --git a/src/fruits/cmc/ui/util/waveform.c b/src/fruits/cmc/ui/util/waveform.c
index bed34b7..624069e 100644
--- a/src/fruits/cmc/ui/util/waveform.c
+++ b/src/fruits/cmc/ui/util/waveform.c
@@ -22,8 +22,8 @@
// extra: #f6ac7f, #F5F4FA, #5b524b
// left channel: n == 0, right channel: n == 1.
#define COLOR_FOR_PIXEL(n, rms) rms ? \
- n == 0 ? nn_htonl(0xE4CAAF99) : nn_htonl(0xFF928FFF) : \
- n == 0 ? nn_htonl(0xF4EAE099) : nn_htonl(0xBC615EFF)
+ n == 0 ? nn_htonl(0xe4caaf99) : nn_htonl(0xff928fff) : \
+ n == 0 ? nn_htonl(0xf4eae099) : nn_htonl(0xbc615eff)
// ss: current sample, ns: next sample.
#define HIT_WAVE(low, high, ss, ns) \
diff --git a/src/fruits/cmsrv/cmsrv.c b/src/fruits/cmsrv/cmsrv.c
index ef66be8..babd5ef 100644
--- a/src/fruits/cmsrv/cmsrv.c
+++ b/src/fruits/cmsrv/cmsrv.c
@@ -1,8 +1,7 @@
-#define CMSRV_LOCAL_SOCKET
-#define CMSRV_USE_UI
-
+#define AL_LOG_SECTION "cmsrv"
#include <al/log.h>
#include <nnwt/common.h>
+#define CMSRV_LOCAL_SOCKET
#ifdef CMSRV_LOCAL_SOCKET
#include <nnwt/line_processor.h>
#endif
@@ -15,9 +14,9 @@
#include "../../cache/handlers/cdio.h"
#endif
-#include "../common.h"
-
+#ifdef CMSRV_USE_UI
#include "ui.h"
+#endif
struct cmsrv {
struct nn_event_loop loop;
@@ -186,8 +185,13 @@ s32 main(s32 argc, char *argv[])
nn_signal_start(&s.quit_signal);
signal(SIGINT, sigint_handler);
+ str addr = al_str_c("127.0.0.1");
+ char *CMSRV_IP = getenv("CMSRV_IP");
+ if (CMSRV_IP) addr = al_str_cr(CMSRV_IP);
+ log_info("Serving from %.*s.", al_str_x(&addr));
+
camu_server_init(&s.server, &s.loop);
- if (!camu_server_listen(&s.server, CAMU_TEST_TYPE, &CAMU_TEST_ADDR, CAMU_PORT)) {
+ if (!camu_server_listen(&s.server, NNWT_SOCKET_TCP, &addr, CAMU_PORT)) {
return EXIT_FAILURE;
}
@@ -198,7 +202,7 @@ s32 main(s32 argc, char *argv[])
s.local.cli.userdata = &s;
nn_line_processor_init(&s.local.cli, &al_str_c("\n"));
nn_line_processor_open_socket(&s.local.cli, &s.local.sock);
- if (nn_socket_bind(&s.local.sock, &CAMU_TEST_CONTROL_PATH, 0) && nn_socket_listen(&s.local.sock)) {
+ if (nn_socket_bind(&s.local.sock, &al_str_c("/tmp/camu_control"), 0) && nn_socket_listen(&s.local.sock)) {
nn_line_processor_run(&s.local.cli, &s.loop);
}
#endif
diff --git a/src/fruits/cmsrv/meson.build b/src/fruits/cmsrv/meson.build
index b12d00d..bfeabe0 100644
--- a/src/fruits/cmsrv/meson.build
+++ b/src/fruits/cmsrv/meson.build
@@ -2,14 +2,14 @@ cmsrv_src = ['cmsrv.c']
cmsrv_deps = [common_deps, server]
cmsrv_args = []
-use_tui = true
-if use_tui
+if use_tuis
cmsrv_src += ['ui.c']
cmsrv_deps += [notcurses_core]
+ cmsrv_args += ['-DCMSRV_USE_UI']
endif
if is_windows and not is_msvc
- cmsrv_args += ['-static', '-static-libgcc', '-static-libstdc++', '-municode', '-mwindows']
+ cmsrv_args += ['-static', '-static-libgcc', '-static-libstdc++', '-mwindows', '-municode']
endif
-executable('cmsrv', cmsrv_src, dependencies: cmsrv_deps, link_args: cmsrv_args, install: true)
+executable('cmsrv', cmsrv_src, dependencies: cmsrv_deps, c_args: cmsrv_args, link_args: cmsrv_args, install: true)
diff --git a/src/fruits/cmsrv/ui.c b/src/fruits/cmsrv/ui.c
index d96c09d..afbee58 100644
--- a/src/fruits/cmsrv/ui.c
+++ b/src/fruits/cmsrv/ui.c
@@ -29,6 +29,7 @@ bool cmsrv_ui_init(struct cmsrv_ui *ui, struct camu_server *server)
ncplane_set_userptr(stdplane, ui);
ui->pending_layout = true;
al_array_init(ui->log.messages);
+ nn_mutex_init(&ui->log.lock);
return true;
}
@@ -51,7 +52,9 @@ void cmsrv_ui_set_pane(struct cmsrv_ui *ui, u8 pane)
void cmsrv_ui_push_message(struct cmsrv_ui *ui, char *message)
{
+ nn_mutex_lock(&ui->log.lock);
al_array_push(ui->log.messages, message);
+ nn_mutex_unlock(&ui->log.lock);
}
static void layout_log(struct cmsrv_ui *ui, struct ncplane *parent)
@@ -80,6 +83,7 @@ static void render_log(struct cmsrv_ui *ui)
u32 max_width = ncplane_dim_x(n) - 2;
u32 max_height = ncplane_dim_y(n) - 2;
+ nn_mutex_lock(&ui->log.lock);
u32 size = ui->log.messages.count;
if (size > max_height) {
u32 index = size - max_height;
@@ -97,6 +101,7 @@ static void render_log(struct cmsrv_ui *ui)
// messages will have wide characters.
ncplane_putstr_yx(n, y, x, al_array_at(ui->log.messages, i));
}
+ nn_mutex_unlock(&ui->log.lock);
u64 c = 0;
ncchannels_set_fg_default(&c);
@@ -251,4 +256,5 @@ void cmsrv_ui_close(struct cmsrv_ui *ui)
al_free(message);
}
al_array_free(ui->log.messages);
+ nn_mutex_destroy(&ui->log.lock);
}
diff --git a/src/fruits/cmsrv/ui.h b/src/fruits/cmsrv/ui.h
index ebf7c1a..3615d3b 100644
--- a/src/fruits/cmsrv/ui.h
+++ b/src/fruits/cmsrv/ui.h
@@ -19,6 +19,7 @@ struct cmsrv_ui {
struct {
struct ncplane *n;
array(char *) messages;
+ struct nn_mutex lock;
} log;
struct {
struct ncplane *n;
diff --git a/src/fruits/cmv/cmv.c b/src/fruits/cmv/cmv.c
index fe0fbd6..535bf72 100644
--- a/src/fruits/cmv/cmv.c
+++ b/src/fruits/cmv/cmv.c
@@ -13,8 +13,6 @@
#include "../../codec/ffmpeg/common.h"
#endif
-#include "../common.h"
-
#if !defined CAMU_SINK_ONLY && !defined NAUNET_ON_WINDOWS
// https://unix.stackexchange.com/a/16884
#define PID_MAX_STR 7 // 0x400000 (2^22).
@@ -46,13 +44,32 @@ static nn_thread_result NNWT_THREADCALL event_loop_thread(void *userdata)
return 0;
}
-// sink-p12-108.52.160.112-aEN-v-sEN.exe
-static bool parse_exe_name_params(str *exe_name, str *addr, struct lia_prefs *prefs)
+static bool decode_addr(str *enc, str *addr)
+{
+ char value[4]; // max = 255.
+ for (u32 i = 0; i < 8; i += 2) {
+ str sub = al_str_substr(enc, i, i + 2);
+ bool error;
+ s64 l = al_str_to_long(&sub, 16, &error);
+ al_assert(l >= 0 && l <= 255);
+ if (error) return false;
+ al_snprintf(value, sizeof(value), "%hhu", (u8)l);
+ al_str_cat(addr, &al_str_cs(value));
+ if (i != 6) {
+ al_str_cat(addr, &al_str_c("."));
+ }
+ }
+ return true;
+}
+
+// sink-p12-976502a7.exe
+static bool parse_exe_name(str *exe_name, str *addr)
{
u32 index = al_str_rfind(exe_name, '/');
if (index == AL_STR_NO_POS) {
- index = al_str_rfind(exe_name, '\\');
- if (index == AL_STR_NO_POS) goto def;
+ if ((index = al_str_rfind(exe_name, '\\')) == AL_STR_NO_POS) {
+ goto def;
+ }
}
str sub = al_str_substr(exe_name, index + 1, exe_name->length);
@@ -62,51 +79,20 @@ static bool parse_exe_name_params(str *exe_name, str *addr, struct lia_prefs *pr
sub = al_str_substr(&sub, 5, sub.length);
// Skip version.
- index = al_str_find(&sub, '-');
- if (index == AL_STR_NO_POS) goto def;
+ if ((index = al_str_find(&sub, '-')) == AL_STR_NO_POS) {
+ goto def;
+ }
sub = al_str_substr(&sub, index + 1, sub.length);
- index = al_str_find(&sub, '-');
- if (index == AL_STR_NO_POS) goto def;
- al_str_clone(addr, &al_str_substr(&sub, 0, index));
-
- for (;;) {
- sub = al_str_substr(&sub, index + 1, sub.length);
- index = al_str_find(&sub, '-');
- if (index == AL_STR_NO_POS) {
- index = al_str_find(&sub, '.');
- if (index == AL_STR_NO_POS) {
- index = sub.length;
- } else {
- sub.length = index;
- }
- }
-
- str value;
- al_str_clone(&value, &al_str_substr(&sub, 0, index));
- al_str_to_lower(&value);
- if (al_str_at(&value, 0) == 'a') {
- prefs->enabled_mask |= 1 << CAMU_STREAM_AUDIO;
- } else if (al_str_at(&value, 0) == 'v') {
- prefs->enabled_mask |= 1 << CAMU_STREAM_VIDEO;
- } else if (al_str_at(&value, 0) == 's') {
- prefs->enabled_mask |= 1 << CAMU_STREAM_SUBTITLE;
- }
- al_str_free(&value);
-
- if (index == sub.length) break;
+ if ((index = al_str_find(&sub, '.')) != AL_STR_NO_POS) {
+ sub = al_str_substr(&sub, 0, index);
}
+ al_str_from(addr, "");
+ decode_addr(&sub, addr);
+ log_debug("Parsed address %.*s from exe filename.", al_str_x(addr));
return true;
def:
-#ifdef CAMU_HAVE_SUBTITLES
- prefs->enabled_mask = (1 << CAMU_STREAM_AUDIO) | (1 << CAMU_STREAM_VIDEO) | (1 << CAMU_STREAM_SUBTITLE);
-#else
- prefs->enabled_mask = (1 << CAMU_STREAM_AUDIO) | (1 << CAMU_STREAM_VIDEO);
-#endif
- prefs->audio_lang = CAMU_LANG_JAPANESE;
- prefs->subtitle_lang = CAMU_LANG_ENGLISH;
-
return false;
}
@@ -141,12 +127,12 @@ s32 window_system_main(u32 argc, str *argv, void *extra)
u8 type;
str addr;
- if (!parse_exe_name_params(&argv[0], &addr, &c.desktop.sink.prefs)) {
+ if (!parse_exe_name(&argv[0], &addr)) {
#ifndef CAMU_SINK_ONLY
if (local) {
#ifdef NAUNET_ON_WINDOWS
- type = NNWT_SOCKET_TCP;;
- al_str_clone(&addr, &CAMU_LOCALHOST);
+ type = NNWT_SOCKET_TCP;
+ al_str_clone(&addr, &al_str_c("127.0.0.1"));
#else
type = NNWT_SOCKET_UNIX;
al_str_clone(&addr, &CMV_UNIX_PATH);
@@ -155,17 +141,26 @@ s32 window_system_main(u32 argc, str *argv, void *extra)
al_str_cat(&addr, &al_str_cs(pid_str));
#endif
} else {
- type = CAMU_TEST_TYPE;
- al_str_clone(&addr, &CAMU_TEST_ADDR);
+ type = NNWT_SOCKET_TCP;
+ al_str_clone(&addr, &al_str_c("127.0.0.1"));
}
#else
type = NNWT_SOCKET_TCP;
- al_str_clone(&addr, &CAMU_TEST_ADDR);
+ al_str_clone(&addr, &al_str_c("127.0.0.1"));
#endif
} else {
type = NNWT_SOCKET_TCP;
}
+ struct lia_prefs *prefs = &c.desktop.sink.prefs;
+#ifdef CAMU_HAVE_SUBTITLES
+ prefs->enabled = (1 << CAMU_STREAM_AUDIO) | (1 << CAMU_STREAM_VIDEO) | (1 << CAMU_STREAM_SUBTITLE);
+#else
+ prefs->enabled = (1 << CAMU_STREAM_AUDIO) | (1 << CAMU_STREAM_VIDEO);
+#endif
+ prefs->language.audio = CAMU_LANG_JAPANESE;
+ prefs->language.subtitles = CAMU_LANG_ENGLISH;
+
#ifndef CAMU_SINK_ONLY
if (local) {
camu_server_init(&c.server, &c.loop);
diff --git a/src/fruits/cmv/meson.build b/src/fruits/cmv/meson.build
index bfd2182..3e78f05 100644
--- a/src/fruits/cmv/meson.build
+++ b/src/fruits/cmv/meson.build
@@ -15,7 +15,7 @@ endif
cmv_link_args = []
if is_windows and not is_msvc
- cmv_link_args += ['-static', '-static-libgcc', '-static-libstdc++', '-municode', '-mwindows']
+ cmv_link_args += ['-static', '-static-libgcc', '-static-libstdc++', '-mwindows', '-municode']
endif
executable('cmv', cmv_src, dependencies: cmv_deps, c_args: cmv_args, link_args: cmv_link_args, install: true)
diff --git a/src/fruits/common.h b/src/fruits/common.h
deleted file mode 100644
index 66a72bc..0000000
--- a/src/fruits/common.h
+++ /dev/null
@@ -1,24 +0,0 @@
-#include <al/str.h>
-#include <nnwt/socket.h>
-
-AL_IGNORE_WARNING("-Wunused-variable")
-
-static str CAMU_DB_PATH = al_str_c("/home/andrew/c/camu/data/camu_db_test");
-
-static str CAMU_TEST_IP = al_str_c("108.52.160.112");
-static str CAMU_LOCALHOST = al_str_c("127.0.0.1");
-
-static str CAMU_TEST_PATH = al_str_c("/tmp/camu_sock");
-static str CAMU_TEST_CONTROL_PATH = al_str_c("/tmp/camu_control_sock");
-
-AL_IGNORE_WARNING_END
-
-#ifdef NAUNET_ON_WINDOWS
-#define CAMU_TEST_TYPE NNWT_SOCKET_TCP
-#define CAMU_TEST_ADDR CAMU_TEST_IP
-#else
-//#define CAMU_TEST_TYPE NNWT_SOCKET_UNIX
-//#define CAMU_TEST_ADDR CAMU_TEST_PATH
-#define CAMU_TEST_TYPE NNWT_SOCKET_TCP
-#define CAMU_TEST_ADDR CAMU_TEST_IP
-#endif
diff --git a/src/fruits/ctv/ctv.c b/src/fruits/ctv/ctv.c
index 45942e6..8b173b0 100644
--- a/src/fruits/ctv/ctv.c
+++ b/src/fruits/ctv/ctv.c
@@ -24,12 +24,12 @@ static nn_thread_result NNWT_THREADCALL event_loop_thread(void *userdata)
struct lia_prefs *prefs = &c->desktop.sink.prefs;
#ifdef CAMU_HAVE_SUBTITLES
- prefs->enabled_mask = (1 << CAMU_STREAM_AUDIO) | (1 << CAMU_STREAM_VIDEO) | (1 << CAMU_STREAM_SUBTITLE);
+ prefs->enabled = (1 << CAMU_STREAM_AUDIO) | (1 << CAMU_STREAM_VIDEO) | (1 << CAMU_STREAM_SUBTITLE);
#else
- prefs->enabled_mask = (1 << CAMU_STREAM_AUDIO) | (1 << CAMU_STREAM_VIDEO);
+ prefs->enabled = (1 << CAMU_STREAM_AUDIO) | (1 << CAMU_STREAM_VIDEO);
#endif
- prefs->audio_lang = CAMU_LANG_JAPANESE;
- prefs->subtitle_lang = CAMU_LANG_ENGLISH;
+ prefs->language.audio = CAMU_LANG_JAPANESE;
+ prefs->language.subtitles = CAMU_LANG_ENGLISH;
if (!camu_desktop_connect(&c->desktop, &al_str_c("ctv"), &c->loop, CAMU_TEST_TYPE, &CAMU_TEST_ADDR, CAMU_PORT)) {
return 0;
diff --git a/src/fruits/meson.build b/src/fruits/meson.build
index 37edd0b..c82d71d 100644
--- a/src/fruits/meson.build
+++ b/src/fruits/meson.build
@@ -1,4 +1,9 @@
-if (get_option('server').enabled() or get_option('client').enabled()) and not is_windows
+use_tuis = true
+if is_windows
+ use_tuis = false
+endif
+
+if use_tuis and (get_option('server').enabled() or get_option('client').enabled())
notcurses = dependency('notcurses', required: false, allow_fallback: false)
notcurses_core = dependency('notcurses-core', required: false, allow_fallback: false)
if not notcurses.found() or not notcurses_core.found()
diff --git a/src/liana/client.c b/src/liana/client.c
index 380ed26..4d5689f 100644
--- a/src/liana/client.c
+++ b/src/liana/client.c
@@ -136,46 +136,52 @@ static void parse_info_packet(struct lia_client *client, struct nn_packet *packe
nn_packet_read_str(packet, &handler);
client->duration = nn_packet_read_u64(packet);
collect_streams(client, packet);
+ if (client->streams.count == 0) {
+ log_warn("Resource has no streams.");
+ goto out;
+ }
struct lia_prefs *prefs = &client->prefs;
u8 selected = 0;
- u32 accept_defaults = 0;
+ u8 accept_defaults = 0;
for (; accept_defaults < 2; accept_defaults++) {
struct camu_codec_stream *stream;
al_array_foreach_ptr(client->streams, i, stream) {
- u8 type_mask = 1 << stream->type;
- if ((selected & type_mask) || !(prefs->enabled_mask & type_mask)) continue;
+ u8 type = stream->type;
+ if (type == CAMU_STREAM_SUBTITLE && client->mask == 0) {
+ log_warn("Ignoring subtitle-only resource.");
+ goto out;
+ }
+ if ((selected & (1 << type)) || !(prefs->enabled & (1 << type))) {
+ continue;
+ }
const char *title = NULL;
#ifdef CAMU_HAVE_FFMPEG
if (stream->mode == CAMU_FFMPEG_COMPAT) {
AVDictionary *metadata = stream->av.stream->metadata;
const AVDictionaryEntry *title_entry = av_dict_get(metadata, "title", NULL, 0);
- if (title_entry) title = title_entry->value;
+ if (title_entry) {
+ title = title_entry->value;
+ }
const AVDictionaryEntry *lang_entry = av_dict_get(metadata, "language", NULL, 0);
if (lang_entry && !accept_defaults) {
u8 lang = CAMU_LANG_UNKNOWN;
- str lang_str = al_str_cr(lang_entry->value);
- if (al_str_eq(&lang_str, &al_str_c("eng"))) lang = CAMU_LANG_ENGLISH;
- else if (al_str_eq(&lang_str, &al_str_c("jpn"))) lang = CAMU_LANG_JAPANESE;
- switch (stream->type) {
- case CAMU_STREAM_AUDIO:
- if (prefs->audio_lang != lang) continue;
- break;
- case CAMU_STREAM_SUBTITLE:
- if (prefs->subtitle_lang != lang) continue;
- break;
- default:
- break;
+ str s = al_str_cr(lang_entry->value);
+ if (al_str_eq(&s, &al_str_c("eng"))) lang = CAMU_LANG_ENGLISH;
+ else if (al_str_eq(&s, &al_str_c("jpn"))) lang = CAMU_LANG_JAPANESE;
+ if ((type == CAMU_STREAM_AUDIO && lang != prefs->language.audio) ||
+ (type == CAMU_STREAM_SUBTITLE && lang != prefs->language.subtitles)) {
+ continue;
}
+ break;
}
}
#endif
if (title) {
- log_info("Selected %s stream (index: %u, title: %s).", stream_type_to_str[stream->type], stream->index, title);
+ log_info("Selected %s stream (index: %u, title: %s).", stream_type_to_str[type], stream->index, title);
} else {
- log_info("Selected %s stream (index: %u).", stream_type_to_str[stream->type], stream->index);
+ log_info("Selected %s stream (index: %u).", stream_type_to_str[type], stream->index);
}
- selected |= type_mask;
- client->mask |= 1 << stream->index;
+ selected |= (1 << type);
struct lia_vcr_track *track = al_alloc_object(struct lia_vcr_track);
track->stream = stream;
track->client = lia_handler_by_name(&handler)->create_client_handler();
@@ -187,10 +193,12 @@ static void parse_info_packet(struct lia_client *client, struct nn_packet *packe
// This will NOT attempt to select another stream.
continue;
}
+ client->mask |= 1 << stream->index;
client->callback(client->userdata, LIANA_CLIENT_CONFIGURE, stream, track);
lia_vcr_add_track(&client->vcr, track);
}
}
+out:
client->callback(client->userdata, LIANA_CLIENT_CONFIGURE_COMPLETE, NULL, NULL);
}
@@ -201,6 +209,7 @@ static void info_packet_callback(void *userdata, struct nn_packet_stream *stream
parse_info_packet(client, packet);
nn_packet_stream_return_packet(stream, packet);
if (client->mask == 0 || lia_vcr_is_empty(&client->vcr)) {
+ log_warn("Discarding resource with no applicable streams.");
al_assert(client->reconnect == RECONNECT_NONE);
nn_packet_stream_disconnect(&client->data);
return;
diff --git a/src/liana/client.h b/src/liana/client.h
index 40093e0..681b134 100644
--- a/src/liana/client.h
+++ b/src/liana/client.h
@@ -27,9 +27,11 @@ struct lia_reconnect_info {
};
struct lia_prefs {
- u8 enabled_mask;
- s8 audio_lang;
- s8 subtitle_lang;
+ u8 enabled;
+ struct {
+ u8 audio;
+ u8 subtitles;
+ } language;
};
struct lia_client {
diff --git a/src/liana/handlers/codec_client.c b/src/liana/handlers/codec_client.c
index abb5b9a..c3dded1 100644
--- a/src/liana/handlers/codec_client.c
+++ b/src/liana/handlers/codec_client.c
@@ -33,7 +33,7 @@ static bool codec_client_init(struct lia_client_handler *handler, struct camu_re
static bool push_av_packet(struct lia_codec_client *codec, AVPacket *pkt)
{
s32 ret = codec->dec->push_av_packet(codec->dec, pkt);
- if (ret == AVERROR(EAGAIN)) {
+ while (ret == AVERROR(EAGAIN)) {
codec->dec->process(codec->dec);
ret = codec->dec->push_av_packet(codec->dec, pkt);
}
diff --git a/src/liana/list.c b/src/liana/list.c
index b47a30c..253ba5f 100644
--- a/src/liana/list.c
+++ b/src/liana/list.c
@@ -91,6 +91,9 @@ static void unref_all_entries(struct lia_list *list, s32 trigger)
}
}
+// @TODO: When implementing list_remove(), consider this case.
+// remove() sequence 0 causes a skip to sequence 1 and sequence 1 fails to load.
+// Do we properly move to list->current = -1 and an idle list?
static bool entry_load_and_get_duration(struct lia_list *list, struct lia_list_entry *entry, s32 sequence, bool *error)
{
u8 last_load = entry->load;
@@ -99,9 +102,6 @@ static bool entry_load_and_get_duration(struct lia_list *list, struct lia_list_e
// If sequence is <0 that must mean entry is not yet added to list->entries.
if (sequence >= 0) {
al_array_remove_at(list->entries, (u32)sequence);
- // @TODO: Consider this when implementing list_remove().
- // In the case of remove sequence 0, causing a skip to sequence 1, and sequence 1
- // fails to load, do we properly move to -1 and an idle list.
if (list->current > sequence) {
struct lia_list_sink *sink;
al_array_foreach(list->sinks, i, sink) {
@@ -111,6 +111,7 @@ static bool entry_load_and_get_duration(struct lia_list *list, struct lia_list_e
list->current--;
}
struct lia_list_cmd *cmd = list->cmd;
+ al_assert(cmd);
al_assert(cmd->sequence != sequence);
if (cmd->sequence > sequence) {
cmd->sequence--;
@@ -141,6 +142,19 @@ static bool entry_load_and_get_duration(struct lia_list *list, struct lia_list_e
static void unload_all_entires(struct lia_list *list)
{
+ struct lia_list_cmd *cmd = list->cmd;
+ if (cmd && cmd->entry) {
+ al_assert(cmd->op == ADD);
+ entry_unload(list, cmd->entry);
+ list->cmd = NULL;
+ }
+ al_array_foreach_rev(list->command_queue, i, cmd) {
+ if (cmd->entry) {
+ al_assert(cmd->op == ADD);
+ entry_unload(list, cmd->entry);
+ al_array_remove_at(list->command_queue, i);
+ }
+ }
struct lia_list_entry *entry;
al_array_foreach(list->entries, i, entry) {
entry_unload(list, entry);
@@ -162,6 +176,7 @@ static inline void sink_pause_entry(struct lia_list_sink *sink, struct lia_list_
sink->callback(sink->userdata, LIANA_SINK_PAUSE, entry, sequence, time);
}
+// @TODO: This should probably give a sequence or ID.
static inline void sink_unset_entry(struct lia_list_sink *sink)
{
sink->callback(sink->userdata, LIANA_SINK_UNSET, NULL, -1, NULL);
@@ -341,13 +356,13 @@ static bool handle_skipto(struct lia_list *list, s32 sequence, s32 index)
bool error;
if (!entry_load_and_get_duration(list, target, index, &error)) {
if (error) {
- // index might point to a different entry after an error.
+ // On an error, sequence will address the same entry but index may not.
return handle_skipto(list, sequence, index);
}
return false;
}
- u64 at = nn_get_timestamp() + LIANA_PAUSE_DELAY;
+ u64 at = nn_get_timestamp() + LIANA_BASE_PING;
u64 pos = target->offset;
u8 pause = skipto_entry(current, target, at);
@@ -438,6 +453,7 @@ static void handle_toggle_pause(struct lia_list *list, s32 sequence, f64 pts)
case LIANA_PAUSE_PAUSE: {
al_assert(entry->start != LIANA_TIMESTAMP_INVALID);
entry->paused_at = now + LIANA_PAUSE_DELAY;
+ // @TODO: There is no reason we couldn't pause during a pre-roll.
if (entry->paused_at < entry->start) {
entry->paused_at = entry->start;
}
@@ -603,6 +619,8 @@ static bool adjust_for_order_change(struct lia_list *list, struct lia_list_entry
}
}
al_assert(cmd->op == SKIPTO);
+ // This SKIPTO being in-place is a consideration for the sequence of any
+ // commands that were queued after this one.
return handle_skipto(list, cmd->sequence, cmd->arg0.i);
}
diff --git a/src/liana/list.h b/src/liana/list.h
index 61dcb1d..c8b1d73 100644
--- a/src/liana/list.h
+++ b/src/liana/list.h
@@ -9,7 +9,7 @@
#define LIANA_SEQUENCE_ANY -1
#define LIANA_BASE_DELAY 450000u // 450ms
-#define LIANA_BASE_PING 125000u // 125ms
+#define LIANA_BASE_PING 150000u // 150ms
#define LIANA_PAUSE_DELAY LIANA_BASE_PING
#define LIANA_DELAY_IGNORE 0u
@@ -100,7 +100,7 @@ struct lia_list_cmd {
struct lia_list_entry *entry;
s32 sequence;
union { s32 i; u32 u; } arg0;
- union { f64 f; u64 l; u32 u; } arg1;
+ union { u64 l; u32 u; f64 f; } arg1;
};
struct lia_list {
diff --git a/src/liana/meson.build b/src/liana/meson.build
index 5c1cc42..40d4598 100644
--- a/src/liana/meson.build
+++ b/src/liana/meson.build
@@ -5,27 +5,28 @@ liana_server_src = [
'handlers.c',
'process.c'
]
+
liana_client_src = [
'client.c',
'vcr.c',
'handlers/codec_client.c',
'handlers.c'
]
+
liana_server_deps = [cache, codecs_server, codecs_client] # codecs_client for transcode.
liana_client_deps = [codecs_client]
-liana_args = []
-if cache_have_cdio
+if 'cdio' in get_option('sources')
liana_server_src += ['handlers/cdio_server.c']
liana_client_src += ['handlers/cdio_client.c']
endif
-if cache_have_libdvd
+if 'libdvd' in get_option('sources')
liana_server_src += ['handlers/dvd_server.c']
liana_client_src += ['handlers/dvd_client.c']
endif
liana_server = declare_dependency(sources: liana_server_src,
- dependencies: liana_server_deps, compile_args: [liana_args, '-DLIANA_SERVER'])
+ dependencies: liana_server_deps, compile_args: ['-DLIANA_SERVER'])
liana_client = declare_dependency(sources: liana_client_src,
- dependencies: liana_client_deps, compile_args: [liana_args, '-DLIANA_CLIENT'])
+ dependencies: liana_client_deps, compile_args: ['-DLIANA_CLIENT'])
diff --git a/src/liana/vcr.c b/src/liana/vcr.c
index 13082f5..4fa548c 100644
--- a/src/liana/vcr.c
+++ b/src/liana/vcr.c
@@ -7,9 +7,9 @@
#include "vcr.h"
#include "common.h"
-#define VCR_BUFFER_BUFFERED MB(4)
-#define VCR_BUFFER_GROW_FACTOR 8
-#define VCR_BUFFER_LOW_OFFSET MB(1)
+#define VCR_BUFFER_BUFFERED MB(6LL)
+#define VCR_BUFFER_GROW_FACTOR 8LL
+#define VCR_BUFFER_LOW_OFFSET KB(500LL)
AL_STATIC_ASSERT(buf_gt_low_offset, VCR_BUFFER_BUFFERED * VCR_BUFFER_GROW_FACTOR, >, VCR_BUFFER_LOW_OFFSET);
enum {
@@ -67,12 +67,12 @@ static void update_metrics(struct lia_vcr *vcr, u64 size)
u64 diff = now - vcr->metrics.last_report_ts;
u64 mark = now - vcr->metrics.last_report_mark;
u64 frame = vcr->metrics.current_frame;
- if (mark > 500000 || (diff > 2000000 && vcr->metrics.current_frame >= KB(500)) || (!size && frame > 0)) {
+ if (mark > 750000 || (diff > 5000000 && vcr->metrics.current_frame >= KB(500)) || (!size && frame > 0)) {
al_assert(frame > 0);
f32 kbps = (frame / 125.f) / (mark / 1000000.f);
f32 average_kbps = vcr->metrics.average_kbps;
average_kbps = average_kbps == 0.f ? kbps : (average_kbps + kbps) / 2.f;
- f32 buffered = al_atomic_load(u64)(&vcr->count, AL_ATOMIC_RELAXED) / (f32)MB(1);
+ f32 buffered = atomic_load(u64)(&vcr->count, AL_ATOMIC_RELAXED) / (f32)MB(1);
f32 capacity = vcr->mark.buffered / (f32)MB(1);
log_info("Receiving packets at %.2fkbps (%.2f/%.2fMB).", average_kbps, buffered, capacity);
vcr->metrics.average_kbps = average_kbps;
@@ -88,9 +88,9 @@ void lia_vcr_init(struct lia_vcr *vcr, struct nn_event_loop *loop, struct nn_pac
vcr->data = data;
vcr->node_id = node_id;
al_array_init(vcr->tracks);
- al_atomic_store(u64)(&vcr->count, 0, AL_ATOMIC_RELAXED);
+ atomic_store(u64)(&vcr->count, 0, AL_ATOMIC_RELAXED);
vcr->mark.buffered = VCR_BUFFER_BUFFERED;
- al_atomic_store(u64)(&vcr->mark.low, 0, AL_ATOMIC_RELAXED);
+ atomic_store(u64)(&vcr->mark.low, 0, AL_ATOMIC_RELAXED);
vcr->expand = VCR_EXPAND_UNTOUCHED;
vcr->started = false;
#ifndef CAMU_DIRECT_MODE
@@ -149,10 +149,10 @@ static nn_thread_result NNWT_THREADCALL vcr_track_thread(void *userdata)
if (packet) {
// Check if we should uncork the packet stream.
u32 size = nn_packet_get_size(packet);
- u64 buffer = al_atomic_sub(u64)(&vcr->count, size, AL_ATOMIC_RELAXED);
+ u64 buffer = atomic_sub(u64)(&vcr->count, size, AL_ATOMIC_RELAXED);
#ifndef CAMU_DIRECT_MODE
- bool buffered = al_atomic_load(bool)(&track->buffered, AL_ATOMIC_RELAXED);
- u64 low = al_atomic_load(u64)(&vcr->mark.low, AL_ATOMIC_RELAXED);
+ bool buffered = atomic_load(bool)(&track->buffered, AL_ATOMIC_RELAXED);
+ u64 low = atomic_load(u64)(&vcr->mark.low, AL_ATOMIC_RELAXED);
if (buffered && (low && buffer <= low)) {
nn_signal_send(&vcr->signal);
}
@@ -176,7 +176,7 @@ static nn_thread_result NNWT_THREADCALL vcr_track_thread(void *userdata)
}
// Take state again because handle_packet() could have caused the track to be corked.
- state = al_atomic_load(s32)(&track->state, AL_ATOMIC_RELAXED);
+ state = atomic_load(s32)(&track->state, AL_ATOMIC_RELAXED);
// We wait if corked (TRACK_STOPPED) or EOF.
corked = (packet && state == VCR_TRACK_STOPPED) || !packet;
@@ -199,7 +199,7 @@ static nn_thread_result NNWT_THREADCALL vcr_track_thread(void *userdata)
nn_cond_wait(&track->cond, &track->mutex);
// Check for possibly updated state.
- state = al_atomic_load(s32)(&track->state, AL_ATOMIC_RELAXED);
+ state = atomic_load(s32)(&track->state, AL_ATOMIC_RELAXED);
nn_mutex_unlock(&track->mutex);
@@ -254,11 +254,11 @@ void lia_vcr_add_track(struct lia_vcr *vcr, struct lia_vcr_track *track)
track->vcr = vcr;
nn_cond_init(&track->cond);
nn_mutex_init(&track->mutex);
- al_atomic_store(bool)(&track->buffered, !VCR_TRACK_THREADED(track), AL_ATOMIC_RELAXED);
+ atomic_store(bool)(&track->buffered, !VCR_TRACK_THREADED(track), AL_ATOMIC_RELAXED);
track->running = false;
nn_packet_cache_init(&track->cache, 256);
al_array_push(vcr->tracks, track);
- al_atomic_store(s32)(&track->state, VCR_TRACK_RUNNING, AL_ATOMIC_RELAXED);
+ atomic_store(s32)(&track->state, VCR_TRACK_RUNNING, AL_ATOMIC_RELAXED);
}
bool lia_vcr_remove_track_by_stream(struct lia_vcr *vcr, struct camu_codec_stream *stream)
@@ -298,13 +298,13 @@ static void cork_if_buffered(struct lia_vcr *vcr, u64 buffer)
bool buffered = true;
struct lia_vcr_track *track;
al_array_foreach(vcr->tracks, i, track) {
- buffered &= al_atomic_load(bool)(&track->buffered, AL_ATOMIC_RELAXED);
+ buffered &= atomic_load(bool)(&track->buffered, AL_ATOMIC_RELAXED);
}
if (buffered) {
if (vcr->expand == VCR_EXPAND_UNTOUCHED) {
vcr->mark.buffered = buffer * VCR_BUFFER_GROW_FACTOR;
vcr->expand = VCR_EXPAND_GROWN;
- log_info("Expanded buffer to size %.2fMB.", vcr->mark.buffered / (f32)MB(1));
+ log_debug("Expanded buffer to size %.2fMB.", vcr->mark.buffered / (f32)MB(1));
return;
}
if (!vcr->corked) {
@@ -316,7 +316,7 @@ static void cork_if_buffered(struct lia_vcr *vcr, u64 buffer)
// Don't set low until after we corked so that vcr_track_thread() will never try uncorking
// until we know what the low mark is.
if (vcr->expand == VCR_EXPAND_GROWN) {
- al_atomic_store(u64)(&vcr->mark.low, vcr->mark.buffered - VCR_BUFFER_LOW_OFFSET, AL_ATOMIC_RELAXED);
+ atomic_store(u64)(&vcr->mark.low, vcr->mark.buffered - VCR_BUFFER_LOW_OFFSET, AL_ATOMIC_RELAXED);
vcr->expand = VCR_EXPAND_COMPLETE;
}
al_array_foreach(vcr->tracks, i, track) {
@@ -342,9 +342,15 @@ void lia_vcr_push_packet(struct lia_vcr *vcr, struct nn_packet *packet)
break;
}
if (VCR_TRACK_THREADED(track)) {
- if (nn_packet_cache_send_packet(&track->cache, packet)) {
- u64 buffer;
- if ((buffer = al_atomic_add(u64)(&vcr->count, size, AL_ATOMIC_RELAXED)) >= vcr->mark.buffered) {
+ u64 buffer = 0;
+ bool can_send = nn_packet_cache_available(&track->cache);
+ if (can_send) {
+ buffer = atomic_add(u64)(&vcr->count, size, AL_ATOMIC_RELAXED);
+ nn_packet_cache_send_packet(&track->cache, packet);
+ }
+ nn_packet_cache_unlock(&track->cache);
+ if (can_send) {
+ if (buffer >= vcr->mark.buffered) {
#ifndef CAMU_DIRECT_MODE
cork_if_buffered(vcr, buffer);
#endif
@@ -360,12 +366,15 @@ void lia_vcr_push_packet(struct lia_vcr *vcr, struct nn_packet *packet)
}
case LIANA_PACKET_EOF:
case LIANA_PACKET_ERROR: {
+ // @TODO: Should ERROR be passed down to LIANA_CLIENT_ERRORED?
#ifndef CAMU_DIRECT_MODE
update_metrics(vcr, 0); // Flush.
#endif
- // @TODO: Should ERROR be passed down to LIANA_CLIENT_ERRORED?
al_array_foreach(vcr->tracks, i, track) {
- nn_packet_cache_send_packet(&track->cache, NULL);
+ if (nn_packet_cache_available(&track->cache)) {
+ nn_packet_cache_send_packet(&track->cache, NULL);
+ }
+ nn_packet_cache_unlock(&track->cache);
}
#ifndef CAMU_DIRECT_MODE
nn_signal_stop(&vcr->signal);
@@ -373,7 +382,7 @@ void lia_vcr_push_packet(struct lia_vcr *vcr, struct nn_packet *packet)
if (op == LIANA_PACKET_EOF) {
log_info("Received EOF.");
} else if (op == LIANA_PACKET_ERROR) {
- log_warn("Forcing EOF because we got an error packet.");
+ log_warn("Forcing EOF due to an error packet.");
}
break;
}
@@ -386,17 +395,17 @@ void lia_vcr_push_packet(struct lia_vcr *vcr, struct nn_packet *packet)
void lia_vcr_set_buffered(struct lia_vcr_track *track)
{
- al_atomic_store(bool)(&track->buffered, true, AL_ATOMIC_RELAXED);
+ atomic_store(bool)(&track->buffered, true, AL_ATOMIC_RELAXED);
}
void lia_vcr_cork(struct lia_vcr_track *track)
{
- al_atomic_store(s32)(&track->state, VCR_TRACK_STOPPED, AL_ATOMIC_RELAXED);
+ atomic_store(s32)(&track->state, VCR_TRACK_STOPPED, AL_ATOMIC_RELAXED);
}
void lia_vcr_uncork(struct lia_vcr_track *track)
{
- if (al_atomic_load(s32)(&track->state, AL_ATOMIC_RELAXED) != VCR_TRACK_STOPPED) {
+ if (atomic_load(s32)(&track->state, AL_ATOMIC_ACQUIRE) != VCR_TRACK_STOPPED) {
// We will get here during normal operation. Early returning is historically
// tricky in vcr_uncork(). If I'm understanding correctly, asserting that
// cond_is_waiting() just below means we are safe.
@@ -404,7 +413,7 @@ void lia_vcr_uncork(struct lia_vcr_track *track)
}
// Lock before setting track->state to avoid a race with cork().
nn_mutex_lock(&track->mutex);
- al_atomic_store(s32)(&track->state, VCR_TRACK_RUNNING, AL_ATOMIC_RELAXED);
+ atomic_store(s32)(&track->state, VCR_TRACK_RUNNING, AL_ATOMIC_RELEASE);
al_assert(nn_cond_is_waiting(&track->cond));
nn_cond_signal(&track->cond);
nn_mutex_unlock(&track->mutex);
@@ -416,7 +425,7 @@ static void vcr_track_close_internal(struct lia_vcr_track *track)
// Calling packet_cache_disable() while holding the track mutex can very possibly deadlock.
nn_packet_cache_disable(&track->cache);
nn_mutex_lock(&track->mutex);
- al_atomic_store(s32)(&track->state, VCR_TRACK_CLOSED, AL_ATOMIC_RELAXED);
+ atomic_store(s32)(&track->state, VCR_TRACK_CLOSED, AL_ATOMIC_RELAXED);
if (nn_cond_is_waiting(&track->cond)) {
nn_cond_signal(&track->cond);
}
@@ -451,17 +460,17 @@ void lia_vcr_flush(struct lia_vcr *vcr)
struct lia_vcr_track *track;
al_array_foreach(vcr->tracks, i, track) {
if (VCR_TRACK_THREADED(track)) {
- al_atomic_store(bool)(&track->buffered, false, AL_ATOMIC_RELAXED);
+ atomic_store(bool)(&track->buffered, false, AL_ATOMIC_RELAXED);
track->client->flush(track->client);
nn_packet_cache_enable(&track->cache);
- al_atomic_store(s32)(&track->state, VCR_TRACK_RUNNING, AL_ATOMIC_RELAXED);
+ atomic_store(s32)(&track->state, VCR_TRACK_RUNNING, AL_ATOMIC_RELAXED);
} else {
track->client->flush(track->client);
}
}
- al_atomic_store(u64)(&vcr->count, 0, AL_ATOMIC_RELAXED);
+ atomic_store(u64)(&vcr->count, 0, AL_ATOMIC_RELAXED);
if (vcr->expand == VCR_EXPAND_COMPLETE) {
- al_atomic_store(u64)(&vcr->mark.low, 0, AL_ATOMIC_RELAXED);
+ atomic_store(u64)(&vcr->mark.low, 0, AL_ATOMIC_RELAXED);
vcr->expand = VCR_EXPAND_GROWN;
}
}
diff --git a/src/libsink/desktop.c b/src/libsink/desktop.c
index 8582cd4..af6fc50 100644
--- a/src/libsink/desktop.c
+++ b/src/libsink/desktop.c
@@ -208,14 +208,16 @@ bool camu_desktop_open(struct camu_desktop *c, const char *window_name)
c->scr.callback = screen_callback;
c->scr.userdata = c;
if (!camu_screen_init(&c->scr) || !camu_screen_create_window(&c->scr, window_name)) {
+ log_error("Failed to create window.");
return false;
}
#if defined CAMU_RENDERER_MOMO
c->renderer = camu_renderer_momo_create();
#elif defined CAMU_RENDERER_LIBPLACEBO
- c->renderer = camu_renderer_lp_create();
+ c->renderer = camu_renderer_libplacebo_create();
#endif
if (!camu_screen_create_renderer(&c->scr, c->renderer)) {
+ log_error("Failed to create renderer.");
return false;
}
// Need to render twice for the window to show early.
@@ -252,7 +254,10 @@ bool camu_desktop_tick(struct camu_desktop *c)
{
bool force;
if (camu_screen_tick(&c->scr, &force) && !c->should_quit) {
- c->renderer->render(c->renderer, &c->scr, force);
+ if (!c->renderer->render(c->renderer, &c->scr, force)) {
+ log_error("render() failed, forcing exit.");
+ c->should_quit = 1;
+ }
}
return !c->should_quit;
}
diff --git a/src/libsink/input_simulator.c b/src/libsink/input_simulator.c
index 19786af..5b9d6e6 100644
--- a/src/libsink/input_simulator.c
+++ b/src/libsink/input_simulator.c
@@ -31,23 +31,23 @@ static nn_thread_result NNWT_THREADCALL input_simulation_thread(void *userdata)
switch (al_random_int(0, MARK - 1)) {
case SKIP: {
s32 n = al_random_int(1, 5);
- log_debug("SKIP (n: %d).", n);
+ log_info("SKIP (n: %d).", n);
camu_sink_skip(sink, n);
break;
}
case BACKSKIP: {
s32 n = al_random_int(-5, -1);
- log_debug("BACKSKIP (n: %d).", n);
+ log_info("BACKSKIP (n: %d).", n);
camu_sink_skip(sink, n);
break;
}
case SHUFFLE: {
- log_debug("SHUFFLE.");
+ log_info("SHUFFLE.");
camu_sink_shuffle(sink);
break;
}
case TOGGLE_PAUSE: {
- log_debug("TOGGLE_PAUSE.");
+ log_info("TOGGLE_PAUSE.");
camu_sink_toggle_pause(sink);
break;
}
@@ -56,7 +56,7 @@ static nn_thread_result NNWT_THREADCALL input_simulation_thread(void *userdata)
if (pos < 0.005) pos = 0.0;
if (pos > 0.995) pos = 1.0;
else if (pos > 0.99) pos = 0.9999;
- log_debug("SEEK (pos: %f).", pos);
+ log_info("SEEK (pos: %f).", pos);
camu_sink_seek(sink, &pos, CAMU_SEEK_PERCENT);
break;
}
diff --git a/src/libsink/sink.c b/src/libsink/sink.c
index e9b4df4..fccbc3e 100644
--- a/src/libsink/sink.c
+++ b/src/libsink/sink.c
@@ -10,7 +10,7 @@
#include "sink.h"
#include "common.h"
-#define CAMU_SINK_LOCAL
+//#define CAMU_SINK_LOCAL
//#define CAMU_SINK_ONESHOT
// Requested state of the sinks outputs.
@@ -71,8 +71,7 @@ AL_STATIC_ASSERT(max_age_lt_lru, ENTRY_MAX_AGE, <, SINK_LRU_MAX);
#define CONNECTION_NUMBER(id) ((id >> 48) & 0xffff)
// Only sink->target can ever be 0xb00b.
-#define OxbOOb ((struct camu_sink_entry *)0xb00b)
-#define ENTRY_IS_VALID(entry) ((entry) && (entry) != OxbOOb)
+#define ENTRY_IS_VALID(entry) ((entry) && (entry) != (struct camu_sink_entry *)0xb00b)
// printf format for entries.
#ifdef AL_DEBUG
@@ -87,14 +86,19 @@ AL_STATIC_ASSERT(max_age_lt_lru, ENTRY_MAX_AGE, <, SINK_LRU_MAX);
#define VIDEO_STATE(entry) ((entry)->video.state)
// If a buffer is still INIT or QUEUED after the entry is configured, it's "empty".
-// Also, if a buffer errors, it will be detached in CLIENT_REMOVE_BUFFERS.
-// Empty is a state that cannot change while a buffer is being used (push()/read()).
+// An ERRORED buffer will be DETACHED after CLIENT_REMOVE_BUFFERS and is then considered empty.
+// Empty is a state that cannot change while a buffer could be in use (push()/read()).
#define AUDIO_EMPTY(entry) (AUDIO_STATE(entry) <= BUFFER_DETACHED)
#define VIDEO_EMPTY(entry) (VIDEO_STATE(entry) <= BUFFER_DETACHED)
#define AUDIO_ENDED(entry) (AUDIO_STATE(entry) >= BUFFER_ENDED)
#define VIDEO_ENDED(entry) (VIDEO_STATE(entry) >= BUFFER_ENDED)
-#define ENTRY_ENDED(entry) ((AUDIO_EMPTY(entry) || AUDIO_ENDED(entry)) && (VIDEO_EMPTY(entry) || VIDEO_ENDED(entry)))
+/*
+#define ENTRY_ENDED(entry) \
+ ((AUDIO_ENDED(entry) && (VIDEO_ENDED(entry) || VIDEO_EMPTY(entry) || VIDEO_IS_SINGLE_FRAME(entry))) || \
+ (AUDIO_EMPTY(entry) && VIDEO_ENDED(entry)))
+*/
+#define ENTRY_ENDED(entry) entry->ended
// IGNORED = ENDED or EMPTY.
#define AUDIO_ADDED_OR_IGNORED(entry) (AUDIO_STATE(entry) >= BUFFER_ADDED || AUDIO_EMPTY(entry))
@@ -113,6 +117,8 @@ AL_STATIC_ASSERT(max_age_lt_lru, ENTRY_MAX_AGE, <, SINK_LRU_MAX);
#define BLOCKING_SLEEP(delay) nn_event_loop_sleep(sink->loop, delay)
#endif
+#define CMD(cmd, ...) ((struct camu_sink_cmd){ .op = cmd, __VA_ARGS__ })
+
// Functions that might happen on separate threads.
// CAMU_MIXER_THREADED:
// audio_buffer_callback()
@@ -127,7 +133,7 @@ AL_STATIC_ASSERT(max_age_lt_lru, ENTRY_MAX_AGE, <, SINK_LRU_MAX);
static inline bool entry_audio_buffer_held(struct camu_sink_entry *entry)
{
#ifdef CAMU_MIXER_THREADED
- return al_atomic_load(u8)(&entry->audio.buf.ref, AL_ATOMIC_RELAXED) == 1;
+ return atomic_load(u8)(&entry->audio.buf.ref, AL_ATOMIC_RELAXED) == 1;
#else
(void)entry;
return false;
@@ -137,19 +143,30 @@ static inline bool entry_audio_buffer_held(struct camu_sink_entry *entry)
static inline bool entry_video_buffer_held(struct camu_sink_entry *entry)
{
#ifdef CAMU_SCREEN_THREADED
- return al_atomic_load(u8)(&entry->video.buf.ref, AL_ATOMIC_RELAXED) == 1;
+ return atomic_load(u8)(&entry->video.buf.ref, AL_ATOMIC_RELAXED) == 1;
#else
(void)entry;
return false;
#endif
}
-static void queue_cmd(struct camu_sink *sink, struct camu_sink_cmd cmd)
+static void queue_cmds(struct camu_sink *sink, u32 count, ...)
{
- camu_queue_push(sink->queue, cmd);
+ camu_queue_lock(sink->queue);
+ va_list cmds;
+ va_start(cmds, count);
+ for (u32 i = 0; i < count; i++) {
+ camu_queue_push(sink->queue, va_arg(cmds, struct camu_sink_cmd));
+ }
+ camu_queue_unlock(sink->queue);
nn_signal_send(&sink->queue_signal);
}
+static void queue_cmd(struct camu_sink *sink, struct camu_sink_cmd cmd)
+{
+ queue_cmds(sink, 1, cmd);
+}
+
static void refresh_video_output(struct camu_sink *sink)
{
#ifndef CAMU_SINK_NO_VIDEO
@@ -165,11 +182,7 @@ static inline void add_entry_audio_buffer(struct camu_sink_entry *entry)
#ifdef CAMU_MIXER_THREADED_START_STOP
sink->callback(sink->userdata, CAMU_SINK_ADD_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf);
#else
- queue_cmd(sink, (struct camu_sink_cmd){
- .op = ADD_BUFFER,
- .value.i = CAMU_SINK_AUDIO,
- .opaque = entry
- });
+ queue_cmd(sink, CMD(ADD_BUFFER, .v.u = CAMU_SINK_AUDIO, .opaque = entry));
#endif
}
@@ -194,11 +207,7 @@ static void remove_entry_audio_buffer(struct camu_sink_entry *entry)
#ifdef CAMU_MIXER_THREADED_START_STOP
sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf);
#else
- queue_cmd(sink, (struct camu_sink_cmd){
- .op = REMOVE_BUFFER,
- .value.i = CAMU_SINK_AUDIO,
- .opaque = entry
- });
+ queue_cmd(sink, CMD(REMOVE_BUFFER, .v.u = CAMU_SINK_AUDIO, .opaque = entry));
#endif
break;
case BUFFER_SET_OR_BUFFERED:
@@ -243,7 +252,6 @@ static void remove_entry_video_buffer(struct camu_sink_entry *entry)
static void remove_entry_buffers(struct camu_sink_entry *entry)
{
log_trace("remove_entry_buffers("ENTRY_FMT"), audio_state: %hhu, video_state: %hhu.", ENTRY_ARG(entry), AUDIO_STATE(entry), VIDEO_STATE(entry));
- al_assert(!entry->ended);
if (!AUDIO_ENDED(entry)) remove_entry_audio_buffer(entry);
if (!VIDEO_ENDED(entry)) remove_entry_video_buffer(entry);
}
@@ -256,14 +264,15 @@ static void add_or_queue_entry(struct camu_sink_entry *entry)
log_trace("add_or_queue_entry("ENTRY_FMT"), audio_state: %hhu, video_state: %hhu.", ENTRY_ARG(entry), AUDIO_STATE(entry), VIDEO_STATE(entry));
al_assert(AUDIO_STATE(entry) != BUFFER_QUEUED);
al_assert(VIDEO_STATE(entry) != BUFFER_QUEUED);
+ // Either buffer could be DETACHED.
if (AUDIO_STATE(entry) == BUFFER_INIT) {
AUDIO_STATE(entry) = BUFFER_QUEUED;
- } else if (!AUDIO_ENDED(entry)) {
+ } else if (!AUDIO_ENDED_OR_EMPTY(entry)) {
add_audio_if_set_and_buffered(entry);
}
if (VIDEO_STATE(entry) == BUFFER_INIT) {
VIDEO_STATE(entry) = BUFFER_QUEUED;
- } else if (!VIDEO_ENDED(entry)) {
+ } else if (!VIDEO_ENDED_OR_EMPTY(entry)) {
add_video_if_set_and_buffered(entry);
}
}
@@ -279,14 +288,14 @@ static void maybe_disconnect_entry(struct camu_sink_entry *entry)
}
#ifdef CAMU_SINK_LOCAL
-static void local_pause(struct camu_sink *sink, struct camu_sink_entry *entry)
+static void local_entry_pause(struct camu_sink *sink, struct camu_sink_entry *entry)
{
if (!camu_clock_is_paused(&entry->clock)) {
entry->paused = true;
camu_clock_pause(&entry->clock, 0);
log_info("Clock paused.");
// Audio stop will be handled by a BUFFER_PAUSED callback.
- if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry) && sink->video.state == SINK_PLAYING) {
+ if (!VIDEO_ENDED_OR_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry) && sink->video.state == SINK_PLAYING) {
#ifndef CAMU_SINK_NO_VIDEO
sink->callback(sink->userdata, CAMU_SINK_STOP, CAMU_SINK_VIDEO, NULL);
#endif
@@ -296,13 +305,13 @@ static void local_pause(struct camu_sink *sink, struct camu_sink_entry *entry)
entry->paused = false;
camu_clock_resume(&entry->clock, 0);
log_info("Clock resumed.");
- if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry) && sink->video.state == SINK_PAUSED) {
+ if (!VIDEO_ENDED_OR_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry) && sink->video.state == SINK_PAUSED) {
#ifndef CAMU_SINK_NO_VIDEO
sink->callback(sink->userdata, CAMU_SINK_START, CAMU_SINK_VIDEO, NULL);
#endif
sink->video.state = SINK_PLAYING;
}
- if (!AUDIO_EMPTY(entry) && sink->audio.state == SINK_PAUSED) {
+ if (!AUDIO_ENDED_OR_EMPTY(entry) && sink->audio.state == SINK_PAUSED) {
sink->callback(sink->userdata, CAMU_SINK_START, CAMU_SINK_AUDIO, NULL);
sink->audio.state = SINK_PLAYING;
}
@@ -310,6 +319,12 @@ static void local_pause(struct camu_sink *sink, struct camu_sink_entry *entry)
}
#endif
+// sink->current could be NULL.
+static inline struct camu_sink_entry *get_entry_for_command(struct camu_sink *sink)
+{
+ return ENTRY_IS_VALID(sink->target) ? sink->target : sink->current;
+}
+
static inline s32 get_sequence_for_command(struct camu_sink_entry *entry)
{
// SEQUENCE_ANY resolves order on the server.
@@ -329,7 +344,7 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
{
switch (cmd->op) {
case START: {
- switch (cmd->value.i) {
+ switch (cmd->v.u) {
case CAMU_SINK_AUDIO:
if (sink->audio.state == SINK_PAUSED) {
sink->callback(sink->userdata, CAMU_SINK_START, CAMU_SINK_AUDIO, NULL);
@@ -348,7 +363,7 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
break;
}
case STOP: {
- switch (cmd->value.i) {
+ switch (cmd->v.u) {
case CAMU_SINK_AUDIO:
if (sink->audio.state == SINK_PLAYING) {
sink->callback(sink->userdata, CAMU_SINK_STOP, CAMU_SINK_AUDIO, NULL);
@@ -368,7 +383,7 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
}
case ADD_BUFFER: {
struct camu_sink_entry *entry = (struct camu_sink_entry *)cmd->opaque;
- switch (cmd->value.i) {
+ switch (cmd->v.u) {
case CAMU_SINK_AUDIO:
sink->callback(sink->userdata, CAMU_SINK_ADD_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf);
break;
@@ -382,7 +397,7 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
}
case REMOVE_BUFFER: {
struct camu_sink_entry *entry = (struct camu_sink_entry *)cmd->opaque;
- switch (cmd->value.i) {
+ switch (cmd->v.u) {
case CAMU_SINK_AUDIO:
sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf);
break;
@@ -395,7 +410,7 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
break;
}
case CLEAR_BUFFERS: {
- switch (cmd->value.i) {
+ switch (cmd->v.u) {
case CAMU_SINK_AUDIO:
sink->callback(sink->userdata, CAMU_SINK_CLEAR, CAMU_SINK_AUDIO, NULL);
break;
@@ -424,21 +439,21 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
nn_packet_write_str(packet, &sink->default_list);
nn_packet_write_u8(packet, CAMU_LIST_SKIP);
nn_packet_write_s32(packet, get_sequence_for_command(entry));
- nn_packet_write_s32(packet, (s32)cmd->value.i);
+ nn_packet_write_s32(packet, (s32)cmd->v.i);
nn_rpc_connection_command(sink->conn, packet, NULL, NULL);
break;
}
case TOGGLE_PAUSE: {
struct camu_sink_entry *entry = (struct camu_sink_entry *)cmd->opaque;
#ifdef CAMU_SINK_LOCAL
- if (!entry->held) local_pause(sink, entry);
+ if (!entry->held) local_entry_pause(sink, entry);
#else
if (!sink->conn) return;
struct nn_packet *packet = nn_rpc_get_packet(&sink->client, CAMU_SERVER_LIST_ACTION);
nn_packet_write_str(packet, &sink->default_list);
nn_packet_write_u8(packet, CAMU_LIST_TOGGLE_PAUSE);
nn_packet_write_s32(packet, get_sequence_for_command(entry));
- nn_packet_write_f64(packet, cmd->value.f);
+ nn_packet_write_f64(packet, cmd->v.f);
nn_rpc_connection_command(sink->conn, packet, NULL, NULL);
#endif
break;
@@ -451,7 +466,7 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
nn_packet_write_u8(packet, CAMU_LIST_SEEK);
nn_packet_write_s32(packet, entry->sequence);
nn_packet_write_u32(packet, REMOTE_ENTRY_ID(entry->id));
- nn_packet_write_u64(packet, cmd->value.u);
+ nn_packet_write_u64(packet, cmd->v.u);
nn_rpc_connection_command(sink->conn, packet, NULL, NULL);
break;
}
@@ -476,7 +491,7 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
nn_packet_write_str(packet, &sink->default_list);
nn_packet_write_u8(packet, CAMU_LIST_END);
nn_packet_write_u32(packet, REMOTE_ENTRY_ID(entry->id));
- nn_packet_write_u32(packet, (u32)cmd->value.u);
+ nn_packet_write_u32(packet, (u32)cmd->v.u);
nn_rpc_connection_command(sink->conn, packet, NULL, NULL);
break;
}
@@ -486,11 +501,11 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
static void queue_signal_callback(void *userdata)
{
struct camu_sink *sink = (struct camu_sink *)userdata;
- u32 size;
+ u32 count;
struct camu_sink_cmd cmd;
for (;;) {
- camu_queue_try_pop(sink->queue, size, cmd);
- if (size == 0) break;
+ camu_queue_try_pop(sink->queue, count, cmd);
+ if (count == 0) break;
handle_sink_cmd(sink, &cmd);
}
}
@@ -498,7 +513,8 @@ static void queue_signal_callback(void *userdata)
static void mixer_callback(void *userdata, u8 op)
{
struct camu_sink *sink = (struct camu_sink *)userdata;
- // @TODO: Isn't the idea of MIXER_EMPTY to not STOP audio when we know the mixer will be empty?
+ // @TODO: Isn't the idea of MIXER_EMPTY to not explicitly STOP the audio in places we
+ // expect the mixer to be empty (rely on MIXER_EMPTY).
if (op == CAMU_MIXER_EMPTY) {
log_info("Mixer empty.");
// Regardless of if we are checking an entry's state here, we have to sync with
@@ -506,10 +522,7 @@ static void mixer_callback(void *userdata, u8 op)
nn_mutex_lock(&sink->lock);
// This feels a bit too loose.
if (!(sink->current && AUDIO_STATE(sink->current) == BUFFER_ADDED)) {
- queue_cmd(sink, (struct camu_sink_cmd){
- .op = STOP,
- .value.i = CAMU_SINK_AUDIO
- });
+ queue_cmd(sink, CMD(STOP, .v.u = CAMU_SINK_AUDIO));
}
nn_mutex_unlock(&sink->lock);
}
@@ -568,10 +581,7 @@ static void maybe_remove_previous(struct camu_sink *sink)
// Cleanup entries from old connections. This is especially important to
// keep reseek() from being overly wasteful.
if (CONNECTION_NUMBER(previous->id) != sink->connection_number) {
- queue_cmd(sink, (struct camu_sink_cmd){
- .op = EJECT_ENTRY,
- .opaque = previous
- });
+ queue_cmd(sink, CMD(EJECT_ENTRY, .opaque = previous));
}
}
sink->previous.count = 0;
@@ -600,10 +610,9 @@ static void maybe_add_to_previous(struct camu_sink *sink, struct camu_sink_entry
{
log_trace("maybe_add_to_previous("ENTRY_FMT", "ENTRY_FMT").", ENTRY_ARG(previous), ENTRY_ARG(target));
al_assert(previous != target);
- al_assert(!previous->ended);
- // If none of the entry's buffers are added, we don't care about adding it to previous.
- bool dangling_target = target == OxbOOb;
- if (dangling_target || target->ended || (AUDIO_STATE(previous) != BUFFER_ADDED && VIDEO_STATE(previous) != BUFFER_ADDED)) {
+ // If none of an entry's buffers are added, we don't care about adding it to previous.
+ bool dangling_target = target == (struct camu_sink_entry *)0xb00b;
+ if (dangling_target || ENTRY_ENDED(target) || (AUDIO_STATE(previous) != BUFFER_ADDED && VIDEO_STATE(previous) != BUFFER_ADDED)) {
remove_entry_buffers(previous);
return;
}
@@ -613,18 +622,14 @@ static void maybe_add_to_previous(struct camu_sink *sink, struct camu_sink_entry
static void after_add_entry(struct camu_sink_entry *entry, bool skip_audio, bool skip_video)
{
maybe_remove_previous(entry->sink);
- queue_cmd(entry->sink, (struct camu_sink_cmd){
- .op = (skip_video || entry->paused) ? STOP : START,
- .value.i = CAMU_SINK_VIDEO
- });
+ queue_cmds(entry->sink, 2,
+ CMD((skip_video || entry->paused) ? STOP : START, .v.u = CAMU_SINK_VIDEO),
+ CMD((skip_audio || entry->paused) ? STOP : START, .v.u = CAMU_SINK_AUDIO)
+ );
if (VIDEO_ENDED_OR_EMPTY(entry)) {
// Clear the screen if skipping from a video to an audio-only entry.
refresh_video_output(entry->sink);
}
- queue_cmd(entry->sink, (struct camu_sink_cmd){
- .op = (skip_audio || entry->paused) ? STOP : START,
- .value.i = CAMU_SINK_AUDIO
- });
}
// Call this after setting state to ADDED because this entry might be in previous.
@@ -641,7 +646,7 @@ static void do_add_entry(struct camu_sink_entry *entry)
void add_audio_if_set_and_buffered(struct camu_sink_entry *entry)
{
- al_assert(!entry->ended);
+ al_assert(!ENTRY_ENDED(entry));
al_assert(AUDIO_STATE(entry) != BUFFER_INIT);
al_assert(AUDIO_STATE(entry) != BUFFER_QUEUED);
al_assert(AUDIO_STATE(entry) != BUFFER_ADDED);
@@ -662,7 +667,7 @@ void add_audio_if_set_and_buffered(struct camu_sink_entry *entry)
void add_video_if_set_and_buffered(struct camu_sink_entry *entry)
{
// Single frame entries will be added/removed with ended set.
- if (entry->ended) al_assert(VIDEO_IS_SINGLE_FRAME(entry));
+ if (ENTRY_ENDED(entry)) al_assert(VIDEO_IS_SINGLE_FRAME(entry));
al_assert(VIDEO_STATE(entry) != BUFFER_INIT);
al_assert(VIDEO_STATE(entry) != BUFFER_QUEUED);
al_assert(VIDEO_STATE(entry) != BUFFER_ADDED);
@@ -690,56 +695,34 @@ static void switch_to(struct camu_sink *sink, struct camu_sink_entry *target)
{
struct camu_sink_entry *current = sink->current;
- log_trace("switch_to("ENTRY_FMT"(ended: %s)), current: "ENTRY_FMT"(ended: %s).",
- ENTRY_ARG(target), BOOLSTR(ENTRY_IS_VALID(target) ? target->ended : false),
- ENTRY_ARG(current), BOOLSTR(current ? current->ended : false));
+ log_trace("switch_to("ENTRY_FMT"), current: "ENTRY_FMT".", ENTRY_ARG(target), ENTRY_ARG(current));
- bool ensure_removed = false;
if (current) {
struct camu_sink_entry *suspended = sink->suspended;
al_assert(current != target);
- ensure_removed = suspended || current->ended;
if (suspended) {
al_assert(suspended == current);
- // A buffer's state being QUEUED should be impossible while it's
- // entry is suspended.
+ // It should be impossible for a buffer to be QUEUED while it's entry is suspended.
al_assert(AUDIO_STATE(suspended) != BUFFER_QUEUED);
al_assert(VIDEO_STATE(suspended) != BUFFER_QUEUED);
sink->suspended = NULL;
log_warn("Unset suspended entry as a substitute for remove.");
- } else if (!current->ended) {
+ } else {
maybe_add_to_previous(sink, current, target);
}
}
- // @TODO: Cleanup stop_video? and log_trace lengths.
- bool stop_video = false;
- bool dangling_target = target == OxbOOb;
+ bool dangling_target = target == (struct camu_sink_entry *)0xb00b;
+ bool stop_video = dangling_target;
if (dangling_target) {
log_trace("Ignored dangling target.");
} else {
- if (!target->ended) {
- remove_previous_if_contains(sink, target);
- add_or_queue_entry(target);
- } else {
- log_trace("Target ended in switch_to().");
- if (!VIDEO_ENDED_OR_EMPTY(target) && VIDEO_IS_SINGLE_FRAME(target)) {
- add_video_if_set_and_buffered(target);
- }
- stop_video = true;
- }
- }
-
- if (ensure_removed) {
- if (!VIDEO_ENDED_OR_EMPTY(current) && VIDEO_IS_SINGLE_FRAME(current)) {
- remove_entry_video_buffer(current);
- }
- al_assert(AUDIO_STATE(current) != BUFFER_ADDED);
- al_assert(VIDEO_STATE(current) != BUFFER_ADDED);
+ remove_previous_if_contains(sink, target);
+ add_or_queue_entry(target);
+ stop_video = VIDEO_ENDED_OR_EMPTY(target) || VIDEO_IS_SINGLE_FRAME(target);
}
if (dangling_target) {
- stop_video = true;
sink->current = NULL;
} else {
target->audio.ignore_paused = false;
@@ -750,10 +733,7 @@ static void switch_to(struct camu_sink *sink, struct camu_sink_entry *target)
}
if (stop_video) {
- queue_cmd(sink, (struct camu_sink_cmd){
- .op = STOP,
- .value.i = CAMU_SINK_VIDEO
- });
+ queue_cmd(sink, CMD(STOP, .v.u = CAMU_SINK_VIDEO));
refresh_video_output(sink);
}
}
@@ -781,17 +761,19 @@ static void pause_and_swap_to(struct camu_sink *sink, struct camu_sink_entry *ta
al_assert(target != current);
al_assert(!sink->target);
// This is extra verbose because the order is important.
- // 1. sink->target has to be set before calling clock_pause().
- // 2. current must still be paused even if it's ended.
- // 3. In the immediate case, switch_to() has to come last.
- if (current && !current->ended) {
+ // 1. sink->target has to be set before calling clock_pause().
+ if (current && !ENTRY_ENDED(current)) {
sink->target = target;
}
+ // 2. current must still be paused, even if it's ended.
if (current) {
current->audio.ignore_paused = true;
camu_clock_pause(&current->clock, at);
}
- if (!current || current->ended) switch_to(sink, target);
+ // 3. In the immediate swap case, switch_to() has to come last.
+ if (!current || ENTRY_ENDED(current)) {
+ switch_to(sink, target);
+ }
}
#endif
@@ -805,16 +787,14 @@ static bool end_entry_and_advance_queue(struct camu_sink *sink, struct camu_sink
#endif
// This entry's buffers cannot be added again until after a reset.
entry->ended = true;
- queue_cmd(sink, (struct camu_sink_cmd){
- .op = END,
- .value.u = entry->reset_token,
- .opaque = entry
- });
+ al_assert(ENTRY_ENDED(entry));
+ queue_cmd(sink, CMD(END, .v.u = entry->reset_token, .opaque = entry));
#ifdef LIANA_LIST_SCUFFED_LOOP
log_info("Looping.");
return true;
#endif
if (sink->target) {
+ log_info("Buffers swapped on end() (Gapless if queued).");
switch_to(sink, sink->target);
sink->target = NULL;
return true;
@@ -843,17 +823,18 @@ static void audio_buffer_callback(void *userdata, u8 op)
nn_mutex_lock(&sink->lock);
if (!entry->audio.ignore_paused && entry->paused) {
log_info("Audio buffer paused.");
- queue_cmd(sink, (struct camu_sink_cmd){
- .op = STOP,
- .value.i = CAMU_SINK_AUDIO
- });
+ queue_cmd(sink, CMD(STOP, .v.u = CAMU_SINK_AUDIO));
}
nn_mutex_unlock(&sink->lock);
break;
case CAMU_BUFFER_EOF:
case CAMU_BUFFER_ERRORED: {
bool error = op == CAMU_BUFFER_ERRORED;
- log_debug(error ? "Audio buffer errored." : "Audio EOF.");
+ if (error) {
+ log_error("Audio buffer errored.");
+ } else {
+ log_debug("Audio EOF.");
+ }
nn_mutex_lock(&sink->lock);
// EOF and ERRORED come from the outputs read() thread. So, having threaded
// outputs means anything could have happened while waiting on the lock above.
@@ -894,7 +875,11 @@ static void video_buffer_callback(void *userdata, u8 op)
case CAMU_BUFFER_EOF:
case CAMU_BUFFER_ERRORED: {
bool error = op == CAMU_BUFFER_ERRORED;
- log_debug(error ? "Video buffer errored." : "Video EOF.");
+ if (error) {
+ log_error("Video buffer errored.");
+ } else {
+ log_debug("Video EOF.");
+ }
nn_mutex_lock(&sink->lock);
if (!AUDIO_EMPTY(entry)) {
camu_audio_buffer_set_no_video(&entry->audio.buf, true);
@@ -915,10 +900,7 @@ static void video_buffer_callback(void *userdata, u8 op)
}
nn_mutex_unlock(&sink->lock);
if (!swapped) {
- queue_cmd(sink, (struct camu_sink_cmd){
- .op = STOP,
- .value.i = CAMU_SINK_VIDEO
- });
+ queue_cmd(sink, CMD(STOP, .v.u = CAMU_SINK_VIDEO));
}
break;
}
@@ -937,10 +919,7 @@ static void clock_callback(void *userdata, u8 op)
switch_to(sink, sink->target);
sink->target = NULL;
} else if (entry->paused) {
- queue_cmd(entry->sink, (struct camu_sink_cmd){
- .op = STOP,
- .value.i = CAMU_SINK_VIDEO
- });
+ queue_cmd(sink, CMD(STOP, .v.u = CAMU_SINK_VIDEO));
}
}
nn_mutex_unlock(&sink->lock);
@@ -953,20 +932,11 @@ static void evaluate_and_set_buffer_params(struct camu_sink *sink, struct camu_s
f64 avg_frame_duration = entry->video.buf.avg_frame_duration;
#ifdef CAMU_SINK_LOCAL
if (!AUDIO_EMPTY(entry) && !ignore_video) {
- // Delay either the audio or video so we can start the clock as
- // soon as possible while keeping A/V sync.
f64 audio = camu_mixer_get_latency(sink->audio.mixer);
struct camu_renderer *renderer = sink->video.renderer;
f64 video = renderer->get_latency(renderer) * avg_frame_duration;
- // If the audio buffer is delayed by less than the audio latency, data will be skipped.
- f64 base = -audio;
- if (audio > video) {
- camu_audio_buffer_set_latency(&entry->audio.buf, base);
- camu_video_buffer_set_latency(&entry->video.buf, base - (audio - video));
- } else if (video > audio) {
- camu_audio_buffer_set_latency(&entry->audio.buf, base);
- camu_video_buffer_set_latency(&entry->video.buf, base + (video - audio));
- }
+ camu_audio_buffer_set_latency(&entry->audio.buf, -audio);
+ camu_video_buffer_set_latency(&entry->video.buf, -video);
}
// If we're local we don't have to worry about syncing audio-only entries.
camu_audio_buffer_set_ignore_desync(&entry->audio.buf, ignore_video);
@@ -1013,6 +983,7 @@ static void remove_from_queue_by_opaque(struct camu_sink *sink, void *opaque)
static void client_callback(void *userdata, u8 op, struct camu_codec_stream *stream, void *opaque)
{
struct camu_sink_entry *entry = (struct camu_sink_entry *)userdata;
+ al_assert(ENTRY_IS_VALID(entry));
struct camu_sink *sink = entry->sink;
switch (op) {
case LIANA_CLIENT_CONFIGURE: {
@@ -1106,13 +1077,8 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
log_trace("remove_buffers("ENTRY_FMT", %s, %s), entry == current: %s.", ENTRY_ARG(entry), BOOLSTR(rec->reconnect), BOOLSTR(rec->unconfigured), BOOLSTR(entry == sink->current));
if (entry == sink->current) {
- if (!entry->ended) {
- // It should only be possible for a buffer to be INIT if entry is not current or ended.
- // Ended case is: (audio or video buffer empty) -> entry switched off of -> entry ended -> switch_to()'d.
- // - The empty buffer doesn't get re-QUEUED because the entry is ended.
- al_assert(AUDIO_STATE(entry) != BUFFER_INIT);
- al_assert(VIDEO_STATE(entry) != BUFFER_INIT);
- }
+ al_assert(AUDIO_STATE(entry) != BUFFER_INIT);
+ al_assert(VIDEO_STATE(entry) != BUFFER_INIT);
if (sink->target) {
// This is necessary to avoid re-adding an entry with an in-between clock state.
// See note in clock.c::camu_clock_seek().
@@ -1140,7 +1106,6 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
// Ignore unconfigured entries.
if (rec->unconfigured) {
- al_assert(!entry->ended);
al_assert(AUDIO_EMPTY(entry) && VIDEO_EMPTY(entry));
nn_mutex_unlock(&sink->lock);
return;
@@ -1309,7 +1274,7 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
remove_from_queue_by_opaque(sink, entry);
if (entry == sink->target) {
- sink->target = OxbOOb;
+ sink->target = (struct camu_sink_entry *)0xb00b;
log_warn("Attempting to handle a disconnected target.");
} else if (entry == sink->current) {
if (sink->suspended) {
@@ -1322,10 +1287,7 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
} else {
sink->current = NULL;
if (removed) { // Don't stop video on exit.
- queue_cmd(entry->sink, (struct camu_sink_cmd){
- .op = STOP,
- .value.i = CAMU_SINK_VIDEO
- });
+ queue_cmd(sink, CMD(STOP, .v.u = CAMU_SINK_VIDEO));
}
}
// If current was never fully added we need to call this here.
@@ -1401,9 +1363,12 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn,
u8 op = nn_packet_read_u8(packet);
if (op == LIANA_SINK_UNSET) {
nn_mutex_lock(&sink->lock);
- if (sink->current) {
- maybe_disconnect_entry(sink->current);
+ struct camu_sink_entry *current = sink->current;
+ nn_mutex_unlock(&sink->lock);
+ if (current) {
+ maybe_disconnect_entry(current);
}
+ nn_mutex_lock(&sink->lock);
goto out;
}
@@ -1499,7 +1464,7 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn,
case LIANA_PAUSE_PAUSE:
if (prev_target) {
al_assert(current);
- al_assert(!current->ended);
+ al_assert(!ENTRY_ENDED(current));
al_assert(prev_target != entry);
if (entry == current) {
// pause_and_swap_to() negated.
@@ -1507,7 +1472,7 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn,
} else {
sink->target = entry;
}
- if (prev_target != OxbOOb) {
+ if (prev_target != (struct camu_sink_entry *)0xb00b) {
camu_clock_pause(&prev_target->clock, at);
}
} else {
@@ -1518,7 +1483,7 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn,
case LIANA_PAUSE_BOTH:
if (prev_target) {
al_assert(current);
- al_assert(!current->ended);
+ al_assert(!ENTRY_ENDED(current));
al_assert(prev_target != entry);
if (entry == current) {
sink->target = NULL;
@@ -1526,7 +1491,7 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn,
} else {
sink->target = entry;
}
- if (prev_target != OxbOOb) {
+ if (prev_target != (struct camu_sink_entry *)0xb00b) {
camu_clock_pause(&prev_target->clock, at);
}
} else {
@@ -1562,12 +1527,14 @@ static bool pause_command_callback(void *userdata, struct nn_rpc_connection *con
struct camu_sink_entry *entry = get_entry_from_id(sink, id);
if (!entry) goto out;
- al_assert(entry->sequence == sequence);
nn_mutex_lock(&sink->lock);
- log_trace("pause("ENTRY_FMT"), %s, audio_state: %hhu, video_state: %hhu.", ENTRY_ARG(entry), lia_pause_op_name(pause), AUDIO_STATE(entry), VIDEO_STATE(entry));
+ // As long as the list discards skips with a non-current sequence, this should hold true.
+ al_assert(entry->sequence == sequence);
+ log_trace("pause("ENTRY_FMT"), %s, audio_state: %hhu, video_state: %hhu.",
+ ENTRY_ARG(entry), lia_pause_op_name(pause), AUDIO_STATE(entry), VIDEO_STATE(entry));
#ifdef CAMU_SINK_LOCAL
(void)at;
- if (!entry->held) local_pause(sink, entry);
+ if (!entry->held) local_entry_pause(sink, entry);
#else
switch (pause) {
case LIANA_PAUSE_PAUSE: {
@@ -1582,18 +1549,12 @@ static bool pause_command_callback(void *userdata, struct nn_rpc_connection *con
entry->paused = false;
camu_clock_resume(&entry->clock, at);
log_info("Clock resumed.");
- if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry)) {
- queue_cmd(entry->sink, (struct camu_sink_cmd){
- .op = START,
- .value.i = CAMU_SINK_VIDEO
- });
+ if (!VIDEO_ENDED_OR_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry)) {
+ queue_cmd(sink, CMD(START, .v.u = CAMU_SINK_VIDEO));
}
- if (!AUDIO_EMPTY(entry)) {
+ if (!AUDIO_ENDED_OR_EMPTY(entry)) {
camu_audio_buffer_resync(&entry->audio.buf);
- queue_cmd(entry->sink, (struct camu_sink_cmd){
- .op = START,
- .value.i = CAMU_SINK_AUDIO
- });
+ queue_cmd(sink, CMD(START, .v.u = CAMU_SINK_AUDIO));
}
break;
}
@@ -1615,7 +1576,6 @@ static bool seek_command_callback(void *userdata, struct nn_rpc_connection *conn
u64 id = LOCAL_ENTRY_ID(sink, nn_packet_read_u32(packet));
s32 sequence = nn_packet_read_s32(packet);
- (void)sequence;
u64 at = nn_packet_read_u64(packet);
u64 pos = nn_packet_read_u64(packet);
u32 reset_token = nn_packet_read_u32(packet);
@@ -1623,7 +1583,8 @@ static bool seek_command_callback(void *userdata, struct nn_rpc_connection *conn
struct camu_sink_entry *entry = get_entry_from_id(sink, id);
if (!entry) goto out;
nn_mutex_lock(&sink->lock);
- log_trace("seek("ENTRY_FMT"), reset_token: %u.", ENTRY_ARG(entry), reset_token);
+ entry->sequence = sequence;
+ log_trace("seek("ENTRY_FMT", %.2f), reset_token: %u.", ENTRY_ARG(entry), pos / 1000000.0, reset_token);
entry->reset_token = reset_token;
nn_mutex_unlock(&sink->lock);
// The rest of the seek is handled in CLIENT_REMOVE_BUFFERS/RESUME_AT/RECONNECTED.
@@ -1761,22 +1722,12 @@ void camu_sink_return_current(struct camu_sink *sink)
nn_mutex_unlock(&sink->lock);
}
-// sink->current could be NULL.
-static inline struct camu_sink_entry *get_entry_for_command(struct camu_sink *sink)
-{
- return ENTRY_IS_VALID(sink->target) ? sink->target : sink->current;
-}
-
void camu_sink_skip(struct camu_sink *sink, s32 n)
{
nn_mutex_lock(&sink->lock);
struct camu_sink_entry *current = get_entry_for_command(sink);
nn_mutex_unlock(&sink->lock);
- queue_cmd(sink, (struct camu_sink_cmd){
- .op = SKIP,
- .value.i = n,
- .opaque = current
- });
+ queue_cmd(sink, CMD(SKIP, .v.i = n, .opaque = current));
}
void camu_sink_toggle_pause(struct camu_sink *sink)
@@ -1785,11 +1736,8 @@ void camu_sink_toggle_pause(struct camu_sink *sink)
struct camu_sink_entry *current = get_entry_for_command(sink);
nn_mutex_unlock(&sink->lock);
if (!current) return;
- queue_cmd(sink, (struct camu_sink_cmd){
- .op = TOGGLE_PAUSE,
- .value.f = camu_clock_get_pts(&current->clock, 0.0, false),
- .opaque = current
- });
+ f64 pts = camu_clock_get_pts(&current->clock, 0.0, false);
+ queue_cmd(sink, CMD(TOGGLE_PAUSE, .v.f = pts, .opaque = current));
}
void camu_sink_seek(struct camu_sink *sink, void *value, u8 mode)
@@ -1811,19 +1759,19 @@ void camu_sink_seek(struct camu_sink *sink, void *value, u8 mode)
switch (mode) {
case CAMU_SEEK_POS: {
u64 pos = *(u64 *)value;
- cmd.value.u = pos;
+ cmd.v.u = pos;
break;
}
case CAMU_SEEK_RELATIVE: {
f64 offset = *(f64 *)value;
pts = MAX(pts + offset, 0.0);
- cmd.value.u = (u64)(pts * 1000000);
+ cmd.v.u = (u64)(pts * 1000000);
break;
}
case CAMU_SEEK_PERCENT: {
// There's probably a way to lose less precision here.
f64 percent = *(f64 *)value;
- cmd.value.u = (u64)(duration * percent);
+ cmd.v.u = (u64)(duration * percent);
break;
}
}
@@ -1832,31 +1780,21 @@ void camu_sink_seek(struct camu_sink *sink, void *value, u8 mode)
void camu_sink_reseek(struct camu_sink *sink)
{
- queue_cmd(sink, (struct camu_sink_cmd){
- .op = RESEEK
- });
+ queue_cmd(sink, CMD(RESEEK));
}
void camu_sink_shuffle(struct camu_sink *sink)
{
- queue_cmd(sink, (struct camu_sink_cmd){
- .op = SHUFFLE
- });
+ queue_cmd(sink, CMD(SHUFFLE));
}
void camu_sink_stop(struct camu_sink *sink)
{
- queue_cmd(sink, (struct camu_sink_cmd){
- .op = STOP,
- .value.i = CAMU_SINK_AUDIO
- });
- queue_cmd(sink, (struct camu_sink_cmd){
- .op = CLEAR_BUFFERS,
- .value.i = CAMU_SINK_AUDIO
- });
- queue_cmd(sink, (struct camu_sink_cmd){
- .op = CLOSE
- });
+ queue_cmds(sink, 3,
+ CMD(STOP, .v.u = CAMU_SINK_AUDIO),
+ CMD(CLEAR_BUFFERS, .v.u = CAMU_SINK_AUDIO),
+ CMD(CLOSE)
+ );
}
void camu_sink_close(struct camu_sink *sink)
diff --git a/src/libsink/sink.h b/src/libsink/sink.h
index 5958829..1e8bbf1 100644
--- a/src/libsink/sink.h
+++ b/src/libsink/sink.h
@@ -56,7 +56,7 @@ struct camu_sink_entry {
bool paused;
struct {
u8 state;
- // Don't stop the audio output on BUFFER_PAUSED from this entry.
+ // Don't stop audio on a BUFFER_PAUSED from this entry.
bool ignore_paused;
struct camu_audio_buffer buf;
struct lia_vcr_track *track;
@@ -71,7 +71,7 @@ struct camu_sink_entry {
struct camu_sink_cmd {
u8 op;
- union { s64 i; u64 u; f64 f; } value;
+ union { s64 i; u64 u; f64 f; } v;
void *opaque;
};
@@ -90,7 +90,7 @@ struct camu_sink {
queue(struct camu_sink_cmd) queue;
struct camu_sink_entry *current;
struct camu_sink_entry *target;
- // If an entry that was current was removed for a reconnect, it's "suspended".
+ // If current was removed for a reconnect, it's "suspended".
struct camu_sink_entry *suspended;
array(struct camu_sink_entry *) previous;
array(struct camu_sink_entry *) entries;
diff --git a/src/mixer/audio_miniaudio.c b/src/mixer/audio_miniaudio.c
index 993bcca..c0e7cf4 100644
--- a/src/mixer/audio_miniaudio.c
+++ b/src/mixer/audio_miniaudio.c
@@ -34,19 +34,19 @@ static ma_backend backends[] = {
#endif
};
-static void *miniaudio_malloc(size_t size, void *userdata)
+static void *ma_on_malloc(size_t size, void *userdata)
{
(void)userdata;
return al_malloc(size);
}
-static void *miniaudio_realloc(void *ptr, size_t size, void *userdata)
+static void *ma_on_realloc(void *ptr, size_t size, void *userdata)
{
(void)userdata;
return al_realloc(ptr, size);
}
-static void miniaudio_free(void *ptr, void *userdata)
+static void ma_on_free(void *ptr, void *userdata)
{
(void)userdata;
al_free(ptr);
@@ -54,12 +54,12 @@ static void miniaudio_free(void *ptr, void *userdata)
static ma_allocation_callbacks alloc_callbacks = {
.pUserData = NULL,
- .onMalloc = miniaudio_malloc,
- .onRealloc = miniaudio_realloc,
- .onFree = miniaudio_free
+ .onMalloc = ma_on_malloc,
+ .onRealloc = ma_on_realloc,
+ .onFree = ma_on_free
};
-static void miniaudio_log_callback(void *userdata, u32 level, const char *message)
+static void log_callback(void *userdata, u32 level, const char *message)
{
(void)userdata;
if (level == MA_LOG_LEVEL_ERROR) {
@@ -71,14 +71,14 @@ static void miniaudio_log_callback(void *userdata, u32 level, const char *messag
}
}
-static bool audio_miniaudio_init(struct camu_audio *audio, str *name)
+static bool audio_ma_init(struct camu_audio *audio, str *name)
{
- struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)audio;
+ struct camu_audio_ma *ma = (struct camu_audio_ma *)audio;
#ifdef CAMU_NO_MINIAUDIO_BACKENDS
al_assert_and_return(false);
#endif
ma_log_init(&alloc_callbacks, &ma->log);
- ma_log_register_callback(&ma->log, ma_log_callback_init(miniaudio_log_callback, NULL));
+ ma_log_register_callback(&ma->log, ma_log_callback_init(log_callback, NULL));
ma->context_config = ma_context_config_init();
ma->context_config.pLog = &ma->log;
#ifdef MA_ENABLE_PULSEAUDIO
@@ -140,9 +140,9 @@ static s32 camu_sample_format_from_miniaudio(s32 fmt)
}
}
-static void audio_miniaudio_pick_format(struct camu_audio *audio, struct camu_resampler_format *fmt)
+static void audio_ma_pick_format(struct camu_audio *audio, struct camu_resampler_format *fmt)
{
- struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)audio;
+ struct camu_audio_ma *ma = (struct camu_audio_ma *)audio;
fmt->req.format = camu_sample_format_from_miniaudio(ma->device.playback.format);
fmt->req.sample_rate = ma->device.playback.internalSampleRate;
u32 channels = ma->device.playback.channels;
@@ -154,7 +154,7 @@ static void audio_miniaudio_pick_format(struct camu_audio *audio, struct camu_re
static void data_callback(ma_device *device, void *output, const void *input, u32 frame_count)
{
- struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)device->pUserData;
+ struct camu_audio_ma *ma = (struct camu_audio_ma *)device->pUserData;
(void)input;
bool silence;
ma->a.data_callback(ma->a.userdata, (u8 *)output, frame_count, &silence);
@@ -170,9 +170,9 @@ static void data_callback(ma_device *device, void *output, const void *input, u3
#define DEFAULT_PERIOD_SIZE_IN_MILLISECONDS 48
#endif
-static bool audio_miniaudio_configure_stream(struct camu_audio *audio, void *opaque)
+static bool audio_ma_configure_stream(struct camu_audio *audio, void *opaque)
{
- struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)audio;
+ struct camu_audio_ma *ma = (struct camu_audio_ma *)audio;
struct camu_ff_resample_fmt *fmt = (struct camu_ff_resample_fmt *)opaque;
(void)fmt;
ma->config = ma_device_config_init(ma_device_type_playback);
@@ -225,9 +225,9 @@ static bool audio_miniaudio_configure_stream(struct camu_audio *audio, void *opa
return true;
}
-static u64 audio_miniaudio_get_latency(struct camu_audio *audio)
+static u64 audio_ma_get_latency(struct camu_audio *audio)
{
- struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)audio;
+ struct camu_audio_ma *ma = (struct camu_audio_ma *)audio;
log_debug("periods: %u, period_size_in_frames: %u, sample_rate: %u.",
ma->device.playback.internalPeriods,
ma->device.playback.internalPeriodSizeInFrames,
@@ -237,21 +237,21 @@ static u64 audio_miniaudio_get_latency(struct camu_audio *audio)
(ma->device.playback.internalSampleRate / 1000000.0);
}
-static void audio_miniaudio_start(struct camu_audio *audio)
+static void audio_ma_start(struct camu_audio *audio)
{
- struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)audio;
+ struct camu_audio_ma *ma = (struct camu_audio_ma *)audio;
ma_device_start(&ma->device);
}
-static void audio_miniaudio_stop(struct camu_audio *audio)
+static void audio_ma_stop(struct camu_audio *audio)
{
- struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)audio;
+ struct camu_audio_ma *ma = (struct camu_audio_ma *)audio;
ma_device_stop(&ma->device);
}
-static void audio_miniaudio_free(struct camu_audio **audio)
+static void audio_ma_free(struct camu_audio **audio)
{
- struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)*audio;
+ struct camu_audio_ma *ma = (struct camu_audio_ma *)*audio;
ma_device_uninit(&ma->device);
ma_context_uninit(&ma->context);
#ifdef MA_ENABLE_PULSEAUDIO
@@ -263,15 +263,15 @@ static void audio_miniaudio_free(struct camu_audio **audio)
struct camu_audio *camu_audio_miniaudio_create(void)
{
- struct camu_audio_miniaudio *ma = al_alloc_object(struct camu_audio_miniaudio);
+ struct camu_audio_ma *ma = al_alloc_object(struct camu_audio_ma);
ma->a = (struct camu_audio){
- .init = audio_miniaudio_init,
- .pick_format = audio_miniaudio_pick_format,
- .configure_stream = audio_miniaudio_configure_stream,
- .get_latency = audio_miniaudio_get_latency,
- .start = audio_miniaudio_start,
- .stop = audio_miniaudio_stop,
- .free = audio_miniaudio_free,
+ .init = audio_ma_init,
+ .pick_format = audio_ma_pick_format,
+ .configure_stream = audio_ma_configure_stream,
+ .get_latency = audio_ma_get_latency,
+ .start = audio_ma_start,
+ .stop = audio_ma_stop,
+ .free = audio_ma_free,
.data_callback = NULL,
.userdata = NULL
};
diff --git a/src/mixer/audio_miniaudio.h b/src/mixer/audio_miniaudio.h
index c067024..47e1088 100644
--- a/src/mixer/audio_miniaudio.h
+++ b/src/mixer/audio_miniaudio.h
@@ -9,7 +9,7 @@
#include "audio.h"
-struct camu_audio_miniaudio {
+struct camu_audio_ma {
struct camu_audio a;
ma_log log;
ma_context_config context_config;
diff --git a/src/portal/src/search.c b/src/portal/src/search.c
index a1b7310..3f012bf 100644
--- a/src/portal/src/search.c
+++ b/src/portal/src/search.c
@@ -75,6 +75,7 @@ static nn_thread_result NNWT_THREADCALL queue_thread(void *userdata)
// Defer python init.
if (!(have_python = camu_python_init())) {
log_error("Failed to initialize python.");
+ bridge->quit = 1;
break;
}
}
@@ -125,13 +126,15 @@ static nn_thread_result NNWT_THREADCALL queue_thread(void *userdata)
}
}
} else {
- log_info("Requested search doesn't exist (id: %d).", search->id);
result.id = -1;
+ log_info("Requested search doesn't exist (id: %d).", cmd->id);
}
break;
}
}
+ camu_queue_lock(bridge->results);
camu_queue_push(bridge->results, result);
+ camu_queue_unlock(bridge->results);
}
bridge->queue.count = 0;
nn_signal_send(&bridge->results_signal);
@@ -145,7 +148,9 @@ static nn_thread_result NNWT_THREADCALL queue_thread(void *userdata)
result.callback = cmd->callback;
result.userdata = cmd->userdata;
result.id = -1;
+ camu_queue_lock(bridge->results);
camu_queue_push(bridge->results, result);
+ camu_queue_unlock(bridge->results);
}
bridge->queue.count = 0;
nn_signal_send(&bridge->results_signal);
@@ -187,13 +192,18 @@ void camu_portal_init(struct camu_portal_bridge *bridge, struct camu_post_cache
void camu_portal_create_search(struct camu_portal_bridge *bridge, str *module, str *query,
void (*callback)(void *, void *, struct camu_portal_result *), void *userdata)
{
+ nn_mutex_lock(&bridge->mutex);
+ if (bridge->quit) {
+ callback(bridge->userdata, userdata, NULL);
+ nn_mutex_unlock(&bridge->mutex);
+ return;
+ }
struct camu_portal_cmd cmd = { 0 };
cmd.op = CAMU_CLIENT_CREATE_SEARCH;
al_str_clone(&cmd.module, module);
al_str_clone(&cmd.query, query);
cmd.callback = callback;
cmd.userdata = userdata;
- nn_mutex_lock(&bridge->mutex);
al_array_push(bridge->queue, cmd);
if (nn_cond_is_waiting(&bridge->cond)) {
nn_cond_signal(&bridge->cond);
@@ -204,13 +214,18 @@ void camu_portal_create_search(struct camu_portal_bridge *bridge, str *module, s
void camu_portal_get_page(struct camu_portal_bridge *bridge, s32 id, u32 num,
void (*callback)(void *, void *, struct camu_portal_result *), void *userdata)
{
+ nn_mutex_lock(&bridge->mutex);
+ if (bridge->quit) {
+ callback(bridge->userdata, userdata, NULL);
+ nn_mutex_unlock(&bridge->mutex);
+ return;
+ }
struct camu_portal_cmd cmd = { 0 };
cmd.op = CAMU_CLIENT_GET_PAGE;
cmd.id = id;
cmd.num = num;
cmd.callback = callback;
cmd.userdata = userdata;
- nn_mutex_lock(&bridge->mutex);
al_array_push(bridge->queue, cmd);
if (nn_cond_is_waiting(&bridge->cond)) {
nn_cond_signal(&bridge->cond);
diff --git a/src/render/meson.build b/src/render/meson.build
index 157cd63..a7ccb58 100644
--- a/src/render/meson.build
+++ b/src/render/meson.build
@@ -9,11 +9,9 @@ if get_option('renderer') == 'momo'
render_args += ['-DCAMU_RENDERER_MOMO']
elif get_option('renderer') == 'libplacebo'
libplacebo_found = false
- if 'libplacebo' not in get_option('force_fallback_for')
+ if 'libplacebo' not in get_option('force_fallback_for') and get_option('wrap_mode') != 'forcefallback'
libplacebo = dependency('libplacebo', required: false, allow_fallback: false)
- if libplacebo.found()
- libplacebo_found = true
- endif
+ libplacebo_found = libplacebo.found()
endif
if not libplacebo_found
libplacebo_opts = ['warning_level=2', 'demos=false', 'tests=false', 'bench=false', 'fuzz=false',
@@ -28,7 +26,7 @@ elif get_option('renderer') == 'libplacebo'
if get_option('renderer-api') == 'vulkan' or get_option('renderer-api') == 'dx11'
if get_option('renderer-compiler') == 'shaderc'
shaderc_found = false
- if 'shaderc' not in get_option('force_fallback_for')
+ if 'shaderc' not in get_option('force_fallback_for') and get_option('wrap_mode') != 'forcefallback'
if is_windows
shaderc = dependency('shaderc_combined', required: false, allow_fallback: false)
else
@@ -47,11 +45,14 @@ elif get_option('renderer') == 'libplacebo'
shaderc_opts.add_cmake_defines({ 'SHADERC_SKIP_TESTS': true })
shaderc_opts.add_cmake_defines({ 'SHADERC_SKIP_EXAMPLES': true })
shaderc_opts.add_cmake_defines({ 'SHADERC_SKIP_COPYRIGHT_CHECK': true })
+ # glslang
shaderc_opts.add_cmake_defines({ 'ENABLE_GLSLANG_BINARIES': false })
- shaderc_opts.add_cmake_defines({ 'ENABLE_SPVREMAPPER': false })
shaderc_opts.add_cmake_defines({ 'ENABLE_HLSL': false })
+ shaderc_opts.add_cmake_defines({ 'ENABLE_SPVREMAPPER': false })
+ shaderc_opts.add_cmake_defines({ 'ENABLE_EXCEPTIONS': false })
spirv_optimizer = true
shaderc_opts.add_cmake_defines({ 'ENABLE_OPT': spirv_optimizer })
+ # spirv-tools
shaderc_opts.add_cmake_defines({ 'ENABLE_EXCEPTIONS_ON_MSVC': false })
shaderc_proj = cmake.subproject('shaderc', options: shaderc_opts)
shaderc = shaderc_proj.dependency('shaderc')
@@ -66,9 +67,7 @@ elif get_option('renderer') == 'libplacebo'
shaderc_proj.dependency('SPIRV-Tools-static')
]
if spirv_optimizer
- render_deps += [
- shaderc_proj.dependency('SPIRV-Tools-opt'),
- ]
+ render_deps += [shaderc_proj.dependency('SPIRV-Tools-opt')]
endif
endif
libplacebo_opts += ['shaderc=enabled', 'glslang=disabled']
@@ -77,19 +76,36 @@ elif get_option('renderer') == 'libplacebo'
if glslang.found()
render_deps += [glslang]
else
+ # Currently does not build.
+ prefer_static = get_option('prefer_static')
+ spirv_optimizer = false
+ if spirv_optimizer
+ spirv_headers_proj = cmake.subproject('SPIRV-Headers')
+ spirv_headers = spirv_headers_proj.dependency('SPIRV-Headers')
+ spirv_tools_opts = cmake.subproject_options()
+ spirv_tools_opts.add_cmake_defines({ 'SPIRV-Headers_SOURCE_DIR': meson.source_root() + '/subprojects/SPIRV-Headers-b824a46' })
+ spirv_tools_opts.add_cmake_defines({ 'ENABLE_EXCEPTIONS_ON_MSVC': false })
+ spirv_tools_proj = cmake.subproject('SPIRV-Tools', options: spirv_tools_opts)
+ spirv_tools = spirv_tools_proj.dependency('SPIRV-Tools-static')
+ spirv_tools_opt = spirv_tools_proj.dependency('SPIRV-Tools-opt')
+ endif
glslang_opts = cmake.subproject_options()
glslang_opts.set_override_option('warning_level', '0')
reltype = is_minsize ? 'MinSizeRel' : 'Release'
glslang_opts.add_cmake_defines({ 'CMAKE_BUILD_TYPE': is_debug ? 'Debug' : reltype })
- spirv_optimizer = false
+ glslang_opts.add_cmake_defines({ 'ENABLE_GLSLANG_BINARIES': false })
+ glslang_opts.add_cmake_defines({ 'ENABLE_HLSL': false })
+ glslang_opts.add_cmake_defines({ 'ENABLE_SPVREMAPPER': false })
+ glslang_opts.add_cmake_defines({ 'ENABLE_EXCEPTIONS': false })
glslang_opts.add_cmake_defines({ 'ENABLE_OPT': spirv_optimizer })
+ glslang_opts.add_cmake_defines({ 'ALLOW_EXTERNAL_SPIRV_TOOLS': spirv_optimizer })
glslang_proj = cmake.subproject('glslang', options: glslang_opts)
spirv = glslang_proj.dependency('SPIRV')
glslang = glslang_proj.dependency('glslang')
glslang_default_resource_limits = glslang_proj.dependency('glslang-default-resource-limits')
- meson.override_dependency('SPIRV', spirv)
- meson.override_dependency('glslang', glslang)
- meson.override_dependency('glslang-default-resource-limits', glslang_default_resource_limits)
+ meson.override_dependency('SPIRV', spirv, static: prefer_static)
+ meson.override_dependency('glslang', glslang, static: prefer_static)
+ meson.override_dependency('glslang-default-resource-limits', glslang_default_resource_limits, static: prefer_static)
render_deps += [
glslang_proj.dependency('MachineIndependent'),
glslang_proj.dependency('OSDependent'),
@@ -98,6 +114,13 @@ elif get_option('renderer') == 'libplacebo'
glslang,
glslang_default_resource_limits
]
+ if spirv_optimizer
+ render_deps += [
+ spirv_tools,
+ spirv_headers,
+ spirv_tools_opt
+ ]
+ endif
endif
libplacebo_opts += ['glslang=enabled', 'shaderc=disabled']
endif
@@ -140,11 +163,9 @@ endif
if get_option('subtitles').enabled()
libass_found = false
- if 'libass' not in get_option('force_fallback_for')
+ if 'libass' not in get_option('force_fallback_for') and get_option('wrap_mode') != 'forcefallback'
libass = dependency('libass', required: false, allow_fallback: false)
- if libass.found()
- libass_found = true
- endif
+ libass_found = libass.found()
endif
if not libass_found
libass_opts = ['test=disabled']
@@ -191,6 +212,7 @@ if get_option('subtitles').enabled()
'gobject=disabled',
'cairo=disabled',
'chafa=disabled',
+ 'icu=disabled',
'freetype=disabled',
# This does not override yield, and yielding breaks the build if tests=enabled.
# https://github.com/mesonbuild/meson/issues/5214
diff --git a/src/render/queue_libplacebo.c b/src/render/queue_libplacebo.c
index c78562c..e3540c3 100644
--- a/src/render/queue_libplacebo.c
+++ b/src/render/queue_libplacebo.c
@@ -164,12 +164,12 @@ static struct camu_overlay *first_nonref_overlay(struct camu_frame_queue_lp *lq,
static struct camu_overlay *create_subtitle_overlay(struct camu_overlay *prev, pl_gpu gpu, ASS_Image *ass_frame)
{
- u32 frame_count = 1;
+ u16 frame_count = 1;
ASS_Image *head_ass_frame = ass_frame;
- while (ass_frame->next) { frame_count++; ass_frame = ass_frame->next; }
+ while (ass_frame->next) { al_assert(frame_count < UINT16_MAX); frame_count++; ass_frame = ass_frame->next; }
ass_frame = head_ass_frame;
- u32 prev_alloc = prev ? prev->alloc : 0;
+ u16 prev_alloc = prev ? prev->alloc : 0;
struct camu_overlay *overlay = camu_overlay_alloc(prev, frame_count,
sizeof(struct pl_overlay) + sizeof(struct pl_overlay_part));
if (frame_count > prev_alloc) {
@@ -225,9 +225,9 @@ static struct camu_overlay *create_subtitle_overlay(struct camu_overlay *prev, p
};
u32 c = ass_frame->color;
current_part->color[0] = (c >> 24) / 255.0;
- current_part->color[1] = ((c >> 16) & 0xFF) / 255.0;
- current_part->color[2] = ((c >> 8) & 0xFF) / 255.0;
- current_part->color[3] = 1.0 - (c & 0xFF) / 255.0;
+ current_part->color[1] = ((c >> 16) & 0xff) / 255.0;
+ current_part->color[2] = ((c >> 8) & 0xff) / 255.0;
+ current_part->color[3] = 1.0 - (c & 0xff) / 255.0;
current->parts = current_part;
current->num_parts = 1;
@@ -317,8 +317,9 @@ static bool map_av_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *
if (overlay) {
overlay->ref++;
out_frame->overlays = CAMU_OVERLAY_OFFSET(overlay, 0);
- out_frame->num_overlays = overlay->num;
((struct pl_source_frame *)src)->frame_data = overlay;
+ } else {
+ out_frame->overlays = NULL;
}
}
nn_mutex_unlock(&lq->subtitle_lock);
diff --git a/src/render/renderer.h b/src/render/renderer.h
index 3331baa..0eae5cb 100644
--- a/src/render/renderer.h
+++ b/src/render/renderer.h
@@ -30,12 +30,13 @@ enum {
struct camu_overlay {
u16 ref;
- u32 alloc;
+ u16 alloc;
s32 num;
struct nn_mutex *lock;
};
#define CAMU_OVERLAY_OFFSET(overlay, n) ((void *)(((u8 *)(overlay)) + sizeof(struct camu_overlay) + (n)))
+#define CAMU_OVERLAY_FROM_HEAD(p) (struct camu_overlay *)(((u8 *)p) - sizeof(struct camu_overlay))
struct camu_frame_queue;
struct camu_codec_stream;
@@ -67,7 +68,7 @@ struct camu_renderer {
u32 (*get_latency)(struct camu_renderer *);
void (*resize)(struct camu_renderer *, u32 *, u32 *);
void (*set)(struct camu_renderer *, u8, u8);
- void (*render)(struct camu_renderer *, struct camu_screen *, bool);
+ bool (*render)(struct camu_renderer *, struct camu_screen *, bool);
#ifdef CAMU_HAVE_FFMPEG
s32 (*get_buffer2)(struct AVCodecContext *s, AVFrame *frame, s32 flags);
void *opaque;
diff --git a/src/render/renderer_libplacebo.c b/src/render/renderer_libplacebo.c
index 3b69c64..01aa764 100644
--- a/src/render/renderer_libplacebo.c
+++ b/src/render/renderer_libplacebo.c
@@ -13,7 +13,8 @@
#include "queue_libplacebo.h"
#ifdef AL_DEBUG
-#define RENDERER_DEBUG 1
+// vkQueueSubmit2: VK_ERROR_VALIDATION_FAILED_EXT leads to an annoying deadlock.
+#define RENDERER_DEBUG 0
#else
#define RENDERER_DEBUG 0
#endif
@@ -37,8 +38,11 @@ static void renderer_lp_resize(struct camu_renderer *renderer, u32 *width, u32 *
struct camu_renderer_lp *lr = (struct camu_renderer_lp *)renderer;
if (lr->swapchain) {
if (lr->have_frame) {
- pl_swapchain_submit_frame(lr->swapchain);
- lr->have_frame = false;
+ if (pl_swapchain_submit_frame(lr->swapchain)) {
+ lr->have_frame = false;
+ } else {
+ log_error("Failed to submit frame on resize.");
+ }
}
pl_swapchain_resize_compat(lr->swapchain, width, height);
}
@@ -365,7 +369,9 @@ static inline intptr_t float_64_hash(f64 value)
return hash;
}
-static void renderer_lp_render(struct camu_renderer *renderer, struct camu_screen *scr, bool force)
+
+
+static bool renderer_lp_render(struct camu_renderer *renderer, struct camu_screen *scr, bool force)
{
struct camu_renderer_lp *lr = (struct camu_renderer_lp *)renderer;
@@ -377,15 +383,14 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree
if (!lr->have_frame) {
struct pl_swapchain_frame frame;
- if (!pl_swapchain_start_frame(lr->swapchain, &frame)) {
- if (force) {
- pl_swapchain_swap_buffers(lr->swapchain);
- }
- return;
+ if (pl_swapchain_start_frame(lr->swapchain, &frame)) {
+ pl_frame_from_swapchain(&lr->target, &frame);
+ pl_frame_clear_rgba(lr->gpu, &lr->target, clear_color);
+ lr->have_frame = true;
+ } else {
+ log_error("Failed to start frame, fatal.");
+ return false;
}
- pl_frame_from_swapchain(&lr->target, &frame);
- pl_frame_clear_rgba(lr->gpu, &lr->target, clear_color);
- lr->have_frame = true;
}
f64 mouse_x, mouse_y;
@@ -405,11 +410,14 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree
lr->params.hooks[0]->parameters[1].data->f = (f32)mouse_y;
}
+ enum {
+ RESULT_SUBMIT = 1,
+ RESULT_SUBMIT_AND_WAIT = 1 << 1
+ };
+
+ u8 result = 0;
struct pl_frame *target = &lr->target;
struct pl_frame_mix mix;
-
- bool do_gpu_finish = false;
-
struct camu_screen_video *video;
while (scr->videos.count > 0) {
bool any_eof = false;
@@ -419,7 +427,7 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree
// If mix.frames is NULL, read() returned QUEUE_MORE.
if (mix.frames) {
// weighted is only set when read() returns QUEUE_OK.
- do_gpu_finish |= weighted;
+ result |= (1 | 1 << weighted);
// Terrible hack. Lets us distinguish single frames with the same dimensions.
// Tied to a libplacebo patch to consider info_priv in the hash.
intptr_t hash = (intptr_t)video->buf;
@@ -431,15 +439,22 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree
hash += float_64_hash(video->view.fov);
lr->params.hooks[0]->parameters[2].data->f = (f32)video->view.fov;
}
- lr->params.info_priv = (void *)hash;
- target->crop = mix.frames[0]->crop;
- if (!scr->subtitles_enabled) {
- // This basically discards the subtitles of each frame before it's rendered.
- // So, toggling the subtitles of a single frame (paused) won't work.
- for (s32 j = 0; j < mix.num_frames; j++) {
- ((struct pl_frame *)mix.frames[j])->num_overlays = 0;
+#ifdef CAMU_HAVE_SUBTITLES
+ for (u32 j = 0; j < (u32)mix.num_frames; j++) {
+ struct pl_frame *mix_frame = (struct pl_frame *)mix.frames[j];
+ if (!mix_frame->overlays) continue;
+ struct camu_overlay *overlay = CAMU_OVERLAY_FROM_HEAD(mix_frame->overlays);
+ if (!overlay->num) continue;
+ if (scr->subtitles_enabled) {
+ mix_frame->num_overlays = overlay->num;
+ hash += overlay->num;
+ } else {
+ mix_frame->num_overlays = 0;
}
}
+#endif
+ lr->params.info_priv = (void *)hash;
+ target->crop = mix.frames[0]->crop;
target->crop.x1 *= video->view.zoom / video->view.stretch;
target->crop.y1 *= video->view.zoom * video->view.stretch;
target->crop.x0 += video->view.x_offset;
@@ -447,7 +462,14 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree
target->crop.x1 += video->view.x_offset;
target->crop.y1 += video->view.y_offset;
target->rotation = video->view.rotation;
- //lr->params.color_adjustment = pl_color_adjustment(.saturation = 0.0);
+ /*
+ //lr->params.color_map_params = pl_color_map_params(.inverse_tone_mapping = true);
+ if () {
+ target->color = pl_color_space_srgb;
+ target->color.hdr.max_luma = PL_COLOR_SDR_WHITE - 50.f;
+ target->color.hdr.min_luma = (PL_COLOR_SDR_WHITE - 50.f) / PL_COLOR_SDR_CONTRAST;
+ }
+ */
pl_render_image_mix(lr->renderer, &mix, target, &lr->params);
}
} else {
@@ -468,32 +490,30 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree
lr->params.hooks = NULL;
lr->params.num_hooks = 0;
- if (!scr->videos.count && !force) {
- // Don't spin too hard on a potential error state.
+ // Considerations about the result of the loop above.
+ // 1. Any given call to video_buffer_read() may not produce a frame.
+ // 2. If a buffer signals EOF, by the time run_queue() happens at the bottom of the loop all
+ // previous buffers could have been queued for removal. Meaning, at this point, videos.count
+ // would be 0 while there could be data we want to display rendered to the frame.
+ if (!(result & RESULT_SUBMIT) && !force) {
nn_thread_sleep(NNWT_TS_FROM_USEC(192));
- return;
+ return true;
}
- if (!pl_swapchain_submit_frame(lr->swapchain)) {
+ if (pl_swapchain_submit_frame(lr->swapchain)) {
+ lr->have_frame = false;
+ } else {
log_error("Failed to submit frame, possibly fatal.");
+ return false;
}
- lr->have_frame = false;
pl_swapchain_swap_buffers(lr->swapchain);
- if (do_gpu_finish) {
- // Block until render completes.
+ if (result & RESULT_SUBMIT_AND_WAIT) {
pl_gpu_finish(lr->gpu);
}
-#if 0
- f64 tick = nn_get_tick();
- f64 frame_time = tick - lr->last_render_tick;
- if (frame_time > (1.0 / 24.0)) { // 24fps is totally arbitrary.
- log_info("FRAME_TIME: %fs (%.2ffps)", frame_time, 1.0 / frame_time);
- }
- lr->last_render_tick = tick;
-#endif
+ return true;
}
void renderer_lp_free(struct camu_renderer **renderer)
@@ -521,7 +541,7 @@ void renderer_lp_free(struct camu_renderer **renderer)
*renderer = NULL;
}
-struct camu_renderer *camu_renderer_lp_create(void)
+struct camu_renderer *camu_renderer_libplacebo_create(void)
{
struct camu_renderer_lp *lr = al_alloc_object(struct camu_renderer_lp);
lr->r.create_renderer = renderer_lp_create_renderer;
diff --git a/src/render/renderer_libplacebo.h b/src/render/renderer_libplacebo.h
index 5d5cf9b..727e0b3 100644
--- a/src/render/renderer_libplacebo.h
+++ b/src/render/renderer_libplacebo.h
@@ -42,4 +42,4 @@ struct camu_renderer_lp {
#endif
};
-struct camu_renderer *camu_renderer_lp_create(void);
+struct camu_renderer *camu_renderer_libplacebo_create(void);
diff --git a/src/render/renderer_momo.c b/src/render/renderer_momo.c
index 3a0e7ed..02ccaee 100644
--- a/src/render/renderer_momo.c
+++ b/src/render/renderer_momo.c
@@ -85,12 +85,13 @@ static u32 renderer_momo_get_latency(struct camu_renderer *renderer)
return 0;
}
-static void renderer_momo_render(struct camu_renderer *renderer, struct camu_screen *scr, bool force)
+static bool renderer_momo_render(struct camu_renderer *renderer, struct camu_screen *scr, bool force)
{
struct camu_renderer_momo *mr = (struct camu_renderer_momo *)renderer;
(void)scr;
(void)force;
MOMO_Render(&mr->mmr);
+ return true;
}
static void renderer_momo_free(struct camu_renderer **renderer)
diff --git a/src/screen/screen.c b/src/screen/screen.c
index 844076e..f6833dd 100644
--- a/src/screen/screen.c
+++ b/src/screen/screen.c
@@ -259,8 +259,8 @@ static bool key_callback(void *userdata, u8 state, u16 button)
break;
case STELA_KEY_RIGHT:
if (SCREEN_MOD1(scr)) {
- f64 plus_five_seconds = 5.0;
- scr->callback(scr->userdata, CAMU_SCREEN_RELATIVE_SEEK, &plus_five_seconds);
+ f64 plus_ten_seconds = 10.0;
+ scr->callback(scr->userdata, CAMU_SCREEN_RELATIVE_SEEK, &plus_ten_seconds);
break;
}
// fallthrough
@@ -276,8 +276,8 @@ static bool key_callback(void *userdata, u8 state, u16 button)
f64 zero = 0.0;
scr->callback(scr->userdata, CAMU_SCREEN_PERCENT_SEEK, &zero);
} else {
- f64 minus_five_seconds = -5.0;
- scr->callback(scr->userdata, CAMU_SCREEN_RELATIVE_SEEK, &minus_five_seconds);
+ f64 minus_ten_seconds = -10.0;
+ scr->callback(scr->userdata, CAMU_SCREEN_RELATIVE_SEEK, &minus_ten_seconds);
}
break;
}
@@ -294,9 +294,11 @@ static bool key_callback(void *userdata, u8 state, u16 button)
if (SCREEN_MOD1(scr)) {
scr->callback(scr->userdata, CAMU_SCREEN_SHUFFLE, NULL);
} else {
+#ifdef CAMU_HAVE_SUBTITLES
scr->subtitles_enabled = !scr->subtitles_enabled;
log_info(scr->subtitles_enabled ? "Subtitles enabled." : "Subtitles disabled.");
return true;
+#endif
}
break;
case STELA_KEY_E:
@@ -484,7 +486,9 @@ bool camu_screen_init(struct camu_screen *scr)
scr->flags = CAMU_SCREEN_ZOOM_PAN_SIMPLE;
scr->scaling_disabled = false;
scr->transparent_background = false;
+#ifdef CAMU_HAVE_SUBTITLES
scr->subtitles_enabled = true;
+#endif
scr->last_click_ts = SCREEN_INVALID_TS;
scr->last_pointer_x = 0.0;
scr->last_pointer_y = 0.0;
diff --git a/src/server/server.c b/src/server/server.c
index ae77ef0..59a0f7a 100644
--- a/src/server/server.c
+++ b/src/server/server.c
@@ -590,28 +590,31 @@ out:
#ifdef CAMU_HAVE_PORTAL
static void simple_search_portal_callback(void *userdata0, void *userdata1, struct camu_portal_result *result)
{
- struct camu_server *server = (struct camu_server *)userdata0;
struct camu_resource *resource = (struct camu_resource *)userdata1;
- switch (result->op) {
- case CAMU_CLIENT_CREATE_SEARCH:
- if (result->id == -1) {
- break;
- }
- camu_portal_get_page(&server->bridge, result->id, 0, simple_search_portal_callback, resource);
- return;
- case CAMU_CLIENT_GET_PAGE:
- if (result->id == -1 || !result->page || !result->page->list.count) {
- break;
+ if (result) {
+ struct camu_server *server = (struct camu_server *)userdata0;
+ switch (result->op) {
+ case CAMU_CLIENT_CREATE_SEARCH:
+ if (result->id == -1) {
+ break;
+ }
+ camu_portal_get_page(&server->bridge, result->id, 0, simple_search_portal_callback, resource);
+ return;
+ case CAMU_CLIENT_GET_PAGE:
+ if (result->id == -1 || !result->page || !result->page->list.count) {
+ break;
+ }
+ resource->post = camu_post_cache_get(&server->cache, &al_array_at(result->page->list, 0));
+ resource->type = CAMU_RESOURCE_PORTAL;
+ prepare_server_resource(server, resource);
+ process_pending(resource);
+ return;
}
- resource->post = camu_post_cache_get(&server->cache, &al_array_at(result->page->list, 0));
- resource->type = CAMU_RESOURCE_PORTAL;
- prepare_server_resource(server, resource);
- process_pending(resource);
- return;
}
// No return is the error case.
log_warn("Failed to process search request.");
resource->load = LIANA_ENTRY_ERRORED;
+ process_pending(resource);
}
#endif
@@ -684,6 +687,7 @@ static void handle_add_command(struct camu_server *server, struct lia_list *list
al_str_from(&query, "link:");
al_str_cat(&query, &line);
}
+ log_info("Processing search request: %.*s.", al_str_x(&query));
camu_portal_create_search(&server->bridge, &al_str_c("youtube"),
&query, simple_search_portal_callback, resource);
al_str_free(&query);
diff --git a/src/util/queue.h b/src/util/queue.h
index 17880e0..08b0a30 100644
--- a/src/util/queue.h
+++ b/src/util/queue.h
@@ -19,13 +19,6 @@
nn_mutex_unlock(&(q).mutex); \
} while (0)
-#define camu_queue_count(q, r) \
- do { \
- nn_mutex_lock(&(q).mutex); \
- r = (q).a.count; \
- nn_mutex_unlock(&(q).mutex); \
- } while (0)
-
#define camu_queue_init(q) \
do { \
al_array_init((q).a); \
@@ -34,16 +27,7 @@
#define camu_queue_push(q, item) \
do { \
- nn_mutex_lock(&(q).mutex); \
al_array_push((q).a, item); \
- nn_mutex_unlock(&(q).mutex); \
- } while (0)
-
-#define camu_queue_pop(q, r) \
- do { \
- nn_mutex_lock(&(q).mutex); \
- al_array_pop_at((q).a, 0, r); \
- nn_mutex_unlock(&(q).mutex); \
} while (0)
#define camu_queue_try_pop(q, s, r) \
diff --git a/subprojects/SPIRV-Cross.wrap b/subprojects/SPIRV-Cross.wrap
index d522ec1..867f649 100644
--- a/subprojects/SPIRV-Cross.wrap
+++ b/subprojects/SPIRV-Cross.wrap
@@ -1,7 +1,7 @@
[wrap-git]
-directory = SPIRV-Cross-adec7ac
+directory = SPIRV-Cross-4f77a07
url = https://github.com/KhronosGroup/SPIRV-Cross.git
-revision = adec7acbf41a988713cdb85f93f26c8ca5ea863e
+revision = 4f77a07af0dcadd0f59d6eb78dc879abdd1d7400
depth = 1
method = cmake
patch_directory = SPIRV-Cross
diff --git a/subprojects/SPIRV-Headers.wrap b/subprojects/SPIRV-Headers.wrap
new file mode 100644
index 0000000..06b420c
--- /dev/null
+++ b/subprojects/SPIRV-Headers.wrap
@@ -0,0 +1,9 @@
+[wrap-git]
+directory = SPIRV-Headers-b824a46
+url = https://github.com/KhronosGroup/SPIRV-Headers.git
+revision = b824a462d4256d720bebb40e78b9eb8f78bbb305
+depth = 1
+method = cmake
+
+[provide]
+dependency_names = SPIRV-Headers
diff --git a/subprojects/dav1d.wrap b/subprojects/dav1d.wrap
new file mode 100644
index 0000000..ed2d7a0
--- /dev/null
+++ b/subprojects/dav1d.wrap
@@ -0,0 +1,8 @@
+[wrap-git]
+directory = dav1d-1.5.2
+url = https://code.videolan.org/videolan/dav1d.git
+revision = 1.5.2
+depth = 1
+
+[provide]
+dav1d = dav1d_dep
diff --git a/subprojects/ffmpeg3.wrap b/subprojects/ffmpeg3.wrap
index 71d9908..64a316d 100644
--- a/subprojects/ffmpeg3.wrap
+++ b/subprojects/ffmpeg3.wrap
@@ -1,13 +1,13 @@
[wrap-file]
-directory = ffmpeg-3.4.14
-#directory = ffmpeg-3.3.9
-source_url = https://ffmpeg.org/releases/ffmpeg-3.4.14.tar.gz
-#source_url = https://ffmpeg.org/releases/ffmpeg-3.3.9.tar.gz
-source_filename = ffmpeg-3.4.14.tar.gz
-#source_filename = ffmpeg-3.3.9.tar.gz
-source_hash = 61e255e824341e62fbc83365d84eb754fa558f53985d28ad610d13aeee895d5a
-#source_hash = f79ef311f55303dbc692014d609ec44a609adcea6ded5f5507b6e220068521a3
+#directory = ffmpeg-3.4.14
+directory = ffmpeg-3.3.9
+#source_url = https://ffmpeg.org/releases/ffmpeg-3.4.14.tar.gz
+source_url = https://ffmpeg.org/releases/ffmpeg-3.3.9.tar.gz
+#source_filename = ffmpeg-3.4.14.tar.gz
+source_filename = ffmpeg-3.3.9.tar.gz
+#source_hash = 61e255e824341e62fbc83365d84eb754fa558f53985d28ad610d13aeee895d5a
+source_hash = f79ef311f55303dbc692014d609ec44a609adcea6ded5f5507b6e220068521a3
patch_directory = ffmpeg
-diff_files = ffmpeg/version_3.4.14.diff, ffmpeg/msvc_build_conform.diff, ffmpeg/ffmpeg.git-effadce6c756247ea8bae32dc13bb3e6f464f0eb.patch
-#diff_files = ffmpeg/version_3.3.9.diff, ffmpeg/msvc_build_conform.diff, ffmpeg/ffmpeg.git-effadce6c756247ea8bae32dc13bb3e6f464f0eb.patch
+#diff_files = ffmpeg/version_3.4.14.diff, ffmpeg/msvc_build_conform_old.diff, ffmpeg/ffmpeg.git-effadce6c756247ea8bae32dc13bb3e6f464f0eb.patch
+diff_files = ffmpeg/version_3.3.9.diff, ffmpeg/msvc_build_conform_old.diff, ffmpeg/ffmpeg.git-effadce6c756247ea8bae32dc13bb3e6f464f0eb.patch
diff --git a/subprojects/ffmpeg4.wrap b/subprojects/ffmpeg4.wrap
index a9c4dc7..f2bde11 100644
--- a/subprojects/ffmpeg4.wrap
+++ b/subprojects/ffmpeg4.wrap
@@ -5,4 +5,4 @@ source_filename = ffmpeg-4.2.11.tar.gz
source_hash = 93ad3689249984016c04cb0a0e02e5e22077a4d0204ca26bb604b0b7f0418eeb
patch_directory = ffmpeg
-diff_files = ffmpeg/version_4.2.11.diff, ffmpeg/msvc_build_conform.diff
+diff_files = ffmpeg/version_4.2.11.diff, ffmpeg/msvc_build_conform_old.diff
diff --git a/subprojects/ffmpeg5.wrap b/subprojects/ffmpeg5.wrap
index 6ae62ca..6c063cf 100644
--- a/subprojects/ffmpeg5.wrap
+++ b/subprojects/ffmpeg5.wrap
@@ -1,8 +1,8 @@
[wrap-file]
-directory = ffmpeg-5.1.7
-source_url = https://ffmpeg.org/releases/ffmpeg-5.1.7.tar.gz
-source_filename = ffmpeg-5.1.7.tar.gz
-source_hash = 75f25adaac2fe64f5c078b1ec3c89863353eb07f6e6a9e38898d79d2fc9527c8
+directory = ffmpeg-5.1.8
+source_url = https://ffmpeg.org/releases/ffmpeg-5.1.8.tar.gz
+source_filename = ffmpeg-5.1.8.tar.gz
+source_hash = 79781c24721427f1855e18b1bebea84f34e86f96e6ccb7a11be7e006d63215dc
patch_directory = ffmpeg
-diff_files = ffmpeg/version_5.1.7.diff, ffmpeg/msvc_build_conform.diff
+diff_files = ffmpeg/version_5.1.8.diff, ffmpeg/msvc_build_conform.diff
diff --git a/subprojects/ffmpeg6.wrap b/subprojects/ffmpeg6.wrap
index 9c76e9c..4a6cdf0 100644
--- a/subprojects/ffmpeg6.wrap
+++ b/subprojects/ffmpeg6.wrap
@@ -1,8 +1,8 @@
[wrap-file]
-directory = ffmpeg-6.1.3
-source_url = https://ffmpeg.org/releases/ffmpeg-6.1.3.tar.gz
-source_filename = ffmpeg-6.1.3.tar.gz
-source_hash = 3be466dfd1a312d752c20ece5b413248a5808fba3c967da0624bac48a4380857
+directory = ffmpeg-6.1.4
+source_url = https://ffmpeg.org/releases/ffmpeg-6.1.4.tar.gz
+source_filename = ffmpeg-6.1.4.tar.gz
+source_hash = e21c2624978f56b2030a0cc138cdf67f0b8888b471665907be16b598d85b98ad
patch_directory = ffmpeg
-diff_files = ffmpeg/version_6.1.3.diff, ffmpeg/msvc_build_conform.diff
+diff_files = ffmpeg/version_6.1.4.diff, ffmpeg/msvc_build_conform.diff
diff --git a/subprojects/ffmpeg7.wrap b/subprojects/ffmpeg7.wrap
index 8187b07..adc4807 100644
--- a/subprojects/ffmpeg7.wrap
+++ b/subprojects/ffmpeg7.wrap
@@ -1,8 +1,8 @@
[wrap-file]
-directory = ffmpeg-7.1.1
-source_url = https://ffmpeg.org/releases/ffmpeg-7.1.1.tar.gz
-source_filename = ffmpeg-7.1.1.tar.gz
-source_hash = 9a6e57a446b671012612aaeb9df5126794d5ac8f2015ca220934f99a6a4e0601
+directory = ffmpeg-7.1.3
+source_url = https://ffmpeg.org/releases/ffmpeg-7.1.3.tar.gz
+source_filename = ffmpeg-7.1.3.tar.gz
+source_hash = 81b452c5afe327a19a6a27e28ff09f6bf217b1d1fca988fc451c4717d8fe8ba7
patch_directory = ffmpeg
-diff_files = ffmpeg/version_7.1.1.diff, ffmpeg/msvc_build_conform.diff
+diff_files = ffmpeg/version_7.1.3.diff, ffmpeg/msvc_build_conform.diff
diff --git a/subprojects/ffmpeg8.wrap b/subprojects/ffmpeg8.wrap
index 2bc2d44..b91ff38 100644
--- a/subprojects/ffmpeg8.wrap
+++ b/subprojects/ffmpeg8.wrap
@@ -5,4 +5,4 @@ source_filename = ffmpeg-8.0.1.tar.gz
source_hash = ed1cfade43aab7711c88937a0afc15b7b22efdc528c6ff074b4027c55a3c175c
patch_directory = ffmpeg
-diff_files = ffmpeg/version_8.0.1.diff
+diff_files = ffmpeg/version_8.0.1.diff, ffmpeg/msvc_build_conform.diff
diff --git a/subprojects/glslang.wrap b/subprojects/glslang.wrap
index 7f4eae4..e06e2f6 100644
--- a/subprojects/glslang.wrap
+++ b/subprojects/glslang.wrap
@@ -1,6 +1,6 @@
[wrap-git]
-directory = glslang-15.3.0
+directory = glslang-16.0.0
url = https://github.com/KhronosGroup/glslang.git
-revision = 15.3.0
+revision = 16.0.0
depth = 1
method = cmake
diff --git a/subprojects/libalabaster.wrap b/subprojects/libalabaster.wrap
index 145c015..4b277ec 100644
--- a/subprojects/libalabaster.wrap
+++ b/subprojects/libalabaster.wrap
@@ -1,5 +1,5 @@
[wrap-git]
-directory = libalabaster-0d1bc6c
+directory = libalabaster-b5b7657
url = https://git.akon.city/libalabaster.git
push-url = git@git.akon.city:libalabaster.git
-revision = 0d1bc6cf7483d2a53c2d5f04d15a0ce3da85f29b
+revision = b5b765781482a7b3ff6026ea71b4eb35a0a155d0
diff --git a/subprojects/libnaunet.wrap b/subprojects/libnaunet.wrap
index df923b0..531910f 100644
--- a/subprojects/libnaunet.wrap
+++ b/subprojects/libnaunet.wrap
@@ -1,5 +1,5 @@
[wrap-git]
-directory = libnaunet-5edcdda
+directory = libnaunet-1e0f603
url = https://git.akon.city/libnaunet.git
push-url = git@git.akon.city:libnaunet.git
-revision = 5edcdda9efadd7c6ff5a47bef2897c0eb995057f
+revision = 1e0f60341192c79bf49e84e6b187ffb7bfded252
diff --git a/subprojects/libplacebo.wrap b/subprojects/libplacebo.wrap
index 9f29483..b84b3b6 100644
--- a/subprojects/libplacebo.wrap
+++ b/subprojects/libplacebo.wrap
@@ -1,7 +1,7 @@
[wrap-git]
-directory = libplacebo-2e5a392
+directory = libplacebo-53719e4
url = https://code.videolan.org/videolan/libplacebo.git
-revision = 2e5a392b7f1e4c25d5a3f931e253d71ab566757f
+revision = 53719e45d264d90d422f51e1fa21a2957131bf1a
clone-recursive = true
depth = 1
diff --git a/subprojects/msvc-wine.wrap b/subprojects/msvc-wine.wrap
index b80ca16..feeace3 100644
--- a/subprojects/msvc-wine.wrap
+++ b/subprojects/msvc-wine.wrap
@@ -1,7 +1,5 @@
[wrap-git]
-directory = msvc-wine
+directory = msvc-wine-32b504c
url = https://github.com/mstorsjo/msvc-wine.git
-revision = 44dc13b5e62ecc2373fbe7e4727a525001f403f4
+revision = 32b504c63b869681cda6824a20e30b74cb718432
depth = 1
-patch_directory = msvc-wine
-diff_files = msvc-wine/msvc-wine-shebang-nixos.diff
diff --git a/subprojects/packagefiles/ffmpeg/meson.build b/subprojects/packagefiles/ffmpeg/meson.build
index 46f2d31..f813c8f 100644
--- a/subprojects/packagefiles/ffmpeg/meson.build
+++ b/subprojects/packagefiles/ffmpeg/meson.build
@@ -62,12 +62,24 @@ endif
#'--extra-ldflags=-Lbuild/subprojects/libalabaster -l:libalabaster.a',
#'--malloc-prefix=al_',
-decoders = 'flac,mp3,mp3float,aac,opus,vorbis,h264,hevc,av1,vp9'
+decoders = 'flac,mp3,mp3float,aac,opus,vorbis,h264,hevc,vp9'
decoders += ',pcm_s32be,pcm_s32le,pcm_s24be,pcm_s24le,pcm_s16be,pcm_s16le'
#decoders += ',pcm_f32be,pcm_f32le,pcm_s32le_planar,pcm_s16be_planar,pcm_s16le_planar'
decoders += ',dca,eac3'
decoders += ',pcm_dvd,mpegvideo,mpeg2video'
#decoders += ',ass,srt'
+if get_option('codec-hwaccels').length() == 0
+ if not is_msvc
+ libdav1d = dependency('dav1d', required: false)
+ if libdav1d.found()
+ decoders += ',libdav1d'
+ extra_options += ['--enable-libdav1d']
+ deps += [libdav1d]
+ endif
+ endif
+else
+ decoders += ',av1'
+endif
demuxers = ''
parsers = ''
@@ -88,16 +100,22 @@ if not get_option('sink-only')
endif
endif
-zlib = dependency('zlib', required: false)
-if zlib.found()
- extra_options += ['--enable-zlib']
- decoders += ',png,apng,mjpeg,jpeg2000,gif,'
- if not get_option('sink-only')
- demuxers += ',image_png_pipe,image2,mjpeg,image2pipe,image_jpeg_pipe,gif'
- parsers += ',png,mjpeg,jpeg2000,gif'
+ffmpeg_image_codecs = true
+if ffmpeg_image_codecs
+ zlib = dependency('zlib', required: false)
+ if zlib.found()
+ extra_options += ['--enable-zlib']
+ decoders += ',png,apng,mjpeg,jpeg2000,webp,gif,'
+ if not get_option('sink-only')
+ demuxers += ',image_png_pipe,image2,mjpeg,image2pipe,image_jpeg_pipe,image_webp_pipe,gif'
+ parsers += ',png,mjpeg,jpeg2000,webp,gif'
+ endif
+ deps += [zlib]
+ else
+ extra_options += ['--disable-zlib']
endif
- deps += [zlib]
endif
+extra_options += ['--disable-xlib']
bsfs = ''
if not get_option('sink-only')
@@ -177,7 +195,12 @@ endif
# Disable unused hwaccels.
extra_options += [
+ '--disable-amf',
+ '--disable-audiotoolbox',
+ '--disable-cuda-llvm',
'--disable-cuvid',
+ '--disable-ffnvcodec',
+ '--disable-nvdec',
'--disable-nvenc',
'--disable-vdpau',
'--disable-audiotoolbox',
@@ -237,7 +260,6 @@ proj = ext_proj.add_project('configure',
'--disable-manpages',
'--disable-podpages',
'--disable-txtpages',
- '--disable-safe-bitstream-reader',
'--disable-schannel',
'--disable-network',
'--disable-securetransport',
diff --git a/subprojects/packagefiles/ffmpeg/msvc_build_conform.diff b/subprojects/packagefiles/ffmpeg/msvc_build_conform.diff
index 3aff7e9..80c6c70 100644
--- a/subprojects/packagefiles/ffmpeg/msvc_build_conform.diff
+++ b/subprojects/packagefiles/ffmpeg/msvc_build_conform.diff
@@ -1,15 +1,26 @@
---- a/configure 2025-03-02 19:22:08.000000000 -0500
-+++ b/configure 2025-05-17 18:43:08.854727216 -0400
-@@ -5184,7 +5184,7 @@
- DEPCCFLAGS=$_flags
+--- a/configure 2025-11-19 21:31:28.000000000 -0500
++++ b/configure 2025-11-24 12:35:16.905137447 -0500
+@@ -5299,8 +5299,8 @@
+ arflags="rcD"
+ ar_o='$@'
+ else
+- arflags="rc"
+- ar_o='$@'
++ arflags="-nologo"
++ ar_o='-out:$@'
fi
-
--if $ar 2>&1 | grep -q Microsoft; then
-+if $ar 2>&1 | grep -q Microsoft || [ "$ar" == "llvm-lib" ]; then
- arflags="-nologo"
- ar_o='-out:$@'
- elif $ar 2>&1 | grep -q "\[D\] "; then
-@@ -5878,6 +5878,8 @@
+
+ # Treat unrecognized flags as errors on MSVC
+@@ -5951,6 +5951,8 @@
+ enabled x86_32 && check_ldflags -Wl,--large-address-aware
+ add_cppflags -DWIN32_LEAN_AND_MEAN
+ shlibdir_default="$bindir_default"
++ LIBPREF=""
++ LIBSUF=".lib"
+ SLIBPREF=""
+ SLIBSUF=".dll"
+ SLIBNAME_WITH_VERSION='$(SLIBPREF)$(FULLNAME)-$(LIBVERSION)$(SLIBSUF)'
+@@ -6002,6 +6004,8 @@
enabled x86_32 && check_ldflags -LARGEADDRESSAWARE
add_cppflags -DWIN32_LEAN_AND_MEAN
shlibdir_default="$bindir_default"
diff --git a/subprojects/packagefiles/ffmpeg/msvc_build_conform_old.diff b/subprojects/packagefiles/ffmpeg/msvc_build_conform_old.diff
new file mode 100644
index 0000000..7811469
--- /dev/null
+++ b/subprojects/packagefiles/ffmpeg/msvc_build_conform_old.diff
@@ -0,0 +1,22 @@
+--- a/configure 2025-11-19 21:31:28.000000000 -0500
++++ b/configure 2025-11-24 12:35:16.905137447 -0500
+@@ -5299,8 +5299,8 @@
+ arflags="rcD"
+ ar_o='$@'
+ else
+- arflags="rc"
+- ar_o='$@'
++ arflags="-nologo"
++ ar_o='-out:$@'
+ fi
+
+ # Treat unrecognized flags as errors on MSVC
+@@ -5951,6 +5951,8 @@
+ enabled x86_32 && check_ldflags -Wl,--large-address-aware
+ add_cppflags -DWIN32_LEAN_AND_MEAN
+ shlibdir_default="$bindir_default"
++ LIBPREF=""
++ LIBSUF=".lib"
+ SLIBPREF=""
+ SLIBSUF=".dll"
+ SLIBNAME_WITH_VERSION='$(SLIBPREF)$(FULLNAME)-$(LIBVERSION)$(SLIBSUF)'
diff --git a/subprojects/packagefiles/ffmpeg/version_3.3.9.diff b/subprojects/packagefiles/ffmpeg/version_3.3.9.diff
index 3887a2f..90911d5 100644
--- a/subprojects/packagefiles/ffmpeg/version_3.3.9.diff
+++ b/subprojects/packagefiles/ffmpeg/version_3.3.9.diff
@@ -1,5 +1,5 @@
---- a/meson.build 2025-08-23 18:44:34.634014892 -0400
-+++ b/meson.build 2025-08-25 15:51:35.605178588 -0400
+--- a/meson.build 2025-11-30 11:51:43.747121512 -0500
++++ b/meson.build 2025-11-30 12:55:07.519116860 -0500
@@ -1,4 +1,4 @@
-project('ffmpeg', 'c', version: '', meson_version: '>=0.57.0')
+project('ffmpeg', 'c', version: '3.3.9', meson_version: '>=0.57.0')
@@ -17,7 +17,40 @@
extra_options += ['--enable-static', '--disable-shared']
if is_windows
-@@ -220,6 +216,15 @@
+@@ -68,18 +64,7 @@
+ decoders += ',dca,eac3'
+ decoders += ',pcm_dvd,mpegvideo,mpeg2video'
+ #decoders += ',ass,srt'
+-if get_option('codec-hwaccels').length() == 0
+- if not is_msvc
+- libdav1d = dependency('dav1d', required: false)
+- if libdav1d.found()
+- decoders += ',libdav1d'
+- extra_options += ['--enable-libdav1d']
+- deps += [libdav1d]
+- endif
+- endif
+-else
+- decoders += ',av1'
+-endif
++decoders += ',av1'
+
+ demuxers = ''
+ parsers = ''
+@@ -195,12 +180,8 @@
+
+ # Disable unused hwaccels.
+ extra_options += [
+- '--disable-amf',
+ '--disable-audiotoolbox',
+- '--disable-cuda-llvm',
+ '--disable-cuvid',
+- '--disable-ffnvcodec',
+- '--disable-nvdec',
+ '--disable-nvenc',
+ '--disable-vdpau',
+ '--disable-audiotoolbox',
+@@ -243,6 +224,15 @@
args += ['-DCAMU_HAVE_SOXR']
endif
@@ -33,7 +66,7 @@
ext_proj = import('unstable-external_project')
proj = ext_proj.add_project('configure',
-@@ -229,7 +234,6 @@
+@@ -252,7 +242,6 @@
'--incdir=@PREFIX@/@INCLUDEDIR@',
'--enable-pic',
'--disable-all',
@@ -41,7 +74,7 @@
'--disable-everything',
'--disable-programs',
'--disable-doc',
-@@ -255,11 +259,14 @@
+@@ -277,11 +266,14 @@
'--enable-swscale',
'--disable-indevs',
'--disable-outdevs',
@@ -56,7 +89,7 @@
avutil = proj.dependency('avutil')
avformat = proj.dependency('avformat')
avcodec = proj.dependency('avcodec')
-@@ -268,14 +275,14 @@
+@@ -290,14 +282,14 @@
swscale = proj.dependency('swscale')
include = include_directories('.', 'build/')
meson.override_dependency('libavutil', declare_dependency(
diff --git a/subprojects/packagefiles/ffmpeg/version_4.2.11.diff b/subprojects/packagefiles/ffmpeg/version_4.2.11.diff
index 27d5ceb..cce132d 100644
--- a/subprojects/packagefiles/ffmpeg/version_4.2.11.diff
+++ b/subprojects/packagefiles/ffmpeg/version_4.2.11.diff
@@ -1,12 +1,32 @@
---- a/meson.build 2025-08-23 18:44:34.634014892 -0400
-+++ b/meson.build 2025-08-25 15:42:23.161139654 -0400
+--- a/meson.build 2025-11-30 11:51:43.747121512 -0500
++++ b/meson.build 2025-11-30 12:52:39.981254464 -0500
@@ -1,4 +1,4 @@
-project('ffmpeg', 'c', version: '', meson_version: '>=0.57.0')
+project('ffmpeg', 'c', version: '4.2.11', meson_version: '>=0.57.0')
compiler = meson.get_compiler('c')
-@@ -268,14 +268,14 @@
+@@ -68,18 +68,7 @@
+ decoders += ',dca,eac3'
+ decoders += ',pcm_dvd,mpegvideo,mpeg2video'
+ #decoders += ',ass,srt'
+-if get_option('codec-hwaccels').length() == 0
+- if not is_msvc
+- libdav1d = dependency('dav1d', required: false)
+- if libdav1d.found()
+- decoders += ',libdav1d'
+- extra_options += ['--enable-libdav1d']
+- deps += [libdav1d]
+- endif
+- endif
+-else
+- decoders += ',av1'
+-endif
++decoders += ',av1'
+
+ demuxers = ''
+ parsers = ''
+@@ -290,14 +279,14 @@
swscale = proj.dependency('swscale')
include = include_directories('.', 'build/')
meson.override_dependency('libavutil', declare_dependency(
diff --git a/subprojects/packagefiles/ffmpeg/version_5.1.8.diff b/subprojects/packagefiles/ffmpeg/version_5.1.8.diff
new file mode 100644
index 0000000..784c8a1
--- /dev/null
+++ b/subprojects/packagefiles/ffmpeg/version_5.1.8.diff
@@ -0,0 +1,29 @@
+--- a/meson.build 2025-08-23 18:44:34.634014892 -0400
++++ b/meson.build 2025-08-25 15:40:17.065798145 -0400
+@@ -1,4 +1,4 @@
+-project('ffmpeg', 'c', version: '', meson_version: '>=0.57.0')
++project('ffmpeg', 'c', version: '5.1.8', meson_version: '>=0.57.0')
+
+ compiler = meson.get_compiler('c')
+
+@@ -268,14 +268,14 @@
+ swscale = proj.dependency('swscale')
+ include = include_directories('.', 'build/')
+ meson.override_dependency('libavutil', declare_dependency(
+- dependencies: [avutil, deps], compile_args: args, include_directories: include, version: ''))
++ dependencies: [avutil, deps], compile_args: args, include_directories: include, version: '57.28.100'))
+ meson.override_dependency('libavformat', declare_dependency(
+- dependencies: [avformat], include_directories: include, version: ''))
++ dependencies: [avformat], include_directories: include, version: '59.27.100'))
+ meson.override_dependency('libavcodec', declare_dependency(
+- dependencies: [avcodec], include_directories: include, version: ''))
++ dependencies: [avcodec], include_directories: include, version: '59.37.100'))
+ meson.override_dependency('libavdevice', declare_dependency(
+- dependencies: [avdevice], include_directories: include, version: ''))
++ dependencies: [avdevice], include_directories: include, version: '59.7.100'))
+ meson.override_dependency('libswresample', declare_dependency(
+- dependencies: [swresample], include_directories: include, version: ''))
++ dependencies: [swresample], include_directories: include, version: '4.7.100'))
+ meson.override_dependency('libswscale', declare_dependency(
+- dependencies: [swscale], include_directories: include, version: ''))
++ dependencies: [swscale], include_directories: include, version: '6.7.100'))
diff --git a/subprojects/packagefiles/ffmpeg/version_6.1.4.diff b/subprojects/packagefiles/ffmpeg/version_6.1.4.diff
new file mode 100644
index 0000000..5f54fe5
--- /dev/null
+++ b/subprojects/packagefiles/ffmpeg/version_6.1.4.diff
@@ -0,0 +1,29 @@
+--- a/meson.build 2025-08-23 18:44:34.634014892 -0400
++++ b/meson.build 2025-08-25 15:37:56.556303265 -0400
+@@ -1,4 +1,4 @@
+-project('ffmpeg', 'c', version: '', meson_version: '>=0.57.0')
++project('ffmpeg', 'c', version: '6.1.4', meson_version: '>=0.57.0')
+
+ compiler = meson.get_compiler('c')
+
+@@ -268,14 +268,14 @@
+ swscale = proj.dependency('swscale')
+ include = include_directories('.', 'build/')
+ meson.override_dependency('libavutil', declare_dependency(
+- dependencies: [avutil, deps], compile_args: args, include_directories: include, version: ''))
++ dependencies: [avutil, deps], compile_args: args, include_directories: include, version: '58.29.100'))
+ meson.override_dependency('libavformat', declare_dependency(
+- dependencies: [avformat], include_directories: include, version: ''))
++ dependencies: [avformat], include_directories: include, version: '60.16.100'))
+ meson.override_dependency('libavcodec', declare_dependency(
+- dependencies: [avcodec], include_directories: include, version: ''))
++ dependencies: [avcodec], include_directories: include, version: '60.31.102'))
+ meson.override_dependency('libavdevice', declare_dependency(
+- dependencies: [avdevice], include_directories: include, version: ''))
++ dependencies: [avdevice], include_directories: include, version: '60.3.100'))
+ meson.override_dependency('libswresample', declare_dependency(
+- dependencies: [swresample], include_directories: include, version: ''))
++ dependencies: [swresample], include_directories: include, version: '4.12.100'))
+ meson.override_dependency('libswscale', declare_dependency(
+- dependencies: [swscale], include_directories: include, version: ''))
++ dependencies: [swscale], include_directories: include, version: '7.5.100'))
diff --git a/subprojects/packagefiles/ffmpeg/version_7.1.3.diff b/subprojects/packagefiles/ffmpeg/version_7.1.3.diff
new file mode 100644
index 0000000..554ef02
--- /dev/null
+++ b/subprojects/packagefiles/ffmpeg/version_7.1.3.diff
@@ -0,0 +1,29 @@
+--- a/meson.build 2025-08-23 18:44:34.634014892 -0400
++++ b/meson.build 2025-08-25 15:35:33.498781349 -0400
+@@ -1,4 +1,4 @@
+-project('ffmpeg', 'c', version: '', meson_version: '>=0.57.0')
++project('ffmpeg', 'c', version: '7.1.3', meson_version: '>=0.57.0')
+
+ compiler = meson.get_compiler('c')
+
+@@ -268,14 +268,14 @@
+ swscale = proj.dependency('swscale')
+ include = include_directories('.', 'build/')
+ meson.override_dependency('libavutil', declare_dependency(
+- dependencies: [avutil, deps], compile_args: args, include_directories: include, version: ''))
++ dependencies: [avutil, deps], compile_args: args, include_directories: include, version: '59.39.100'))
+ meson.override_dependency('libavformat', declare_dependency(
+- dependencies: [avformat], include_directories: include, version: ''))
++ dependencies: [avformat], include_directories: include, version: '61.7.100'))
+ meson.override_dependency('libavcodec', declare_dependency(
+- dependencies: [avcodec], include_directories: include, version: ''))
++ dependencies: [avcodec], include_directories: include, version: '61.19.100'))
+ meson.override_dependency('libavdevice', declare_dependency(
+- dependencies: [avdevice], include_directories: include, version: ''))
++ dependencies: [avdevice], include_directories: include, version: '61.3.100'))
+ meson.override_dependency('libswresample', declare_dependency(
+- dependencies: [swresample], include_directories: include, version: ''))
++ dependencies: [swresample], include_directories: include, version: '5.3.100'))
+ meson.override_dependency('libswscale', declare_dependency(
+- dependencies: [swscale], include_directories: include, version: ''))
++ dependencies: [swscale], include_directories: include, version: '8.3.100'))
diff --git a/subprojects/packagefiles/miniaudio/meson.build b/subprojects/packagefiles/miniaudio/meson.build
index 8522b5f..a2889f1 100644
--- a/subprojects/packagefiles/miniaudio/meson.build
+++ b/subprojects/packagefiles/miniaudio/meson.build
@@ -1,2 +1,2 @@
-project('miniaudio', 'c', version: 'git-78cdb9c')
+project('miniaudio', 'c', version: '0.11.23')
miniaudio = declare_dependency(include_directories: include_directories('.'))
diff --git a/subprojects/packagefiles/msvc-wine/msvc-wine-shebang-nixos.diff b/subprojects/packagefiles/msvc-wine/msvc-wine-shebang-nixos.diff
deleted file mode 100644
index 37295a9..0000000
--- a/subprojects/packagefiles/msvc-wine/msvc-wine-shebang-nixos.diff
+++ /dev/null
@@ -1,405 +0,0 @@
-https://github.com/mstorsjo/msvc-wine/pull/187
-
-diff --git a/fixinclude b/fixinclude
-index 4a8c733..bd5280c 100755
---- a/fixinclude
-+++ b/fixinclude
-@@ -1,4 +1,4 @@
--#!/usr/bin/perl
-+#!/usr/bin/env perl
- #
- # Copyright (c) 2019 Martin Storsjo
- #
-diff --git a/install.sh b/install.sh
-index ac00d9f..039922f 100755
---- a/install.sh
-+++ b/install.sh
-@@ -1,4 +1,4 @@
--#!/bin/sh
-+#!/usr/bin/env sh
- #
- # Copyright (c) 2019 Martin Storsjo
- #
-diff --git a/lowercase b/lowercase
-index ee10cc7..32902f7 100755
---- a/lowercase
-+++ b/lowercase
-@@ -1,4 +1,4 @@
--#!/usr/bin/perl
-+#!/usr/bin/env perl
- #
- # Copyright (c) 2019 Martin Storsjo
- #
-diff --git a/msvcenv-native.sh b/msvcenv-native.sh
-index e83c687..f3a99f1 100755
---- a/msvcenv-native.sh
-+++ b/msvcenv-native.sh
-@@ -1,4 +1,4 @@
--#!/bin/sh
-+#!/usr/bin/env sh
- #
- # Copyright (c) 2019 Martin Storsjo
- #
-@@ -33,9 +33,9 @@ else
- if [ ! -f "$ENV" ]; then
- echo $ENV doesn\'t exist
- else
-- export INCLUDE="$(bash -c ". $ENV && /bin/echo \"\$INCLUDE\"" | sed s/z://g | sed 's/\\/\//g')"
-- export LIB="$(bash -c ". $ENV && /bin/echo \"\$LIB\"" | sed s/z://g | sed 's/\\/\//g')"
-- MSVCARCH="$(bash -c ". $ENV && /bin/echo \"\$ARCH\"")"
-+ export INCLUDE="$(bash -c ". $ENV && /usr/bin/env echo \"\$INCLUDE\"" | sed s/z://g | sed 's/\\/\//g')"
-+ export LIB="$(bash -c ". $ENV && /usr/bin/env echo \"\$LIB\"" | sed s/z://g | sed 's/\\/\//g')"
-+ MSVCARCH="$(bash -c ". $ENV && /usr/bin/env echo \"\$ARCH\"")"
- case $MSVCARCH in
- x86) TARGET_ARCH=i686 ;;
- x64) TARGET_ARCH=x86_64 ;;
-diff --git a/test/test-asm.sh b/test/test-asm.sh
-index 5774c02..ba9511b 100755
---- a/test/test-asm.sh
-+++ b/test/test-asm.sh
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2023 Martin Storsjo
- #
-diff --git a/test/test-cl.sh b/test/test-cl.sh
-index 250f092..41fe51b 100755
---- a/test/test-cl.sh
-+++ b/test/test-cl.sh
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2023 Huang Qinjin
- #
-diff --git a/test/test-clang-cl-cmds.sh b/test/test-clang-cl-cmds.sh
-index 885835b..9409d2b 100755
---- a/test/test-clang-cl-cmds.sh
-+++ b/test/test-clang-cl-cmds.sh
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2023 Martin Storsjo
- #
-diff --git a/test/test-clang-cl.sh b/test/test-clang-cl.sh
-index ba7f82d..f99b745 100755
---- a/test/test-clang-cl.sh
-+++ b/test/test-clang-cl.sh
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2023 Martin Storsjo
- #
-diff --git a/test/test-cmake-clang-cl.sh b/test/test-cmake-clang-cl.sh
-index 78485ab..4a6ce3f 100755
---- a/test/test-cmake-clang-cl.sh
-+++ b/test/test-cmake-clang-cl.sh
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2023 Martin Storsjo
- #
-diff --git a/test/test-cmake.sh b/test/test-cmake.sh
-index 6beface..994d07d 100755
---- a/test/test-cmake.sh
-+++ b/test/test-cmake.sh
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2023 Huang Qinjin
- #
-diff --git a/test/test-dumpbin.sh b/test/test-dumpbin.sh
-index 68608d4..dd3841d 100755
---- a/test/test-dumpbin.sh
-+++ b/test/test-dumpbin.sh
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2023 Huang Qinjin
- #
-diff --git a/test/test-mc.sh b/test/test-mc.sh
-index 836a210..2e7d8ab 100755
---- a/test/test-mc.sh
-+++ b/test/test-mc.sh
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2024 Mark Harmstone
- #
-diff --git a/test/test-meson.sh b/test/test-meson.sh
-index 68ac271..bfd6d6d 100755
---- a/test/test-meson.sh
-+++ b/test/test-meson.sh
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2023 Martin Storsjo
- #
-diff --git a/test/test-midl.sh b/test/test-midl.sh
-index 45d9f92..8f9319e 100755
---- a/test/test-midl.sh
-+++ b/test/test-midl.sh
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2023 Martin Storsjo
- #
-diff --git a/test/test-msbuild.sh b/test/test-msbuild.sh
-index 00f03c6..faf284a 100755
---- a/test/test-msbuild.sh
-+++ b/test/test-msbuild.sh
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2024 Sergey Kvachonok
- #
-diff --git a/test/test-mt.sh b/test/test-mt.sh
-index 5f1881b..eda11ab 100755
---- a/test/test-mt.sh
-+++ b/test/test-mt.sh
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2023 Huang Qinjin
- #
-diff --git a/test/test-vcpkg.sh b/test/test-vcpkg.sh
-index dc3d3ce..79ebe51 100755
---- a/test/test-vcpkg.sh
-+++ b/test/test-vcpkg.sh
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2024 Huang Qinjin
- #
-diff --git a/test/test-wdk-msbuild.sh b/test/test-wdk-msbuild.sh
-index b37b3de..90f4189 100755
---- a/test/test-wdk-msbuild.sh
-+++ b/test/test-wdk-msbuild.sh
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2024 Sergey Kvachonok
- #
-diff --git a/test/test.sh b/test/test.sh
-index 1825216..ad9f9c1 100755
---- a/test/test.sh
-+++ b/test/test.sh
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2023 Huang Qinjin
- #
-diff --git a/vsdownload.py b/vsdownload.py
-index d9b4dcf..7598eec 100755
---- a/vsdownload.py
-+++ b/vsdownload.py
-@@ -1,4 +1,4 @@
--#!/usr/bin/python3
-+#!/usr/bin/env python3
- #
- # Copyright (c) 2019 Martin Storsjo
- #
-diff --git a/wdk-download.sh b/wdk-download.sh
-index e0ea631..7fa8bf0 100755
---- a/wdk-download.sh
-+++ b/wdk-download.sh
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2024 Sergey Kvachonok
- #
-diff --git a/wrappers/armasm b/wrappers/armasm
-index 3797431..bf8ea5a 100755
---- a/wrappers/armasm
-+++ b/wrappers/armasm
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2018 Martin Storsjo
- #
-diff --git a/wrappers/armasm64 b/wrappers/armasm64
-index fdfdb38..eb1553f 100755
---- a/wrappers/armasm64
-+++ b/wrappers/armasm64
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2018 Martin Storsjo
- #
-diff --git a/wrappers/cl b/wrappers/cl
-index 35c1b23..6ca6a56 100755
---- a/wrappers/cl
-+++ b/wrappers/cl
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2018 Martin Storsjo
- #
-diff --git a/wrappers/cmd b/wrappers/cmd
-index 88a39f0..a7ab236 100755
---- a/wrappers/cmd
-+++ b/wrappers/cmd
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2018 Martin Storsjo
- #
-diff --git a/wrappers/dumpbin b/wrappers/dumpbin
-index c6f7f9e..18c5591 100755
---- a/wrappers/dumpbin
-+++ b/wrappers/dumpbin
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2018 Martin Storsjo
- #
-diff --git a/wrappers/findstr b/wrappers/findstr
-index 1146022..136ac0e 100755
---- a/wrappers/findstr
-+++ b/wrappers/findstr
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2024 Huang Qinjin
- #
-diff --git a/wrappers/lib b/wrappers/lib
-index 143da0c..3967675 100755
---- a/wrappers/lib
-+++ b/wrappers/lib
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2018 Martin Storsjo
- #
-diff --git a/wrappers/link b/wrappers/link
-index dba1774..53920d4 100755
---- a/wrappers/link
-+++ b/wrappers/link
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2018 Martin Storsjo
- #
-diff --git a/wrappers/mc b/wrappers/mc
-index c1c5d80..bf71882 100755
---- a/wrappers/mc
-+++ b/wrappers/mc
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2018 Martin Storsjo
- #
-diff --git a/wrappers/midl b/wrappers/midl
-index dd34988..adddcfa 100755
---- a/wrappers/midl
-+++ b/wrappers/midl
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2018 Martin Storsjo
- #
-diff --git a/wrappers/ml b/wrappers/ml
-index db671c7..a5d537b 100755
---- a/wrappers/ml
-+++ b/wrappers/ml
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2018 Martin Storsjo
- #
-diff --git a/wrappers/ml64 b/wrappers/ml64
-index 0708ebe..df65803 100755
---- a/wrappers/ml64
-+++ b/wrappers/ml64
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2018 Martin Storsjo
- #
-diff --git a/wrappers/msbuild b/wrappers/msbuild
-index 65bbf8f..53fdd8f 100755
---- a/wrappers/msbuild
-+++ b/wrappers/msbuild
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2024 Sergey Kvachonok
- #
-diff --git a/wrappers/msvcenv.sh b/wrappers/msvcenv.sh
-index 101e9bb..2d2a918 100755
---- a/wrappers/msvcenv.sh
-+++ b/wrappers/msvcenv.sh
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2018 Martin Storsjo
- #
-diff --git a/wrappers/mt b/wrappers/mt
-index edebd75..ed92f96 100755
---- a/wrappers/mt
-+++ b/wrappers/mt
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2018 Martin Storsjo
- #
-diff --git a/wrappers/nmake b/wrappers/nmake
-index 88102d1..3eaf0f5 100755
---- a/wrappers/nmake
-+++ b/wrappers/nmake
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2018 Martin Storsjo
- #
-diff --git a/wrappers/rc b/wrappers/rc
-index f7b8d97..7afb1ad 100755
---- a/wrappers/rc
-+++ b/wrappers/rc
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2018 Martin Storsjo
- #
-diff --git a/wrappers/wine-msvc.sh b/wrappers/wine-msvc.sh
-index a267283..bb08891 100755
---- a/wrappers/wine-msvc.sh
-+++ b/wrappers/wine-msvc.sh
-@@ -1,4 +1,4 @@
--#!/bin/bash
-+#!/usr/bin/env bash
- #
- # Copyright (c) 2018 Martin Storsjo
- #
diff --git a/subprojects/packagefiles/wuffs/meson.build b/subprojects/packagefiles/wuffs/meson.build
index c595249..5d877f6 100644
--- a/subprojects/packagefiles/wuffs/meson.build
+++ b/subprojects/packagefiles/wuffs/meson.build
@@ -1,2 +1,2 @@
-project('wuff', 'c', version: 'git-d662ef5')
+project('wuff', 'c', version: 'git-1e2e58c')
wuffs = declare_dependency(include_directories: include_directories('./release/c'))
diff --git a/subprojects/shaderc.wrap b/subprojects/shaderc.wrap
index d510ec9..efef095 100644
--- a/subprojects/shaderc.wrap
+++ b/subprojects/shaderc.wrap
@@ -1,7 +1,7 @@
[wrap-git]
-directory = shaderc-v2025.4
+directory = shaderc-v2025.5
url = https://github.com/google/shaderc.git
-revision = v2025.4
+revision = v2025.5
depth = 1
method = cmake
patch_directory = shaderc
diff --git a/subprojects/stela.wrap b/subprojects/stela.wrap
index 7122113..fda63dd 100644
--- a/subprojects/stela.wrap
+++ b/subprojects/stela.wrap
@@ -1,5 +1,5 @@
[wrap-git]
-directory = stela-9d69eec
+directory = stela-22e9e4d
url = https://git.akon.city/stela.git
push-url = git@git.akon.city:stela.git
-revision = 9d69eec9ab48f009a04b9ab710f57c85b240dd19
+revision = 22e9e4d6e2c2e8e5eea065e5b7cd04fa6626559c