summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cross/i686-w64-mingw32.txt1
-rw-r--r--cross/x86_64-w64-mingw32.txt1
-rw-r--r--flake.lock30
-rw-r--r--flake.nix113
-rwxr-xr-xscripts/run_valgrind.sh4
-rw-r--r--scripts/screenrc2
-rw-r--r--src/buffer/clock.c18
-rw-r--r--src/buffer/clock.h2
-rw-r--r--src/buffer/meson.build6
-rw-r--r--src/buffer/video.c82
-rw-r--r--src/buffer/video.h8
-rw-r--r--src/buffer/video_null.h98
-rw-r--r--src/cache/backings/file.c25
-rw-r--r--src/cache/backings/file.h1
-rw-r--r--src/cache/backings/file_common.c5
-rw-r--r--src/cache/backings/file_mapped.c20
-rw-r--r--src/cache/entry.c4
-rw-r--r--src/cache/handle.c23
-rw-r--r--src/cache/range.h4
-rw-r--r--src/cache/threaded_waits.c117
-rw-r--r--src/cache/threaded_waits.h14
-rw-r--r--src/cache/wait.h2
-rw-r--r--src/codec/codec.h4
-rw-r--r--src/codec/ffmpeg/avio.c4
-rw-r--r--src/codec/ffmpeg/decoder.c8
-rw-r--r--src/codec/ffmpeg/decoder.h4
-rw-r--r--src/codec/ffmpeg/demuxer.c5
-rw-r--r--src/fruits/cmsrv/ui.c2
-rw-r--r--src/fruits/ctv/ctv.c127
-rw-r--r--src/fruits/ctv/meson.build7
-rw-r--r--src/fruits/droid/ctv/gradle/libs.versions.toml2
-rw-r--r--src/fruits/droid/ctv/gradle/wrapper/gradle-wrapper.properties2
-rw-r--r--src/liana/client.c79
-rw-r--r--src/liana/client.h2
-rw-r--r--src/liana/handlers/codec_client.c2
-rw-r--r--src/liana/list.c166
-rw-r--r--src/liana/list.h2
-rw-r--r--src/liana/server.c6
-rw-r--r--src/liana/server.h3
-rw-r--r--src/liana/vcr.c4
-rw-r--r--src/libsink/sink.c1057
-rw-r--r--src/libsink/sink.h35
-rw-r--r--src/mixer/mixer.h8
-rw-r--r--src/render/renderer_libplacebo.c3
-rw-r--r--src/screen/screen.c160
-rw-r--r--src/screen/screen.h14
-rw-r--r--src/server/common.h2
-rw-r--r--src/server/server.c19
-rw-r--r--src/server/server.h1
-rw-r--r--src/sink/common.c84
-rw-r--r--src/sink/common.h4
-rw-r--r--src/sink/desktop.c188
-rw-r--r--src/sink/input_simulator.c2
-rw-r--r--src/sink/meson.build2
-rw-r--r--subprojects/SPIRV-Cross.wrap4
-rw-r--r--subprojects/ffmpeg.wrap8
-rw-r--r--subprojects/libalabaster.wrap2
-rw-r--r--subprojects/libnaunet.wrap2
-rw-r--r--subprojects/libplacebo.wrap6
-rw-r--r--subprojects/miniaudio.wrap4
-rw-r--r--subprojects/packagefiles/ffmpeg/meson.build2
-rw-r--r--subprojects/packagefiles/libplacebo/d3d11_scaling_none.diff14
-rw-r--r--subprojects/packagefiles/libplacebo/dependency_version_override.diff24
-rw-r--r--subprojects/packagefiles/libplacebo/no_dlltool_no_shaderc_version.diff58
-rw-r--r--subprojects/stela.wrap2
65 files changed, 1264 insertions, 1450 deletions
diff --git a/cross/i686-w64-mingw32.txt b/cross/i686-w64-mingw32.txt
index 0153449..e07fa49 100644
--- a/cross/i686-w64-mingw32.txt
+++ b/cross/i686-w64-mingw32.txt
@@ -4,6 +4,7 @@ cpp = 'i686-w64-mingw32-g++'
ar = 'i686-w64-mingw32-gcc-ar'
strip = 'i686-w64-mingw32-strip'
windres = 'i686-w64-mingw32-windres'
+dlltool = 'i686-w64-mingw32-dlltool'
exe_wrapper = 'wine'
[properties]
diff --git a/cross/x86_64-w64-mingw32.txt b/cross/x86_64-w64-mingw32.txt
index aa0e051..37e4e24 100644
--- a/cross/x86_64-w64-mingw32.txt
+++ b/cross/x86_64-w64-mingw32.txt
@@ -6,6 +6,7 @@ cpp = 'x86_64-w64-mingw32-g++'
ar = 'x86_64-w64-mingw32-gcc-ar'
strip = 'x86_64-w64-mingw32-strip'
windres = 'x86_64-w64-mingw32-windres'
+dlltool = 'x86_64-w64-mingw32-dlltool'
exe_wrapper = 'wine64'
[properties]
diff --git a/flake.lock b/flake.lock
index 133fbee..cddfbd1 100644
--- a/flake.lock
+++ b/flake.lock
@@ -23,11 +23,11 @@
]
},
"locked": {
- "lastModified": 1741056285,
- "narHash": "sha256-/JKDMVqq8PIqcGonBVKbKq1SooV3kzGmv+cp3rKAgPA=",
+ "lastModified": 1741955947,
+ "narHash": "sha256-2lbURKclgKqBNm7hVRtWh0A7NrdsibD0EaWhahUVhhY=",
"owner": "nix-community",
"repo": "home-manager",
- "rev": "70fbbf05a5594b0a72124ab211bff1d502c89e3f",
+ "rev": "4e12151c9e014e2449e0beca2c0e9534b96a26b4",
"type": "github"
},
"original": {
@@ -39,11 +39,11 @@
},
"nixos-hardware": {
"locked": {
- "lastModified": 1740646007,
- "narHash": "sha256-dMReDQobS3kqoiUCQIYI9c0imPXRZnBubX20yX/G5LE=",
+ "lastModified": 1741792691,
+ "narHash": "sha256-f0BVt1/cvA0DQ/q3rB+HY4g4tKksd03ZkzI4xehC2Ew=",
"owner": "NixOS",
"repo": "nixos-hardware",
- "rev": "009b764ac98a3602d41fc68072eeec5d24fc0e49",
+ "rev": "e1f12151258b12c567f456d8248e4694e9390613",
"type": "github"
},
"original": {
@@ -59,11 +59,11 @@
"nixpkgs": "nixpkgs"
},
"locked": {
- "lastModified": 1740567864,
- "narHash": "sha256-eTS2wrC1jKR6PKXC9jZqQy5PwqbIOBLSLF3dwLiFJ8M=",
+ "lastModified": 1741870048,
+ "narHash": "sha256-odXRdNZGdXg1LmwlAeWL85kgy/FVHsgKlDwrvbR2BsU=",
"owner": "nix-community",
"repo": "NixOS-WSL",
- "rev": "1f40b43d01626ce994eb47150afa0d7215f396ca",
+ "rev": "5d76001e33ee19644a598ad80e7318ab0957b122",
"type": "github"
},
"original": {
@@ -75,11 +75,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1740463929,
- "narHash": "sha256-4Xhu/3aUdCKeLfdteEHMegx5ooKQvwPHNkOgNCXQrvc=",
+ "lastModified": 1740865531,
+ "narHash": "sha256-h00vGIh/jxcGl8aWdfnVRD74KuLpyY3mZgMFMy7iKIc=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "5d7db4668d7a0c6cc5fc8cf6ef33b008b2b1ed8b",
+ "rev": "5ef6c425980847c78a80d759abc476e941a9bf42",
"type": "github"
},
"original": {
@@ -91,11 +91,11 @@
},
"nixpkgs_2": {
"locked": {
- "lastModified": 1741010256,
- "narHash": "sha256-WZNlK/KX7Sni0RyqLSqLPbK8k08Kq7H7RijPJbq9KHM=",
+ "lastModified": 1741851582,
+ "narHash": "sha256-cPfs8qMccim2RBgtKGF+x9IBCduRvd/N5F4nYpU0TVE=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "ba487dbc9d04e0634c64e3b1f0d25839a0a68246",
+ "rev": "6607cf789e541e7873d40d3a8f7815ea92204f32",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index 2dea414..31dd09a 100644
--- a/flake.nix
+++ b/flake.nix
@@ -162,12 +162,12 @@
NIX_HARDENING_ENABLE = "";
nativeBuildInputs = packages.${system}.default.nativeBuildInputs ++ [
gdb
- clang-tools
clang-analyzer
- ruff
- ruff-lsp
perf-tools
valgrind
+ clang-tools
+ ruff
+ ruff-lsp
kdePackages.kcachegrind
perl
asciidoctor
@@ -177,7 +177,24 @@
shellHook = ''
export SHELL="${pkgs.bashInteractive}/bin/bash"
export NIX_BUILD_SHELL=$SHELL
- export NIX_SHELL="camu"
+ export NIX_SHELL="camu-${system}+dev"
+ export PS1="(\$NIX_SHELL)"$PS1
+ '';
+ };
+
+ buildtest = with pkgs; mkShell {
+ NIX_HARDENING_ENABLE = "";
+ nativeBuildInputs = packages.${system}.default.nativeBuildInputs ++ [
+ gdb
+ clang-analyzer
+ perf-tools
+ valgrind
+ ];
+ buildInputs = packages.${system}.default.buildInputs;
+ shellHook = ''
+ export SHELL="${pkgs.bashInteractive}/bin/bash"
+ export NIX_BUILD_SHELL=$SHELL
+ export NIX_SHELL="camu-${system}"
export PS1="(\$NIX_SHELL)"$PS1
'';
};
@@ -211,7 +228,7 @@
bison
libGLU
libGL
- mesa.osmesa
+ #mesa.osmesa
libdrm
wayland
wayland-scanner
@@ -220,87 +237,18 @@
wayland.dev
libxkbcommon.dev
libgbm
+ # vkd3d-proton build
+ glslang
];
buildInputs = with pkgsCross.ucrt64; [
- (zlib.override { shared = false; static = true; })
+ (zlib.override { static = true; shared = false; })
(soxr.overrideAttrs (oldAttrs: {
- cmakeFlags = [
- "-DBUILD_SHARED_LIBS=OFF"
- "-DBUILD_STATIC_LIBS=ON"
- ];
+ cmakeFlags = [ "-DBUILD_STATIC_LIBS=ON" "-DBUILD_SHARED_LIBS=OFF" ];
}))
(openssl.override { static = true; })
xxHash
- vulkan-headers
- # Many more things need to be fixed for notcurses on Windows.
- ((ncurses.override { enableStatic = true; withCxx = true; withTermlib = false; }).overrideAttrs (oldAttrs: rec {
- version = "6.5.20250118";
- src = fetchurl {
- url = "https://invisible-island.net/archives/ncurses/ncurses-${lib.versions.majorMinor version}.tar.gz";
- hash = "sha256-E22RvCaamleF5fnpgLx2q1dCj2BM4+WlqQzrx2eXHMY=";
- };
- #configureFlags = lib.remove "--enable-sp-funcs" (lib.remove "--enable-term-driver" oldAttrs.configureFlags) ++ [
- configureFlags = oldAttrs.configureFlags ++ [
- "--without-pthread"
- "--without-ada"
- "--enable-interop"
- "--enable-database"
- "--disable-home-terminfo"
- "--disable-rpath"
- "--disable-symlinks"
- #"--enable-exp-win32"
- ];
- patches = (lib.lists.forEach [
- #{ date = "20240427"; hash = "qiAf4WrIiihsLaekMPJFZFh8qdCUrp+9U9zmlrG4lSw="; }
- { date = "20240504"; hash = "e7YUKYS+AH/i00dBQCGUEqqwXlcwqnDWkX8KM7RIEPE="; }
- { date = "20240511"; hash = "rjl7Vi71pL5S4hWZdqe2nLQ0ILUOaQjRQyGTvwZFmRs="; }
- { date = "20240518"; hash = "7qenzh+Qc5q6wv773CZXsc+L3sgVwravibMSSCB1D4Y="; }
- { date = "20240519"; hash = "4eG9rnusBnjPz/RF4fnBXmZMNEUfVHX+rbxOu6iVuZA="; }
- { date = "20240525"; hash = "Q3F05g1WGkjkKnUO0GRqKS7YrWH9PvC/KRDknYmJqZc="; }
- { date = "20240601"; hash = "WXMM3dZ4gM9x2WPIeytA7Bcn4L+XgwS8idXzzlDWXP8="; }
- { date = "20240608"; hash = "ItbeBCMGlxy8/jk65CEvrGeVmtaHMlp46sI1cQlkv9k="; }
- { date = "20240615"; hash = "SurjpGVjeL7jo7tMbbZjhLKcMOI0WJdWSlP8UqxZo2o="; }
- { date = "20240622"; hash = "xPfbRD3VEV/JIlSJVj5s5DyvQ/N5yqcG0GuplhOAT18="; }
- { date = "20240629"; hash = "pqs1/Tiv4thvTluN3OozD/cjWPTX+I+R0SV/whSdhAc="; }
- { date = "20240706"; hash = "PAVI20IoDvIE1tFAbfAkBrZelqVflUaDxR3hDX48wvU="; }
- { date = "20240713"; hash = "f6SWjki6RSYMA2+bnyODkHl4Ywf1Dh6b+xy9zbIoKA0="; }
- { date = "20240720"; hash = "WDWigk9EjZsdVBWO2ZUqafDi5THqk2adlEqVGZrARgg="; }
- { date = "20240727"; hash = "8ZgMLcTGG0c9x+sfhCavwvl2h53Sqk/BI9B3797Vwao="; }
- { date = "20240810"; hash = "CRQBjskmBbh0C9JFBPzylGddXyd8gbUIxXZjsvCfwSk="; }
- { date = "20240817"; hash = "utPRoefg/ZHP/GpbNR028k6Cfwhg7L1CdGv9gNf1tOs="; }
- { date = "20240824"; hash = "9Iy229KtZn0Mx6b3O4G04y3sL/VeUWhDLYsNZjikjN4="; }
- { date = "20240831"; hash = "L4T4dhGZKTY7HYBys2gJHuzDGfRLd9ls4l2ePsmyysk="; }
- { date = "20240914"; hash = "IW8ixyj+RfkwyNAXSzUo9yuMtSWSNLHWp3342c1i+gc="; }
- { date = "20240922"; hash = "nFf/2kCBVEabZKE4lQ71eovpWjMXlO7SjMpzS+IwEMA="; }
- { date = "20240928"; hash = "dsR1zWo020PBv/QtvPuqHYbNerFnTMenhO6jVzIOoKM="; }
- { date = "20241006"; hash = "UCcpwSwLVbjUf7reM8juR2NPs59pfSed06Qt4n+PfTs="; }
- { date = "20241019"; hash = "7drIhiHIhRJbFPJwfu0KAprwOcBFxJL9p049EuaHx3Y="; }
- { date = "20241026"; hash = "0gxRfBsCtaGarqvFTsqUZuo4aeHLkr76ZuGG5cMFIWQ="; }
- { date = "20241102"; hash = "Ws28hQEIX3Nrk84WsvozkIiRQmSZUoGUplQkMCmlNnw="; }
- { date = "20241109"; hash = "ZvA2c9O3K4y2mqfGgQJYhsWSybfnAlhFxQHZb6q7YSs="; }
- { date = "20241123"; hash = "J8ErjvP8ZvuMT9jnFiq75zMImbypsnWB1CuNMiJX5Nc="; }
- { date = "20241130"; hash = "Yy8bkjr55psDj32Nj/n10J5yG8urOV91LPESlf1t3pg="; }
- { date = "20241207"; hash = "IqJEPkWZV5BNiHpc4YNP2lFWg3zngG+lf345szoAYdo="; }
- { date = "20241214"; hash = "Kq1qLk8WtAVCD9itpXIrluUUixGH3x9QRV/+UMD9CCE="; }
- { date = "20241221"; hash = "FUpH6KDLRS00KA3MvI18N8npoL7DnoavHA0yrg8dl74="; }
- { date = "20241228"; hash = "r4EvgE2DamJiJ8EucqmWQIYmbKyLaQ+nPbvg5lwxewo="; }
- { date = "20250104"; hash = "pkb/MXJlPwXUH3n+DuGH+4M1EROGUrd2iP+8xBngzEk="; }
- { date = "20250111"; hash = "FpvEIhyTA5NxWlQpsyGlMoSjDEe5P45irwUZmXXGr3A="; }
- { date = "20250118"; hash = "uBHtMR/FR3hb+2PIgg4ahApk8/AxZ5FOMIXxtpGSB4o="; }
- ] (ver: pkgs.fetchurl rec {
- name = "ncurses-6.5-${ver.date}.patch";
- url = "https://invisible-island.net/archives/ncurses/6.5/${name}.gz";
- sha256 = ver.hash;
- postFetch = ''
- cp $out src.gz
- ${pkgs.gzip}/bin/gzip -dc src.gz > $out
- '';
- })) ++ [
- ];
- }))
- libunistring
(libiconv.override { enableStatic = true; enableShared = false; })
- windows.mingw_w64_pthreads
+ vulkan-headers
];
shellHook = ''
export SHELL="${pkgs.bashInteractive}/bin/bash"
@@ -320,8 +268,13 @@
NIX_HARDENING_ENABLE = "";
nativeBuildInputs = mingw64.nativeBuildInputs;
buildInputs = with pkgsCross.mingw32; [
- (zlib.override { shared = false; static = true; })
+ (zlib.override { static = true; shared = false; })
+ (soxr.overrideAttrs (oldAttrs: {
+ cmakeFlags = [ "-DBUILD_STATIC_LIBS=ON" "-DBUILD_SHARED_LIBS=OFF" ];
+ }))
+ (openssl.override { static = true; })
xxHash
+ (libiconv.override { enableStatic = true; enableShared = false; })
];
shellHook = ''
export SHELL="${pkgs.bashInteractive}/bin/bash"
diff --git a/scripts/run_valgrind.sh b/scripts/run_valgrind.sh
index ed7e6ef..bccc34c 100755
--- a/scripts/run_valgrind.sh
+++ b/scripts/run_valgrind.sh
@@ -1,5 +1,5 @@
#! /usr/bin/env sh
source ../scripts/python_env
#valgrind --leak-check=full ./src/fruits/cmv/cmv "$@"
-valgrind --leak-check=full --show-leak-kinds=all ./src/fruits/cmv/cmv "$@"
-#valgrind --leak-check=no --show-error-list=yes ./src/fruits/cmv/cmv "$@"
+#valgrind --leak-check=full --show-leak-kinds=all ./src/fruits/cmv/cmv "$@"
+valgrind --leak-check=no --show-error-list=yes ./src/fruits/cmv/cmv "$@"
diff --git a/scripts/screenrc b/scripts/screenrc
index 3c9618c..6381a9e 100644
--- a/scripts/screenrc
+++ b/scripts/screenrc
@@ -4,7 +4,7 @@ mousetrack on
defmousetrack on
screen -t 'cmsrv' ./src/fruits/cmsrv/cmsrv
split
-resize -v 60%
+resize -v 75%
focus
screen -t 'cmsrv_debug'
stuff 'sleep 1 && gdb -p $(pgrep cmsrv) -ex cont '
diff --git a/src/buffer/clock.c b/src/buffer/clock.c
index 1ca2a49..a1a1eab 100644
--- a/src/buffer/clock.c
+++ b/src/buffer/clock.c
@@ -28,19 +28,20 @@ void camu_clock_set(struct camu_clock *clock, f64 base)
al_atomic_store(f64)(&clock->tick, -1.0, AL_ATOMIC_RELAXED);
al_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);
}
void camu_clock_seek(struct camu_clock *clock, f64 base, u64 target)
{
clock->base = base;
clock->offset = 0.0;
- f64 tick = nn_get_tick();
+ al_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);
} else {
- tick = calc_tick_offset(tick, nn_get_timestamp(), target);
+ f64 tick = calc_tick_offset(nn_get_tick(), nn_get_timestamp(), target);
al_atomic_store(f64)(&clock->tick, tick, AL_ATOMIC_RELAXED);
}
} else {
@@ -136,5 +137,16 @@ f64 camu_clock_get_pts(struct camu_clock *clock, f64 offset, bool allow_set)
}
}
- return (clock->base - clock->offset) + (current - tick) + offset;
+ f64 pts = (clock->base - clock->offset) + (current - tick);
+
+ if (allow_set) {
+ al_atomic_store(f64)(&clock->last_pts, pts, AL_ATOMIC_RELAXED);
+ }
+
+ return pts + offset;
+}
+
+f64 camu_clock_get_last_pts(struct camu_clock *clock)
+{
+ return al_atomic_load(f64)(&clock->last_pts, AL_ATOMIC_RELAXED);
}
diff --git a/src/buffer/clock.h b/src/buffer/clock.h
index b312a1c..57bdb7d 100644
--- a/src/buffer/clock.h
+++ b/src/buffer/clock.h
@@ -33,6 +33,7 @@ struct camu_clock {
atomic(f64) tick;
atomic(f64) pause;
f64 paused_at;
+ atomic(f64) last_pts;
void (*callback)(void *, u8);
void *userdata;
};
@@ -49,3 +50,4 @@ bool camu_clock_is_paused(struct camu_clock *clock);
f64 camu_clock_get_base_pts(struct camu_clock *clock);
f64 camu_clock_get_pts(struct camu_clock *clock, f64 offset, bool allow_set);
+f64 camu_clock_get_last_pts(struct camu_clock *clock);
diff --git a/src/buffer/meson.build b/src/buffer/meson.build
index 6301206..7eef600 100644
--- a/src/buffer/meson.build
+++ b/src/buffer/meson.build
@@ -1,9 +1,5 @@
buffer_src = ['audio.c', 'clock.c', 'peak_buffer.c']
-buffer_deps = []
-buffer_args = []
-
if not no_video
buffer_src += ['video.c']
endif
-
-buffer = declare_dependency(sources: buffer_src, dependencies: buffer_deps, compile_args: buffer_args)
+buffer = declare_dependency(sources: buffer_src)
diff --git a/src/buffer/video.c b/src/buffer/video.c
index ab1503a..ed56ff8 100644
--- a/src/buffer/video.c
+++ b/src/buffer/video.c
@@ -1,3 +1,4 @@
+#define AL_LOG_SECTION "video_buffer"
#include <al/log.h>
#include "video.h"
@@ -22,7 +23,6 @@ bool camu_video_buffer_init(struct camu_video_buffer *buf, struct camu_clock *cl
buf->clock = clock;
buf->latency = 0.0;
al_atomic_store(f64)(&buf->pts, -1.0, AL_ATOMIC_RELAXED);
- buf->last_pts = -1.0;
// The least confusing behavior for single_frame is that it can't be
// set if the buffer is empty.
buf->single_frame = false;
@@ -46,73 +46,54 @@ bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_code
buf->queue = renderer->create_queue(renderer);
buf->queue->buf = buf;
struct camu_video_format *fmt = &buf->stream->video.fmt;
+ const char *format_name = camu_pixel_format_name(fmt->format);
switch (stream->mode) {
case CAMU_NORMAL: {
- camu_video_format_copy(&buf->fmt.in, fmt);
-
buf->single_frame = true;
buf->avg_frame_duration = 0.0;
-
- const char *format_name = camu_pixel_format_name(fmt->format);
- if (!format_name) format_name = "unknown";
-
- al_log_info("video_buffer", "Stream: %s (%ux%u) %s.",
- format_name, fmt->width, fmt->height, "IMAGE");
-
+ info("Stream: %s (%ux%u) IMAGE.", format_name, fmt->width, fmt->height);
break;
}
#ifdef CAMU_HAVE_FFMPEG
case CAMU_FFMPEG_COMPAT: {
- AVCodecParameters *codecpar = stream->av.stream->codecpar;
AVRational frame_rate = stream->av.stream->avg_frame_rate;
-
- fmt->width = (u32)codecpar->width;
- fmt->height = (u32)codecpar->height;
- fmt->format = codecpar->format;
- camu_video_format_copy(&buf->fmt.in, fmt);
-
buf->single_frame = stream->duration == 0 || frame_rate.den == 0;
buf->avg_frame_duration = frame_rate.den > 0 ? av_q2d(av_inv_q(frame_rate)) : 0.0;
-
- const char *format_name = av_get_pix_fmt_name(fmt->format);
if (buf->single_frame) {
- al_log_info("video_buffer", "Stream: %s (%ux%u) %s.",
- format_name, fmt->width, fmt->height, "IMAGE");
+ info("Stream: %s (%ux%u) IMAGE.", format_name, fmt->width, fmt->height);
} else {
- al_log_info("video_buffer", "Stream: %s (%ux%u) %s %.3ffps.",
- format_name, fmt->width, fmt->height, "VIDEO", av_q2d(frame_rate));
+ info("Stream: %s (%ux%u) VIDEO %.3ffps.", format_name, fmt->width, fmt->height, av_q2d(frame_rate));
}
-
break;
}
#endif
}
-#ifdef CAMU_VIDEO_BUFFER_FORCE_SCALER
-#ifdef CAMU_HAVE_FFMPEG
+ struct camu_video_format *in = &buf->fmt.in;
+ struct camu_video_format *req = &buf->fmt.req;
+ camu_video_format_copy(in, fmt);
+#ifndef CAMU_VIDEO_BUFFER_FORCE_SCALER
+ camu_video_format_copy(req, in);
+#else
if (fmt->width > 0 && fmt->height > 0 &&
(fmt->format != CAMU_PIXEL_FORMAT_RGBA && fmt->format != CAMU_PIXEL_FORMAT_RGB)) {
buf->fmt.scaler_needed = true;
- buf->fmt.req.width = fmt->width;
- buf->fmt.req.height = fmt->height;
- buf->fmt.req.format = CAMU_PIXEL_FORMAT_RGBA;
+#ifndef CAMU_HAVE_FFMPEG
+ return false;
+#endif
+ req->width = fmt->width;
+ req->height = fmt->height;
+ req->format = CAMU_PIXEL_FORMAT_RGBA;
buf->scaler = camu_ff_scaler_create();
- if (buf->scaler->init(buf->scaler, &buf->fmt)) {
- const char *format_name = av_get_pix_fmt_name(buf->fmt.req.format);
- al_log_info("video_buffer", "Scaling to: %s (%ux%u).",
- format_name, buf->fmt.req.width, buf->fmt.req.height);
- } else {
+ if (!buf->scaler->init(buf->scaler, &buf->fmt)) {
// Scaler will be freed in video_buffer_free().
return false;
}
+ const char *req_format_name = camu_pixel_format_name(req->format);
+ info("Scaling to: %s (%ux%u).", req_format_name, req->width, req->height);
} else {
buf->fmt.scaler_needed = false;
- camu_video_format_copy(&buf->fmt.req, &buf->fmt.in);
+ camu_video_format_copy(req, in);
}
-#else
- return false;
-#endif
-#else
- camu_video_format_copy(&buf->fmt.req, &buf->fmt.in);
#endif
return true;
}
@@ -138,7 +119,6 @@ static void after_push_internal(struct camu_video_buffer *buf)
{
s32 count = buf->queue->count(buf->queue);
f64 have = count * buf->avg_frame_duration;
-
if (!buf->buffered && (buf->single_frame || have >= BUFFER_MARK_BUFFERED)) {
// Preserve order of: set flow -> flush -> callback, for single frames.
if (buf->single_frame) {
@@ -147,12 +127,10 @@ static void after_push_internal(struct camu_video_buffer *buf)
}
buf->buffered = true;
buf->buffered_with_one_frame = count == 1;
- al_log_debug("video_buffer", "Buffered (mark: %.2fs).", have);
+ debug("Buffered (mark: %.2fs).", have);
buf->callback(buf->userdata, CAMU_BUFFER_BUFFERED);
- }
-
- if (have >= BUFFER_MARK_RESET) {
- al_log_warn("video_buffer", "Buffer overflow, resetting.");
+ } else if (have >= BUFFER_MARK_RESET) {
+ warn("Buffer overflow, resetting.");
buf->queue->reset(buf->queue);
} else if (have >= BUFFER_MARK_HIGH) {
buf->callback(buf->userdata, CAMU_BUFFER_CORK);
@@ -167,7 +145,6 @@ static bool push_av_frame_internal(struct camu_video_buffer *buf, AVFrame *frame
f64 duration = camu_ff_frame_duration(frame) * av_q2d(stream->time_base);
f64 base_pts = al_atomic_load(f64)(&buf->pts, AL_ATOMIC_ACQUIRE);
if (!buf->single_frame && frame_is_late(buf->clock, base_pts, pts, duration)) {
- al_log_info("video_buffer", "Discarding late frame.");
return false;
}
if (base_pts == -1.0) al_atomic_store(f64)(&buf->pts, pts, AL_ATOMIC_RELEASE);
@@ -223,14 +200,14 @@ void camu_video_buffer_push_subtitle(struct camu_video_buffer *buf, struct camu_
// flush() always comes from the same thread as push().
void camu_video_buffer_flush(struct camu_video_buffer *buf)
{
- al_log_debug("video_buffer", "Flush requested.");
+ debug("Flush requested.");
al_atomic_store(u8)(&buf->flow, FLUSHED, AL_ATOMIC_RELAXED);
buf->queue->flush(buf->queue);
if (!buf->buffered) {
s32 count = buf->queue->count(buf->queue);
buf->buffered = true;
buf->buffered_with_one_frame = count == 1;
- al_log_debug("video_buffer", "Buffered (flush).");
+ debug("Buffered (flush).");
buf->callback(buf->userdata, CAMU_BUFFER_BUFFERED);
}
}
@@ -238,8 +215,7 @@ void camu_video_buffer_flush(struct camu_video_buffer *buf)
// Not thread-safe, must be called while the buffer is not being read from or written to.
void camu_video_buffer_reset(struct camu_video_buffer *buf)
{
- buf->last_pts = al_atomic_load(f64)(&buf->pts, AL_ATOMIC_ACQUIRE);
- al_atomic_store(f64)(&buf->pts, -1.0, AL_ATOMIC_RELEASE);
+ al_atomic_store(f64)(&buf->pts, -1.0, AL_ATOMIC_RELAXED);
if (buf->queue) buf->queue->reset(buf->queue);
buf->buffered = false;
al_atomic_store(u8)(&buf->flow, FLOWING, AL_ATOMIC_RELAXED);
@@ -269,7 +245,7 @@ bool camu_video_buffer_read(struct camu_video_buffer *buf, void *out, bool *weig
// In our code that 1 frame buffer might be a video, in which case that behavior is erroneous.
bool eof = ret == CAMU_QUEUE_EOF || (buf->buffered_with_one_frame && ret == CAMU_QUEUE_OK);
if (flow == FLUSHED && eof) {
- al_log_debug("video_buffer", "Flushed.");
+ debug("Flushed.");
buf->callback(buf->userdata, CAMU_BUFFER_EOF);
al_atomic_store(u8)(&buf->flow, SIGNALED, AL_ATOMIC_RELEASE);
} else if (flow == FLOWING) {
@@ -284,7 +260,7 @@ bool camu_video_buffer_read(struct camu_video_buffer *buf, void *out, bool *weig
*weighted = buf->weighted_read;
buf->weighted_read = false;
} else if (ret == CAMU_QUEUE_MORE) {
- al_log_warn("video_buffer", "Underrun.");
+ trace("Underrun.");
}
return ret == CAMU_QUEUE_OK || ret == CAMU_QUEUE_MORE;
diff --git a/src/buffer/video.h b/src/buffer/video.h
index c587af1..401fa48 100644
--- a/src/buffer/video.h
+++ b/src/buffer/video.h
@@ -8,8 +8,8 @@
#include "../render/renderer.h"
#include "../screen/view.h"
-#include "clock.h"
#include "frame_queue.h"
+#include "clock.h"
struct camu_video_buffer {
struct camu_codec_stream *stream;
@@ -17,7 +17,6 @@ struct camu_video_buffer {
struct camu_clock *clock;
atomic(f64) pts;
f64 latency;
- f64 last_pts;
bool single_frame;
f64 avg_frame_duration;
@@ -36,12 +35,13 @@ struct camu_video_buffer {
atomic(u8) flow;
- struct camu_view view;
-
#ifdef CAMU_SCREEN_THREADED
atomic(u8) ref;
#endif
+ // Previous view, set from screen.
+ struct camu_view view;
+
void (*callback)(void *, u8);
void *userdata;
};
diff --git a/src/buffer/video_null.h b/src/buffer/video_null.h
new file mode 100644
index 0000000..69da85f
--- /dev/null
+++ b/src/buffer/video_null.h
@@ -0,0 +1,98 @@
+#pragma once
+
+#include <al/atomic.h>
+
+#include "../codec/codec.h"
+
+#include "clock.h"
+
+struct camu_video_buffer {
+ f64 avg_frame_duration;
+#ifdef CAMU_SCREEN_THREADED
+ atomic(u8) ref;
+#endif
+ void (*callback)(void *, u8);
+ void *userdata;
+};
+
+struct camu_renderer {
+ void (*add_font)(struct camu_renderer *, struct camu_codec_stream *);
+ u32 (*get_latency)(struct camu_renderer *);
+};
+
+AL_UNUSED_FUNCTION_PUSH
+
+static bool camu_video_buffer_init(struct camu_video_buffer *buf, struct camu_clock *clock)
+{
+ (void)clock;
+ buf->avg_frame_duration = 0.0;
+#ifdef CAMU_SCREEN_THREADED
+ al_atomic_store(u8)(&buf->ref, 0, AL_ATOMIC_RELAXED);
+#endif
+ return true;
+}
+
+static bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_codec_stream *stream,
+ struct camu_renderer *renderer)
+{
+ (void)buf;
+ (void)stream;
+ (void)renderer;
+ return true;
+}
+
+static bool camu_video_buffer_configure_subtitles(struct camu_video_buffer *buf, struct camu_codec_stream *stream)
+{
+ (void)buf;
+ (void)stream;
+ return true;
+}
+
+static void camu_video_buffer_set_latency(struct camu_video_buffer *buf, s32 frames)
+{
+ (void)buf;
+ (void)frames;
+}
+
+static bool camu_video_buffer_is_single_frame(struct camu_video_buffer *buf)
+{
+ (void)buf;
+ return false;
+}
+
+static void camu_video_buffer_push(struct camu_video_buffer *buf, struct camu_codec_frame *frame)
+{
+ (void)buf;
+ camu_codec_frame_discard(frame);
+}
+
+static void camu_video_buffer_push_subtitle(struct camu_video_buffer *buf, struct camu_codec_packet *packet)
+{
+ (void)buf;
+ (void)packet;
+}
+
+static void camu_video_buffer_flush(struct camu_video_buffer *buf)
+{
+ (void)buf;
+}
+
+static void camu_video_buffer_reset(struct camu_video_buffer *buf)
+{
+ (void)buf;
+}
+
+static bool camu_video_buffer_read(struct camu_video_buffer *buf, void *out, bool *weighted)
+{
+ (void)buf;
+ (void)out;
+ *weighted = false;
+ return false;
+}
+
+static void camu_video_buffer_free(struct camu_video_buffer *buf)
+{
+ (void)buf;
+}
+
+AL_UNUSED_FUNCTION_POP
diff --git a/src/cache/backings/file.c b/src/cache/backings/file.c
index bf62190..f421623 100644
--- a/src/cache/backings/file.c
+++ b/src/cache/backings/file.c
@@ -17,12 +17,14 @@ static void file_backing_write(struct cch_backing *backing, u8 *buf, off_t index
file->u.pointer = nn_file_seek(&file->file, index, SEEK_SET);
al_assert(file->u.pointer == index);
}
- if (file->size >= 0 && index + ((off_t)*size) >= file->size) {
- *size = MAX(file->size - index, 0L);
+ off_t n = (off_t)*size;
+ if (file->size >= 0 && index + n >= file->size) {
+ n = MAX(file->size - index, (off_t)0);
}
- nn_file_write(&file->file, buf, *size);
- file->u.pointer += *size;
- cch_backing_fill_range(&file->backing, index, *size);
+ nn_file_write(&file->file, buf, n);
+ file->u.pointer += n;
+ cch_backing_fill_range(&file->backing, index, n);
+ *size = (size_t)n;
nn_mutex_unlock(&file->mutex);
}
@@ -34,11 +36,13 @@ static void file_backing_read(struct cch_backing *backing, u8 *buf, off_t index,
file->u.pointer = nn_file_seek(&file->file, index, SEEK_SET);
al_assert(file->u.pointer == index);
}
- if (file->size >= 0 && index + ((off_t)*size) >= file->size) {
- *size = MAX(file->size - index, 0L);
+ off_t n = (off_t)*size;
+ if (file->size >= 0 && index + n >= file->size) {
+ n = MAX(file->size - index, (off_t)0);
}
- nn_file_read(&file->file, buf, *size);
- file->u.pointer += *size;
+ nn_file_read(&file->file, buf, n);
+ file->u.pointer += n;
+ *size = (size_t)n;
nn_mutex_unlock(&file->mutex);
}
@@ -53,9 +57,8 @@ static void file_backing_resize(struct cch_backing *backing, size_t size)
struct cch_backing_file *file = (struct cch_backing_file *)backing;
nn_mutex_lock(&file->mutex);
file->size = size;
- if ((off_t)size > file->filesize) {
+ if (size > file->file.size) {
nn_file_truncate(&file->file, size);
- file->filesize = size;
}
nn_mutex_unlock(&file->mutex);
}
diff --git a/src/cache/backings/file.h b/src/cache/backings/file.h
index 8a2bf7a..9ffd650 100644
--- a/src/cache/backings/file.h
+++ b/src/cache/backings/file.h
@@ -11,7 +11,6 @@ struct cch_backing_file {
struct cch_backing backing;
struct nn_file file;
off_t size;
- off_t filesize;
union {
void *map; // CACHE_BACKING_MAPPED
off_t pointer; // CACHE_BACKING_READ
diff --git a/src/cache/backings/file_common.c b/src/cache/backings/file_common.c
index 0ee2574..e8bd2ca 100644
--- a/src/cache/backings/file_common.c
+++ b/src/cache/backings/file_common.c
@@ -11,13 +11,12 @@ static bool file_open_internal(struct cch_backing_file *file, str *path, size_t
{
s32 flags = size != 0 ? NNWT_FILE_CREATE : NNWT_FILE_READONLY;
if (!nn_file_open(&file->file, path, flags)) return false;
- file->filesize = nn_file_get_filesize(&file->file);
if (!size) {
- file->size = file->filesize;
+ file->size = file->file.size;
cch_backing_fill_range(&file->backing, 0, file->size);
} else {
file->size = -1;
- if (file->filesize < (off_t)size) {
+ if (file->file.size < size) {
nn_file_truncate(&file->file, size);
}
}
diff --git a/src/cache/backings/file_mapped.c b/src/cache/backings/file_mapped.c
index 35ab267..e13d602 100644
--- a/src/cache/backings/file_mapped.c
+++ b/src/cache/backings/file_mapped.c
@@ -13,11 +13,13 @@ static void file_backing_write(struct cch_backing *backing, u8 *buf, off_t index
{
struct cch_backing_file *file = (struct cch_backing_file *)backing;
nn_mutex_lock(&file->mutex);
- if (file->size >= 0 && index + (off_t)*size >= file->size) {
- *size = MAX(file->size - index, 0L);
+ off_t n = (off_t)*size;
+ if (file->size >= 0 && index + n >= file->size) {
+ n = MAX(file->size - index, (off_t)0);
}
- al_memcpy(file->u.map + index, buf, *size);
- cch_backing_fill_range(&file->backing, index, *size);
+ al_memcpy(file->u.map + index, buf, n);
+ cch_backing_fill_range(&file->backing, index, n);
+ *size = (size_t)n;
nn_mutex_unlock(&file->mutex);
}
@@ -25,9 +27,11 @@ static u8 *file_backing_get_ptr(struct cch_backing *backing, off_t index, size_t
{
struct cch_backing_file *file = (struct cch_backing_file *)backing;
nn_mutex_lock(&file->mutex);
- if (file->size >= 0 && index + (off_t)*size >= file->size) {
- *size = MAX(file->size - index, 0L);
+ off_t n = (off_t)*size;
+ if (file->size >= 0 && index + n >= file->size) {
+ n = MAX(file->size - index, (off_t)0);
}
+ *size = (size_t)n;
return (u8 *)(file->u.map + index);
}
@@ -42,11 +46,11 @@ static void file_backing_resize(struct cch_backing *backing, size_t size)
struct cch_backing_file *file = (struct cch_backing_file *)backing;
nn_mutex_lock(&file->mutex);
file->size = size;
- if ((off_t)size > file->filesize) {
+ if (size > file->file.size) {
nn_file_munmap(&file->file, file->u.map);
nn_file_truncate(&file->file, size);
file->u.map = nn_file_mmap(&file->file);
- file->filesize = size;
+ file->file.size = size;
}
nn_mutex_unlock(&file->mutex);
}
diff --git a/src/cache/entry.c b/src/cache/entry.c
index 3cae147..560e5ee 100644
--- a/src/cache/entry.c
+++ b/src/cache/entry.c
@@ -8,7 +8,7 @@ bool cch_entry_get_handle(struct cch_entry *entry, struct cch_handle *handle)
handle->pointer = 0;
handle->wait.disabled = false;
nn_cond_init(&handle->wait.cond);
- nn_mutex_init(&handle->wait.mutex);
+ nn_mutex_init(&handle->wait.lock);
nn_mutex_lock(&entry->mutex);
entry->ref_count++;
nn_mutex_unlock(&entry->mutex);
@@ -42,7 +42,7 @@ void cch_entry_return_handle(struct cch_entry *entry, struct cch_handle *handle)
{
handle->entry = NULL;
entry->ref_count--;
- nn_mutex_destroy(&handle->wait.mutex);
+ nn_mutex_destroy(&handle->wait.lock);
nn_cond_destroy(&handle->wait.cond);
}
diff --git a/src/cache/handle.c b/src/cache/handle.c
index 9f39f8e..1b351a2 100644
--- a/src/cache/handle.c
+++ b/src/cache/handle.c
@@ -1,4 +1,5 @@
-#include <al/lib.h>
+#define AL_LOG_SECTION "cache_handle"
+//#define AL_LOG_ENABLE_TRACE
#include <al/log.h>
#include "../codec/codec.h"
@@ -11,6 +12,7 @@ static off_t wait_for_size(struct cch_handle *handle, struct cch_handler *handle
{
handle->wait.end = -1;
if (!handler->wait_for_range(handler, &handle->wait)) {
+ trace("Wait for size failed.");
return -1;
}
return cch_entry_get_size(handler->entry);
@@ -19,6 +21,7 @@ static off_t wait_for_size(struct cch_handle *handle, struct cch_handler *handle
s32 cch_handle_read(struct cch_handle *handle, u8 *buf, s32 size)
{
struct cch_handler *handler = handle->entry->handler;
+ // @TODO: Unknown size is unhandled in backings.
// filesize < 0: Size is yet to be evaluated and we can wait on it.
// filesize = 0: Size is explicitly unknown.
off_t filesize = cch_entry_get_size(handle->entry);
@@ -26,14 +29,14 @@ s32 cch_handle_read(struct cch_handle *handle, u8 *buf, s32 size)
if (filesize > 0) {
if (handle->pointer >= filesize) {
return CAMU_ERR_EOF;
- }
- if (handle->pointer + size >= filesize) {
+ } else if (handle->pointer + size >= filesize) {
size = filesize - handle->pointer;
}
}
handle->wait.start = handle->pointer;
handle->wait.end = handle->pointer + size;
if (!handler->wait_for_range(handler, &handle->wait)) {
+ trace("Wait for range (%zd-%zd) failed.", handle->wait.start, handle->wait.end);
return CAMU_ERR_EOF;
}
struct cch_backing *backing = handle->entry->backing;
@@ -49,7 +52,7 @@ s32 cch_handle_read(struct cch_handle *handle, u8 *buf, s32 size)
backing->read(backing, buf, handle->pointer, &available);
}
handle->pointer += available;
- al_assert(available <= INT32_MAX);
+ trace("read(%u), pointer: %zd.", size, handle->pointer);
return available > 0 ? (s32)available : CAMU_ERR_EOF;
}
@@ -59,21 +62,27 @@ off_t cch_handle_seek(struct cch_handle *handle, off_t offset, s32 whence)
off_t filesize = cch_entry_get_size(handle->entry);
if (filesize < 0) filesize = wait_for_size(handle, handler);
if (filesize <= 0) return -1;
- if (whence == CAMU_SEEK_SIZE) return filesize;
+ if (whence == CAMU_SEEK_SIZE) {
+ trace("SEEK_SIZE(), pointer: %zd, filesize: %zd.", handle->pointer, filesize);
+ return filesize;
+ }
switch (whence) {
case SEEK_SET:
- if (offset >= filesize || offset < 0) {
+ trace("SEEK_SET(%zd, %d), pointer: %zd.", offset, whence, handle->pointer);
+ if (offset > filesize || offset < 0) {
return -1;
}
handle->pointer = offset;
break;
case SEEK_CUR:
- if (handle->pointer + offset >= filesize || handle->pointer + offset < 0) {
+ trace("SEEK_CUR(%zd, %d), pointer: %zd.", offset, whence, handle->pointer);
+ if (handle->pointer + offset > filesize || handle->pointer + offset < 0) {
return -1;
}
handle->pointer += offset;
break;
case SEEK_END:
+ trace("SEEK_END(%zd, %d), pointer: %zd.", offset, whence, handle->pointer);
handle->pointer = filesize + offset;
break;
default:
diff --git a/src/cache/range.h b/src/cache/range.h
index 9ec1159..d6c7539 100644
--- a/src/cache/range.h
+++ b/src/cache/range.h
@@ -5,8 +5,8 @@
AL_UNUSED_FUNCTION_PUSH
// @TODO:
-// cch_backing_remove_range().
-// Report overlap, don't unlock in backing_write() and handle fill in handler.
+// - cch_backing_remove_range().
+// - Report overlap, don't unlock in backing_write() and handle fill in handler.
static void cch_backing_fill_range(struct cch_backing *backing, off_t index, off_t size)
{
diff --git a/src/cache/threaded_waits.c b/src/cache/threaded_waits.c
index 541bee6..6d8a5d8 100644
--- a/src/cache/threaded_waits.c
+++ b/src/cache/threaded_waits.c
@@ -1,151 +1,140 @@
#include "threaded_waits.h"
-void cch_threaded_waits_init(struct cch_handler_waits *waits)
+void cch_threaded_waits_init(struct cch_handler_waits *thw)
{
- waits->disabled = false;
- nn_mutex_init(&waits->mutex);
- al_array_init(waits->active);
+ thw->disabled = false;
+ nn_mutex_init(&thw->lock);
+ al_array_init(thw->active);
}
static bool wait_range_satisfied(struct cch_backing *backing, struct cch_handler_wait *wait)
{
- if (wait->end < 0) {
- return true;
- }
-
+ if (wait->end < 0) return true;
struct cch_range *range;
al_array_foreach_ptr(backing->available, i, range) {
if (wait->start >= range->start && range->end >= wait->end) {
return true;
}
}
-
return false;
}
-bool cch_threaded_wait_for_range(struct cch_handler_waits *waits, struct cch_entry *entry,
+bool cch_threaded_wait_for_range(struct cch_handler_waits *thw, struct cch_entry *entry,
struct cch_backing *backing, struct cch_handler_wait *wait)
{
- nn_mutex_lock(&waits->mutex);
-
off_t size = cch_entry_get_size(entry);
+
+ // Lock before checking wait_range_satisfied() in case the result changes
+ // before wait is possibly added to active.
+ nn_mutex_lock(&thw->lock);
+
backing->lock(backing);
bool satisfied = size >= 0 && wait_range_satisfied(backing, wait);
backing->unlock(backing);
- nn_mutex_lock(&wait->mutex);
-
- bool canceled = waits->disabled || wait->disabled;
-
+ nn_mutex_lock(&wait->lock);
+ bool canceled = thw->disabled || wait->disabled;
if (!canceled && !satisfied) {
- al_array_push(waits->active, wait);
-
+ al_array_push(thw->active, wait);
// Unlock handler to wait.
- nn_mutex_unlock(&waits->mutex);
- nn_cond_wait(&wait->cond, &wait->mutex);
+ nn_mutex_unlock(&thw->lock);
+ nn_cond_wait(&wait->cond, &wait->lock);
// A successful wait will have been removed before signaling.
-
if (wait->disabled) {
canceled = true;
// Re-lock to remove.
- nn_mutex_lock(&waits->mutex);
- al_array_remove(waits->active, wait);
+ nn_mutex_lock(&thw->lock);
+ al_array_remove(thw->active, wait);
}
}
+ nn_mutex_unlock(&wait->lock);
- nn_mutex_unlock(&wait->mutex);
if (canceled || satisfied) {
// Else, we already unlocked to wait.
- nn_mutex_unlock(&waits->mutex);
+ nn_mutex_unlock(&thw->lock);
}
return !canceled;
}
-void cch_threaded_waits_signal_any(struct cch_handler_waits *waits)
+void cch_threaded_waits_signal_any(struct cch_handler_waits *thw)
{
- nn_mutex_lock(&waits->mutex);
-
+ nn_mutex_lock(&thw->lock);
struct cch_handler_wait *wait;
- al_array_foreach_rev(waits->active, i, wait) {
+ al_array_foreach_rev(thw->active, i, wait) {
if (wait->end < 0) {
// This will be considered a successful wait.
- al_array_remove_at(waits->active, i);
- nn_mutex_lock(&wait->mutex);
+ al_array_remove_at(thw->active, i);
+ nn_mutex_lock(&wait->lock);
nn_cond_signal(&wait->cond);
- nn_mutex_unlock(&wait->mutex);
+ nn_mutex_unlock(&wait->lock);
}
}
-
- nn_mutex_unlock(&waits->mutex);
+ nn_mutex_unlock(&thw->lock);
}
-void cch_threaded_waits_evaluate(struct cch_handler_waits *waits, struct cch_backing *backing)
+void cch_threaded_waits_evaluate(struct cch_handler_waits *thw, struct cch_backing *backing)
{
- nn_mutex_lock(&waits->mutex);
+ nn_mutex_lock(&thw->lock);
backing->lock(backing);
-
struct cch_handler_wait *wait;
- al_array_foreach_rev(waits->active, i, wait) {
+ al_array_foreach_rev(thw->active, i, wait) {
if (wait_range_satisfied(backing, wait)) {
- al_array_remove_at(waits->active, i);
- nn_mutex_lock(&wait->mutex);
+ al_array_remove_at(thw->active, i);
+ nn_mutex_lock(&wait->lock);
nn_cond_signal(&wait->cond);
- nn_mutex_unlock(&wait->mutex);
+ nn_mutex_unlock(&wait->lock);
}
}
-
backing->unlock(backing);
- nn_mutex_unlock(&waits->mutex);
+ nn_mutex_unlock(&thw->lock);
}
void cch_threaded_wait_disable(struct cch_handler_wait *wait)
{
- nn_mutex_lock(&wait->mutex);
-
- // If this wait is in waits->active, cond_is_waiting() will be true.
- // This should mean we can be assured that successive calls to
- // disable() -> enable() won't leave this wait in waits->active.
+ // If this wait is in active, cond_is_waiting() will be true.
+ // So, signaling cond while holding the lock here will ensure it's removed before
+ // unlocking. This enables synchronization of successive disable() -> enable() calls.
+ nn_mutex_lock(&wait->lock);
wait->disabled = true;
if (nn_cond_is_waiting(&wait->cond)) {
nn_cond_signal(&wait->cond);
}
-
- nn_mutex_unlock(&wait->mutex);
+ nn_mutex_unlock(&wait->lock);
}
void cch_threaded_wait_enable(struct cch_handler_wait *wait)
{
- nn_mutex_lock(&wait->mutex);
+ nn_mutex_lock(&wait->lock);
wait->disabled = false;
- nn_mutex_unlock(&wait->mutex);
+ nn_mutex_unlock(&wait->lock);
}
-void cch_threaded_waits_disable_all(struct cch_handler_waits *waits)
+void cch_threaded_waits_disable_all(struct cch_handler_waits *thw)
{
- nn_mutex_lock(&waits->mutex);
+ nn_mutex_lock(&thw->lock);
- if (waits->disabled) {
- nn_mutex_unlock(&waits->mutex);
+ if (thw->disabled) {
+ nn_mutex_unlock(&thw->lock);
return;
}
// Disallow any further waits.
- waits->disabled = true;
+ thw->disabled = true;
struct cch_handler_wait *wait;
- al_array_foreach_rev(waits->active, i, wait) {
- nn_mutex_lock(&wait->mutex);
+ al_array_foreach_rev(thw->active, i, wait) {
+ nn_mutex_lock(&wait->lock);
wait->disabled = true;
nn_cond_signal(&wait->cond);
- nn_mutex_unlock(&wait->mutex);
+ nn_mutex_unlock(&wait->lock);
}
- nn_mutex_unlock(&waits->mutex);
+ nn_mutex_unlock(&thw->lock);
}
-void cch_threaded_waits_close(struct cch_handler_waits *waits)
+void cch_threaded_waits_close(struct cch_handler_waits *thw)
{
- nn_mutex_destroy(&waits->mutex);
- al_array_free(waits->active);
+ nn_mutex_destroy(&thw->lock);
+ al_array_free(thw->active);
}
diff --git a/src/cache/threaded_waits.h b/src/cache/threaded_waits.h
index 1893395..ce0f19c 100644
--- a/src/cache/threaded_waits.h
+++ b/src/cache/threaded_waits.h
@@ -8,16 +8,16 @@
struct cch_handler_waits {
bool disabled;
- struct nn_mutex mutex;
+ struct nn_mutex lock;
array(struct cch_handler_wait *) active;
};
-void cch_threaded_waits_init(struct cch_handler_waits *waits);
-bool cch_threaded_wait_for_range(struct cch_handler_waits *waits, struct cch_entry *entry,
+void cch_threaded_waits_init(struct cch_handler_waits *thw);
+bool cch_threaded_wait_for_range(struct cch_handler_waits *thw, struct cch_entry *entry,
struct cch_backing *backing, struct cch_handler_wait *wait);
-void cch_threaded_waits_signal_any(struct cch_handler_waits *waits);
-void cch_threaded_waits_evaluate(struct cch_handler_waits *waits, struct cch_backing *backing);
+void cch_threaded_waits_signal_any(struct cch_handler_waits *thw);
+void cch_threaded_waits_evaluate(struct cch_handler_waits *thw, struct cch_backing *backing);
void cch_threaded_wait_disable(struct cch_handler_wait *wait);
void cch_threaded_wait_enable(struct cch_handler_wait *wait);
-void cch_threaded_waits_disable_all(struct cch_handler_waits *waits);
-void cch_threaded_waits_close(struct cch_handler_waits *waits);
+void cch_threaded_waits_disable_all(struct cch_handler_waits *thw);
+void cch_threaded_waits_close(struct cch_handler_waits *thw);
diff --git a/src/cache/wait.h b/src/cache/wait.h
index 00c5749..143e2b9 100644
--- a/src/cache/wait.h
+++ b/src/cache/wait.h
@@ -6,5 +6,5 @@ struct cch_handler_wait {
off_t start, end;
bool disabled;
struct nn_cond cond;
- struct nn_mutex mutex;
+ struct nn_mutex lock;
};
diff --git a/src/codec/codec.h b/src/codec/codec.h
index 7e66e5d..09a1892 100644
--- a/src/codec/codec.h
+++ b/src/codec/codec.h
@@ -337,6 +337,9 @@ static inline void camu_video_format_copy(struct camu_video_format *dest, struct
static const char *camu_pixel_format_name(s32 format)
{
+#ifdef CAMU_HAVE_FFMPEG
+ return av_get_pix_fmt_name(format);
+#else
switch (format) {
case CAMU_PIXEL_FORMAT_RGBA: return "rgba";
case CAMU_PIXEL_FORMAT_RGB: return "rgb";
@@ -344,6 +347,7 @@ static const char *camu_pixel_format_name(s32 format)
case CAMU_PIXEL_FORMAT_GREY: return "grey";
default: return "(unknown)";
}
+#endif
}
static inline s32 camu_pixel_format_from_channels(s32 channels)
diff --git a/src/codec/ffmpeg/avio.c b/src/codec/ffmpeg/avio.c
index 143bc1f..855a4de 100644
--- a/src/codec/ffmpeg/avio.c
+++ b/src/codec/ffmpeg/avio.c
@@ -2,10 +2,10 @@
#include "../../cache/handle.h"
-s32 camu_avio_read(void *data, u8 *buf, s32 buf_size)
+s32 camu_avio_read(void *data, u8 *buf, s32 size)
{
struct cch_handle *handle = (struct cch_handle *)data;
- return cch_handle_read(handle, buf, buf_size);
+ return cch_handle_read(handle, buf, size);
}
s64 camu_avio_seek(void *data, s64 offset, s32 whence)
diff --git a/src/codec/ffmpeg/decoder.c b/src/codec/ffmpeg/decoder.c
index 5f4de67..dfabe05 100644
--- a/src/codec/ffmpeg/decoder.c
+++ b/src/codec/ffmpeg/decoder.c
@@ -11,15 +11,11 @@
#ifdef CAMU_FF_DECODER_HWACCEL
#if defined CAMU_RENDERER_VULKAN
static const char *hwdevces[] = { "vulkan" };
-#elif defined CAMU_RENDERER_DX11
+#elif defined NAUNET_ON_WINDOWS
static const char *hwdevces[] = { "d3d11va" };
#elif defined CAMU_RENDERER_OPENGL
-#ifdef NAUNET_ON_WINDOWS
-static const char *hwdevces[] = { "d3d11va" };
-#else
static const char *hwdevces[] = { "vaapi" };
#endif
-#endif
static s32 get_buffer2(AVCodecContext *context, AVFrame *pic, s32 flags)
{
@@ -277,7 +273,7 @@ static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *rend
}
const char *long_name = codec->long_name ? codec->long_name : codec->name;
- s64 kbps = codecpar->bit_rate > 0 ? codecpar->bit_rate / 1000L : 0L;
+ s64 kbps = codecpar->bit_rate > 0 ? codecpar->bit_rate / 1000 : 0;
al_log_info("ff_decoder", "Codec: %s (%s) %lldkbps.", codec->name, long_name, kbps);
av->callback = callback;
diff --git a/src/codec/ffmpeg/decoder.h b/src/codec/ffmpeg/decoder.h
index f698a6d..aa9eb57 100644
--- a/src/codec/ffmpeg/decoder.h
+++ b/src/codec/ffmpeg/decoder.h
@@ -5,15 +5,13 @@
#include "../codec.h"
-#ifndef CAMU_SINK_NO_VIDEO
//#define CAMU_FF_DECODER_HWACCEL
-#endif
struct camu_ff_decoder {
struct camu_decoder dec;
AVCodecContext *codec_context;
- struct camu_renderer *renderer;
#ifdef CAMU_FF_DECODER_HWACCEL
+ struct camu_renderer *renderer;
array(const AVCodec *) supported_hw_codecs;
array(enum AVHWDeviceType) supported_hw_devices;
AVBufferRef *hw_context;
diff --git a/src/codec/ffmpeg/demuxer.c b/src/codec/ffmpeg/demuxer.c
index 5cb3ceb..9764f58 100644
--- a/src/codec/ffmpeg/demuxer.c
+++ b/src/codec/ffmpeg/demuxer.c
@@ -54,8 +54,9 @@ static bool ff_demuxer_init(struct camu_demuxer *demux, struct cch_handle *handl
// FFmpeg says the avio protocol configures "buffers and access patterns".
// I think we already do enough buffering but, better access patterns for
// network streams sounds like something we want.
- if (avformat_open_input(&av->format_context, "file:", NULL, &opts) < 0) {
- al_log_error("ff_demuxer", "Failed to open input.");
+ s32 ret = avformat_open_input(&av->format_context, "file:", NULL, &opts);
+ if (ret < 0) {
+ al_log_error("ff_demuxer", "Failed to open input (%s).", av_err2str(ret));
av_dict_free(&opts);
goto err;
}
diff --git a/src/fruits/cmsrv/ui.c b/src/fruits/cmsrv/ui.c
index e64f580..9059632 100644
--- a/src/fruits/cmsrv/ui.c
+++ b/src/fruits/cmsrv/ui.c
@@ -5,7 +5,7 @@
#include "ui.h"
-#define LOG_RATIO 1.5
+#define LOG_RATIO 1.25
static s32 resize_cb(struct ncplane *p)
{
diff --git a/src/fruits/ctv/ctv.c b/src/fruits/ctv/ctv.c
index c9be50f..c3af943 100644
--- a/src/fruits/ctv/ctv.c
+++ b/src/fruits/ctv/ctv.c
@@ -1,131 +1,66 @@
-#include <stl/window.h>
#include <stl/platform.h>
#include <al/log.h>
#include "../../libsink/sink.h"
-#include "../../screen/screen.h"
-#include "../../render/renderer_libplacebo.h"
-#include "../../mixer/mixer.h"
-#include "../../mixer/audio_null.h"
-#include "../../mixer/audio_miniaudio.h"
-#include "../../sink/common.h"
-#include "../../server/common.h"
+#include "../../sink/desktop.h"
+#ifdef CAMU_HAVE_FFMPEG
+#include "../../codec/ffmpeg/common.h"
+#endif
#include "../common.h"
struct ctv {
struct nn_event_loop loop;
- struct camu_screen scr;
- struct camu_renderer *renderer;
- struct camu_mixer mixer;
- struct camu_sink sink;
+ struct camu_desktop desktop;
struct nn_thread thread;
- bool created;
};
-static u8 sink_callback(void *userdata, u8 op, u8 type, void *opaque)
-{
- struct ctv *c = (struct ctv *)userdata;
- if (!camu_default_sink_callback(&c->scr, &c->mixer, op, type, opaque)) {
- camu_sink_close(&c->sink);
- }
- return CAMU_SINK_OK;
-}
-
-static void screen_callback(void *userdata, u8 op, void *opaque)
-{
- struct ctv *c = (struct ctv *)userdata;
- (void)c;
- (void)op;
- (void)opaque;
-}
-
static nn_thread_result NNWT_THREADCALL event_loop_thread(void *userdata)
{
struct ctv *c = (struct ctv *)userdata;
- nn_event_loop_run(&c->loop);
- return 0;
-}
-
-static void to_be_removed_inline_create_client(struct ctv *c, GLFMDisplay *display)
-{
- c->scr.callback = screen_callback;
- c->scr.userdata = c;
- camu_screen_init(&c->scr, display);
- camu_screen_create_window(&c->scr, "ctv");
- c->renderer = camu_renderer_lp_create();
- camu_screen_create_renderer(&c->scr, c->renderer);
-
- camu_mixer_init(&c->mixer, camu_audio_miniaudio_create());
- c->mixer.audio->configure_stream(c->mixer.audio, NULL);
- camu_mixer_pick_format(&c->mixer, &c->mixer.fmt);
nn_event_loop_init(&c->loop);
- camu_sink_init(&c->sink, &c->loop, &c->mixer, c->renderer);
- al_str_from(&c->sink.default_list, "default");
- c->sink.callback = sink_callback;
- c->sink.userdata = c;
- camu_sink_connect(&c->sink, NNWT_SOCKET_TCP, &CAMU_TEST_IP, CAMU_PORT, &al_str_c("ctv"));
-
- nn_thread_create(&c->thread, event_loop_thread, c);
-}
-
-static void onSurfaceCreated(GLFMDisplay *display, s32 width, s32 height)
-{
- struct ctv *c = (struct ctv *)glfmGetUserData(display);
- (void)width;
- (void)height;
-
- GLFMRenderingAPI api = glfmGetRenderingAPI(display);
- al_log_info("window_glfm", "Created GLES context of version %s.",
- api == GLFMRenderingAPIOpenGLES32 ? "3.2" :
- api == GLFMRenderingAPIOpenGLES31 ? "3.1" :
- api == GLFMRenderingAPIOpenGLES3 ? "3.0" : "2.0");
+ struct lia_prefs *prefs = &c->desktop.sink.prefs;
+ prefs->enabled_mask = CAMU_MASK_AUDIO | CAMU_MASK_VIDEO | CAMU_MASK_SUBTITLE;
+ prefs->audio_lang = CAMU_LANG_ENGLISH;
+ prefs->subtitle_lang = CAMU_LANG_ENGLISH;
- if (!c->created) {
- to_be_removed_inline_create_client(c, display);
- c->created = true;
+ if (!camu_desktop_connect(&c->desktop, NNWT_SOCKET_TCP, &c->loop, &CAMU_TEST_ADDR, CAMU_PORT)) {
+ return 0;
}
-}
-static void onSurfaceDestroyed(GLFMDisplay *display)
-{
- (void)display;
-}
+ nn_event_loop_run(&c->loop);
-static void onDraw(GLFMDisplay *display)
-{
- struct ctv *c = (struct ctv *)glfmGetUserData(display);
- bool force;
- if (camu_screen_tick(&c->scr, &force)) {
- c->renderer->render(c->renderer, &c->scr, force);
- }
+ return 0;
}
static s32 log_callback(void *userdata, u8 level, char *message)
{
(void)userdata;
(void)level;
- __android_log_print(ANDROID_LOG_DEBUG, "CTV", "%.*s", AL_LOG_MESSAGE_SIZE, message);
+ __android_log_print(ANDROID_LOG_DEBUG, "CTV", "%*.*s", 0, AL_LOG_MESSAGE_SIZE, message);
return al_strnlen(message, AL_LOG_MESSAGE_SIZE);
}
-void glfmMain(GLFMDisplay *display)
+static struct ctv c = { 0 };
+
+s32 window_system_main(s32 argc, str *argv)
{
- struct ctv *c = al_alloc_object(struct ctv);
- c->created = false;
+ (void)argc;
+ (void)argv;
+
+ al_set_print(log_callback, NULL);
+
+#ifdef CAMU_HAVE_FFMPEG
+ camu_ff_common_init();
+#endif
+
+ if (!camu_desktop_init(&c.desktop, "ctv")) {
+ return 0;
+ }
- al_set_print(log_callback, c);
+ nn_thread_create(&c.thread, event_loop_thread, &c);
- glfmSetUserData(display, c);
- glfmSetDisplayConfig(display,
- GLFMRenderingAPIOpenGLES2,
- GLFMColorFormatRGBA8888,
- GLFMDepthFormatNone,
- GLFMStencilFormatNone,
- GLFMMultisampleNone);
- glfmSetSurfaceCreatedFunc(display, onSurfaceCreated);
- glfmSetSurfaceDestroyedFunc(display, onSurfaceDestroyed);
- glfmSetRenderFunc(display, onDraw);
+ return EXIT_SUCCESS;
}
diff --git a/src/fruits/ctv/meson.build b/src/fruits/ctv/meson.build
index 1b653e7..bf6f9e5 100644
--- a/src/fruits/ctv/meson.build
+++ b/src/fruits/ctv/meson.build
@@ -1,5 +1,4 @@
-ctv_src = ['ctv.c', '../../sink/common.c']
-ctv_deps = [common_deps, libsink]
-ctv_args = ['-DCAMU_MIXER_THREADED', '-DCAMU_SCREEN_THREADED']
+ctv_src = ['ctv.c']
+ctv_deps = [common_deps, desktop]
# https://github.com/google/ExoPlayer/issues/9933#issuecomment-1029775358
-shared_library('ctv', ctv_src, dependencies: ctv_deps, c_args: ctv_args, link_args: ['-Wl,-Bsymbolic', '-static-libstdc++'])
+shared_library('ctv', ctv_src, dependencies: ctv_deps, link_args: ['-Wl,-Bsymbolic', '-static-libstdc++'])
diff --git a/src/fruits/droid/ctv/gradle/libs.versions.toml b/src/fruits/droid/ctv/gradle/libs.versions.toml
index e1ef643..6869a38 100644
--- a/src/fruits/droid/ctv/gradle/libs.versions.toml
+++ b/src/fruits/droid/ctv/gradle/libs.versions.toml
@@ -1,5 +1,5 @@
[versions]
-agp = "8.8.0"
+agp = "8.9.0"
[libraries]
diff --git a/src/fruits/droid/ctv/gradle/wrapper/gradle-wrapper.properties b/src/fruits/droid/ctv/gradle/wrapper/gradle-wrapper.properties
index 698ab10..6c7d7ac 100644
--- a/src/fruits/droid/ctv/gradle/wrapper/gradle-wrapper.properties
+++ b/src/fruits/droid/ctv/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Wed Oct 30 14:10:25 EDT 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/src/liana/client.c b/src/liana/client.c
index b53fb7e..726f8ff 100644
--- a/src/liana/client.c
+++ b/src/liana/client.c
@@ -29,12 +29,8 @@ static s32 stream_compare(const void *a, const void *b)
return (s32)((struct camu_codec_stream *)a)->type - (s32)((struct camu_codec_stream *)b)->type;
}
-static void parse_info_packet(struct lia_client *client, struct nn_packet *packet)
+static void collect_streams(struct lia_client *client, struct nn_packet *packet)
{
- str liana;
- nn_packet_read_str(packet, &liana);
- client->duration = nn_packet_read_u64(packet);
-
u32 count = nn_packet_read_u32(packet);
for (u32 i = 0; i < count; i++) {
// Very important zero-initialization.
@@ -77,13 +73,23 @@ static void parse_info_packet(struct lia_client *client, struct nn_packet *packe
continue;
}
stream.av.format_context = format_context;
- if (type == CAMU_STREAM_AUDIO) {
+ AVCodecParameters *codecpar = stream.av.stream->codecpar;
+ switch (type) {
+ case CAMU_STREAM_AUDIO: {
struct camu_audio_format *fmt = &stream.audio.fmt;
- AVCodecParameters *codecpar = stream.av.stream->codecpar;
fmt->format = codecpar->format;
fmt->sample_rate = codecpar->sample_rate;
av_channel_layout_copy(&fmt->channel_layout, &codecpar->ch_layout);
fmt->channel_count = codecpar->ch_layout.nb_channels;
+ break;
+ }
+ case CAMU_STREAM_VIDEO: {
+ struct camu_video_format *fmt = &stream.video.fmt;
+ fmt->width = (u32)codecpar->width;
+ fmt->height = (u32)codecpar->height;
+ fmt->format = codecpar->format;
+ break;
+ }
}
break;
}
@@ -95,56 +101,44 @@ static void parse_info_packet(struct lia_client *client, struct nn_packet *packe
stream.index = index;
al_array_push(client->streams, stream);
}
-
// Video streams have to come before subtitle streams.
al_array_sort(client->streams, struct camu_codec_stream, stream_compare);
+}
- bool have_audio = false;
- bool have_video = false;
- bool have_subs = false;
+static u8 type_to_mask[] = {
+ [CAMU_STREAM_AUDIO] = CAMU_MASK_AUDIO,
+ [CAMU_STREAM_VIDEO] = CAMU_MASK_VIDEO,
+ [CAMU_STREAM_SUBTITLE] = CAMU_MASK_SUBTITLE
+};
+static void parse_info_packet(struct lia_client *client, struct nn_packet *packet)
+{
+ str liana;
+ nn_packet_read_str(packet, &liana);
+ client->duration = nn_packet_read_u64(packet);
+ collect_streams(client, packet);
+ u8 selected = 0;
struct camu_codec_stream *stream;
al_array_foreach_ptr(client->streams, i, stream) {
- switch (stream->type) {
- case CAMU_STREAM_AUDIO:
- if (have_audio || !(client->prefs.enabled_mask & CAMU_MASK_AUDIO)) {
- continue;
- }
- have_audio = true;
- break;
- case CAMU_STREAM_VIDEO:
- if (have_video || !(client->prefs.enabled_mask & CAMU_MASK_VIDEO)) {
- continue;
- }
- have_video = true;
- break;
- case CAMU_STREAM_SUBTITLE:
- if (have_subs || !(client->prefs.enabled_mask & CAMU_MASK_SUBTITLE)) {
- continue;
- }
- have_subs = true;
- break;
+ u8 type_mask = type_to_mask[stream->type];
+ if ((selected & type_mask) || !(client->prefs.enabled_mask & type_mask)) {
+ continue;
}
-
+ selected |= type_mask;
client->mask |= 1 << stream->index;
-
struct lia_vcr_track *track = al_alloc_object(struct lia_vcr_track);
track->stream = stream;
track->client = lia_handler_by_name(&liana)->create_client_handler();
track->client->callback = client->callback;
track->client->userdata = client->userdata;
-
if (!track->client->init(track->client, client->renderer, track->stream)) {
track->client->free(&track->client);
al_free(track);
continue;
}
-
client->callback(client->userdata, LIANA_CLIENT_CONFIGURE, stream, track);
-
lia_vcr_add_track(&client->vcr, track);
}
-
client->callback(client->userdata, LIANA_CLIENT_CONFIGURE_COMPLETE, NULL, NULL);
}
@@ -185,12 +179,12 @@ static bool connection_callback(void *userdata, struct nn_packet_stream *stream)
.pause = LIANA_PAUSE_NONE
};
client->callback(client->userdata, LIANA_CLIENT_RESUME_AT, NULL, &time);
- if (client->mask == 0) {
+ bool unconfigured = client->mask == 0;
+ if (unconfigured) {
al_assert(client->connection_id == 0);
al_log_warn("liana", "Handling reconnect on unconfigured client.");
- } else {
- client->callback(client->userdata, LIANA_CLIENT_RECONNECTED, NULL, NULL);
}
+ client->callback(client->userdata, LIANA_CLIENT_RECONNECTED, NULL, &unconfigured);
} else {
al_assert(client->connection_id == 0);
}
@@ -218,9 +212,7 @@ static void connection_closed_callback(void *userdata, struct nn_packet_stream *
} else {
lia_vcr_close_all(&client->vcr);
}
- // REMOVE_BUFFERS can possibly run the event loop while waiting.
- // This should be accounted for in the client code here to not cause
- // any unexpected behavior.
+ // We need to account for REMOVE_BUFFERS possibly running the event loop to wait.
client->callback(client->userdata, LIANA_CLIENT_REMOVE_BUFFERS, NULL, &client->reconnect);
if (client->reconnect == RECONNECT_ON_CONNECTION_CLOSED) {
// If reconnect() errors, this will close the client on recursion.
@@ -236,7 +228,7 @@ static void connection_closed_callback(void *userdata, struct nn_packet_stream *
}
void lia_client_connect(struct lia_client *client, struct nn_event_loop *loop,
- u8 type, str *addr, u16 port, u32 node_id, u64 pos, struct camu_renderer *renderer)
+ u8 type, str *addr, u16 port, u32 node_id, u64 pos)
{
client->loop = loop;
client->node_id = node_id;
@@ -249,7 +241,6 @@ void lia_client_connect(struct lia_client *client, struct nn_event_loop *loop,
client->port = port;
client->connection_id = 0;
nn_packet_stream_init(&client->data, connection_callback, connection_closed_callback, client);
- client->renderer = renderer;
#ifdef CAMU_DIRECT_MODE
(void)type;
nn_multiplex_direct_connect(&client->data, CAMU_MULTIPLEX_LIANA);
diff --git a/src/liana/client.h b/src/liana/client.h
index 9e49d0e..4e2ff9e 100644
--- a/src/liana/client.h
+++ b/src/liana/client.h
@@ -45,7 +45,7 @@ struct lia_client {
};
void lia_client_connect(struct lia_client *client, struct nn_event_loop *loop,
- u8 type, str *addr, u16 port, u32 node_id, u64 pos, struct camu_renderer *renderer);
+ u8 type, str *addr, u16 port, u32 node_id, u64 pos);
void lia_client_seek(struct lia_client *client, u64 pos, u64 at);
void lia_client_reseek(struct lia_client *client);
void lia_client_disconnect(struct lia_client *client);
diff --git a/src/liana/handlers/codec_client.c b/src/liana/handlers/codec_client.c
index 6f7e9f4..a412b5c 100644
--- a/src/liana/handlers/codec_client.c
+++ b/src/liana/handlers/codec_client.c
@@ -46,8 +46,8 @@ static bool push_av_packet(struct lia_codec_client *codec, AVPacket *pkt)
static void passthrough_subtitle(struct lia_codec_client *codec, AVPacket *pkt)
{
- struct camu_codec_packet packet = { .av.pkt = pkt };
struct camu_codec_stream *stream = codec->handler.stream;
+ struct camu_codec_packet packet = { .av.pkt = pkt };
codec->handler.callback(codec->handler.userdata, LIANA_CLIENT_SUBTITLE, stream, &packet);
}
#endif
diff --git a/src/liana/list.c b/src/liana/list.c
index 650b6c5..47b8545 100644
--- a/src/liana/list.c
+++ b/src/liana/list.c
@@ -1,18 +1,17 @@
#include <nnwt/thread.h>
#include <al/random.h>
#include <al/lib.h>
+#define AL_LOG_SECTION "list"
+//#define AL_LOG_ENABLE_TRACE
#include <al/log.h>
#include "list.h"
#include "list_cmp.h"
-//#define LIANA_LIST_TRACE
-
enum {
ADD_SINK = 0,
REMOVE_SINK,
ADD,
- UNSET,
SKIPTO,
SKIP,
TOGGLE_PAUSE,
@@ -21,6 +20,7 @@ enum {
REVERSE,
SORT,
SHUFFLE,
+ UNSET,
CLEAR
};
@@ -73,17 +73,17 @@ static bool entry_load_and_get_duration(struct lia_list *list, struct lia_list_e
if (status == LIANA_ENTRY_ERRORED) {
if (sequence >= 0) {
al_array_remove_at(list->entries, (u32)sequence);
- if (list->cmd->sequence == sequence) {
- // @TODO: This command has to be effectively discarded.
- list->cmd->sequence = -1;
- } else if (list->cmd->sequence > sequence) {
- list->cmd->sequence--;
- }
- if (list->current >= sequence) {
+ if (list->current > sequence) {
list->current--;
- // This maps to the behavior of only skipping ahead on errors.
- if ((u32)list->current == list->entries.count) {
- list->idle = true;
+ }
+ struct lia_list_cmd *cmd = list->cmd;
+ al_assert(cmd->sequence != sequence);
+ if (cmd->sequence > sequence) {
+ cmd->sequence--;
+ }
+ al_array_foreach(list->command_queue, i, cmd) {
+ if (cmd->sequence > sequence) {
+ cmd->sequence--;
}
}
}
@@ -150,8 +150,7 @@ static void set_current(struct lia_list *list, struct lia_list_entry *entry, s32
entry_ref(list, entry);
list->current = sequence;
list->idle = false;
- // @TODO: This is really wrong. Switching back and forth between 2 entries
- // will unload everything else.
+ // @TODO: Skipping back and forth between 2 entries will unload everything else.
unref_all_entries(list);
struct lia_list_sink *sink;
al_array_foreach(list->sinks, i, sink) {
@@ -164,14 +163,8 @@ static void pump_queue(struct lia_list *list);
static bool handle_add_sink(struct lia_list *list, struct lia_list_sink *sink)
{
- struct lia_list_entry *current;
if (list->current >= 0 && !list->idle) {
- current = al_array_at(list->entries, list->current);
- bool error;
- if (!entry_load_and_get_duration(list, current, list->current, &error)) {
- if (error) pump_queue(list);
- return false;
- }
+ struct lia_list_entry *current = al_array_at(list->entries, list->current);
u64 now = nn_get_timestamp();
u8 pause;
u64 at = LIANA_TIMESTAMP_INVALID;
@@ -205,19 +198,33 @@ static bool handle_add_sink(struct lia_list *list, struct lia_list_sink *sink)
static void handle_remove_sink(struct lia_list *list, void *userdata)
{
+ struct lia_list_cmd *cmd = list->cmd;
+ if (cmd && cmd->op == ADD_SINK && cmd->sink->userdata == userdata) {
+ al_free(cmd->sink);
+ al_free(cmd);
+ list->cmd = NULL;
+ return;
+ }
+ al_array_foreach(list->command_queue, i, cmd) {
+ if (cmd->op == ADD_SINK && cmd->sink->userdata == userdata) {
+ al_free(cmd->sink);
+ al_free(cmd);
+ al_array_remove_at(list->command_queue, i);
+ return;
+ }
+ }
struct lia_list_sink *sink;
al_array_foreach(list->sinks, i, sink) {
if (sink->userdata == userdata) {
al_array_remove_at(list->sinks, i);
al_free(sink);
- break;
+ return;
}
}
}
static bool handle_add(struct lia_list *list, struct lia_list_entry *entry)
{
- // The list being idle doesn't mean list->current or sink->set aren't set.
if (list->idle) {
bool error;
if (!entry_load_and_get_duration(list, entry, -1, &error)) {
@@ -241,28 +248,6 @@ static bool handle_add(struct lia_list *list, struct lia_list_entry *entry)
return true;
}
-static void unset_all(struct lia_list *list)
-{
- list->current = -1;
- list->queued = -1;
- struct lia_list_sink *sink;
- al_array_foreach(list->sinks, i, sink) {
- sink->set = -1;
- sink->queued = -1;
- }
-}
-
-static void handle_unset(struct lia_list *list)
-{
- unset_all(list);
- struct lia_list_sink *sink;
- al_array_foreach(list->sinks, i, sink) {
- sink_unset(sink);
- }
- list->current = list->entries.count - 1;
- list->idle = true;
-}
-
static struct lia_list_entry *get_entry_from_sequence(struct lia_list *list, s32 sequence)
{
s32 size = (s32)list->entries.count;
@@ -274,7 +259,7 @@ static struct lia_list_entry *get_entry_from_sequence(struct lia_list *list, s32
static s32 get_sequence_from_entry_id(struct lia_list *list, u32 id)
{
- // Not returning the entry pointer here seems wasteful but it's a meaningful simplification.
+ // Not returning the entry pointer here is a meaningful simplification.
struct lia_list_entry *entry;
al_array_foreach(list->entries, i, entry) {
if (entry->id == id) {
@@ -369,6 +354,7 @@ static bool handle_skipto(struct lia_list *list, s32 sequence, s32 index)
//
// @TODO: Why not hold an ended entry? The idea of ended entries being
// unpaused but never held seems like a over-complication.
+ // sink-side ended should only reference buffer state? i.e. pause the entries clock even if ended.
if (!current_ended && current->paused_at == LIANA_TIMESTAMP_INVALID) {
current->paused_at = at;
if (current->paused_at < current->start) {
@@ -379,10 +365,8 @@ static bool handle_skipto(struct lia_list *list, s32 sequence, s32 index)
current->held = true;
}
-#ifdef LIANA_LIST_TRACE
- al_log_info("list", "skipto [#%u-#%u]: pause: %hhu, held: %s, current_ended: %s, target_ended: %s.",
+ trace("skipto (#%u-#%u): pause: %hhu, held: %s, current_ended: %s, target_ended: %s.",
current->id, target->id, pause, BOOLSTR(current->held), BOOLSTR(current_ended), BOOLSTR(target_ended));
-#endif
struct lia_timing time = {
.at = at,
@@ -424,6 +408,7 @@ static void handle_toggle_pause(struct lia_list *list, s32 sequence, f64 pts)
u64 at;
bool ended = assume_ended(entry, now);
if (ended) return;
+
switch (pause) {
case LIANA_PAUSE_PAUSE:
al_assert(entry->start != LIANA_TIMESTAMP_INVALID);
@@ -443,11 +428,7 @@ static void handle_toggle_pause(struct lia_list *list, s32 sequence, f64 pts)
break;
}
-#ifdef LIANA_LIST_TRACE
- al_log_info("list", "toggle_pause [#%u]: pts: %f, pause: %hhu.", entry->id, pts, pause);
-#else
- (void)pts;
-#endif
+ trace("toggle_pause (#%u): pts: %f, pause: %hhu.", entry->id, pts, pause);
struct lia_timing time = {
.at = at,
@@ -462,32 +443,30 @@ static void handle_toggle_pause(struct lia_list *list, s32 sequence, f64 pts)
}
}
-static void handle_seek(struct lia_list *list, s32 sequence, u32 id, f64 percent)
+static void handle_seek(struct lia_list *list, s32 sequence, u32 id, u64 pos)
{
- struct lia_list_entry *entry;
if (sequence == LIANA_SEQUENCE_ANY) {
sequence = list->current;
} else {
sequence = get_sequence_from_entry_id(list, id);
}
-
if (sequence < 0) return;
- entry = get_entry_from_sequence(list, sequence);
+ struct lia_list_entry *entry = get_entry_from_sequence(list, sequence);
al_assert(entry);
- if (entry->duration == LIANA_TIMESTAMP_INVALID) {
- al_log_warn("list", "Skipping seek on entry with no duration.");
+ if (entry->duration == LIANA_TIMESTAMP_INVALID || entry->duration == 0) {
+ warn("Skipping seek on entry with no duration.");
return;
}
- entry->ended = false;
- entry->reset_id = get_incremental_id(list);
-
+ pos = CLAMP(pos, (u64)0, entry->duration);
u64 now = nn_get_timestamp();
- u64 pos = (u64)(entry->duration * percent);
u64 at = now + LIANA_BASE_DELAY;
u8 pause = entry->paused_at == LIANA_TIMESTAMP_INVALID ? LIANA_PAUSE_RESUME : LIANA_PAUSE_NONE;
+
+ entry->ended = false;
+ entry->reset_id = get_incremental_id(list);
entry->offset = pos;
if (pause == LIANA_PAUSE_RESUME) {
entry->start = at;
@@ -495,9 +474,7 @@ static void handle_seek(struct lia_list *list, s32 sequence, u32 id, f64 percent
list->idle = false;
-#ifdef LIANA_LIST_TRACE
- al_log_info("list", "seek [#%u]: pos: %f.", entry->id, pos / 1000000.0);
-#endif
+ trace("seek (#%u): pos: %f, pause: %hhu.", entry->id, pos / 1000000.0, pause);
struct lia_timing time = {
.at = at,
@@ -524,18 +501,16 @@ static bool handle_end(struct lia_list *list, u32 id, u32 reset_id)
struct lia_list_entry *entry = get_entry_from_sequence(list, sequence);
if (reset_id != entry->reset_id) {
- al_log_warn("list", "Got end() with out of order or incorrect reset id, ignoring.");
+ warn("Got end() with out of order or incorrect reset id, ignoring.");
return true;
}
if (entry->ended) {
- al_log_warn("list", "Got end() from an already ended resource, ignoring.");
+ warn("Got end() from an already ended resource, ignoring.");
return true;
}
-#ifdef LIANA_LIST_TRACE
- al_log_info("list", "end [#%u].", entry->id);
-#endif
+ trace("end (#%u).", entry->id);
entry->ended = true;
entry->offset = entry->duration;
@@ -582,7 +557,7 @@ static bool adjust_current(struct lia_list *list, struct lia_list_entry *previou
{
al_assert(list->current >= 0);
struct lia_list_cmd *cmd = list->cmd;
- struct lia_list_entry *entry;
+ struct lia_list_entry *entry = NULL;
al_array_foreach(list->entries, i, entry) {
if (entry->opaque == previous->opaque) {
if (i == (u32)list->current) {
@@ -595,7 +570,7 @@ static bool adjust_current(struct lia_list *list, struct lia_list_entry *previou
break;
}
}
- al_assert(cmd->op == SKIPTO && !entry->held);
+ al_assert(cmd->op == SKIPTO && entry && !entry->held);
pump_queue(list);
return false;
}
@@ -651,6 +626,20 @@ static bool handle_shuffle(struct lia_list *list)
return adjust_current(list, previous);
}
+static void handle_unset(struct lia_list *list)
+{
+ // @TODO: Unset behavior (flag on list):
+ // SKIP: Based on previous current.
+ // ADD: Skip to added entry.
+ // SEEK: Set and seek previous current.
+ // Explicitly ignore all other events.
+ struct lia_list_sink *sink;
+ al_array_foreach(list->sinks, i, sink) {
+ sink->set = -1;
+ sink_unset(sink);
+ }
+}
+
static void run_queue(struct lia_list *list)
{
// @TODO: What does current = -1/currentless really mean.
@@ -673,9 +662,6 @@ static void run_queue(struct lia_list *list)
return;
}
break;
- case UNSET:
- handle_unset(list);
- break;
case SKIPTO:
if (!handle_skipto(list, cmd->sequence, cmd->arg0.i)) {
// Target entry not loaded.
@@ -692,7 +678,7 @@ static void run_queue(struct lia_list *list)
handle_toggle_pause(list, cmd->sequence, cmd->argf);
break;
case SEEK:
- handle_seek(list, cmd->sequence, cmd->arg0.u, cmd->argf);
+ handle_seek(list, cmd->sequence, cmd->arg0.u, cmd->arg1.u);
break;
case END:
if (!handle_end(list, cmd->arg0.u, cmd->arg1.u)) {
@@ -715,6 +701,9 @@ static void run_queue(struct lia_list *list)
return;
}
break;
+ case UNSET:
+ handle_unset(list);
+ break;
case CLEAR:
break;
}
@@ -775,13 +764,6 @@ void lia_list_add(struct lia_list *list, void *opaque, u64 duration, wstr *name)
pump_queue(list);
}
-void lia_list_unset(struct lia_list *list)
-{
- struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd);
- cmd->op = UNSET;
- al_array_push(list->command_queue, cmd);
- pump_queue(list);
-}
void lia_list_skipto(struct lia_list *list, s32 sequence, s32 index)
{
@@ -813,13 +795,13 @@ void lia_list_toggle_pause(struct lia_list *list, s32 sequence, f64 pts)
pump_queue(list);
}
-void lia_list_seek(struct lia_list *list, s32 sequence, u32 id, f64 percent)
+void lia_list_seek(struct lia_list *list, s32 sequence, u32 id, u64 pos)
{
struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd);
cmd->op = SEEK;
cmd->sequence = sequence;
cmd->arg0.u = id;
- cmd->argf = percent;
+ cmd->arg1.u = pos;
al_array_push(list->command_queue, cmd);
pump_queue(list);
}
@@ -858,6 +840,14 @@ void lia_list_shuffle(struct lia_list *list)
pump_queue(list);
}
+void lia_list_unset(struct lia_list *list)
+{
+ struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd);
+ cmd->op = UNSET;
+ al_array_push(list->command_queue, cmd);
+ pump_queue(list);
+}
+
/*
void lia_list_clear(struct lia_list *list)
{
diff --git a/src/liana/list.h b/src/liana/list.h
index 0d80dde..754a4bd 100644
--- a/src/liana/list.h
+++ b/src/liana/list.h
@@ -130,7 +130,7 @@ void lia_list_unset(struct lia_list *list);
void lia_list_skipto(struct lia_list *list, s32 sequence, s32 i);
void lia_list_skip(struct lia_list *list, s32 sequence, s32 n);
void lia_list_toggle_pause(struct lia_list *list, s32 sequence, f64 pts);
-void lia_list_seek(struct lia_list *list, s32 sequence, u32 id, f64 percent);
+void lia_list_seek(struct lia_list *list, s32 sequence, u32 id, u64 pos);
void lia_list_end(struct lia_list *list, u32 id, u32 reset_id);
void lia_list_reverse(struct lia_list *list);
diff --git a/src/liana/server.c b/src/liana/server.c
index 13ca0aa..9d3661c 100644
--- a/src/liana/server.c
+++ b/src/liana/server.c
@@ -450,7 +450,11 @@ static void duration_signal_callback(void *userdata)
if (should_free_node(node)) {
free_node(node);
} else {
- node->callback(node->userdata, LIANA_NODE_DURATION, node->duration);
+ if (node->errored) {
+ node->callback(node->userdata, LIANA_NODE_ERRORED, LIANA_TIMESTAMP_INVALID);
+ } else {
+ node->callback(node->userdata, LIANA_NODE_DURATION, node->duration);
+ }
}
}
diff --git a/src/liana/server.h b/src/liana/server.h
index 364008e..679d607 100644
--- a/src/liana/server.h
+++ b/src/liana/server.h
@@ -40,7 +40,8 @@ struct lia_node_connection {
};
enum {
- LIANA_NODE_DURATION = 0
+ LIANA_NODE_DURATION = 0,
+ LIANA_NODE_ERRORED
};
struct lia_node {
diff --git a/src/liana/vcr.c b/src/liana/vcr.c
index e06ff8e..a70a19a 100644
--- a/src/liana/vcr.c
+++ b/src/liana/vcr.c
@@ -250,9 +250,9 @@ static void update_metrics(struct lia_vcr *vcr, u32 size)
return;
}
f32 kbps = (frame / 125.f) / (diff / 1000000.f);
- f32 size = vcr->mark.buffered / (f32)MB(1);
+ f32 capacity = vcr->mark.buffered / (f32)MB(1);
f32 buffered = al_atomic_load(u64)(&vcr->count, AL_ATOMIC_RELAXED) / (f32)MB(1);
- al_log_info("vcr", "Receiving packets at %.2fkbps (%.2f/%.2fMB).", kbps, buffered, size);
+ al_log_info("vcr", "Receiving packets at %.2fkbps (%.2f/%.2fMB).", kbps, buffered, capacity);
}
}
diff --git a/src/libsink/sink.c b/src/libsink/sink.c
index 3f337d5..e5f9351 100644
--- a/src/libsink/sink.c
+++ b/src/libsink/sink.c
@@ -1,3 +1,5 @@
+#define AL_LOG_SECTION "sink"
+//#define AL_LOG_ENABLE_TRACE
#include <al/log.h>
#include <nnwt/multiplex.h>
@@ -10,7 +12,6 @@
#define CAMU_SINK_LOCAL
//#define CAMU_SINK_ONESHOT
-//#define CAMU_SINK_TRACE
// Requested state of the sinks outputs.
enum {
@@ -20,14 +21,15 @@ enum {
};
enum {
- BUFFER_INIT = 0,
+ // Created.
+ BUFFER_INIT,
// Set but not configured.
BUFFER_QUEUED,
// Ready to receive data.
BUFFER_CONFIGURED,
// The next call to add can add the buffer.
BUFFER_SET_OR_BUFFERED,
- // Treat the buffer like it's added, even though it might not be.
+ // Treat the buffer like it's added, even if it might not be.
BUFFER_ADDED,
// Effectively SET_OR_BUFFERED but not addable until after a reset.
BUFFER_ENDED
@@ -41,52 +43,49 @@ enum {
ADD_BUFFER,
REMOVE_BUFFER,
CLEAR_BUFFERS,
- ERROR_OUT,
+ EJECT_ENTRY,
CLOSE,
// List actions.
+ SKIP,
TOGGLE_PAUSE,
SEEK,
- SKIP,
- SHUFFLE,
RESEEK,
- UNSET,
+ SHUFFLE,
END
};
+#define SINK_LRU_MAX UINT16_MAX
+
// Number of entries to keep buffered at one time.
#define ENTRY_MAX_AGE 3
+// printf format for entries.
+#if defined C89ATOMIC_64BIT
+#define ENTRY_FMT "#%u(0x%llx)"
+#elif defined C89ATOMIC_32BIT
+#define ENTRY_FMT "#%u(0x%lx)"
+#endif
+#define ENTRY_ARG(entry) \
+ ((entry) && (entry) != (struct camu_sink_entry *)0xb00b) ? (entry)->id : 0, (entry) ? (entry) : 0x0
+
+#define AUDIO_STATE(entry) ((entry)->audio.state)
+#define VIDEO_STATE(entry) ((entry)->video.state)
+
// If a buffer is still INIT or QUEUED after the entry is configured, it's "empty".
#define BUFFER_EMPTY(buf) ((buf)->state == BUFFER_INIT || (buf)->state == BUFFER_QUEUED)
#define AUDIO_EMPTY(entry) BUFFER_EMPTY(&(entry)->audio)
-#ifndef CAMU_SINK_NO_VIDEO
#define VIDEO_EMPTY(entry) BUFFER_EMPTY(&(entry)->video)
-#endif
#define AUDIO_ADDED_OR_EMPTY(entry) ((entry)->audio.state == BUFFER_ADDED || BUFFER_EMPTY(&(entry)->audio))
-#ifndef CAMU_SINK_NO_VIDEO
#define VIDEO_ADDED_OR_EMPTY(entry) ((entry)->video.state == BUFFER_ADDED || BUFFER_EMPTY(&(entry)->video))
-#else
-#define VIDEO_ADDED_OR_EMPTY(entry) true
-#endif
#define AUDIO_ENDED_OR_EMPTY(entry) ((entry)->audio.state == BUFFER_ENDED || BUFFER_EMPTY(&(entry)->audio))
-#ifndef CAMU_SINK_NO_VIDEO
#define VIDEO_ENDED_OR_EMPTY(entry) ((entry)->video.state == BUFFER_ENDED || BUFFER_EMPTY(&(entry)->video))
-#else
-#define VIDEO_ENDED_OR_EMPTY(entry) true
-#endif
#define AUDIO_NOT_ADDED(entry) ((entry)->audio.state != BUFFER_ADDED)
-#ifndef CAMU_SINK_NO_VIDEO
#define VIDEO_NOT_ADDED(entry) ((entry)->video.state != BUFFER_ADDED)
-#else
-#define VIDEO_NOT_ADDED(entry) true
-#endif
-#ifndef CAMU_SINK_NO_VIDEO
#define VIDEO_IS_SINGLE_FRAME(entry) camu_video_buffer_is_single_frame(&(entry)->video.buf)
-#endif
#if defined CAMU_SCREEN_THREADED && defined CAMU_MIXER_THREADED
#define BLOCKING_SLEEP(delay) nn_thread_sleep(delay)
@@ -104,7 +103,6 @@ static inline bool entry_audio_buffer_held(struct camu_sink_entry *entry)
#endif
}
-#ifndef CAMU_SINK_NO_VIDEO
static inline bool entry_video_buffer_held(struct camu_sink_entry *entry)
{
#ifdef CAMU_SCREEN_THREADED
@@ -114,14 +112,23 @@ static inline bool entry_video_buffer_held(struct camu_sink_entry *entry)
return false;
#endif
}
-#endif
static void queue_cmd(struct camu_sink *sink, struct camu_sink_cmd cmd)
{
+#ifdef CAMU_SINK_NO_VIDEO
+ if (cmd.value.i == CAMU_SINK_VIDEO) return;
+#endif
camu_queue_push(sink->queue, cmd);
nn_signal_send(&sink->queue_signal);
}
+static void request_video_refresh(struct camu_sink *sink)
+{
+#ifndef CAMU_SINK_NO_VIDEO
+ sink->callback(sink->userdata, CAMU_SINK_REFRESH_VIDEO, 0, NULL);
+#endif
+}
+
static inline void add_entry_audio_buffer(struct camu_sink_entry *entry)
{
struct camu_sink *sink = entry->sink;
@@ -136,20 +143,21 @@ static inline void add_entry_audio_buffer(struct camu_sink_entry *entry)
#endif
}
-#ifndef CAMU_SINK_NO_VIDEO
static inline void add_entry_video_buffer(struct camu_sink_entry *entry)
{
+#ifndef CAMU_SINK_NO_VIDEO
struct camu_sink *sink = entry->sink;
sink->callback(sink->userdata, CAMU_SINK_ADD_BUFFER, CAMU_SINK_VIDEO, &entry->video.buf);
-}
#endif
+}
static void remove_entry_audio_buffer(struct camu_sink *sink, struct camu_sink_entry *entry)
{
- al_assert(entry->audio.state != BUFFER_ENDED && entry->audio.state != BUFFER_INIT);
-
- if (entry->audio.state == BUFFER_ADDED) {
- entry->audio.state = BUFFER_SET_OR_BUFFERED;
+ al_assert(AUDIO_STATE(entry) != BUFFER_ENDED);
+ al_assert(AUDIO_STATE(entry) != BUFFER_INIT);
+ switch (AUDIO_STATE(entry)) {
+ case BUFFER_ADDED:
+ AUDIO_STATE(entry) = BUFFER_SET_OR_BUFFERED;
#ifdef CAMU_MIXER_THREADED_START_STOP
sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf);
#else
@@ -159,72 +167,71 @@ static void remove_entry_audio_buffer(struct camu_sink *sink, struct camu_sink_e
.opaque = entry
});
#endif
- } else if (entry->audio.state == BUFFER_SET_OR_BUFFERED) {
- entry->audio.state = BUFFER_CONFIGURED;
- } else if (entry->audio.state == BUFFER_QUEUED) {
- entry->audio.state = BUFFER_INIT;
+ break;
+ case BUFFER_SET_OR_BUFFERED:
+ AUDIO_STATE(entry) = BUFFER_CONFIGURED;
+ break;
+ case BUFFER_QUEUED:
+ AUDIO_STATE(entry) = BUFFER_INIT;
+ break;
}
}
-#ifndef CAMU_SINK_NO_VIDEO
static void remove_entry_video_buffer(struct camu_sink *sink, struct camu_sink_entry *entry)
{
// Don't assert !entry->ended here because of single frame handling.
- al_assert(entry->video.state != BUFFER_ENDED && entry->video.state != BUFFER_INIT);
-
- if (entry->video.state == BUFFER_ADDED) {
- entry->video.state = BUFFER_SET_OR_BUFFERED;
+ al_assert(VIDEO_STATE(entry) != BUFFER_ENDED);
+ al_assert(VIDEO_STATE(entry) != BUFFER_INIT);
+ switch (VIDEO_STATE(entry)) {
+ case BUFFER_ADDED:
+ VIDEO_STATE(entry) = BUFFER_SET_OR_BUFFERED;
+#ifndef CAMU_SINK_NO_VIDEO
sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_VIDEO, &entry->video.buf);
- } else if (entry->video.state == BUFFER_SET_OR_BUFFERED) {
- entry->video.state = BUFFER_CONFIGURED;
- } else if (entry->video.state == BUFFER_QUEUED) {
+#endif
+ break;
+ case BUFFER_SET_OR_BUFFERED:
+ VIDEO_STATE(entry) = BUFFER_CONFIGURED;
+ break;
+ case BUFFER_QUEUED:
// This can be hit when skipping through entries very fast.
- entry->video.state = BUFFER_INIT;
+ VIDEO_STATE(entry) = BUFFER_INIT;
+ break;
}
}
-#endif
// It's possible for some of an entries buffers to be ENDED while others are still
// ADDED and playing. We handle that by making remove_entry_buffers() and
// add_audio/video_if_set_and_buffered() no-ops for ENDED buffers.
static void remove_entry_buffers(struct camu_sink *sink, struct camu_sink_entry *entry)
{
-#ifdef CAMU_SINK_TRACE
- al_log_info("sink", "remove_entry_buffers(0x%llx), audio_state: %hhu, video_state: %hhu",
- entry, entry->audio.state, entry->video.state);
-#endif
+ 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 (entry->audio.state != BUFFER_ENDED) {
+ if (AUDIO_STATE(entry) != BUFFER_ENDED) {
remove_entry_audio_buffer(sink, entry);
}
-#ifndef CAMU_SINK_NO_VIDEO
- if (entry->video.state != BUFFER_ENDED) {
+ if (VIDEO_STATE(entry) != BUFFER_ENDED) {
remove_entry_video_buffer(sink, entry);
}
-#endif
}
static void add_audio_if_set_and_buffered(struct camu_sink_entry *entry);
-#ifndef CAMU_SINK_NO_VIDEO
static void add_video_if_set_and_buffered(struct camu_sink_entry *entry);
-#endif
static void add_or_queue_entry(struct camu_sink_entry *entry)
{
- if (entry->audio.state == BUFFER_INIT) {
- entry->audio.state = BUFFER_QUEUED;
+ if (AUDIO_STATE(entry) == BUFFER_INIT) {
+ AUDIO_STATE(entry) = BUFFER_QUEUED;
} else {
- al_assert(entry->audio.state != BUFFER_QUEUED);
+ al_assert(AUDIO_STATE(entry) != BUFFER_QUEUED);
add_audio_if_set_and_buffered(entry);
}
-#ifndef CAMU_SINK_NO_VIDEO
- if (entry->video.state == BUFFER_INIT) {
- entry->video.state = BUFFER_QUEUED;
+ if (VIDEO_STATE(entry) == BUFFER_INIT) {
+ VIDEO_STATE(entry) = BUFFER_QUEUED;
} else {
- al_assert(entry->video.state != BUFFER_QUEUED);
+ al_assert(VIDEO_STATE(entry) != BUFFER_QUEUED);
add_video_if_set_and_buffered(entry);
}
-#endif
}
// Disconnecting a packet stream twice before a reconnect is an error.
@@ -240,28 +247,29 @@ static void maybe_disconnect_entry(struct camu_sink_entry *entry)
#ifdef CAMU_SINK_LOCAL
static void sink_local_pause(struct camu_sink *sink, struct camu_sink_entry *entry)
{
- if (camu_clock_is_paused(&entry->clock)) {
- entry->buffers_paused = false;
+ if (!camu_clock_is_paused(&entry->clock)) {
+ entry->paused = true;
+ camu_clock_pause(&entry->clock, 0);
+ // Audio stop will be handled by a BUFFER_PAUSED callback.
+ if (!VIDEO_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
+ sink->video.state = SINK_PAUSED;
+ }
+ } else {
+ entry->paused = false;
camu_clock_resume(&entry->clock, 0);
if (!AUDIO_EMPTY(entry) && sink->audio.state == SINK_PAUSED) {
sink->callback(sink->userdata, CAMU_SINK_START, CAMU_SINK_AUDIO, NULL);
sink->audio.state = SINK_PLAYING;
}
-#ifndef CAMU_SINK_NO_VIDEO
if (!VIDEO_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);
- sink->video.state = SINK_PLAYING;
- }
#endif
- } else {
- entry->buffers_paused = true;
- camu_clock_pause(&entry->clock, 0);
-#ifndef CAMU_SINK_NO_VIDEO
- if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry) && sink->video.state == SINK_PLAYING) {
- sink->callback(sink->userdata, CAMU_SINK_STOP, CAMU_SINK_VIDEO, NULL);
- sink->video.state = SINK_PAUSED;
+ sink->video.state = SINK_PLAYING;
}
-#endif
}
}
#endif
@@ -272,7 +280,7 @@ static inline s32 get_sequence_for_command(struct camu_sink *sink)
s32 sequence = LIANA_SEQUENCE_ANY;
#if 0
// Setting an explicit sequence makes skip and pause act on "what you see".
- // This is likely not the expected behavior in the common case. The user might
+ // This is unlikely to be expected behavior in the common case. The user might
// feel like their input was eaten if skipping after a different skip happens
// on the server but is yet to be reflected on their end.
if (sink->target) {
@@ -297,14 +305,14 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
sink->audio.state = SINK_PLAYING;
}
break;
-#ifndef CAMU_SINK_NO_VIDEO
case CAMU_SINK_VIDEO:
if (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;
}
break;
-#endif
}
break;
}
@@ -316,14 +324,14 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
sink->audio.state = SINK_PAUSED;
}
break;
-#ifndef CAMU_SINK_NO_VIDEO
case CAMU_SINK_VIDEO:
if (sink->video.state == SINK_PLAYING) {
+#ifndef CAMU_SINK_NO_VIDEO
sink->callback(sink->userdata, CAMU_SINK_STOP, CAMU_SINK_VIDEO, NULL);
+#endif
sink->video.state = SINK_PAUSED;
}
break;
-#endif
}
break;
}
@@ -333,11 +341,11 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
case CAMU_SINK_AUDIO:
sink->callback(sink->userdata, CAMU_SINK_ADD_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf);
break;
-#ifndef CAMU_SINK_NO_VIDEO
case CAMU_SINK_VIDEO:
+#ifndef CAMU_SINK_NO_VIDEO
sink->callback(sink->userdata, CAMU_SINK_ADD_BUFFER, CAMU_SINK_VIDEO, &entry->video.buf);
- break;
#endif
+ break;
}
break;
}
@@ -347,11 +355,11 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
case CAMU_SINK_AUDIO:
sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf);
break;
-#ifndef CAMU_SINK_NO_VIDEO
case CAMU_SINK_VIDEO:
+#ifndef CAMU_SINK_NO_VIDEO
sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_VIDEO, &entry->video.buf);
- break;
#endif
+ break;
}
break;
}
@@ -360,15 +368,15 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
case CAMU_SINK_AUDIO:
sink->callback(sink->userdata, CAMU_SINK_CLEAR, CAMU_SINK_AUDIO, NULL);
break;
-#ifndef CAMU_SINK_NO_VIDEO
case CAMU_SINK_VIDEO:
+#ifndef CAMU_SINK_NO_VIDEO
sink->callback(sink->userdata, CAMU_SINK_CLEAR, CAMU_SINK_VIDEO, NULL);
- break;
#endif
+ break;
}
break;
}
- case ERROR_OUT: {
+ case EJECT_ENTRY: {
struct camu_sink_entry *entry = (struct camu_sink_entry *)cmd->opaque;
maybe_disconnect_entry(entry);
break;
@@ -388,14 +396,6 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
nn_rpc_connection_command(sink->conn, packet, NULL, NULL);
break;
}
- case SHUFFLE: {
- 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_SHUFFLE);
- nn_rpc_connection_command(sink->conn, packet, NULL, NULL);
- break;
- }
case TOGGLE_PAUSE: {
#ifdef CAMU_SINK_LOCAL
sink_local_pause(sink, (struct camu_sink_entry *)cmd->opaque);
@@ -416,14 +416,9 @@ 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_SEEK);
struct camu_sink_entry *entry = (struct camu_sink_entry *)cmd->opaque;
- if (entry) {
- nn_packet_write_s32(packet, entry->sequence);
- nn_packet_write_u32(packet, entry->id);
- } else {
- nn_packet_write_s32(packet, LIANA_SEQUENCE_ANY);
- nn_packet_write_u32(packet, 0);
- }
- nn_packet_write_f64(packet, cmd->value.f);
+ nn_packet_write_s32(packet, entry->sequence);
+ nn_packet_write_u32(packet, entry->id);
+ nn_packet_write_u64(packet, cmd->value.u);
nn_rpc_connection_command(sink->conn, packet, NULL, NULL);
break;
}
@@ -432,11 +427,11 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
lia_client_reseek(&entry->client);
break;
}
- case UNSET: {
+ case SHUFFLE: {
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_UNSET);
+ nn_packet_write_u8(packet, CAMU_LIST_SHUFFLE);
nn_rpc_connection_command(sink->conn, packet, NULL, NULL);
break;
}
@@ -468,34 +463,30 @@ static void queue_signal_callback(void *userdata)
static void mixer_callback(void *userdata, u8 op)
{
- struct camu_sink *sink = (struct camu_sink *)userdata;
if (op == CAMU_MIXER_EMPTY) {
- al_log_info("sink", "Mixer empty.");
+ info("Mixer empty.");
#ifndef LIANA_LIST_SCUFFED_LOOP
+ struct camu_sink *sink = (struct camu_sink *)userdata;
queue_cmd(sink, (struct camu_sink_cmd){
.op = STOP,
.value.i = CAMU_SINK_AUDIO
});
-#else
- (void)sink;
#endif
}
}
bool camu_sink_init(struct camu_sink *sink, struct nn_event_loop *loop,
- struct camu_mixer *mixer
-#ifndef CAMU_SINK_NO_VIDEO
- , struct camu_renderer *renderer
-#endif
- )
+ struct camu_mixer *mixer, struct camu_renderer *renderer)
{
sink->loop = loop;
nn_mutex_init(&sink->lock);
nn_signal_init(&sink->queue_signal, sink->loop, queue_signal_callback, sink);
nn_signal_start(&sink->queue_signal);
camu_queue_init(sink->queue);
- sink->queued = NULL;
sink->current = NULL;
+ sink->queued = NULL;
+ sink->target = NULL;
+ sink->reconnecting = NULL;
al_array_init(sink->previous);
al_array_init(sink->entries);
sink->lru = 0;
@@ -503,18 +494,58 @@ bool camu_sink_init(struct camu_sink *sink, struct nn_event_loop *loop,
mixer->userdata = sink;
sink->audio.mixer = mixer;
sink->audio.state = SINK_PAUSED;
-#ifndef CAMU_SINK_NO_VIDEO
sink->video.renderer = renderer;
sink->video.state = SINK_PAUSED;
-#endif
return true;
}
+static s32 entry_lru_compare(const void *a, const void *b)
+{
+ struct camu_sink_entry *aa = *((struct camu_sink_entry **)a);
+ struct camu_sink_entry *bb = *((struct camu_sink_entry **)b);
+ if (aa->lru > bb->lru) return -1;
+ else if (aa->lru < bb->lru) return 1;
+ return 0;
+}
+
+static void maybe_cleanup_old_entries(struct camu_sink *sink)
+{
+ al_array_sort(sink->entries, struct camu_sink_entry *, entry_lru_compare);
+ // We check size <= MAX_AGE in the loops because sink->lru is not
+ // indicative of the amount of entries we have loaded.
+ // The most obvious reason being it's incremented when moving back
+ // and forth between two entries. As well as for buffer and queue operations.
+ u16 max_age = ENTRY_MAX_AGE;
+ // We have to handle sink->lru wrapping in a step before the default case.
+ // 0 65532 65533 65534 65535
+ // 0 1 65533 65534 65535
+ // 0 1 2 65534 65535
+ // 0 1 2 3 65535
+ // 0 1 2 3 4
+ struct camu_sink_entry *entry;
+ al_array_foreach_rev(sink->entries, i, entry) {
+ if (entry->lru > sink->lru && ((SINK_LRU_MAX - entry->lru) + 1) + sink->lru >= max_age) {
+ al_array_remove_at(sink->entries, i);
+ maybe_disconnect_entry(entry);
+ }
+ if (sink->entries.count <= max_age) return;
+ }
+ // Make sure we don't have to consider wrapping here.
+ if (sink->lru >= max_age) {
+ al_array_foreach_rev(sink->entries, i, entry) {
+ al_assert(sink->lru >= entry->lru);
+ if (sink->lru - entry->lru >= max_age) {
+ al_array_remove_at(sink->entries, i);
+ maybe_disconnect_entry(entry);
+ }
+ if (sink->entries.count <= max_age) return;
+ }
+ }
+}
+
static void maybe_remove_previous(struct camu_sink *sink)
{
-#ifdef CAMU_SINK_TRACE
- al_log_info("sink", "maybe_remove_previous(), previous_count: %u", sink->previous.count);
-#endif
+ trace("maybe_remove_previous(), previous_count: %u.", sink->previous.count);
struct camu_sink_entry *previous;
al_array_foreach(sink->previous, i, previous) {
remove_entry_buffers(sink, previous);
@@ -538,9 +569,7 @@ static void remove_previous_if_contains(struct camu_sink *sink, struct camu_sink
// An obvious example of this is at the point an entry gets freed.
static void maybe_remove_from_previous(struct camu_sink *sink, struct camu_sink_entry *entry)
{
-#ifdef CAMU_SINK_TRACE
- al_log_info("sink", "maybe_remove_from_previous(0x%llx)", entry);
-#endif
+ trace("maybe_remove_from_previous("ENTRY_FMT").", ENTRY_ARG(entry));
al_array_remove_all(sink->previous, entry);
}
@@ -548,226 +577,140 @@ static void maybe_remove_from_previous(struct camu_sink *sink, struct camu_sink_
static void maybe_add_to_previous(struct camu_sink *sink, struct camu_sink_entry *previous,
struct camu_sink_entry *target)
{
-#ifdef CAMU_SINK_TRACE
- al_log_info("sink", "maybe_add_to_previous(0x%llx, 0x%llx), audio_state: %hhu, video_state: %hhu",
- previous, target, previous->audio.state, previous->video.state);
-#endif
-
- al_assert(previous != target && !previous->ended);
-
- // If neither of the entries audio or video buffer is ADDED, we don't care about adding it
- // to previous (waiting for the next added entry to remove it).
-#ifndef CAMU_SINK_NO_VIDEO
- if (!(previous->audio.state == BUFFER_ADDED || previous->video.state == BUFFER_ADDED) || target->ended) {
-#else
- if (!(previous->audio.state == BUFFER_ADDED) || target->ended) {
-#endif
+ trace("maybe_add_to_previous("ENTRY_FMT"), "ENTRY_FMT"), audio_state: %hhu, video_state: %hhu.",
+ ENTRY_ARG(previous), ENTRY_ARG(target), AUDIO_STATE(previous), VIDEO_STATE(previous));
+ 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 == (struct camu_sink_entry *)0xb00b;
+ if (dangling_target || target->ended || !(AUDIO_STATE(previous) == BUFFER_ADDED || VIDEO_STATE(previous) == BUFFER_ADDED)) {
remove_entry_buffers(sink, previous);
return;
}
-
al_array_push(sink->previous, previous);
}
-static s32 entry_lru_compare(const void *a, const void *b)
+// Call this after setting the buffer's state to ADDED because this entry might be in previous.
+static void do_add_entry(struct camu_sink_entry *entry)
{
- struct camu_sink_entry *aa = *((struct camu_sink_entry **)a);
- struct camu_sink_entry *bb = *((struct camu_sink_entry **)b);
- if (aa->lru > bb->lru) return -1;
- else if (aa->lru < bb->lru) return 1;
- return 0;
-}
-
-static void maybe_cleanup_old_entries(struct camu_sink *sink)
-{
- al_array_sort(sink->entries, struct camu_sink_entry *, entry_lru_compare);
-
- // We check size <= MAX_AGE in the loops because sink->lru is
- // not indicative of the amount of entries we have loaded.
- // The most obvious reason being it's incremented when moving
- // back and forth between two entries. As well as for buffer and
- // queue operations.
-
- struct camu_sink_entry *entry;
- // Handle sink->lru wrapping. This must happen in a step before the no wrapping case.
- al_array_foreach_rev(sink->entries, i, entry) {
- // 0 65532 65533 65534 65535
- // 0 1 65533 65534 65535
- // 0 1 2 65534 65535
- // 0 1 2 3 65535
- // 0 1 2 3 4
- if (entry->lru > sink->lru && ((UINT16_MAX - entry->lru) + 1) + sink->lru >= ENTRY_MAX_AGE) {
- al_array_remove_at(sink->entries, i);
- maybe_disconnect_entry(entry);
- }
- if (sink->entries.count <= ENTRY_MAX_AGE) return;
+ // Single frames are unconditionally added in add_video_if_set_and_buffered().
+ if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry)) {
+ add_entry_video_buffer(entry);
}
-
- // Checking sink->lru >= ENTRY_MAX_AGE should guarantee
- // we don't have to consider wrapping here.
- if (sink->lru >= ENTRY_MAX_AGE) {
- al_array_foreach_rev(sink->entries, i, entry) {
- al_assert(sink->lru >= entry->lru);
- if (sink->lru - entry->lru >= ENTRY_MAX_AGE) {
- al_array_remove_at(sink->entries, i);
- maybe_disconnect_entry(entry);
- }
- if (sink->entries.count <= ENTRY_MAX_AGE) return;
- }
+ if (!AUDIO_EMPTY(entry)) {
+ add_entry_audio_buffer(entry);
}
+ maybe_remove_previous(entry->sink);
+ queue_cmd(entry->sink, (struct camu_sink_cmd){
+ .op = VIDEO_EMPTY(entry) || VIDEO_IS_SINGLE_FRAME(entry) || entry->paused ? STOP : START,
+ .value.i = CAMU_SINK_VIDEO
+ });
+ if (VIDEO_EMPTY(entry)) {
+ // Clear the screen if skipping from a video to an audio-only entry.
+ request_video_refresh(entry->sink);
+ }
+ queue_cmd(entry->sink, (struct camu_sink_cmd){
+ .op = AUDIO_EMPTY(entry) || entry->paused ? STOP : START,
+ .value.i = CAMU_SINK_AUDIO
+ });
}
void add_audio_if_set_and_buffered(struct camu_sink_entry *entry)
{
al_assert(!entry->ended);
- al_assert(entry->audio.state != BUFFER_INIT &&
- (entry->audio.state != BUFFER_QUEUED) &&
- (entry->audio.state != BUFFER_ADDED));
-
- if (entry->audio.state == BUFFER_ENDED) {
- al_log_warn("sink", "Tried to add an ended audio buffer.");
+ al_assert(AUDIO_STATE(entry) != BUFFER_INIT);
+ al_assert(AUDIO_STATE(entry) != BUFFER_QUEUED);
+ al_assert(AUDIO_STATE(entry) != BUFFER_ADDED);
+ switch (AUDIO_STATE(entry)) {
+ case BUFFER_ENDED:
+ warn("Tried to add an ended audio buffer.");
return;
- }
-
- if (entry->audio.state == BUFFER_CONFIGURED) {
- entry->audio.state = BUFFER_SET_OR_BUFFERED;
- } else if (entry->audio.state == BUFFER_SET_OR_BUFFERED) {
- entry->audio.state = BUFFER_ADDED;
-
+ case BUFFER_CONFIGURED:
+ AUDIO_STATE(entry) = BUFFER_SET_OR_BUFFERED;
+ break;
+ case BUFFER_SET_OR_BUFFERED:
+ AUDIO_STATE(entry) = BUFFER_ADDED;
if (VIDEO_ADDED_OR_EMPTY(entry)) {
-#ifndef CAMU_SINK_NO_VIDEO
- bool single_frame = VIDEO_IS_SINGLE_FRAME(entry);
-
- // Single frames are unconditionally added in add_video_if_set_and_buffered().
- if (!VIDEO_EMPTY(entry) && !single_frame) {
- add_entry_video_buffer(entry);
- }
-#endif
- add_entry_audio_buffer(entry);
-
- // This must be called after setting the buffer's state to ADDED because
- // this entry might be in previous. It's confusing but well defined,
- // although could probably be avoided by a greater simplification.
- maybe_remove_previous(entry->sink);
-
-#ifndef CAMU_SINK_NO_VIDEO
- // This must come after maybe_remove_previous().
- if (!single_frame) {
- queue_cmd(entry->sink, (struct camu_sink_cmd){
- .op = VIDEO_EMPTY(entry) || entry->buffers_paused ? STOP : START,
- .value.i = CAMU_SINK_VIDEO
- });
- // This is the only place to clear the screen if skipping from a video
- // to an audio-only entry.
- if (VIDEO_EMPTY(entry)) {
- struct camu_sink *sink = entry->sink;
- sink->callback(sink->userdata, CAMU_SINK_REFRESH_VIDEO, 0, NULL);
- }
- }
-#endif
- queue_cmd(entry->sink, (struct camu_sink_cmd){
- .op = entry->buffers_paused ? STOP : START,
- .value.i = CAMU_SINK_AUDIO
- });
+ do_add_entry(entry);
}
+ break;
}
}
-#ifndef CAMU_SINK_NO_VIDEO
void add_video_if_set_and_buffered(struct camu_sink_entry *entry)
{
- // Single frames will be added/removed with ended set.
- al_assert(entry->video.state != BUFFER_INIT &&
- (entry->video.state != BUFFER_QUEUED) &&
- (entry->video.state != BUFFER_ADDED));
-
- if (entry->video.state == BUFFER_ENDED) {
- al_log_warn("sink", "Tried to add an ended video buffer.");
+ // Single frame entries will be added/removed with ended set.
+ al_assert(VIDEO_STATE(entry) != BUFFER_INIT);
+ al_assert(VIDEO_STATE(entry) != BUFFER_QUEUED);
+ al_assert(VIDEO_STATE(entry) != BUFFER_ADDED);
+ if (VIDEO_STATE(entry) == BUFFER_ENDED) {
+ warn("Tried to add an ended video buffer.");
return;
- }
-
- if (entry->video.state == BUFFER_CONFIGURED) {
- entry->video.state = BUFFER_SET_OR_BUFFERED;
- } else if (entry->video.state == BUFFER_SET_OR_BUFFERED) {
- entry->video.state = BUFFER_ADDED;
-
- bool single_frame = VIDEO_IS_SINGLE_FRAME(entry);
-
- if (AUDIO_ADDED_OR_EMPTY(entry)) {
+ } else if (VIDEO_STATE(entry) == BUFFER_CONFIGURED) {
+ VIDEO_STATE(entry) = BUFFER_SET_OR_BUFFERED;
+ } else if (VIDEO_STATE(entry) == BUFFER_SET_OR_BUFFERED) {
+ VIDEO_STATE(entry) = BUFFER_ADDED;
+ if (VIDEO_IS_SINGLE_FRAME(entry)) {
add_entry_video_buffer(entry);
- if (!AUDIO_EMPTY(entry)) {
- add_entry_audio_buffer(entry);
- }
-
- // This entry could be in previous, see note in add_audio_if_set_and_buffered().
- maybe_remove_previous(entry->sink);
-
- queue_cmd(entry->sink, (struct camu_sink_cmd){
- .op = single_frame || entry->buffers_paused ? STOP : START,
- .value.i = CAMU_SINK_VIDEO
- });
- queue_cmd(entry->sink, (struct camu_sink_cmd){
- .op = AUDIO_EMPTY(entry) || entry->buffers_paused ? STOP : START,
- .value.i = CAMU_SINK_AUDIO
- });
- } else if (single_frame) {
- add_entry_video_buffer(entry);
- // Stopping video here is needed if skipping from a video to an image.
- queue_cmd(entry->sink, (struct camu_sink_cmd){
- .op = STOP,
- .value.i = CAMU_SINK_VIDEO
- });
+ }
+ if (AUDIO_ADDED_OR_EMPTY(entry)) {
+ do_add_entry(entry);
}
}
}
-#endif
static void switch_to(struct camu_sink *sink, struct camu_sink_entry *target)
{
-#ifdef CAMU_SINK_TRACE
- al_log_info("sink", "switch_to(0x%llx)", target);
-#endif
+ trace("switch_to("ENTRY_FMT"), current: "ENTRY_FMT".",
+ ENTRY_ARG(target), ENTRY_ARG(sink->current));
+
if (sink->current) {
struct camu_sink_entry *current = sink->current;
al_assert(current != target);
-
- if (!current->ended) {
+ bool ensure_removed = sink->reconnecting || current->ended;
+ if (sink->reconnecting) {
+ al_assert(sink->reconnecting == current);
+ sink->reconnecting = NULL;
+ warn("Unset reconnecting as a substitute for remove.");
+ } else if (!current->ended) {
maybe_add_to_previous(sink, current, target);
- } else {
-#ifndef CAMU_SINK_NO_VIDEO
+ }
+ if (ensure_removed) {
if (VIDEO_IS_SINGLE_FRAME(current)) {
remove_entry_video_buffer(sink, current);
}
-#endif
- al_assert(AUDIO_NOT_ADDED(current) && VIDEO_NOT_ADDED(current));
+ al_assert(AUDIO_NOT_ADDED(current));
+ al_assert(VIDEO_NOT_ADDED(current));
}
+ }
- if (sink->reconnecting) {
- al_assert(sink->reconnecting == current);
- sink->reconnecting = NULL;
- }
+ if (target == (struct camu_sink_entry *)0xb00b) {
+ sink->current = NULL;
+ queue_cmd(sink, (struct camu_sink_cmd){
+ .op = STOP,
+ .value.i = CAMU_SINK_VIDEO
+ });
+ request_video_refresh(sink);
+ return;
}
if (!target->ended) {
remove_previous_if_contains(sink, target);
add_or_queue_entry(target);
} else {
-#ifndef CAMU_SINK_NO_VIDEO
if (VIDEO_IS_SINGLE_FRAME(target)) {
add_video_if_set_and_buffered(target);
} else {
- // @TODO: current-less
queue_cmd(sink, (struct camu_sink_cmd){
.op = STOP,
.value.i = CAMU_SINK_VIDEO
});
}
-#endif
}
sink->current = target;
sink->current->audio.ignore_paused = false;
- if (!sink->current->buffers_paused) {
+ if (!sink->current->paused) {
camu_audio_buffer_resync(&sink->current->audio.buf);
}
}
@@ -776,9 +719,8 @@ static void switch_to(struct camu_sink *sink, struct camu_sink_entry *target)
static void pause_and_swap_to(struct camu_sink *sink, struct camu_sink_entry *target, u64 at)
{
struct camu_sink_entry *current = sink->current;
-#ifdef CAMU_SINK_TRACE
- al_log_info("sink", "pause_and_swap_to(%llx, %llu), current: %llx", target, at, current);
-#endif
+ trace("pause_and_swap_to("ENTRY_FMT"), %llu), current: "ENTRY_FMT".",
+ ENTRY_ARG(target), at, ENTRY_ARG(current));
al_assert(target != current);
if (!current || current->ended) {
switch_to(sink, target);
@@ -792,20 +734,20 @@ static void pause_and_swap_to(struct camu_sink *sink, struct camu_sink_entry *ta
static bool end_entry_and_advance_queue(struct camu_sink *sink, struct camu_sink_entry *entry)
{
- al_log_info("sink", "Entry ended.");
+ info("Entry ("ENTRY_FMT") ended.", ENTRY_ARG(entry));
#ifdef CAMU_SINK_ONESHOT
sink->callback(sink->userdata, CAMU_SINK_MOCK_CLOSE, 0, NULL);
return false;
#endif
- entry->ended = true;
maybe_remove_from_previous(sink, entry);
+ entry->ended = true;
queue_cmd(sink, (struct camu_sink_cmd){
.op = END,
.value.u = entry->reset_id,
.opaque = entry
});
#ifdef LIANA_LIST_SCUFFED_LOOP
- al_log_info("sink", "Looping.");
+ info("Looping.");
return true;
#endif
if (sink->target) {
@@ -839,8 +781,8 @@ static void audio_buffer_callback(void *userdata, u8 op)
break;
case CAMU_BUFFER_PAUSED:
nn_mutex_lock(&sink->lock);
- if (!entry->audio.ignore_paused && entry->buffers_paused) {
- al_log_info("sink", "Audio buffer paused.");
+ if (!entry->audio.ignore_paused && entry->paused) {
+ info("Audio buffer paused.");
queue_cmd(sink, (struct camu_sink_cmd){
.op = STOP,
.value.i = CAMU_SINK_AUDIO
@@ -849,42 +791,33 @@ static void audio_buffer_callback(void *userdata, u8 op)
nn_mutex_unlock(&sink->lock);
break;
case CAMU_BUFFER_EOF: {
+ // @TODO: Sync around BUFFER_EOF is not well tested.
+ info("Audio EOF.");
nn_mutex_lock(&sink->lock);
- al_log_info("sink", "Audio EOF.");
- // @TODO: This is not well synced. EOF can happen at any time
- // while other stuff is happening in the sink. For example
- // while seeking, if EOF happens right after a REMOVE_BUFFERS,
- // we might assert during RECONNECTED because the entry is ended.
- //
- // state can be something other than BUFFER_ADDED here
- // because it could have changed while waiting on the lock above.
- if (entry->audio.state == BUFFER_ADDED) {
+ // Getting EOF on a buffer that isn't ADDED is very possible if the audio
+ // output is threaded. Even more if we had to wait on the lock above.
+ if (AUDIO_STATE(entry) == BUFFER_ADDED) {
remove_entry_audio_buffer(sink, entry);
}
// This assert likely doesn't matter due to the handling of the ENDED state.
- al_assert(entry->audio.state == BUFFER_SET_OR_BUFFERED);
- entry->audio.state = BUFFER_ENDED;
- bool end_entry = VIDEO_ENDED_OR_EMPTY(entry);
-#ifndef CAMU_SINK_NO_VIDEO
- end_entry = end_entry || VIDEO_IS_SINGLE_FRAME(entry);
-#endif
- if (end_entry) {
+ al_assert(AUDIO_STATE(entry) == BUFFER_SET_OR_BUFFERED);
+ AUDIO_STATE(entry) = BUFFER_ENDED;
+ if (VIDEO_ENDED_OR_EMPTY(entry) || VIDEO_IS_SINGLE_FRAME(entry)) {
end_entry_and_advance_queue(sink, entry);
}
nn_mutex_unlock(&sink->lock);
break;
}
case CAMU_BUFFER_ERRORED:
- al_log_error("sink", "Audio buffer errored.");
+ error("Audio buffer errored.");
queue_cmd(sink, (struct camu_sink_cmd){
- .op = ERROR_OUT,
+ .op = EJECT_ENTRY,
.opaque = entry
});
break;
}
}
-#ifndef CAMU_SINK_NO_VIDEO
static void video_buffer_callback(void *userdata, u8 op)
{
struct camu_sink_entry *entry = (struct camu_sink_entry *)userdata;
@@ -904,19 +837,19 @@ static void video_buffer_callback(void *userdata, u8 op)
lia_vcr_uncork(entry->video.track);
break;
case CAMU_BUFFER_EOF: {
+ info("Video EOF.");
bool swapped = false;
bool single_frame = VIDEO_IS_SINGLE_FRAME(entry);
nn_mutex_lock(&sink->lock);
- al_log_info("sink", "Video EOF.");
if (!AUDIO_EMPTY(entry)) {
camu_audio_buffer_set_no_video(&entry->audio.buf, true);
}
if (!single_frame) {
- if (entry->video.state == BUFFER_ADDED) {
+ if (VIDEO_STATE(entry) == BUFFER_ADDED) {
remove_entry_video_buffer(sink, entry);
}
- al_assert(entry->video.state == BUFFER_SET_OR_BUFFERED);
- entry->video.state = BUFFER_ENDED;
+ al_assert(VIDEO_STATE(entry) == BUFFER_SET_OR_BUFFERED);
+ VIDEO_STATE(entry) = BUFFER_ENDED;
if (AUDIO_ENDED_OR_EMPTY(entry)) {
swapped = end_entry_and_advance_queue(sink, entry);
}
@@ -931,36 +864,32 @@ static void video_buffer_callback(void *userdata, u8 op)
break;
}
case CAMU_BUFFER_ERRORED:
- al_log_error("sink", "Video buffer errored.");
+ error("Video buffer errored.");
queue_cmd(sink, (struct camu_sink_cmd){
- .op = ERROR_OUT,
+ .op = EJECT_ENTRY,
.opaque = entry
});
break;
}
}
-#endif
static void clock_callback(void *userdata, u8 op)
{
struct camu_sink_entry *entry = (struct camu_sink_entry *)userdata;
struct camu_sink *sink = entry->sink;
if (op == CAMU_CLOCK_PAUSED) {
-#ifdef CAMU_SINK_TRACE
- al_log_info("sink", "clock_callback(), target: 0x%llx", sink->target);
-#endif
nn_mutex_lock(&sink->lock);
+ trace("clock_callback(CAMU_CLOCK_PAUSED, "ENTRY_FMT"), target: "ENTRY_FMT".",
+ ENTRY_ARG(entry), ENTRY_ARG(sink->target));
if (entry == sink->current) {
if (sink->target) {
switch_to(sink, sink->target);
sink->target = NULL;
- } else if (entry->buffers_paused) {
-#ifndef CAMU_SINK_NO_VIDEO
+ } else if (entry->paused) {
queue_cmd(entry->sink, (struct camu_sink_cmd){
.op = STOP,
.value.i = CAMU_SINK_VIDEO
});
-#endif
}
}
nn_mutex_unlock(&sink->lock);
@@ -970,7 +899,6 @@ static void clock_callback(void *userdata, u8 op)
static void evaluate_and_set_buffer_params(struct camu_sink *sink, struct camu_sink_entry *entry)
{
#ifdef CAMU_SINK_LOCAL
-#ifndef CAMU_SINK_NO_VIDEO
bool ignore_video = VIDEO_EMPTY(entry) || VIDEO_IS_SINGLE_FRAME(entry);
if (!AUDIO_EMPTY(entry) && !ignore_video) {
f64 audio = camu_mixer_get_latency(sink->audio.mixer);
@@ -978,11 +906,6 @@ static void evaluate_and_set_buffer_params(struct camu_sink *sink, struct camu_s
frames -= sink->video.renderer->get_latency(sink->video.renderer);
camu_video_buffer_set_latency(&entry->video.buf, -frames);
}
-#else
- (void)sink;
- (void)entry;
- bool ignore_video = true;
-#endif
// 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);
camu_audio_buffer_set_no_video(&entry->audio.buf, ignore_video);
@@ -990,16 +913,12 @@ static void evaluate_and_set_buffer_params(struct camu_sink *sink, struct camu_s
// To sync clients with differing audio latencies our only option is to factor the mixer
// latency directly into the audio buffer.
f64 audio = camu_mixer_get_latency(sink->audio.mixer);
-#ifndef CAMU_SINK_NO_VIDEO
if (!VIDEO_EMPTY(entry)) {
s32 frames = audio / entry->video.buf.avg_frame_duration;
frames += sink->video.renderer->get_latency(sink->video.renderer);
camu_video_buffer_set_latency(&entry->video.buf, frames);
}
bool ignore_video = VIDEO_EMPTY(entry) || VIDEO_IS_SINGLE_FRAME(entry);
-#else
- bool ignore_video = true;
-#endif
camu_audio_buffer_set_latency(&entry->audio.buf, audio);
camu_audio_buffer_set_no_video(&entry->audio.buf, ignore_video);
#endif
@@ -1018,6 +937,18 @@ static void run_queue_by_opaque(struct camu_sink *sink, void *opaque)
camu_queue_unlock(sink->queue);
}
+static void remove_from_queue_by_opaque(struct camu_sink *sink, void *opaque)
+{
+ camu_queue_lock(sink->queue);
+ struct camu_sink_cmd *cmd;
+ al_array_foreach_ptr_rev(sink->queue.a, i, cmd) {
+ if (cmd->opaque == opaque) {
+ al_array_remove_at(sink->queue.a, i);
+ }
+ }
+ camu_queue_unlock(sink->queue);
+}
+
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;
@@ -1029,49 +960,45 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
case CAMU_STREAM_AUDIO:
entry->audio.track = (struct lia_vcr_track *)opaque;
if (!camu_audio_buffer_configure(&entry->audio.buf, stream, sink->audio.mixer)) {
- al_log_warn("sink", "Audio buffer failed to configure.");
+ error("Audio buffer failed to configure.");
maybe_disconnect_entry(entry);
return;
}
nn_mutex_lock(&sink->lock);
- if (entry->audio.state == BUFFER_QUEUED) {
- entry->audio.state = BUFFER_SET_OR_BUFFERED;
- } else if (entry->audio.state == BUFFER_INIT) {
- entry->audio.state = BUFFER_CONFIGURED;
- } else {
- al_assert(false);
+ al_assert(AUDIO_STATE(entry) == BUFFER_QUEUED || AUDIO_STATE(entry) == BUFFER_INIT);
+ if (AUDIO_STATE(entry) == BUFFER_QUEUED) {
+ AUDIO_STATE(entry) = BUFFER_SET_OR_BUFFERED;
+ } else if (AUDIO_STATE(entry) == BUFFER_INIT) {
+ AUDIO_STATE(entry) = BUFFER_CONFIGURED;
}
nn_mutex_unlock(&sink->lock);
break;
-#ifndef CAMU_SINK_NO_VIDEO
case CAMU_STREAM_VIDEO:
entry->video.track = (struct lia_vcr_track *)opaque;
if (!camu_video_buffer_configure(&entry->video.buf, stream, sink->video.renderer)) {
- al_log_warn("sink", "Video buffer failed to configure.");
+ error("Video buffer failed to configure.");
maybe_disconnect_entry(entry);
return;
}
nn_mutex_lock(&sink->lock);
- if (entry->video.state == BUFFER_QUEUED) {
- entry->video.state = BUFFER_SET_OR_BUFFERED;
- } else if (entry->video.state == BUFFER_INIT) {
- entry->video.state = BUFFER_CONFIGURED;
- } else {
- al_assert(false);
+ al_assert(VIDEO_STATE(entry) == BUFFER_QUEUED || VIDEO_STATE(entry) == BUFFER_INIT);
+ if (VIDEO_STATE(entry) == BUFFER_QUEUED) {
+ VIDEO_STATE(entry) = BUFFER_SET_OR_BUFFERED;
+ } else if (VIDEO_STATE(entry) == BUFFER_INIT) {
+ VIDEO_STATE(entry) = BUFFER_CONFIGURED;
}
nn_mutex_unlock(&sink->lock);
break;
case CAMU_STREAM_SUBTITLE:
if (!camu_video_buffer_configure_subtitles(&entry->video.buf, stream)) {
- al_log_warn("sink", "Video buffer failed to configure subtitles.");
+ warn("Video buffer failed to configure subtitles.");
}
break;
case CAMU_STREAM_ATTACHMENT: {
struct camu_renderer *renderer = sink->video.renderer;
- renderer->add_font(renderer, stream);
+ if (renderer) renderer->add_font(renderer, stream);
break;
}
-#endif
}
break;
}
@@ -1086,26 +1013,19 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
struct camu_codec_frame *frame = (struct camu_codec_frame *)opaque;
switch (stream->type) {
case CAMU_STREAM_AUDIO:
- if (!AUDIO_EMPTY(entry)) {
- camu_audio_buffer_push(&entry->audio.buf, frame);
- return;
- }
- break;
-#ifndef CAMU_SINK_NO_VIDEO
+ al_assert(!AUDIO_EMPTY(entry));
+ camu_audio_buffer_push(&entry->audio.buf, frame);
+ return;
case CAMU_STREAM_VIDEO:
- if (!VIDEO_EMPTY(entry)) {
- camu_video_buffer_push(&entry->video.buf, frame);
- return;
- }
- break;
-#endif
+ al_assert(!VIDEO_EMPTY(entry));
+ camu_video_buffer_push(&entry->video.buf, frame);
+ return;
default:
+ camu_codec_frame_discard(frame);
break;
}
- camu_codec_frame_discard(frame);
break;
}
-#ifndef CAMU_SINK_NO_VIDEO
case LIANA_CLIENT_SUBTITLE: {
switch (stream->type) {
case CAMU_STREAM_SUBTITLE: {
@@ -1115,24 +1035,16 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
}
break;
}
-#endif
case LIANA_CLIENT_REMOVE_BUFFERS: {
bool reconnect = *(bool *)opaque;
nn_mutex_lock(&sink->lock);
-#ifdef CAMU_SINK_TRACE
- al_log_info("sink", "remove_buffers(%s), entry == current: %s", BOOLSTR(reconnect), BOOLSTR(entry == sink->current));
-#endif
+ trace("remove_buffers("ENTRY_FMT", %s), entry == current: %s.",
+ ENTRY_ARG(entry), BOOLSTR(reconnect), BOOLSTR(entry == sink->current));
- if (reconnect) {
- if (entry == sink->current) {
- sink->reconnecting = entry;
- if (sink->target) {
- switch_to(sink, sink->target);
- sink->target = NULL;
- }
- }
+ if (reconnect && entry == sink->current) {
+ sink->reconnecting = entry;
}
// Entry might be in previous here if it was added to previous then,
@@ -1140,19 +1052,13 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
// - it was seeked.
remove_previous_if_contains(sink, entry);
- bool skip_audio = sink->audio.state == SINK_PAUSED;
- if (entry->audio.state == BUFFER_ADDED) {
+ if (AUDIO_STATE(entry) == BUFFER_ADDED) {
remove_entry_audio_buffer(sink, entry);
}
-
-#ifndef CAMU_SINK_NO_VIDEO
- bool ignore_video = VIDEO_EMPTY(entry) || (reconnect && VIDEO_IS_SINGLE_FRAME(entry));
- if (!ignore_video) {
- if (entry->video.state == BUFFER_ADDED) {
- remove_entry_video_buffer(sink, entry);
- }
+ bool ignore_video = reconnect && VIDEO_IS_SINGLE_FRAME(entry);
+ if (!ignore_video && VIDEO_STATE(entry) == BUFFER_ADDED) {
+ remove_entry_video_buffer(sink, entry);
}
-#endif
// Resolve any queued REMOVE_BUFFER requests before blocking.
// This is why we are safe to block the event loop thread even if
@@ -1161,13 +1067,9 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
nn_mutex_unlock(&sink->lock);
- while ( // Block until buffers are removed.
-#ifndef CAMU_SINK_NO_VIDEO
- (!skip_audio && entry_audio_buffer_held(entry)) || (!ignore_video && entry_video_buffer_held(entry))
-#else
- (!skip_audio && entry_audio_buffer_held(entry))
-#endif
- ) { BLOCKING_SLEEP(NNWT_TS_FROM_USEC(2000)); }
+ while (entry_audio_buffer_held(entry) || (!ignore_video && entry_video_buffer_held(entry))) {
+ BLOCKING_SLEEP(NNWT_TS_FROM_USEC(2000));
+ }
// Reset possible ENDED state here in case the entry ended at
// some point after unlocking to block above.
@@ -1175,28 +1077,20 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
if (reconnect && entry->ended) {
entry->ended = false;
- if (entry->audio.state == BUFFER_QUEUED) {
- entry->audio.state = BUFFER_INIT;
+ if (AUDIO_STATE(entry) == BUFFER_QUEUED) {
+ AUDIO_STATE(entry) = BUFFER_INIT;
}
-#ifndef CAMU_SINK_NO_VIDEO
- if (entry->video.state == BUFFER_QUEUED) {
- entry->video.state = BUFFER_INIT;
+ if (VIDEO_STATE(entry) == BUFFER_QUEUED) {
+ VIDEO_STATE(entry) = BUFFER_INIT;
}
-#endif
}
-
// SET_OR_BUFFERED, ADDED, or ENDED.
- if (entry->audio.state > BUFFER_CONFIGURED) {
- entry->audio.state = BUFFER_CONFIGURED;
+ if (AUDIO_STATE(entry) > BUFFER_CONFIGURED) {
+ AUDIO_STATE(entry) = BUFFER_CONFIGURED;
}
-
-#ifndef CAMU_SINK_NO_VIDEO
- if (!ignore_video) {
- if (entry->video.state > BUFFER_CONFIGURED) {
- entry->video.state = BUFFER_CONFIGURED;
- }
+ if (!ignore_video && VIDEO_STATE(entry) > BUFFER_CONFIGURED) {
+ VIDEO_STATE(entry) = BUFFER_CONFIGURED;
}
-#endif
nn_mutex_unlock(&sink->lock);
@@ -1204,52 +1098,51 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
if (!AUDIO_EMPTY(entry)) {
camu_audio_buffer_reset(&entry->audio.buf);
}
-#ifndef CAMU_SINK_NO_VIDEO
if (!VIDEO_EMPTY(entry) && !ignore_video) {
camu_video_buffer_reset(&entry->video.buf);
}
-#endif
}
break;
}
case LIANA_CLIENT_RESUME_AT: {
struct lia_timing *time = (struct lia_timing *)opaque;
-#ifdef CAMU_SINK_TRACE
- al_log_info("sink", "resume_at(%llu, %llu)", time->seek_pos, time->at);
-#endif
+ trace("resume_at("ENTRY_FMT"), %llu, %llu), paused_at: %f.",
+ ENTRY_ARG(entry), time->seek_pos, time->at, entry->clock.paused_at);
nn_mutex_lock(&sink->lock);
-#if defined LIANA_LIST_SCUFFED_LOOP && !defined CAMU_SINK_NO_VIDEO
- struct camu_video_buffer *buf = &entry->video.buf;
- if (time->seek_pos == 0 && buf->last_pts >= 0.0) {
- camu_clock_loop(&entry->clock, buf->last_pts);
+#if defined LIANA_LIST_SCUFFED_LOOP
+ if (time->seek_pos == 0) {
+ camu_clock_loop(&entry->clock, camu_clock_get_last_pts(&entry->clock));
} else {
+#endif
camu_clock_seek(&entry->clock, time->seek_pos / 1000000.0, time->at);
+#if defined LIANA_LIST_SCUFFED_LOOP
}
-#else
- camu_clock_seek(&entry->clock, time->seek_pos / 1000000.0, time->at);
#endif
nn_mutex_unlock(&sink->lock);
break;
}
case LIANA_CLIENT_RECONNECTED: {
+ bool unconfigured = *(bool *)opaque;
nn_mutex_lock(&sink->lock);
+ trace("reconnected("ENTRY_FMT"), reconnecting: "ENTRY_FMT".",
+ ENTRY_ARG(entry), ENTRY_ARG(sink->reconnecting));
if (entry == sink->reconnecting) {
al_assert(entry == sink->current);
- if (entry->audio.state > BUFFER_QUEUED) {
+ if (AUDIO_STATE(entry) > BUFFER_QUEUED) {
+ al_assert(!unconfigured);
add_audio_if_set_and_buffered(entry);
} else {
- entry->audio.state = BUFFER_QUEUED;
+ AUDIO_STATE(entry) = BUFFER_QUEUED;
}
-#ifndef CAMU_SINK_NO_VIDEO
- if (entry->video.state > BUFFER_QUEUED) {
+ if (VIDEO_STATE(entry) > BUFFER_QUEUED) {
+ al_assert(!unconfigured);
if (!VIDEO_IS_SINGLE_FRAME(entry)) {
add_video_if_set_and_buffered(entry);
}
} else {
- entry->video.state = BUFFER_QUEUED;
+ VIDEO_STATE(entry) = BUFFER_QUEUED;
}
-#endif
sink->reconnecting = NULL;
}
nn_mutex_unlock(&sink->lock);
@@ -1263,7 +1156,6 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
}
break;
}
-#ifndef CAMU_SINK_NO_VIDEO
case CAMU_STREAM_VIDEO: {
// Single frames are immediately flushed inside the buffer.
if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry)) {
@@ -1271,7 +1163,6 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
}
break;
}
-#endif
}
break;
}
@@ -1279,15 +1170,20 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
// LIANA_CLIENT_REMOVE_BUFFERS has been called on this entry before we're here.
nn_mutex_lock(&sink->lock);
+ if (VIDEO_STATE(entry) == BUFFER_ADDED) {
+ al_assert(VIDEO_IS_SINGLE_FRAME(entry));
+ remove_entry_video_buffer(sink, entry);
+ while (entry_video_buffer_held(entry)) { BLOCKING_SLEEP(NNWT_TS_FROM_USEC(2000)); }
+ }
+
+ // @TODO: Mark for removal here instead.
bool removed;
al_array_remove_checked(sink->entries, entry, removed);
+ remove_from_queue_by_opaque(sink, entry);
if (entry == sink->target) {
- // @TODO: current-less
- // current could be paused and targeting this entry.
- // That could cause an error in the logic of set_command_callback()
- // where we check if target (prev_target) is set.
- sink->target = NULL;
+ sink->target = (struct camu_sink_entry *)0xb00b;
+ warn("Attempting to handle a disconnected target.");
} else if (entry == sink->current) {
if (sink->reconnecting) {
al_assert(sink->reconnecting == sink->current);
@@ -1297,16 +1193,13 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
switch_to(sink, sink->target);
sink->target = NULL;
} else {
- // @TODO: current-less
sink->current = NULL;
-#ifndef CAMU_SINK_NO_VIDEO
if (removed) { // Don't stop video on exit.
queue_cmd(entry->sink, (struct camu_sink_cmd){
.op = STOP,
.value.i = CAMU_SINK_VIDEO
});
}
-#endif
}
// If current was never fully added we need to call this here.
maybe_remove_previous(sink);
@@ -1318,10 +1211,8 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
lia_client_free(&entry->client);
camu_audio_buffer_free(&entry->audio.buf);
-#ifndef CAMU_SINK_NO_VIDEO
camu_video_buffer_free(&entry->video.buf);
-#endif
- al_log_info("sink", "Entry (0x%llx) closed by %s.", entry, removed ? "cleanup" : "exit");
+ info("Entry ("ENTRY_FMT") closed by %s.", ENTRY_ARG(entry), removed ? "error" : "cleanup");
al_free(entry);
break;
@@ -1338,21 +1229,20 @@ static struct camu_sink_entry *create_entry(struct camu_sink *sink, u32 id)
entry->ended = false;
camu_clock_init(&entry->clock, clock_callback, entry);
- entry->buffers_paused = false;
+ entry->paused = false;
- entry->audio.state = BUFFER_INIT;
+ AUDIO_STATE(entry) = BUFFER_INIT;
entry->audio.ignore_paused = false;
camu_audio_buffer_init(&entry->audio.buf, &entry->clock);
entry->audio.buf.callback = audio_buffer_callback;
entry->audio.buf.userdata = entry;
-#ifndef CAMU_SINK_NO_VIDEO
- entry->video.state = BUFFER_INIT;
+ VIDEO_STATE(entry) = BUFFER_INIT;
camu_video_buffer_init(&entry->video.buf, &entry->clock);
entry->video.buf.callback = video_buffer_callback;
entry->video.buf.userdata = entry;
-#endif
+ entry->client.renderer = sink->video.renderer;
entry->client.callback = client_callback;
entry->client.userdata = entry;
entry->client.prefs = sink->prefs;
@@ -1377,14 +1267,18 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn,
struct nn_packet *packet, struct nn_packet *rpacket)
{
struct camu_sink *sink = (struct camu_sink *)userdata;
- (void)conn;
(void)rpacket;
nn_mutex_lock(&sink->lock);
u8 op = nn_packet_read_u8(packet);
if (op == LIANA_SINK_UNSET) {
- // @TODO: current-less
+ if (sink->current) {
+ queue_cmd(sink, (struct camu_sink_cmd){
+ .op = EJECT_ENTRY,
+ .opaque = sink->current
+ });
+ }
goto out;
}
@@ -1403,32 +1297,34 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn,
bool ended = nn_packet_read_bool(packet);
u32 reset_id = nn_packet_read_u32(packet);
- bool created = false;
+ struct camu_sink_entry *current = sink->current;
+ struct camu_sink_entry *prev_target = sink->target;
struct camu_sink_entry *entry = get_entry_from_id(sink, id);
- if (!entry) {
+ bool create = !entry;
+ if (create) {
entry = create_entry(sink, id);
- created = true;
}
entry->sequence = sequence;
sink->lru = al_u16_inc_wrap(sink->lru);
entry->lru = sink->lru;
entry->reset_id = reset_id;
- if (created) {
+ if (create) {
camu_clock_set(&entry->clock, seek_pos / 1000000.0);
- // ended here does not map to entry->ended, we use it as a hint.
+ // ended here does not map to entry->ended, it means the server expects the entry to be ended.
if (!ended) {
- entry->buffers_paused = pause == LIANA_PAUSE_NONE || pause == LIANA_PAUSE_PAUSE;
+ entry->paused = pause == LIANA_PAUSE_NONE || pause == LIANA_PAUSE_PAUSE;
} else {
+#ifndef CAMU_SINK_LOCAL
camu_clock_resume(&entry->clock, 0);
- }
- struct camu_renderer *renderer = NULL;
-#ifndef CAMU_SINK_NO_VIDEO
- renderer = sink->video.renderer;
#endif
- lia_client_connect(&entry->client, sink->loop, sink->type, &addr, port, node_id, seek_pos, renderer);
+ }
+ lia_client_connect(&entry->client, sink->loop, sink->type, &addr, port, node_id, seek_pos);
}
+ trace("set("ENTRY_FMT", %llu), created: %s, pause: %hhu, current: "ENTRY_FMT", target: "ENTRY_FMT".",
+ ENTRY_ARG(entry), at, BOOLSTR(create), pause, ENTRY_ARG(current), ENTRY_ARG(prev_target));
+
if (op == LIANA_SINK_BUFFER) {
goto out;
} else if (op == LIANA_SINK_BUFFER_AND_QUEUE) {
@@ -1443,28 +1339,26 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn,
goto out;
}
- struct camu_sink_entry *current = sink->current;
-
#ifdef CAMU_SINK_LOCAL
- (void)at;
- if (current && !current->ended && !camu_clock_is_paused(&current->clock)) {
+ al_assert(entry != current);
+ if (current && !current->ended) {
current->audio.ignore_paused = true;
- camu_clock_pause(&current->clock, 0);
+ if (!current->paused) {
+ camu_clock_pause(&current->clock, 0);
+ }
}
-
- if (!entry->ended && camu_clock_is_paused(&entry->clock)) {
- // This will resume user-paused entries, but whatever.
- entry->buffers_paused = false;
+ if (!entry->ended) {
entry->audio.ignore_paused = false;
- camu_clock_resume(&entry->clock, 0);
+ if (!entry->paused) {
+ camu_clock_resume(&entry->clock, 0);
+ }
}
-
- al_assert(entry != current);
switch_to(sink, entry);
#else
+ // For target to be set that must mean current is set, armed to pause, and not ended.
switch (pause) {
- case LIANA_PAUSE_NONE: {
- if (sink->target) {
+ case LIANA_PAUSE_NONE:
+ if (prev_target) {
sink->target = NULL;
} else {
al_assert(entry != current);
@@ -1473,9 +1367,8 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn,
switch_to(sink, entry);
}
break;
- }
- case LIANA_PAUSE_RESUME: {
- if (sink->target) {
+ case LIANA_PAUSE_RESUME:
+ if (prev_target) {
sink->target = NULL;
} else {
al_assert(entry != current);
@@ -1487,30 +1380,28 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn,
}
camu_clock_resume(&entry->clock, at);
break;
- }
- case LIANA_PAUSE_PAUSE: {
- struct camu_sink_entry *prev_target = sink->target;
+ case LIANA_PAUSE_PAUSE:
if (prev_target) {
- // For target to be set that must mean that current is
- // set, armed to pause, and not ended.
- al_assert(current && !current->ended);
+ al_assert(current);
+ al_assert(!current->ended);
al_assert(prev_target != entry);
if (current != entry) {
sink->target = entry;
} else {
sink->target = NULL;
}
- camu_clock_pause(&prev_target->clock, at);
+ if (prev_target != (struct camu_sink_entry *)0xb00b) {
+ camu_clock_pause(&prev_target->clock, at);
+ }
} else {
al_assert(current != entry);
pause_and_swap_to(sink, entry, at);
}
break;
- }
- case LIANA_PAUSE_BOTH: {
- struct camu_sink_entry *prev_target = sink->target;
+ case LIANA_PAUSE_BOTH:
if (prev_target) {
- al_assert(current && !current->ended);
+ al_assert(current);
+ al_assert(!current->ended);
al_assert(prev_target != entry);
if (current != entry) {
sink->target = entry;
@@ -1518,7 +1409,9 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn,
sink->target = NULL;
camu_audio_buffer_resync(&entry->audio.buf);
}
- camu_clock_pause(&prev_target->clock, at);
+ if (prev_target != (struct camu_sink_entry *)0xb00b) {
+ camu_clock_pause(&prev_target->clock, at);
+ }
} else {
al_assert(current != entry);
pause_and_swap_to(sink, entry, at);
@@ -1526,7 +1419,6 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn,
camu_clock_resume(&entry->clock, at);
break;
}
- }
#endif
out:
@@ -1544,7 +1436,6 @@ static bool pause_command_callback(void *userdata, struct nn_rpc_connection *con
struct nn_packet *packet, struct nn_packet *rpacket)
{
struct camu_sink *sink = (struct camu_sink *)userdata;
- (void)conn;
(void)rpacket;
u32 id = nn_packet_read_u32(packet);
@@ -1557,20 +1448,22 @@ static bool pause_command_callback(void *userdata, struct nn_rpc_connection *con
al_assert(entry->sequence == sequence);
nn_mutex_lock(&sink->lock);
+
+ trace("pause("ENTRY_FMT", %llu), pause: %hhu, audio_state: %hhu, video_state: %hhu.",
+ ENTRY_ARG(entry), at, pause, AUDIO_STATE(entry), VIDEO_STATE(entry));
+
#ifdef CAMU_SINK_LOCAL
- (void)at;
- (void)pause;
sink_local_pause(sink, entry);
#else
switch (pause) {
case LIANA_PAUSE_PAUSE:
- entry->buffers_paused = true;
+ entry->paused = true;
camu_clock_pause(&entry->clock, at);
- // Audio will be stopped in a BUFFER_PAUSED callback and video
- // will be stopped in clock_callback().
+ // Audio will be stopped in a BUFFER_PAUSED callback.
+ // Video will be stopped in a CLOCK_PAUSED callback.
break;
case LIANA_PAUSE_RESUME:
- entry->buffers_paused = false;
+ entry->paused = false;
camu_clock_resume(&entry->clock, at);
if (!AUDIO_EMPTY(entry)) {
camu_audio_buffer_resync(&entry->audio.buf);
@@ -1579,17 +1472,16 @@ static bool pause_command_callback(void *userdata, struct nn_rpc_connection *con
.value.i = CAMU_SINK_AUDIO
});
}
-#ifndef CAMU_SINK_NO_VIDEO
if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry)) {
queue_cmd(entry->sink, (struct camu_sink_cmd){
.op = START,
.value.i = CAMU_SINK_VIDEO
});
}
-#endif
break;
}
#endif
+
nn_mutex_unlock(&sink->lock);
out:
@@ -1602,7 +1494,6 @@ static bool seek_command_callback(void *userdata, struct nn_rpc_connection *conn
struct nn_packet *packet, struct nn_packet *rpacket)
{
struct camu_sink *sink = (struct camu_sink *)userdata;
- (void)conn;
(void)rpacket;
u32 id = nn_packet_read_u32(packet);
@@ -1616,10 +1507,12 @@ static bool seek_command_callback(void *userdata, struct nn_rpc_connection *conn
if (!entry) goto out;
entry->reset_id = reset_id;
+ trace("seek("ENTRY_FMT"), reset_id: %u.", ENTRY_ARG(entry), reset_id);
+
#ifdef CAMU_SINK_LOCAL
at = 0;
#endif
- // The rest of the seek is handled in LIANA_CLIENT_REMOVE_BUFFERS.
+ // The rest of the seek is handled in client_callback()'s.
lia_client_seek(&entry->client, pos, at);
out:
@@ -1634,25 +1527,30 @@ static struct nn_rpc_command commands[] = {
{ .op = CAMU_SINK_SEEK, .callback = seek_command_callback, .userdata = NULL }
};
-static void idd_callback(void *userdata, struct nn_rpc_connection *conn, struct nn_packet *packet)
+static void identify_callback(void *userdata, struct nn_rpc_connection *conn, struct nn_packet *packet)
{
struct camu_sink *sink = (struct camu_sink *)userdata;
(void)sink;
nn_packet_stream_return_packet(conn->stream, packet);
}
-static void connection_callback(void *userdata, struct nn_rpc_connection *conn)
+static void identify_on_connection(struct camu_sink *sink, struct nn_rpc_connection *conn)
{
- struct camu_sink *sink = (struct camu_sink *)userdata;
sink->conn = conn;
- nn_timer_stop(&sink->periodic_timer);
struct nn_packet *packet = nn_rpc_get_packet(&sink->client, CAMU_SERVER_IDENTIFY);
nn_packet_write_u8(packet, CAMU_SINK);
nn_packet_write_str(packet, &sink->name);
- nn_rpc_connection_command(sink->conn, packet, idd_callback, sink);
+ nn_rpc_connection_command(sink->conn, packet, identify_callback, sink);
}
-static void periodic_timer_callback(void *userdata, struct nn_timer *timer)
+static void connection_callback(void *userdata, struct nn_rpc_connection *conn)
+{
+ struct camu_sink *sink = (struct camu_sink *)userdata;
+ identify_on_connection(sink, conn);
+ nn_timer_stop(&sink->reconnect_timer);
+}
+
+static void reconnect_timer_callback(void *userdata, struct nn_timer *timer)
{
struct camu_sink *sink = (struct camu_sink *)userdata;
(void)timer;
@@ -1666,17 +1564,15 @@ static void connection_closed_callback(void *userdata, struct nn_rpc_connection
al_assert(sink->conn == conn);
sink->conn = NULL;
}
- nn_timer_again(&sink->periodic_timer);
+ nn_timer_again(&sink->reconnect_timer);
}
-bool camu_sink_connect(struct camu_sink *sink, u8 type, str *addr, u16 port, str *name)
+bool camu_sink_connect(struct camu_sink *sink, str *name, u8 type, str *addr, u16 port)
{
al_str_clone(&sink->name, name);
sink->type = type;
al_str_clone(&sink->addr, addr);
sink->port = port;
- nn_timer_init(&sink->periodic_timer, sink->loop, periodic_timer_callback, sink);
- nn_timer_set_repeat(&sink->periodic_timer, NNWT_TS_FROM_USEC(1000000));
nn_rpc_init(&sink->client, sink->loop, connection_callback, connection_closed_callback, sink);
for (u32 i = 0; i < ARRAY_SIZE(commands); i++) {
commands[i].userdata = sink;
@@ -1684,8 +1580,10 @@ bool camu_sink_connect(struct camu_sink *sink, u8 type, str *addr, u16 port, str
nn_rpc_add_command(&sink->client, &commands[i]);
}
nn_rpc_prepare_client(&sink->client);
- // Directly set conn here to handle never connected case.
+ // This client may never connect, make sure conn is still set in that case.
sink->conn = sink->client.conn;
+ nn_timer_init(&sink->reconnect_timer, sink->loop, reconnect_timer_callback, sink);
+ nn_timer_set_repeat(&sink->reconnect_timer, NNWT_TS_FROM_USEC(1000000));
#ifdef CAMU_DIRECT_MODE
struct nn_rpc_connection *conn = sink->client.conn;
nn_multiplex_direct_connect(conn->stream, CAMU_MULTIPLEX_RPC);
@@ -1714,13 +1612,6 @@ void camu_sink_skip(struct camu_sink *sink, s32 n)
});
}
-void camu_sink_shuffle(struct camu_sink *sink)
-{
- queue_cmd(sink, (struct camu_sink_cmd){
- .op = SHUFFLE
- });
-}
-
void camu_sink_toggle_pause(struct camu_sink *sink)
{
nn_mutex_lock(&sink->lock);
@@ -1735,43 +1626,37 @@ void camu_sink_toggle_pause(struct camu_sink *sink)
}
}
-void camu_sink_seek(struct camu_sink *sink, f64 precent)
+void camu_sink_seek(struct camu_sink *sink, void *value, u8 mode)
{
nn_mutex_lock(&sink->lock);
struct camu_sink_entry *current = sink->current;
+ u64 duration;
+ if (current) duration = current->client.duration;
+ f64 pts = camu_clock_get_last_pts(&current->clock);
nn_mutex_unlock(&sink->lock);
- queue_cmd(sink, (struct camu_sink_cmd){
+ if (!current || duration == 0) return;
+ struct camu_sink_cmd cmd = {
.op = SEEK,
- .value.f = precent,
.opaque = current
- });
-}
-
-// @TODO: We need at standard way to get the entries pts even when paused.
-static f64 tmp_get_entry_pts(struct camu_sink_entry *entry)
-{
- f64 pts = camu_clock_get_pts(&entry->clock, 0.0, false);
- if (pts == -1.0) {
-#ifndef CAMU_SINK_NO_VIDEO
- pts = al_atomic_load(f64)(&entry->video.buf.pts, AL_ATOMIC_RELAXED);
-#endif
+ };
+ switch (mode) {
+ case CAMU_SEEK_POS: {
+ u64 pos = *(u64 *)value;
+ cmd.value.u = pos;
+ break;
}
- return pts;
-}
-
-void camu_sink_relative_seek(struct camu_sink *sink, f64 offset)
-{
- nn_mutex_lock(&sink->lock);
- struct camu_sink_entry *current = sink->current;
- nn_mutex_unlock(&sink->lock);
- if (!current) return;
- f64 pts = tmp_get_entry_pts(current) + offset;
- f64 duration = current->client.duration / 1000000.0;
- queue_cmd(sink, (struct camu_sink_cmd){
- .op = SEEK,
- .value.f = pts / duration,
- .opaque = current
- });
+ case CAMU_SEEK_RELATIVE: {
+ f64 offset = *(f64 *)value;
+ cmd.value.u = (u64)((pts + offset) * 1000000);
+ break;
+ }
+ case CAMU_SEEK_PERCENT: {
+ f64 percent = *(f64 *)value;
+ cmd.value.u = (u64)(duration * percent);
+ break;
+ }
+ }
+ queue_cmd(sink, cmd);
}
void camu_sink_reseek(struct camu_sink *sink)
@@ -1779,66 +1664,21 @@ void camu_sink_reseek(struct camu_sink *sink)
nn_mutex_lock(&sink->lock);
struct camu_sink_entry *current = sink->current;
nn_mutex_unlock(&sink->lock);
- if (!current) return;
- queue_cmd(sink, (struct camu_sink_cmd){
- .op = RESEEK,
- .opaque = current
- });
+ if (current) {
+ queue_cmd(sink, (struct camu_sink_cmd){
+ .op = RESEEK,
+ .opaque = current
+ });
+ }
}
-void camu_sink_unset(struct camu_sink *sink)
+void camu_sink_shuffle(struct camu_sink *sink)
{
queue_cmd(sink, (struct camu_sink_cmd){
- .op = UNSET
+ .op = SHUFFLE
});
}
-void camu_sink_set_volume(struct camu_sink *sink, f32 volume)
-{
- camu_mixer_set_volume(sink->audio.mixer, volume);
-}
-
-void camu_sink_offset_volume(struct camu_sink *sink, f32 amount)
-{
- camu_mixer_offset_volume(sink->audio.mixer, amount);
-}
-
-static char status[128];
-
-void camu_sink_status(struct camu_sink *sink)
-{
- nn_mutex_lock(&sink->lock);
- struct camu_sink_entry *current = sink->current;
- nn_mutex_unlock(&sink->lock);
- if (!current) {
- al_log_info("sink", "Nothing playing.");
- return;
- }
- f64 pts = tmp_get_entry_pts(current);
- f64 duration = current->client.duration / 1000000.0;
- s32 text = 0;
- u32 minute = (u32)(pts / 60);
- u32 hour = minute / 60;
- minute -= hour * 60;
- if (camu_clock_is_paused(&current->clock)) {
- text += al_snprintf(status + text, sizeof(status) - text, "⏸ ");
- } else {
- text += al_snprintf(status + text, sizeof(status) - text, "⏵ ");
- }
- text += al_snprintf(status + text, sizeof(status) - text, "[");
- bool show_hour = duration >= 60.0 * 60.0;
- if (show_hour) text += al_snprintf(status + text, sizeof(status) - text, "%.2d:", hour);
- text += al_snprintf(status + text, sizeof(status) - text, "%.2d:%.2d/", minute, (u32)pts % 60);
- minute = (u32)(duration / 60);
- hour = minute / 60;
- minute -= hour * 60;
- if (show_hour) text += al_snprintf(status + text, sizeof(status) - text, "%.2d:", hour);
- text += al_snprintf(status + text, sizeof(status) - text, "%.2d:%.2d", minute, (u32)duration % 60);
- text += al_snprintf(status + text, sizeof(status) - text, "]");
- status[text] = '\0';
- al_log_info("sink", "%s", status);
-}
-
void camu_sink_stop(struct camu_sink *sink)
{
queue_cmd(sink, (struct camu_sink_cmd){
@@ -1856,8 +1696,8 @@ void camu_sink_stop(struct camu_sink *sink)
void camu_sink_close(struct camu_sink *sink)
{
- nn_timer_stop(&sink->periodic_timer);
- nn_timer_disable(&sink->periodic_timer);
+ nn_timer_stop(&sink->reconnect_timer);
+ nn_timer_disable(&sink->reconnect_timer);
if (sink->conn) nn_rpc_conn_disconnect(sink->conn);
struct camu_sink_entry *entry;
al_array_foreach_rev(sink->entries, i, entry) {
@@ -1868,10 +1708,7 @@ void camu_sink_close(struct camu_sink *sink)
void camu_sink_free(struct camu_sink *sink)
{
- struct camu_sink_entry *entry;
- al_array_foreach(sink->entries, i, entry) {
- al_free(entry);
- }
+ al_assert(sink->entries.count == 0);
al_array_free(sink->entries);
nn_rpc_free(&sink->client);
camu_queue_free(sink->queue);
diff --git a/src/libsink/sink.h b/src/libsink/sink.h
index ae8e2e4..32fdc26 100644
--- a/src/libsink/sink.h
+++ b/src/libsink/sink.h
@@ -12,15 +12,15 @@
#include "../buffer/audio.h"
#ifndef CAMU_SINK_NO_VIDEO
#include "../buffer/video.h"
+#else
+#include "../buffer/video_null.h"
#endif
#include "../liana/client.h"
enum {
CAMU_SINK_AUDIO = 0,
-#ifndef CAMU_SINK_NO_VIDEO
CAMU_SINK_VIDEO
-#endif
};
enum {
@@ -38,6 +38,12 @@ enum {
CAMU_SINK_OK = 0
};
+enum {
+ CAMU_SEEK_POS = 0,
+ CAMU_SEEK_RELATIVE,
+ CAMU_SEEK_PERCENT
+};
+
struct camu_sink_entry {
u32 id;
struct lia_client client;
@@ -47,7 +53,7 @@ struct camu_sink_entry {
bool ended;
u32 reset_id;
struct camu_clock clock;
- bool buffers_paused;
+ bool paused;
struct {
u8 state;
// Don't stop the audio output when this entry is paused.
@@ -55,13 +61,11 @@ struct camu_sink_entry {
struct camu_audio_buffer buf;
struct lia_vcr_track *track;
} audio;
-#ifndef CAMU_SINK_NO_VIDEO
struct {
u8 state;
struct camu_video_buffer buf;
struct lia_vcr_track *track;
} video;
-#endif
struct camu_sink *sink;
};
@@ -80,7 +84,7 @@ struct camu_sink {
struct nn_rpc client;
struct nn_rpc_connection *conn;
struct nn_mutex lock;
- struct nn_timer periodic_timer;
+ struct nn_timer reconnect_timer;
struct nn_signal queue_signal;
queue(struct camu_sink_cmd) queue;
str default_list;
@@ -95,12 +99,10 @@ struct camu_sink {
u8 state;
struct camu_mixer *mixer;
} audio;
-#ifndef CAMU_SINK_NO_VIDEO
struct {
u8 state;
struct camu_renderer *renderer;
} video;
-#endif
struct lia_prefs prefs;
struct lia_server *local_server;
u8 (*callback)(void *, u8, u8, void *);
@@ -108,24 +110,15 @@ struct camu_sink {
};
bool camu_sink_init(struct camu_sink *sink, struct nn_event_loop *loop,
- struct camu_mixer *mixer
-#ifndef CAMU_SINK_NO_VIDEO
- , struct camu_renderer *renderer
-#endif
- );
-bool camu_sink_connect(struct camu_sink *sink, u8 type, str *addr, u16 port, str *name);
+ struct camu_mixer *mixer, struct camu_renderer *renderer);
+bool camu_sink_connect(struct camu_sink *sink, str *name, u8 type, str *addr, u16 port);
struct camu_sink_entry *camu_sink_get_current(struct camu_sink *sink);
void camu_sink_return_current(struct camu_sink *sink);
void camu_sink_skip(struct camu_sink *sink, s32 n);
-void camu_sink_shuffle(struct camu_sink *sink);
void camu_sink_toggle_pause(struct camu_sink *sink);
-void camu_sink_seek(struct camu_sink *sink, f64 pos);
-void camu_sink_relative_seek(struct camu_sink *sink, f64 offset);
+void camu_sink_seek(struct camu_sink *sink, void *value, u8 mode);
void camu_sink_reseek(struct camu_sink *sink);
-void camu_sink_unset(struct camu_sink *sink);
-void camu_sink_set_volume(struct camu_sink *sink, f32 volume);
-void camu_sink_offset_volume(struct camu_sink *sink, f32 amount);
-void camu_sink_status(struct camu_sink *sink);
+void camu_sink_shuffle(struct camu_sink *sink);
void camu_sink_stop(struct camu_sink *sink);
void camu_sink_close(struct camu_sink *sink);
void camu_sink_free(struct camu_sink *sink);
diff --git a/src/mixer/mixer.h b/src/mixer/mixer.h
index 8c14ea1..7f443ea 100644
--- a/src/mixer/mixer.h
+++ b/src/mixer/mixer.h
@@ -10,10 +10,10 @@
#include "../codec/codec.h"
#ifdef CAMU_MIXER_THREADED
-// Do we need to lock in order to synchronize the mixers paused state.
-// Disabling this is a very specific optimization to allow the audio device to
-// buffer data during start(). It requires pause(), resume(), and remove_buffer()
-// to all come from the same thread.
+// Do we need a lock in order to synchronize the mixers paused state.
+// Disabling this is a very specific optimization to allow the audio device to buffer
+// data during start(). It requires pause(), resume(), and remove_buffer() to all
+// come from the same thread.
//#define CAMU_MIXER_THREADED_START_STOP
#endif
diff --git a/src/render/renderer_libplacebo.c b/src/render/renderer_libplacebo.c
index a3a3109..9fbcfc0 100644
--- a/src/render/renderer_libplacebo.c
+++ b/src/render/renderer_libplacebo.c
@@ -314,7 +314,8 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree
// Add in last_pts to account for buffer resets. This is a completely
// broken "hash" of the f64 pts value but hopefully works for now.
// https://www.virtualdub.org/blog2/entry_259.html
- lr->params.info_priv = video->buf + (intptr_t)*((u64 *)&video->buf->last_pts);
+ f64 last_pts = camu_clock_get_last_pts(video->buf->clock);
+ lr->params.info_priv = video->buf + (intptr_t)*((u64 *)&last_pts);
target->crop = mix.frames[0]->crop;
target->crop.x1 *= video->view.zoom / video->view.stretch;
target->crop.y1 *= video->view.zoom * video->view.stretch;
diff --git a/src/screen/screen.c b/src/screen/screen.c
index d0926da..ed4345d 100644
--- a/src/screen/screen.c
+++ b/src/screen/screen.c
@@ -5,28 +5,36 @@
#include "view.h"
#include "screen.h"
+#define SCREEN_MOD1(scr) ((scr)->flags & CAMU_SCREEN_MOD_CONTROL)
+#define SCREEN_MOD2(scr) ((scr)->flags & CAMU_SCREEN_MOD_SHIFT)
+#define SCREEN_IS_DRAGGING(scr) ((scr)->flags & CAMU_SCREEN_DRAGGING)
+#define SCREEN_ZOOM_MODE(scr, mode) ((scr)->flags & mode)
#define SCREEN_INVALID_TS ((u64)-1)
+#define SCREEN_LAST_CLICK_WITHIN(ns) \
+ (((scr)->last_click_ts != SCREEN_INVALID_TS) && (nn_get_timestamp() - (scr)->last_click_ts <= ns))
-static struct camu_view *get_view_from_mouse_pos(struct camu_screen *scr)
+static void should_close_callback(void *userdata)
{
- struct camu_view *view = NULL;
- struct camu_screen_video *video;
- al_array_foreach_ptr(scr->videos, i, video) {
- if (!view) {
- view = &video->view;
- continue;
- }
- if (view->zindex < video->view.zindex) {
- view = &video->view;
- }
+ struct camu_screen *scr = (struct camu_screen *)userdata;
+ scr->callback(scr->userdata, CAMU_SCREEN_CLOSE, NULL);
+}
+
+static void render_callback(void *userdata)
+{
+ struct camu_screen *scr = (struct camu_screen *)userdata;
+ bool force;
+ if (scr->renderer && camu_screen_tick(scr, &force)) {
+ scr->renderer->render(scr->renderer, scr, force);
}
- return view;
}
-static void should_close_callback(void *userdata)
+static void refresh_callback(void *userdata)
{
struct camu_screen *scr = (struct camu_screen *)userdata;
- scr->callback(scr->userdata, CAMU_SCREEN_CLOSE, NULL);
+ if (scr->renderer) {
+ // The thread-safety of this is questionable.
+ scr->renderer->render(scr->renderer, scr, true);
+ }
}
static void do_resize(struct camu_screen *scr, u32 width, u32 height)
@@ -51,29 +59,30 @@ static void resize_callback(void *userdata, u32 width, u32 height)
do_resize(scr, width, height);
}
-static void refresh_callback(void *userdata)
+static struct camu_view *get_view_from_mouse_pos(struct camu_screen *scr)
{
- struct camu_screen *scr = (struct camu_screen *)userdata;
- if (scr->renderer) {
- // The thread-safety of this is questionable.
- scr->renderer->render(scr->renderer, scr, true);
+ struct camu_view *view = NULL;
+ struct camu_screen_video *video;
+ al_array_foreach_ptr(scr->videos, i, video) {
+ if (!view) {
+ view = &video->view;
+ continue;
+ }
+ if (view->zindex < video->view.zindex) {
+ view = &video->view;
+ }
}
+ return view;
}
-static void seek_to_percent_at_pointer(struct camu_screen *scr)
-{
- f64 percent = scr->last_mouse_x / scr->width;
- percent = CLAMP(percent, 0.0, 100.0);
- scr->callback(scr->userdata, CAMU_SCREEN_SEEK, &percent);
-}
static bool pointer_pos_callback(void *userdata, f64 x, f64 y)
{
struct camu_screen *scr = (struct camu_screen *)userdata;
struct camu_view *view = get_view_from_mouse_pos(scr);
bool queue_refresh = false;
- if (scr->flags & CAMU_SCREEN_DRAGGING) {
- if (view && scr->flags & CAMU_SCREEN_ZOOM_PAN_SIMPLE) {
+ if (SCREEN_IS_DRAGGING(scr)) {
+ if (view && SCREEN_ZOOM_MODE(scr, CAMU_SCREEN_ZOOM_PAN_SIMPLE)) {
f64 dx = x - scr->last_mouse_x;
f64 dy = y - scr->last_mouse_y;
if (camu_view_pan_simple(view, scr->width, scr->height, dx, dy)) {
@@ -90,6 +99,13 @@ static bool pointer_pos_callback(void *userdata, f64 x, f64 y)
return queue_refresh;
}
+static void seek_to_percent_at_pointer(struct camu_screen *scr)
+{
+ f64 percent = scr->last_mouse_x / scr->width;
+ percent = CLAMP(percent, 0.0, 100.0);
+ scr->callback(scr->userdata, CAMU_SCREEN_PERCENT_SEEK, &percent);
+}
+
static bool mouse_button_callback(void *userdata, u8 state, u8 button)
{
struct camu_screen *scr = (struct camu_screen *)userdata;
@@ -102,30 +118,17 @@ static bool mouse_button_callback(void *userdata, u8 state, u8 button)
break;
case STELA_BUTTON_RELEASED:
scr->flags &= ~CAMU_SCREEN_DRAGGING;
- if (scr->last_click_ts != SCREEN_INVALID_TS && nn_get_timestamp() - scr->last_click_ts <= 300000) {
- if (scr->flags & CAMU_SCREEN_MOD_CONTROL) {
+ if (SCREEN_LAST_CLICK_WITHIN(200000)) {
+ if (SCREEN_MOD1(scr)) {
seek_to_percent_at_pointer(scr);
} else {
- if (scr->last_mouse_x >= scr->width / 2.0) {
- scr->callback(scr->userdata, CAMU_SCREEN_NEXT, NULL);
- } else {
- scr->callback(scr->userdata, CAMU_SCREEN_PREVIOUS, NULL);
- }
+ s32 n = (scr->last_mouse_x >= scr->width / 2.0) ? 1 : -1;
+ scr->callback(scr->userdata, CAMU_SCREEN_SKIP, &n);
}
}
break;
}
break;
- case STELA_MOUSE2:
- switch (state) {
- case STELA_BUTTON_PRESSED:
- scr->flags |= CAMU_SCREEN_MOD_CONTROL;
- break;
- case STELA_BUTTON_RELEASED:
- scr->flags &= ~CAMU_SCREEN_MOD_CONTROL;
- break;
- }
- break;
case STELA_MOUSE3: {
switch (state) {
case STELA_BUTTON_RELEASED: {
@@ -141,30 +144,28 @@ static bool mouse_button_callback(void *userdata, u8 state, u8 button)
return false;
}
+static bool scroll_callback(void *userdata, f64 y)
+{
+ struct camu_screen *scr = (struct camu_screen *)userdata;
+ if (SCREEN_MOD1(scr)) {
+ f32 change = y < 0.0 ? 0.025 : -0.025;
+ scr->callback(scr->userdata, CAMU_SCREEN_OFFSET_VOLUME, &change);
+ } else {
+ // This sucks.
#if defined STELA_WINDOW_WAYLAND
-#define SCROLL_MULTIPLIER 160.0
+ y = -y / 160.0;
#elif defined STELA_WINDOW_WIN32
-#define SCROLL_MULTIPLIER 1200.0
+ y = -y / 1600.0;
#else
-#define SCROLL_MULTIPLIER 20.0
+ y = -y / 20.0;
#endif
-
-static bool scroll_callback(void *userdata, f64 y)
-{
- struct camu_screen *scr = (struct camu_screen *)userdata;
- y = -y / SCROLL_MULTIPLIER;
- if (!(scr->flags & CAMU_SCREEN_MOD_CONTROL)) {
struct camu_view *view = get_view_from_mouse_pos(scr);
- if (view) {
- if (scr->flags & CAMU_SCREEN_ZOOM_PAN_SIMPLE) {
- if (camu_view_zoom_simple(view, scr->width, scr->height, scr->last_mouse_x, scr->last_mouse_y, y)) {
- view->mode = CAMU_VIEW_DETACHED;
- return true;
- }
+ if (view && SCREEN_ZOOM_MODE(scr, CAMU_SCREEN_ZOOM_PAN_SIMPLE)) {
+ if (camu_view_zoom_simple(view, scr->width, scr->height, scr->last_mouse_x, scr->last_mouse_y, y)) {
+ view->mode = CAMU_VIEW_DETACHED;
+ return true;
}
}
- } else {
- scr->callback(scr->userdata, CAMU_SCREEN_OFFSET_VOLUME, &y);
}
return false;
}
@@ -187,27 +188,36 @@ static bool key_callback(void *userdata, u8 state, u16 button)
scr->callback(scr->userdata, CAMU_SCREEN_CLOSE, NULL);
break;
case STELA_KEY_RIGHT:
- if (scr->flags & CAMU_SCREEN_MOD_CONTROL) {
- f64 plus_ten = 10.0;
- scr->callback(scr->userdata, CAMU_SCREEN_RELATIVE_SEEK, &plus_ten);
+ if (SCREEN_MOD1(scr)) {
+ f64 plus_ten_seconds = 10.0;
+ scr->callback(scr->userdata, CAMU_SCREEN_RELATIVE_SEEK, &plus_ten_seconds);
break;
}
// fallthrough
case STELA_KEY_D:
- case STELA_KEY_N:
- scr->callback(scr->userdata, CAMU_SCREEN_NEXT, NULL);
+ case STELA_KEY_N: {
+ s32 n = 1;
+ scr->callback(scr->userdata, CAMU_SCREEN_SKIP, &n);
break;
+ }
case STELA_KEY_LEFT:
- if (scr->flags & CAMU_SCREEN_MOD_CONTROL) {
- f64 minus_ten = -10.0;
- scr->callback(scr->userdata, CAMU_SCREEN_RELATIVE_SEEK, &minus_ten);
+ if (SCREEN_MOD1(scr)) {
+ if (SCREEN_MOD2(scr)) {
+ f64 zero = 0.0;
+ scr->callback(scr->userdata, CAMU_SCREEN_PERCENT_SEEK, &zero);
+ } else {
+ f64 minus_ten_seconds = -10.0;
+ scr->callback(scr->userdata, CAMU_SCREEN_RELATIVE_SEEK, &minus_ten_seconds);
+ }
break;
}
// fallthrough
case STELA_KEY_B:
- case STELA_KEY_A:
- scr->callback(scr->userdata, CAMU_SCREEN_PREVIOUS, NULL);
+ case STELA_KEY_A: {
+ s32 n = -1;
+ scr->callback(scr->userdata, CAMU_SCREEN_SKIP, &n);
break;
+ }
case STELA_KEY_SPACE:
scr->callback(scr->userdata, CAMU_SCREEN_TOGGLE_PAUSE, NULL);
break;
@@ -233,7 +243,7 @@ static bool key_callback(void *userdata, u8 state, u16 button)
case STELA_KEY_R: {
struct camu_view *view = get_view_from_mouse_pos(scr);
if (view) {
- if (scr->flags & CAMU_SCREEN_MOD_SHIFT) {
+ if (SCREEN_MOD2(scr)) {
switch (view->rotation) {
case CAMU_VIEW_ROTATION_0:
case CAMU_VIEW_ROTATION_360:
@@ -249,7 +259,7 @@ static bool key_callback(void *userdata, u8 state, u16 button)
view->rotation = CAMU_VIEW_ROTATION_180;
break;
}
- } else if (scr->flags & CAMU_SCREEN_MOD_CONTROL) {
+ } else if (SCREEN_MOD1(scr)) {
switch (view->rotation) {
case CAMU_VIEW_ROTATION_0:
case CAMU_VIEW_ROTATION_360:
@@ -336,13 +346,14 @@ bool camu_screen_init(struct camu_screen *scr, void *context)
{
al_atomic_store(s32)(&scr->state, CAMU_SCREEN_PAUSED, AL_ATOMIC_RELAXED);
scr->window = stl_window_create(context);
+ scr->window->render_callback = render_callback;
+ scr->window->refresh_callback = refresh_callback;
scr->window->pointer_pos_callback = pointer_pos_callback;
scr->window->scroll_callback = scroll_callback;
scr->window->mouse_button_callback = mouse_button_callback;
scr->window->key_callback = key_callback;
scr->window->key_immediate_callback = key_immediate_callback;
scr->window->resize_callback = resize_callback;
- scr->window->refresh_callback = refresh_callback;
scr->window->should_close_callback = should_close_callback;
scr->window->userdata = scr;
scr->renderer = NULL;
@@ -351,7 +362,6 @@ bool camu_screen_init(struct camu_screen *scr, void *context)
scr->last_click_ts = SCREEN_INVALID_TS;
scr->last_mouse_x = 0.0;
scr->last_mouse_y = 0.0;
- scr->last_seek_ts = SCREEN_INVALID_TS;
al_array_init(scr->videos);
#ifdef CAMU_SCREEN_THREADED
al_array_init(scr->add_queue);
diff --git a/src/screen/screen.h b/src/screen/screen.h
index 4b2e4c0..d79d977 100644
--- a/src/screen/screen.h
+++ b/src/screen/screen.h
@@ -31,15 +31,14 @@ enum {
};
enum {
- CAMU_SCREEN_NEXT = 0,
- CAMU_SCREEN_PREVIOUS,
- CAMU_SCREEN_SHUFFLE,
+ CAMU_SCREEN_SET_VOLUME = 0, // f32
+ CAMU_SCREEN_OFFSET_VOLUME, // f32
+ CAMU_SCREEN_SKIP, // s32
CAMU_SCREEN_TOGGLE_PAUSE,
- CAMU_SCREEN_SEEK,
- CAMU_SCREEN_RELATIVE_SEEK,
+ CAMU_SCREEN_PERCENT_SEEK, // f64
+ CAMU_SCREEN_RELATIVE_SEEK, // f64
CAMU_SCREEN_RESEEK,
- CAMU_SCREEN_SET_VOLUME,
- CAMU_SCREEN_OFFSET_VOLUME,
+ CAMU_SCREEN_SHUFFLE,
CAMU_SCREEN_STATUS,
#ifdef CAMU_SCREEN_DEBUG_KEY
CAMU_SCREEN_DEBUG,
@@ -67,7 +66,6 @@ struct camu_screen {
u64 last_click_ts;
f64 last_mouse_y;
f64 last_mouse_x;
- u64 last_seek_ts;
array(struct camu_screen_video) videos;
#ifdef CAMU_SCREEN_THREADED
array(struct camu_video_buffer *) add_queue;
diff --git a/src/server/common.h b/src/server/common.h
index 80afb9b..ffb98ec 100644
--- a/src/server/common.h
+++ b/src/server/common.h
@@ -31,9 +31,9 @@ enum {
CAMU_LIST_ADD = 0,
CAMU_LIST_SKIP,
CAMU_LIST_SKIPTO,
- CAMU_LIST_SHUFFLE,
CAMU_LIST_TOGGLE_PAUSE,
CAMU_LIST_SEEK,
+ CAMU_LIST_SHUFFLE,
CAMU_LIST_UNSET,
CAMU_LIST_END
};
diff --git a/src/server/server.c b/src/server/server.c
index d91dd6f..a1c3657 100644
--- a/src/server/server.c
+++ b/src/server/server.c
@@ -148,6 +148,7 @@ static bool identify_callback(void *userdata, struct nn_rpc_connection *conn,
static void list_sink_callback(void *userdata, u8 op, struct lia_list_entry *entry, s32 sequence, struct lia_timing *timing)
{
struct camu_server_sink *sink = (struct camu_server_sink *)userdata;
+ if (!sink->conn) return;
switch (op) {
case LIANA_SINK_SET:
case LIANA_SINK_BUFFER:
@@ -225,9 +226,12 @@ static void node_callback(void *userdata, u8 op, u64 duration)
case LIANA_NODE_DURATION:
resource->load = LIANA_ENTRY_LOADED;
resource->duration = duration;
- process_pending(resource);
+ break;
+ case LIANA_NODE_ERRORED:
+ resource->load = LIANA_ENTRY_ERRORED;
break;
}
+ process_pending(resource);
}
static void send_clients_current_changed(struct camu_server *server, struct lia_list *list, struct lia_list_entry *entry)
@@ -671,10 +675,6 @@ static bool list_action_callback(void *userdata, struct nn_rpc_connection *conn,
lia_list_skipto(list, sequence, i);
break;
}
- case CAMU_LIST_SHUFFLE: {
- lia_list_shuffle(list);
- break;
- }
case CAMU_LIST_TOGGLE_PAUSE: {
s32 sequence = nn_packet_read_s32(packet);
f64 pts = nn_packet_read_f64(packet);
@@ -684,9 +684,12 @@ static bool list_action_callback(void *userdata, struct nn_rpc_connection *conn,
case CAMU_LIST_SEEK: {
s32 sequence = nn_packet_read_s32(packet);
u32 id = nn_packet_read_u32(packet);
- f64 percent = nn_packet_read_f64(packet);
- percent = CLAMP(percent, 0.0, 100.0);
- lia_list_seek(list, sequence, id, percent);
+ u64 pos = nn_packet_read_u64(packet);
+ lia_list_seek(list, sequence, id, pos);
+ break;
+ }
+ case CAMU_LIST_SHUFFLE: {
+ lia_list_shuffle(list);
break;
}
case CAMU_LIST_UNSET: {
diff --git a/src/server/server.h b/src/server/server.h
index db7d30c..e335b94 100644
--- a/src/server/server.h
+++ b/src/server/server.h
@@ -55,4 +55,5 @@ void camu_server_bind_direct(struct camu_server *server);
void camu_server_close(struct camu_server *server);
void camu_server_free(struct camu_server *server);
+// Local compat.
void camu_server_local_add(struct camu_server *server, struct nn_packet *packet);
diff --git a/src/sink/common.c b/src/sink/common.c
deleted file mode 100644
index 9ed6a44..0000000
--- a/src/sink/common.c
+++ /dev/null
@@ -1,84 +0,0 @@
-#include <al/log.h>
-
-#include "../libsink/sink.h"
-
-#include "common.h"
-
-bool camu_default_sink_callback(struct camu_screen *scr, struct camu_mixer *mixer, u8 op, u8 type, void *opaque)
-{
- switch (op) {
- case CAMU_SINK_ADD_BUFFER:
- switch (type) {
- case CAMU_SINK_AUDIO: {
- struct camu_audio_buffer *buf = (struct camu_audio_buffer *)opaque;
- camu_mixer_add_buffer(mixer, buf);
- al_log_info("camu_desktop", "Audio buffer added.");
- break;
- }
- case CAMU_SINK_VIDEO: {
- struct camu_video_buffer *buf = (struct camu_video_buffer *)opaque;
- camu_screen_add_buffer(scr, buf);
- al_log_info("camu_desktop", "Video buffer added.");
- break;
- }
- }
- break;
- case CAMU_SINK_REMOVE_BUFFER:
- switch (type) {
- case CAMU_SINK_AUDIO: {
- struct camu_audio_buffer *buf = (struct camu_audio_buffer *)opaque;
- camu_mixer_remove_buffer(mixer, buf);
- al_log_info("camu_desktop", "Audio buffer removed.");
- break;
- }
- case CAMU_SINK_VIDEO: {
- struct camu_video_buffer *buf = (struct camu_video_buffer *)opaque;
- camu_screen_remove_buffer(scr, buf);
- al_log_info("camu_desktop", "Video buffer removed.");
- break;
- }
- }
- break;
- case CAMU_SINK_START:
- switch (type) {
- case CAMU_SINK_AUDIO:
- camu_mixer_resume(mixer);
- al_log_info("camu_desktop", "Audio started.");
- break;
- case CAMU_SINK_VIDEO:
- camu_screen_set_state(scr, CAMU_SCREEN_PLAYING);
- camu_screen_wake(scr);
- al_log_info("camu_desktop", "Video started.");
- break;
- }
- break;
- case CAMU_SINK_STOP:
- switch (type) {
- case CAMU_SINK_AUDIO:
- camu_mixer_pause(mixer);
- al_log_info("camu_desktop", "Audio stopped.");
- break;
- case CAMU_SINK_VIDEO:
- camu_screen_set_state(scr, CAMU_SCREEN_PAUSED);
- al_log_info("camu_desktop", "Video stopped.");
- break;
- }
- break;
- case CAMU_SINK_REFRESH_VIDEO:
- camu_screen_force_refresh(scr);
- break;
- case CAMU_SINK_CLEAR:
- switch (type) {
- case CAMU_SINK_AUDIO:
- camu_mixer_clear(mixer);
- break;
- case CAMU_SINK_VIDEO:
- camu_screen_clear(scr);
- break;
- }
- break;
- case CAMU_SINK_EXIT:
- return false;
- }
- return true;
-}
diff --git a/src/sink/common.h b/src/sink/common.h
deleted file mode 100644
index 5d02ed1..0000000
--- a/src/sink/common.h
+++ /dev/null
@@ -1,4 +0,0 @@
-#include "../screen/screen.h"
-#include "../mixer/mixer.h"
-
-bool camu_default_sink_callback(struct camu_screen *scr, struct camu_mixer *mixer, u8 op, u8 type, void *opaque);
diff --git a/src/sink/desktop.c b/src/sink/desktop.c
index 73a1667..6632827 100644
--- a/src/sink/desktop.c
+++ b/src/sink/desktop.c
@@ -1,7 +1,13 @@
+#define AL_LOG_SECTION "desktop"
#include <al/log.h>
+//#define DESKTOP_NULL_AUDIO
+
+#ifdef DESKTOP_NULL_AUDIO
#include "../mixer/audio_null.h"
+#else
#include "../mixer/audio_miniaudio.h"
+#endif
#if defined CAMU_RENDERER_TIGER
#include "../render/renderer_tiger.h"
#elif defined CAMU_RENDERER_LIBPLACEBO
@@ -9,7 +15,6 @@
#endif
#include "desktop.h"
-#include "common.h"
#ifdef CAMU_SCREEN_DEBUG_KEY
#include "input_simulator.h"
#endif
@@ -17,52 +22,169 @@
static u8 sink_callback(void *userdata, u8 op, u8 type, void *opaque)
{
struct camu_desktop *c = (struct camu_desktop *)userdata;
-
- if (op == CAMU_SINK_MOCK_CLOSE) {
+ struct camu_mixer *mixer = &c->mixer;
+ struct camu_screen *scr = &c->scr;
+ switch (op) {
+ case CAMU_SINK_MOCK_CLOSE:
c->should_quit = 1;
camu_screen_wake(&c->scr);
- return CAMU_SINK_OK;
- }
-
- if (!camu_default_sink_callback(&c->scr, &c->mixer, op, type, opaque)) {
+ break;
+ case CAMU_SINK_ADD_BUFFER:
+ switch (type) {
+ case CAMU_SINK_AUDIO: {
+ struct camu_audio_buffer *buf = (struct camu_audio_buffer *)opaque;
+ camu_mixer_add_buffer(mixer, buf);
+ info("Audio buffer added.");
+ break;
+ }
+ case CAMU_SINK_VIDEO: {
+ struct camu_video_buffer *buf = (struct camu_video_buffer *)opaque;
+ camu_screen_add_buffer(scr, buf);
+ info("Video buffer added.");
+ break;
+ }
+ }
+ break;
+ case CAMU_SINK_REMOVE_BUFFER:
+ switch (type) {
+ case CAMU_SINK_AUDIO: {
+ struct camu_audio_buffer *buf = (struct camu_audio_buffer *)opaque;
+ camu_mixer_remove_buffer(mixer, buf);
+ info("Audio buffer removed.");
+ break;
+ }
+ case CAMU_SINK_VIDEO: {
+ struct camu_video_buffer *buf = (struct camu_video_buffer *)opaque;
+ camu_screen_remove_buffer(scr, buf);
+ info("Video buffer removed.");
+ break;
+ }
+ }
+ break;
+ case CAMU_SINK_START:
+ switch (type) {
+ case CAMU_SINK_AUDIO:
+ camu_mixer_resume(mixer);
+ info("Audio started.");
+ break;
+ case CAMU_SINK_VIDEO:
+ camu_screen_set_state(scr, CAMU_SCREEN_PLAYING);
+ camu_screen_wake(scr);
+ info("Video started.");
+ break;
+ }
+ break;
+ case CAMU_SINK_STOP:
+ switch (type) {
+ case CAMU_SINK_AUDIO:
+ camu_mixer_pause(mixer);
+ info("Audio stopped.");
+ break;
+ case CAMU_SINK_VIDEO:
+ camu_screen_set_state(scr, CAMU_SCREEN_PAUSED);
+ info("Video stopped.");
+ break;
+ }
+ break;
+ case CAMU_SINK_REFRESH_VIDEO:
+ camu_screen_force_refresh(scr);
+ break;
+ case CAMU_SINK_CLEAR:
+ switch (type) {
+ case CAMU_SINK_AUDIO:
+ camu_mixer_clear(mixer);
+ break;
+ case CAMU_SINK_VIDEO:
+ camu_screen_clear(scr);
+ break;
+ }
+ break;
+ case CAMU_SINK_EXIT:
camu_sink_close(&c->sink);
if (c->exit_callback) {
c->exit_callback(c->userdata, c);
}
}
-
return CAMU_SINK_OK;
}
+static char status[128];
+
+static void print_status(struct camu_sink *sink)
+{
+ struct camu_sink_entry *current = camu_sink_get_current(sink);
+ if (!current) {
+ info("Not playing.");
+ camu_sink_return_current(sink);
+ return;
+ }
+
+ f64 pts = camu_clock_get_last_pts(&current->clock);
+ f64 duration = current->client.duration / 1000000.0;
+ bool paused = current->paused;
+
+ camu_sink_return_current(sink);
+
+ s32 text = 0;
+ if (paused) {
+ text += al_snprintf(status + text, sizeof(status) - text, "⏸ ");
+ } else {
+ text += al_snprintf(status + text, sizeof(status) - text, "⏵ ");
+ }
+
+ text += al_snprintf(status + text, sizeof(status) - text, "[");
+
+ u32 minute = (u32)(pts / 60);
+ u32 hour = minute / 60;
+ minute -= hour * 60;
+ bool show_hour = duration >= 60.0 * 60.0;
+ if (show_hour) text += al_snprintf(status + text, sizeof(status) - text, "%.2d:", hour);
+ text += al_snprintf(status + text, sizeof(status) - text, "%.2d:%.2d/", minute, (u32)pts % 60);
+
+ minute = (u32)(duration / 60);
+ hour = minute / 60;
+ minute -= hour * 60;
+ if (show_hour) text += al_snprintf(status + text, sizeof(status) - text, "%.2d:", hour);
+ text += al_snprintf(status + text, sizeof(status) - text, "%.2d:%.2d", minute, (u32)duration % 60);
+
+ text += al_snprintf(status + text, sizeof(status) - text, "]");
+
+ status[text] = '\0';
+
+ info("%s", status);
+}
+
static void screen_callback(void *userdata, u8 op, void *opaque)
{
struct camu_desktop *c = (struct camu_desktop *)userdata;
switch (op) {
- case CAMU_SCREEN_NEXT:
- camu_sink_skip(&c->sink, 1);
+ case CAMU_SCREEN_SET_VOLUME:
+ camu_mixer_set_volume(&c->mixer, *(f32 *)opaque);
break;
- case CAMU_SCREEN_PREVIOUS:
- camu_sink_skip(&c->sink, -1);
+ case CAMU_SCREEN_OFFSET_VOLUME:
+ camu_mixer_offset_volume(&c->mixer, *(f32 *)opaque);
break;
- case CAMU_SCREEN_SHUFFLE:
- camu_sink_shuffle(&c->sink);
+ case CAMU_SCREEN_SKIP:
+ camu_sink_skip(&c->sink, *(s32 *)opaque);
break;
case CAMU_SCREEN_TOGGLE_PAUSE:
camu_sink_toggle_pause(&c->sink);
break;
- case CAMU_SCREEN_SEEK: {
- f64 percent = *(f64 *)opaque;
- camu_sink_seek(&c->sink, percent);
+ case CAMU_SCREEN_PERCENT_SEEK:
+ camu_sink_seek(&c->sink, opaque, CAMU_SEEK_PERCENT);
break;
- }
- case CAMU_SCREEN_RELATIVE_SEEK: {
- f64 offset = *(f64 *)opaque;
- camu_sink_relative_seek(&c->sink, offset);
+ case CAMU_SCREEN_RELATIVE_SEEK:
+ camu_sink_seek(&c->sink, opaque, CAMU_SEEK_RELATIVE);
break;
- }
case CAMU_SCREEN_RESEEK:
camu_sink_reseek(&c->sink);
break;
+ case CAMU_SCREEN_SHUFFLE:
+ camu_sink_shuffle(&c->sink);
+ break;
+ case CAMU_SCREEN_STATUS:
+ print_status(&c->sink);
+ break;
#ifdef CAMU_SCREEN_DEBUG_KEY
case CAMU_SCREEN_DEBUG:
if (camu_input_simulator_running()) {
@@ -70,23 +192,8 @@ static void screen_callback(void *userdata, u8 op, void *opaque)
} else {
camu_input_simulator_run(&c->sink);
}
- //camu_sink_unset(&c->sink);
break;
#endif
- case CAMU_SCREEN_SET_VOLUME: {
- f32 volume = *(f32 *)opaque;
- camu_sink_set_volume(&c->sink, volume);
- break;
- }
- case CAMU_SCREEN_OFFSET_VOLUME: {
- f64 amount = *(f64 *)opaque;
- camu_sink_offset_volume(&c->sink, (f32)amount);
- break;
- }
- case CAMU_SCREEN_STATUS: {
- camu_sink_status(&c->sink);
- break;
- }
case CAMU_SCREEN_CLOSE:
c->should_quit = 1;
break;
@@ -109,8 +216,11 @@ bool camu_desktop_init(struct camu_desktop *c, const char *name)
return false;
}
c->renderer->render(c->renderer, &c->scr, true);
+#ifdef DESKTOP_NULL_AUDIO
+ if (!camu_mixer_init(&c->mixer, (struct camu_audio *)&audio_plugin_null)) {
+#else
if (!camu_mixer_init(&c->mixer, camu_audio_miniaudio_create())) {
- //if (!camu_mixer_init(&c->mixer, (struct camu_audio *)&audio_plugin_null)) {
+#endif
return false;
}
if (!c->mixer.audio->configure_stream(c->mixer.audio, NULL)) {
@@ -130,7 +240,7 @@ bool camu_desktop_connect(struct camu_desktop *c, u8 type, struct nn_event_loop
al_str_from(&c->sink.default_list, "default");
c->sink.callback = sink_callback;
c->sink.userdata = c;
- return camu_sink_connect(&c->sink, type, addr, port, &al_str_c("desktop"));
+ return camu_sink_connect(&c->sink, &al_str_c("desktop"), type, addr, port);
}
bool camu_desktop_tick(struct camu_desktop *c)
diff --git a/src/sink/input_simulator.c b/src/sink/input_simulator.c
index 9cf395f..80fde02 100644
--- a/src/sink/input_simulator.c
+++ b/src/sink/input_simulator.c
@@ -41,7 +41,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 = .9999;
- camu_sink_seek(sink, pos);
+ camu_sink_seek(sink, &pos, CAMU_SEEK_PERCENT);
break;
}
}
diff --git a/src/sink/meson.build b/src/sink/meson.build
index 0c115f8..8d13b26 100644
--- a/src/sink/meson.build
+++ b/src/sink/meson.build
@@ -1,4 +1,4 @@
-desktop_src = ['desktop.c', 'common.c', 'input_simulator.c']
+desktop_src = ['desktop.c', 'input_simulator.c']
desktop_deps = [libsink]
desktop_args = ['-DCAMU_MIXER_THREADED', '-DCAMU_SCREEN_THREADED']
desktop = declare_dependency(sources: desktop_src, dependencies: desktop_deps, compile_args: desktop_args)
diff --git a/subprojects/SPIRV-Cross.wrap b/subprojects/SPIRV-Cross.wrap
index c6ff02b..58f9d79 100644
--- a/subprojects/SPIRV-Cross.wrap
+++ b/subprojects/SPIRV-Cross.wrap
@@ -1,6 +1,6 @@
[wrap-git]
-directory = SPIRV-Cross-2c32b6b
+directory = SPIRV-Cross-1823c11
url = https://github.com/KhronosGroup/SPIRV-Cross.git
-revision = 2c32b6bf86f3c4a5539aa1f0bacbd59fe61759cf
+revision = 1823c119c4d7311469199c1afecf2e255e26eb16
depth = 1
method = cmake
diff --git a/subprojects/ffmpeg.wrap b/subprojects/ffmpeg.wrap
index e6f7abf..fdd551c 100644
--- a/subprojects/ffmpeg.wrap
+++ b/subprojects/ffmpeg.wrap
@@ -7,6 +7,8 @@ directory = ffmpeg-7.1.1
#directory = ffmpeg-7.0
#directory = ffmpeg-6.1.1
#directory = ffmpeg-5.1.4
+#directory = ffmpeg-4.4.5
+
source_url = https://ffmpeg.org/releases/ffmpeg-7.1.1.tar.gz
#source_url = https://ffmpeg.org/releases/ffmpeg-7.1.tar.gz
#source_url = https://www.ffmpeg.org/releases/ffmpeg-5.1.6.tar.gz
@@ -15,6 +17,8 @@ source_url = https://ffmpeg.org/releases/ffmpeg-7.1.1.tar.gz
#source_url = https://www.ffmpeg.org/releases/ffmpeg-7.0.tar.gz
#source_url = https://www.ffmpeg.org/releases/ffmpeg-6.1.1.tar.gz
#source_url = https://www.ffmpeg.org/releases/ffmpeg-5.1.4.tar.gz
+#source_url = https://www.ffmpeg.org/releases/ffmpeg-4.4.5.tar.gz
+
source_filename = ffmpeg-7.1.1.tar.gz
#source_filename = ffmpeg-7.1.tar.gz
#source_filename = ffmpeg-5.1.6.tar.gz
@@ -23,6 +27,8 @@ source_filename = ffmpeg-7.1.1.tar.gz
#source_filename = ffmpeg-7.0.tar.gz
#source_filename = ffmpeg-6.1.1.tar.gz
#source_filename = ffmpeg-5.1.4.tar.gz
+#source_filename = ffmpeg-4.4.5.tar.gz
+
source_hash = 9a6e57a446b671012612aaeb9df5126794d5ac8f2015ca220934f99a6a4e0601
#source_hash = 42a7dc0d1583885d1b8f6559fa7ce28f97acafea6803de6a8f73e3ba229348bd
#source_hash = 2a963f9f627dc55cac7ce4d2bb2e063ce52c8ab57185d2b592a631c36be56fda
@@ -31,4 +37,6 @@ source_hash = 9a6e57a446b671012612aaeb9df5126794d5ac8f2015ca220934f99a6a4e0601
#source_hash = 943a2a28044947c17a905c39075494b0da46ec0795224c2c61eff986518321eb
#source_hash = b0aee531e4a91fed6bc031445dfc3ad8eb36de1fcb0fa2acdb589ecc9f8dc9cb
#source_hash = ad1d83e0f022ba405928ea4985c9cab978c4698bb7937738a1085808c3eb78b4
+#source_hash = 77e0eedd8ebeafde5807011e700fdea82b306b9f76fe469b4abc5dfefd9579bd
+
patch_directory = ffmpeg
diff --git a/subprojects/libalabaster.wrap b/subprojects/libalabaster.wrap
index adaec6a..014f5ab 100644
--- a/subprojects/libalabaster.wrap
+++ b/subprojects/libalabaster.wrap
@@ -1,4 +1,4 @@
[wrap-git]
url = https://git.akon.city/libalabaster
-revision = 31715673a5b98991ef276decbd90f026559eaf4b
+revision = cf45d71c49a7e7364b315dcc45fe7eac060590df
depth = 1
diff --git a/subprojects/libnaunet.wrap b/subprojects/libnaunet.wrap
index a869e82..609cb77 100644
--- a/subprojects/libnaunet.wrap
+++ b/subprojects/libnaunet.wrap
@@ -1,4 +1,4 @@
[wrap-git]
url = https://git.akon.city/libnaunet
-revision = ce102493f377d0452e44c3a4bb6e6e15ab0c819d
+revision = b4e45dd89e6dd53d8cb940e5609b812e19ade913
depth = 1
diff --git a/subprojects/libplacebo.wrap b/subprojects/libplacebo.wrap
index 23885d4..594c0d2 100644
--- a/subprojects/libplacebo.wrap
+++ b/subprojects/libplacebo.wrap
@@ -1,11 +1,11 @@
[wrap-git]
-directory = libplacebo-056b852
+directory = libplacebo-300ebb1
url = https://code.videolan.org/videolan/libplacebo.git
-revision = 056b852018db04aa2ebc0982e27713afcea8106b
+revision = 300ebb182f90036e4b635ad7db0f425f0e57653a
clone-recursive = true
depth = 1
-diff_files = libplacebo/cache_ref_rect.diff, libplacebo/fix_rotate.diff, libplacebo/no_dlltool_no_shaderc_version.diff, libplacebo/compiler_warnings.diff, libplacebo/glClientWaitSync_0_return.diff, libplacebo/info_priv_hash.diff
+diff_files = libplacebo/cache_ref_rect.diff, libplacebo/fix_rotate.diff, libplacebo/dependency_version_override.diff, libplacebo/compiler_warnings.diff, libplacebo/glClientWaitSync_0_return.diff, libplacebo/info_priv_hash.diff, libplacebo/d3d11_scaling_none.diff
[provide]
dependency_names = libplacebo
diff --git a/subprojects/miniaudio.wrap b/subprojects/miniaudio.wrap
index 2483267..f643ff8 100644
--- a/subprojects/miniaudio.wrap
+++ b/subprojects/miniaudio.wrap
@@ -1,7 +1,7 @@
[wrap-git]
-directory = miniaudio-445cdcb
+directory = miniaudio-484a564
url = https://github.com/mackron/miniaudio.git
-revision = 445cdcb82bb948e0d78e8a7c11aa28a2b4d1b198
+revision = 484a56499d3e03a5f6fa8702bdc5a8ab0817c7ac
depth = 1
patch_directory = miniaudio
diff_files = miniaudio/miniaudio_pulse_flush.diff, miniaudio/miniaudio_alsa_compile.diff
diff --git a/subprojects/packagefiles/ffmpeg/meson.build b/subprojects/packagefiles/ffmpeg/meson.build
index 8aa2f02..f50f30f 100644
--- a/subprojects/packagefiles/ffmpeg/meson.build
+++ b/subprojects/packagefiles/ffmpeg/meson.build
@@ -58,7 +58,7 @@ decoders += ',dca'
#decoders += ',pcm_dvd,mpegvideo,mpeg2video'
#decoders += ',ass,srt'
-demuxers = 'flac,mp3,aac,wav,image2,mjpeg,image2pipe,image_jpeg_pipe,gif,matroska,mov'
+demuxers = 'flac,mp3,aac,wav,image2,mjpeg,image2pipe,image_jpeg_pipe,gif,matroska,mov,avi'
#demuxers += ',mpegts,mpegtsraw,mpegps,mpegvideo'
#demuxers += ',ass,srt'
diff --git a/subprojects/packagefiles/libplacebo/d3d11_scaling_none.diff b/subprojects/packagefiles/libplacebo/d3d11_scaling_none.diff
new file mode 100644
index 0000000..ba59ad8
--- /dev/null
+++ b/subprojects/packagefiles/libplacebo/d3d11_scaling_none.diff
@@ -0,0 +1,14 @@
+diff --git a/src/d3d11/swapchain.c b/src/d3d11/swapchain.c
+index 8a53632..bbe5182 100644
+--- a/src/d3d11/swapchain.c
++++ b/src/d3d11/swapchain.c
+@@ -456,7 +456,8 @@ static HRESULT create_swapchain_1_2(struct d3d11_ctx *ctx,
+ .Format = format,
+ .SampleDesc.Count = 1,
+ .BufferUsage = DXGI_USAGE_SHADER_INPUT | DXGI_USAGE_RENDER_TARGET_OUTPUT,
+- .Flags = params->flags,
++ .Scaling = DXGI_SCALING_NONE,
++ .Flags = params->flags
+ };
+
+ if (ID3D11Device_GetFeatureLevel(ctx->dev) >= D3D_FEATURE_LEVEL_11_0)
diff --git a/subprojects/packagefiles/libplacebo/dependency_version_override.diff b/subprojects/packagefiles/libplacebo/dependency_version_override.diff
new file mode 100644
index 0000000..f9a0688
--- /dev/null
+++ b/subprojects/packagefiles/libplacebo/dependency_version_override.diff
@@ -0,0 +1,24 @@
+diff --git a/src/d3d11/meson.build b/src/d3d11/meson.build
+index d4c4b44..ef96256 100644
+--- a/src/d3d11/meson.build
++++ b/src/d3d11/meson.build
+@@ -5,7 +5,7 @@ d3d11_headers_extra = [ # needed internally
+ cc.check_header('dxgi1_6.h', required: d3d11),
+ ]
+ d3d11_deps = [
+- dependency('spirv-cross-c-shared', version: '>=0.29.0', required: d3d11),
++ dependency('spirv-cross-c-shared', required: d3d11),
+ cc.find_library('version', required: d3d11),
+ ]
+
+diff --git a/src/glsl/meson.build b/src/glsl/meson.build
+index 8fb5e69..4adfa18 100644
+--- a/src/glsl/meson.build
++++ b/src/glsl/meson.build
+@@ -1,5 +1,5 @@
+ # shaderc
+-shaderc = dependency('shaderc', version: '>=2019.1', required: get_option('shaderc'))
++shaderc = dependency('shaderc', required: get_option('shaderc'))
+ components.set('shaderc', shaderc.found())
+ if shaderc.found()
+ build_deps += shaderc
diff --git a/subprojects/packagefiles/libplacebo/no_dlltool_no_shaderc_version.diff b/subprojects/packagefiles/libplacebo/no_dlltool_no_shaderc_version.diff
deleted file mode 100644
index a579431..0000000
--- a/subprojects/packagefiles/libplacebo/no_dlltool_no_shaderc_version.diff
+++ /dev/null
@@ -1,58 +0,0 @@
-diff --git a/src/glsl/meson.build b/src/glsl/meson.build
-index 5a88196..d6bb430 100644
---- a/src/glsl/meson.build
-+++ b/src/glsl/meson.build
-@@ -1,5 +1,5 @@
- # shaderc
--shaderc = dependency('shaderc', version: '>=2019.1', required: get_option('shaderc'))
-+shaderc = dependency('shaderc', required: get_option('shaderc'))
- components.set('shaderc', shaderc.found())
- if shaderc.found()
- build_deps += shaderc
-diff --git a/src/meson.build b/src/meson.build
-index 6162f8f..f93eb4c 100644
---- a/src/meson.build
-+++ b/src/meson.build
-@@ -18,24 +18,24 @@ link_args = []
- link_depends = []
-
- # Looks like meson in certain configuration returns ' ' instead of empty string
--mingw32 = cc.get_define('__MINGW32__').strip()
--if host_machine.system() == 'windows' and mingw32 != '' and host_machine.cpu() in ['aarch64', 'arm', 'x86_64']
-- # MinGW-w64 math functions are significantly slower than the UCRT ones.
-- # In particular powf is over 7 times slower than UCRT counterpart.
-- # MinGW-w64 explicitly excludes some math functions from their ucrtbase def
-- # file and replaces with own versions. To workaround the issue, generate the
-- # import library and link it with UCRT versions of math functions.
-- dlltool = find_program('llvm-dlltool', 'dlltool')
-- ucrt_math = custom_target('ucrt_math.lib',
-- output : ['ucrt_math.lib'],
-- input : 'ucrt_math.def',
-- command : [dlltool, '-d', '@INPUT@', '-l', '@OUTPUT@'])
-- link_args += ucrt_math.full_path()
-- link_depends += ucrt_math
-- # MinGW-w64 inlines functions like powf, rewriting them to pow. We want to use
-- # the powf specialization from UCRT, so disable inlining.
-- add_project_arguments(['-D__CRT__NO_INLINE'], language: ['c', 'cpp'])
--endif
-+#mingw32 = cc.get_define('__MINGW32__').strip()
-+#if host_machine.system() == 'windows' and mingw32 != '' and host_machine.cpu() in ['aarch64', 'arm', 'x86_64']
-+# # MinGW-w64 math functions are significantly slower than the UCRT ones.
-+# # In particular powf is over 7 times slower than UCRT counterpart.
-+# # MinGW-w64 explicitly excludes some math functions from their ucrtbase def
-+# # file and replaces with own versions. To workaround the issue, generate the
-+# # import library and link it with UCRT versions of math functions.
-+# dlltool = find_program('llvm-dlltool', 'dlltool')
-+# ucrt_math = custom_target('ucrt_math.lib',
-+# output : ['ucrt_math.lib'],
-+# input : 'ucrt_math.def',
-+# command : [dlltool, '-d', '@INPUT@', '-l', '@OUTPUT@'])
-+# link_args += ucrt_math.full_path()
-+# link_depends += ucrt_math
-+# # MinGW-w64 inlines functions like powf, rewriting them to pow. We want to use
-+# # the powf specialization from UCRT, so disable inlining.
-+# add_project_arguments(['-D__CRT__NO_INLINE'], language: ['c', 'cpp'])
-+#endif
-
- # Work around missing atomics on some (obscure) platforms
- atomic_test = '''
diff --git a/subprojects/stela.wrap b/subprojects/stela.wrap
index 004179f..3d8c7ad 100644
--- a/subprojects/stela.wrap
+++ b/subprojects/stela.wrap
@@ -1,4 +1,4 @@
[wrap-git]
url = https://git.akon.city/stela
-revision = 95e41dbad47f6c5d3868143b46938ec25582b6f1
+revision = d7f90f660e2a944cd60f3c72906e2c7cd0ab4203
depth = 1