From 846bd3d1b7bcec3653aa9d662f559235c4689e1b Mon Sep 17 00:00:00 2001 From: Andrew Opalach Date: Sat, 15 Mar 2025 20:40:38 -0400 Subject: Sink refactor wip Dangling target won't stay around. I have a couple of ideas about how to massively simplify the handling of ended entries. Signed-off-by: Andrew Opalach --- cross/i686-w64-mingw32.txt | 1 + cross/x86_64-w64-mingw32.txt | 1 + flake.lock | 30 +- flake.nix | 113 +-- scripts/run_valgrind.sh | 4 +- scripts/screenrc | 2 +- src/buffer/clock.c | 18 +- src/buffer/clock.h | 2 + src/buffer/meson.build | 6 +- src/buffer/video.c | 82 +- src/buffer/video.h | 8 +- src/buffer/video_null.h | 98 ++ src/cache/backings/file.c | 25 +- src/cache/backings/file.h | 1 - src/cache/backings/file_common.c | 5 +- src/cache/backings/file_mapped.c | 20 +- src/cache/entry.c | 4 +- src/cache/handle.c | 23 +- src/cache/range.h | 4 +- src/cache/threaded_waits.c | 117 +-- src/cache/threaded_waits.h | 14 +- src/cache/wait.h | 2 +- src/codec/codec.h | 4 + src/codec/ffmpeg/avio.c | 4 +- src/codec/ffmpeg/decoder.c | 8 +- src/codec/ffmpeg/decoder.h | 4 +- src/codec/ffmpeg/demuxer.c | 5 +- src/fruits/cmsrv/ui.c | 2 +- src/fruits/ctv/ctv.c | 131 +-- src/fruits/ctv/meson.build | 7 +- src/fruits/droid/ctv/gradle/libs.versions.toml | 2 +- .../ctv/gradle/wrapper/gradle-wrapper.properties | 2 +- src/liana/client.c | 79 +- src/liana/client.h | 2 +- src/liana/handlers/codec_client.c | 2 +- src/liana/list.c | 166 ++- src/liana/list.h | 2 +- src/liana/server.c | 6 +- src/liana/server.h | 3 +- src/liana/vcr.c | 4 +- src/libsink/sink.c | 1057 +++++++++----------- src/libsink/sink.h | 35 +- src/mixer/mixer.h | 8 +- src/render/renderer_libplacebo.c | 3 +- src/screen/screen.c | 160 +-- src/screen/screen.h | 14 +- src/server/common.h | 2 +- src/server/server.c | 19 +- src/server/server.h | 1 + src/sink/common.c | 84 -- src/sink/common.h | 4 - src/sink/desktop.c | 188 +++- src/sink/input_simulator.c | 2 +- src/sink/meson.build | 2 +- subprojects/SPIRV-Cross.wrap | 4 +- subprojects/ffmpeg.wrap | 8 + subprojects/libalabaster.wrap | 2 +- subprojects/libnaunet.wrap | 2 +- subprojects/libplacebo.wrap | 6 +- subprojects/miniaudio.wrap | 4 +- subprojects/packagefiles/ffmpeg/meson.build | 2 +- .../libplacebo/d3d11_scaling_none.diff | 14 + .../libplacebo/dependency_version_override.diff | 24 + .../libplacebo/no_dlltool_no_shaderc_version.diff | 58 -- subprojects/stela.wrap | 2 +- 65 files changed, 1266 insertions(+), 1452 deletions(-) create mode 100644 src/buffer/video_null.h delete mode 100644 src/sink/common.c delete mode 100644 src/sink/common.h create mode 100644 subprojects/packagefiles/libplacebo/d3d11_scaling_none.diff create mode 100644 subprojects/packagefiles/libplacebo/dependency_version_override.diff delete mode 100644 subprojects/packagefiles/libplacebo/no_dlltool_no_shaderc_version.diff 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 #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 + +#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 +#define AL_LOG_SECTION "cache_handle" +//#define AL_LOG_ENABLE_TRACE #include #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 #include #include #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); -} + 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; -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"); - - 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; - - al_set_print(log_callback, c); - - glfmSetUserData(display, c); - glfmSetDisplayConfig(display, - GLFMRenderingAPIOpenGLES2, - GLFMColorFormatRGBA8888, - GLFMDepthFormatNone, - GLFMStencilFormatNone, - GLFMMultisampleNone); - glfmSetSurfaceCreatedFunc(display, onSurfaceCreated); - glfmSetSurfaceDestroyedFunc(display, onSurfaceDestroyed); - glfmSetRenderFunc(display, onDraw); + (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; + } + + nn_thread_create(&c.thread, event_loop_thread, &c); + + 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 #include #include +#define AL_LOG_SECTION "list" +//#define AL_LOG_ENABLE_TRACE #include #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 #include @@ -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)) { - 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) { + } 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); - // 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(¤t->clock)) { + al_assert(entry != current); + if (current && !current->ended) { current->audio.ignore_paused = true; - camu_clock_pause(¤t->clock, 0); + if (!current->paused) { + camu_clock_pause(¤t->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(¤t->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(¤t->clock)) { - text += al_snprintf(status + text, sizeof(status) - text, "⏸ "); - } else { - text += al_snprintf(status + text, sizeof(status) - text, "⏵ "); - } - text += al_snprintf(status + text, sizeof(status) - text, "["); - bool show_hour = duration >= 60.0 * 60.0; - if (show_hour) text += al_snprintf(status + text, sizeof(status) - text, "%.2d:", hour); - text += al_snprintf(status + text, sizeof(status) - text, "%.2d:%.2d/", minute, (u32)pts % 60); - minute = (u32)(duration / 60); - hour = minute / 60; - minute -= hour * 60; - if (show_hour) text += al_snprintf(status + text, sizeof(status) - text, "%.2d:", hour); - text += al_snprintf(status + text, sizeof(status) - text, "%.2d:%.2d", minute, (u32)duration % 60); - text += al_snprintf(status + text, sizeof(status) - text, "]"); - status[text] = '\0'; - al_log_info("sink", "%s", status); -} - void camu_sink_stop(struct camu_sink *sink) { queue_cmd(sink, (struct camu_sink_cmd){ @@ -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 - -#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 +//#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(¤t->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 -- cgit v1.2.3-101-g0448