summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Opalach <andrew@akon.city> 2025-06-23 17:49:11 -0400
committerAndrew Opalach <andrew@akon.city> 2025-06-23 17:49:11 -0400
commite0fcbf0910b52a6e66eb733a2850ec58a53cf0e3 (patch)
treea24ff2b9313263666d121536064e01436b480571
parente9475ce94ba69bd437d8cf0cf3f78062be928568 (diff)
downloadcamu-e0fcbf0910b52a6e66eb733a2850ec58a53cf0e3.tar.gz
camu-e0fcbf0910b52a6e66eb733a2850ec58a53cf0e3.tar.bz2
camu-e0fcbf0910b52a6e66eb733a2850ec58a53cf0e3.zip
Improve FFmpeg hwaccel fallback, VR emulation
- Optimize sink seeking with single video frame. - Small cleanups. Signed-off-by: Andrew Opalach <andrew@akon.city>
-rw-r--r--README.txt8
-rw-r--r--env/nixpkgs-patches/libavcodec_mdec.c.patch15
-rw-r--r--flake.lock36
-rw-r--r--flake.nix12
-rw-r--r--meson.build2
-rw-r--r--meson_options.txt13
-rwxr-xr-xscripts/run_valgrind.sh6
-rw-r--r--src/buffer/audio.c2
-rw-r--r--src/buffer/peak_buffer.c24
-rw-r--r--src/buffer/peak_buffer.h12
-rw-r--r--src/buffer/video.c39
-rw-r--r--src/buffer/video_null.h4
-rw-r--r--src/cache/backings/memory.c4
-rw-r--r--src/cache/handlers/cdio.c2
-rw-r--r--src/codec/codec.h7
-rw-r--r--src/codec/ffmpeg/decoder.c132
-rw-r--r--src/codec/ffmpeg/decoder.h2
-rw-r--r--src/codec/ffmpeg/demuxer.c3
-rw-r--r--src/codec/ffmpeg/encoder.c10
-rw-r--r--src/codec/ffmpeg/packet_ext.c2
-rw-r--r--src/codec/ffmpeg/resampler.c6
-rw-r--r--src/codec/ffmpeg/scaler.c9
-rw-r--r--src/fruits/cmc/cmc.c12
-rw-r--r--src/fruits/cmv/cmv.c4
-rw-r--r--src/liana/common.h1
-rw-r--r--src/liana/handlers/cdio_client.c2
-rw-r--r--src/liana/handlers/cdio_server.c2
-rw-r--r--src/liana/handlers/codec.h6
-rw-r--r--src/liana/handlers/codec_client.c8
-rw-r--r--src/liana/handlers/dvd_server.c2
-rw-r--r--src/liana/list.c7
-rw-r--r--src/liana/vcr.c9
-rw-r--r--src/libsink/desktop.c3
-rw-r--r--src/libsink/sink.c27
-rw-r--r--src/mixer/audio_miniaudio.c13
-rw-r--r--src/render/meson.build2
-rw-r--r--src/render/queue_libplacebo.c20
-rw-r--r--src/render/renderer_libplacebo.c64
-rw-r--r--src/render/renderer_libplacebo.h1
-rw-r--r--src/render/shaders/vr_video.h46
-rw-r--r--src/screen/screen.c148
-rw-r--r--src/screen/screen.h15
-rw-r--r--src/screen/view.c12
-rw-r--r--src/screen/view.h2
-rw-r--r--subprojects/freetype2.wrap2
-rw-r--r--subprojects/libalabaster.wrap2
-rw-r--r--subprojects/libass.wrap4
-rw-r--r--subprojects/libnaunet.wrap2
-rw-r--r--subprojects/libplacebo.wrap4
-rw-r--r--subprojects/notcurses.wrap3
-rw-r--r--subprojects/packagefiles/ffmpeg-compat/meson_options.txt4
-rw-r--r--subprojects/packagefiles/ffmpeg-compat/meson_project.diff16
-rw-r--r--subprojects/packagefiles/ffmpeg/meson_options.txt4
-rw-r--r--subprojects/packagefiles/libplacebo/oversample_testing.diff18
-rw-r--r--subprojects/stela.wrap2
55 files changed, 533 insertions, 284 deletions
diff --git a/README.txt b/README.txt
index e2fa8cb..c0ce8f0 100644
--- a/README.txt
+++ b/README.txt
@@ -1,8 +1,6 @@
:source-highlighter: pygments
-== camu
-
-=== Simple Build and Test
+== Simple Build and Test
[source,console]
----
$ (optional) nix develop .
@@ -10,7 +8,7 @@ $ meson setup build
$ cd build && ninja
$ ./src/fruits/cmv/cmv [path_to_file]
----
-==== MinGW
+=== MinGW
[source,console]
----
$ nix develop .#mingw64
@@ -19,7 +17,7 @@ $ cd build && ninja
$ wine ./src/fruits/cmv/cmv.exe [path_to_file]
----
-=== Binds
+== Keybinds
*Left/Right Arrow* - Next/Previous +
*Space* - Pause/Resume +
*Control + Left Click* - Seek to percentage of screen width (temp) +
diff --git a/env/nixpkgs-patches/libavcodec_mdec.c.patch b/env/nixpkgs-patches/libavcodec_mdec.c.patch
new file mode 100644
index 0000000..f16aaeb
--- /dev/null
+++ b/env/nixpkgs-patches/libavcodec_mdec.c.patch
@@ -0,0 +1,15 @@
+X-Git-Url: http://git.ffmpeg.org/gitweb/ffmpeg.git/blobdiff_plain/e664f4465ab0f76adcf2452e85741ee468f507b7..36b402f80d0adc6b0687e014e3f51a00b72335eb:/libavcodec/mdec.c
+
+diff --git a/libavcodec/mdec.c b/libavcodec/mdec.c
+index 44b12471a9..c4904216b8 100644
+--- a/libavcodec/mdec.c
++++ b/libavcodec/mdec.c
+@@ -68,7 +68,7 @@ static inline int mdec_decode_block_intra(MDECContext *a, int16_t *block, int n)
+ const int qscale = a->qscale;
+
+ /* DC coefficient */
+- if (a->version == 2) {
++ if (a->version <= 2) {
+ block[0] = 2 * get_sbits(&a->gb, 10) + 1024;
+ } else {
+ component = (n <= 3 ? 0 : n - 4 + 1);
diff --git a/flake.lock b/flake.lock
index e159d99..e6b6997 100644
--- a/flake.lock
+++ b/flake.lock
@@ -3,11 +3,11 @@
"flake-compat": {
"flake": false,
"locked": {
- "lastModified": 1733328505,
- "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
+ "lastModified": 1747046372,
+ "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
"owner": "edolstra",
"repo": "flake-compat",
- "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
+ "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"type": "github"
},
"original": {
@@ -23,11 +23,11 @@
]
},
"locked": {
- "lastModified": 1749160002,
- "narHash": "sha256-IM3xKjsKxhu7Y1WdgTltrLKiOJS8nW7D4SUDEMNr7CI=",
+ "lastModified": 1750614446,
+ "narHash": "sha256-6WH0aRFay79r775RuTqUcnoZNm6A4uHxU1sbcNIk63s=",
"owner": "nix-community",
"repo": "home-manager",
- "rev": "68cc9eeb3875ae9682c04629f20738e1e79d72aa",
+ "rev": "7c35504839f915abec86a96435b881ead7eb6a2b",
"type": "github"
},
"original": {
@@ -39,11 +39,11 @@
},
"nixos-hardware": {
"locked": {
- "lastModified": 1749056381,
- "narHash": "sha256-QITcurR19KZlrCngBoCjsFF2BdYsiCG4UqmlrVcLb8Q=",
+ "lastModified": 1750431636,
+ "narHash": "sha256-vnzzBDbCGvInmfn2ijC4HsIY/3W1CWbwS/YQoFgdgPg=",
"owner": "NixOS",
"repo": "nixos-hardware",
- "rev": "029bd66faa180e11262dd1bc2732254c33415f52",
+ "rev": "1552a9f4513f3f0ceedcf90320e48d3d47165712",
"type": "github"
},
"original": {
@@ -59,11 +59,11 @@
"nixpkgs": "nixpkgs"
},
"locked": {
- "lastModified": 1746453552,
- "narHash": "sha256-r66UGha+7KVHkI7ksrcMjnw/mm9Sg4l5bQlylxHwdGU=",
+ "lastModified": 1749574455,
+ "narHash": "sha256-fm2/8KPOYvvIAnNVtjDlTt/My00lIbZQ+LMrfQIWVzs=",
"owner": "nix-community",
"repo": "NixOS-WSL",
- "rev": "be618645aa0adf461f778500172b6896d5ab2d01",
+ "rev": "917af390377c573932d84b5e31dd9f2c1b5c0f09",
"type": "github"
},
"original": {
@@ -75,11 +75,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1742937945,
- "narHash": "sha256-lWc+79eZRyvHp/SqMhHTMzZVhpxkRvthsP1Qx6UCq0E=",
+ "lastModified": 1749173751,
+ "narHash": "sha256-ENY3y3v6S9ZmLDDLI3LUT8MXmfXg/fSt2eA4GCnMVCE=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "d02d88f8de5b882ccdde0465d8fa2db3aa1169f7",
+ "rev": "ed29f002b6d6e5e7e32590deb065c34a31dc3e91",
"type": "github"
},
"original": {
@@ -91,11 +91,11 @@
},
"nixpkgs_2": {
"locked": {
- "lastModified": 1748929857,
- "narHash": "sha256-lcZQ8RhsmhsK8u7LIFsJhsLh/pzR9yZ8yqpTzyGdj+Q=",
+ "lastModified": 1750506804,
+ "narHash": "sha256-VLFNc4egNjovYVxDGyBYTrvVCgDYgENp5bVi9fPTDYc=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4",
+ "rev": "4206c4cb56751df534751b058295ea61357bbbaa",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index e3ae6d0..2605a20 100644
--- a/flake.nix
+++ b/flake.nix
@@ -34,6 +34,11 @@
./subprojects/packagefiles/libcue/composer_rem.diff
];
});
+ ffmpeg_6 = super.ffmpeg_6.overrideAttrs (oldAttrs: {
+ patches = oldAttrs.patches ++ [
+ ./env/nixpkgs-patches/libavcodec_mdec.c.patch
+ ];
+ });
})
];
forAllSupportedSystems = function:
@@ -60,6 +65,7 @@
];
}) {
inherit system;
+ inherit overlays;
config = {
allowUnfree = true;
android_sdk.accept_license = true;
@@ -194,6 +200,7 @@
perl
asciidoctor
pandoc
+ #makemkv
# mesa build
#buildPackages.mesa.cross_tools
#mesa.depsBuildBuild
@@ -205,7 +212,11 @@
#chafa
#rxvt-unicode
#st
+ #xfce.xfce4-terminal
# market research
+ #vlc
+ #mpv
+ #clapper
#mpd
#(ncmpcpp.override { visualizerSupport = true; clockSupport = false; })
#cmus
@@ -219,6 +230,7 @@
#kdePackages.elisa
#rhythmbox
#lollypop
+ #xfce.parole
];
buildInputs = packages.${system}.default.buildInputs;
shellHook = ''
diff --git a/meson.build b/meson.build
index d89ea77..fcd5ee3 100644
--- a/meson.build
+++ b/meson.build
@@ -33,7 +33,7 @@ no_video = meson.is_subproject()
#if not no_video
stela_opts = ['poll=inline', 'event-buffer=false', 'pause=true']
if is_linux
- stela_opts += [get_option('renderer-api') == 'vulkan' ? 'window=glfw' : 'window=wayland']
+ stela_opts += ['window=wayland']
elif is_android
stela_opts += ['window=glfm']
elif is_windows
diff --git a/meson_options.txt b/meson_options.txt
index 47ea575..4a52f07 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,14 +1,15 @@
+# Options
option('codecs', type: 'array', choices: ['ffmpeg', 'stb_image', 'spng', 'wuffs'], value: ['ffmpeg'])
-option('codec-hwaccel', type: 'feature', value: 'disabled')
+option('codec-hwaccel', type: 'feature', value: 'disabled', yield: true)
option('renderer', type: 'combo', choices: ['libplacebo', 'momo'], value: 'libplacebo')
-option('renderer-api', type: 'combo', choices: ['vulkan', 'dx11', 'gl', 'gl-rpi'], value: 'gl')
+option('renderer-api', type: 'combo', choices: ['vulkan', 'dx11', 'gl', 'gl-rpi'], value: 'vulkan', yield: true)
option('subtitles', type: 'feature', value: 'enabled')
+option('win32-compat', type: 'boolean', value: false, yield: true)
+option('tests', type: 'feature', value: 'auto', yield: true)
+# Executables ("Fruits")
option('portal', type: 'feature', value: 'disabled')
option('server', type: 'feature', value: 'enabled')
option('sink', type: 'feature', value: 'enabled')
-option('sink-only', type: 'boolean', value: false)
+option('sink-only', type: 'boolean', value: false, yield: true)
option('client', type: 'feature', value: 'disabled')
-
-option('win32-compat', type: 'boolean', value: false, yield: true)
-option('tests', type: 'feature', value: 'auto', yield: true)
diff --git a/scripts/run_valgrind.sh b/scripts/run_valgrind.sh
index bccc34c..c6ccd3d 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=definite ./src/fruits/cmv/cmv "$@"
+valgrind --leak-check=full --show-leak-kinds=all ./src/fruits/cmv/cmv "$@"
+#valgrind --leak-check=no --show-error-list=yes ./src/fruits/cmv/cmv "$@"
diff --git a/src/buffer/audio.c b/src/buffer/audio.c
index fd395e0..699b05e 100644
--- a/src/buffer/audio.c
+++ b/src/buffer/audio.c
@@ -12,7 +12,7 @@
#define BUFFER_SIZE 8.0
#define BUFFER_MARK_MIN 3.25 // Must be a most half of the buffer size.
-#define BUFFER_MARK_BUFFERED 0.25
+#define BUFFER_MARK_BUFFERED 0.35
#ifdef CAMU_AUDIO_BUFFER_FADE
#define FADE_STEP(fmt) (2.50f / (fmt)->sample_rate)
diff --git a/src/buffer/peak_buffer.c b/src/buffer/peak_buffer.c
index c1fe254..854595a 100644
--- a/src/buffer/peak_buffer.c
+++ b/src/buffer/peak_buffer.c
@@ -4,29 +4,29 @@
// We use ptrdiff_t here instead of nn_buffer's size_t just to map better to the types in audio_buffer.
-void camu_peak_buffer_init(struct camu_peak_buffer *buf, ptrdiff_t size)
+void camu_peak_buffer_init(struct camu_peak_buffer *peak, ptrdiff_t size)
{
- nn_buffer_init(&buf->buf);
- nn_buffer_ensure_space(&buf->buf, (size_t)size);
+ nn_buffer_init(&peak->buffer);
+ nn_buffer_ensure_space(&peak->buffer, (size_t)size);
}
-void camu_peak_buffer_push(struct camu_peak_buffer *buf, u8 *data, ptrdiff_t size)
+void camu_peak_buffer_push(struct camu_peak_buffer *peak, u8 *data, ptrdiff_t size)
{
- nn_buffer_append(&buf->buf, data, (size_t)size);
+ nn_buffer_append(&peak->buffer, data, (size_t)size);
}
-ptrdiff_t camu_peak_buffer_get_size(struct camu_peak_buffer *buf)
+ptrdiff_t camu_peak_buffer_get_size(struct camu_peak_buffer *peak)
{
- return (ptrdiff_t)nn_buffer_get_size(&buf->buf);
+ return (ptrdiff_t)peak->buffer.size;
}
-u8 *camu_peak_buffer_flush(struct camu_peak_buffer *buf)
+u8 *camu_peak_buffer_flush(struct camu_peak_buffer *peak)
{
- nn_buffer_set_size(&buf->buf, 0);
- return nn_buffer_get_ptr(&buf->buf, 0);
+ peak->buffer.size = 0;
+ return nn_buffer_get_ptr(&peak->buffer, 0);
}
-void camu_peak_buffer_free(struct camu_peak_buffer *buf)
+void camu_peak_buffer_free(struct camu_peak_buffer *peak)
{
- nn_buffer_free(&buf->buf);
+ nn_buffer_free(&peak->buffer);
}
diff --git a/src/buffer/peak_buffer.h b/src/buffer/peak_buffer.h
index 73465ec..2a68e9c 100644
--- a/src/buffer/peak_buffer.h
+++ b/src/buffer/peak_buffer.h
@@ -4,11 +4,11 @@
#include <nnwt/common.h>
struct camu_peak_buffer {
- struct nn_buffer buf;
+ struct nn_buffer buffer;
};
-void camu_peak_buffer_init(struct camu_peak_buffer *buf, ptrdiff_t size);
-void camu_peak_buffer_push(struct camu_peak_buffer *buf, u8 *data, ptrdiff_t size);
-ptrdiff_t camu_peak_buffer_get_size(struct camu_peak_buffer *buf);
-u8 *camu_peak_buffer_flush(struct camu_peak_buffer *buf);
-void camu_peak_buffer_free(struct camu_peak_buffer *buf);
+void camu_peak_buffer_init(struct camu_peak_buffer *peak, ptrdiff_t size);
+void camu_peak_buffer_push(struct camu_peak_buffer *peak, u8 *data, ptrdiff_t size);
+ptrdiff_t camu_peak_buffer_get_size(struct camu_peak_buffer *peak);
+u8 *camu_peak_buffer_flush(struct camu_peak_buffer *peak);
+void camu_peak_buffer_free(struct camu_peak_buffer *peak);
diff --git a/src/buffer/video.c b/src/buffer/video.c
index 9a33540..8bedbf6 100644
--- a/src/buffer/video.c
+++ b/src/buffer/video.c
@@ -10,10 +10,15 @@
#include "common.h"
#include "common_internal.h"
-#define BUFFER_MARK_LOW ((1.0 / 24.0) * 2)
-#define BUFFER_MARK_BUFFERED ((1.0 / 24.0) * 4) // Must be >1.
-#define BUFFER_MARK_HIGH ((1.0 / 24.0) * 6)
-#define BUFFER_MARK_RESET (BUFFER_MARK_HIGH * 2.0)
+// This used to scale based on avg_frame_duration, but that's actually
+// quite incompatible with very low frame rates.
+// MARK_LOW is considered directly after reading a frame, so in other words,
+// it will trigger at the point where there is about (LOW+1) frames left. Even at
+// something like 240fps that's still ~12 whole ms to uncork and produce a new frame.
+#define BUFFER_MARK_LOW 2
+#define BUFFER_MARK_BUFFERED 5 // Must be >1.
+#define BUFFER_MARK_HIGH 7
+#define BUFFER_MARK_RESET (BUFFER_MARK_HIGH * 2)
bool camu_video_buffer_init(struct camu_video_buffer *buf, struct camu_clock *clock)
{
@@ -70,7 +75,7 @@ bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_code
camu_video_format_copy(req, in);
if (fmt->width > 0 && fmt->height > 0 &&
#ifdef CAMU_VIDEO_BUFFER_REQUIRE_RGB
- (fmt->format != CAMU_PIXEL_FORMAT_RGBA && fmt->format != CAMU_PIXEL_FORMAT_RGB)
+ (fmt->format != CAMU_PIXEL_FORMAT_RGBA && fmt->format != CAMU_PIXEL_FORMAT_RGB32)
#else
(fmt->format == CAMU_PIXEL_FORMAT_PAL8)
#endif
@@ -79,8 +84,10 @@ bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_code
req->width = fmt->width;
req->height = fmt->height;
#ifdef CAMU_HAVE_FFMPEG
- const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt->format);
- req->format = (desc->flags & AV_PIX_FMT_FLAG_ALPHA) ? CAMU_PIXEL_FORMAT_RGBA : CAMU_PIXEL_FORMAT_RGB;
+ //const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get((enum AVPixelFormat)fmt->format);
+ //bool has_alpha = (desc->flags & AV_PIX_FMT_FLAG_ALPHA) != 0;
+ //req->format = CAMU_PIXEL_FORMAT_RGB32;
+ req->format = CAMU_PIXEL_FORMAT_RGBA;
buf->scaler = camu_ff_scaler_create();
if (!buf->scaler->init(buf->scaler, &buf->fmt)) {
// Scaler will be freed in video_buffer_free().
@@ -116,8 +123,7 @@ static void after_push_internal(struct camu_video_buffer *buf)
// If a reset where to happen from the this thread, the latest read()
// frame could be freed before it was used.
s32 count = buf->queue->count(buf->queue);
- f64 have = count * buf->avg_frame_duration;
- if (!buf->buffered && (buf->single_frame || have >= BUFFER_MARK_BUFFERED)) {
+ if (!buf->buffered && (buf->single_frame || count >= BUFFER_MARK_BUFFERED)) {
// Preserve order of: set flow -> flush -> callback, for single frames.
if (buf->single_frame) {
al_atomic_store(u8)(&buf->flow, FLUSHED, AL_ATOMIC_RELAXED);
@@ -125,9 +131,9 @@ static void after_push_internal(struct camu_video_buffer *buf)
}
buf->buffered = true;
buf->buffered_with_one_frame = count == 1;
- log_debug("Buffered (mark: %.2fs).", have);
+ log_debug("Buffered (mark: %.2fs).", count * buf->avg_frame_duration);
buf->callback(buf->userdata, CAMU_BUFFER_BUFFERED);
- } else if (have >= BUFFER_MARK_HIGH) {
+ } else if (count >= BUFFER_MARK_HIGH) {
buf->callback(buf->userdata, CAMU_BUFFER_CORK);
}
}
@@ -157,8 +163,8 @@ static bool push_av_frame_internal(struct camu_video_buffer *buf, AVFrame *frame
void camu_video_buffer_push(struct camu_video_buffer *buf, struct camu_codec_frame *frame)
{
- // A single frame will be sent again after a seek, discard it here (for now).
if (buf->single_frame && buf->buffered) {
+ log_warn("Unexpected duplicate frame received.");
camu_codec_frame_discard(frame);
return;
}
@@ -198,10 +204,9 @@ void camu_video_buffer_flush(struct camu_video_buffer *buf)
buf->queue->flush(buf->queue);
if (!buf->buffered) {
s32 count = buf->queue->count(buf->queue);
- f64 have = count * buf->avg_frame_duration;
buf->buffered = true;
buf->buffered_with_one_frame = count == 1;
- log_debug("Buffered (mark: %.2fs).", have);
+ log_debug("Buffered (mark: %.2fs).", count * buf->avg_frame_duration);
buf->callback(buf->userdata, CAMU_BUFFER_BUFFERED);
}
}
@@ -247,10 +252,10 @@ bool camu_video_buffer_read(struct camu_video_buffer *buf, void *out, bool *weig
al_atomic_store(u8)(&buf->flow, SIGNALED, AL_ATOMIC_RELEASE);
} else if (flow == FLOWING) {
// Never attempt to uncork if flow = FLUSHED.
- f64 have = buf->queue->count(buf->queue) * buf->avg_frame_duration;
- if (have <= BUFFER_MARK_LOW) {
+ s32 count = buf->queue->count(buf->queue);
+ if (count <= BUFFER_MARK_LOW) {
buf->callback(buf->userdata, CAMU_BUFFER_UNCORK);
- } else if (have >= BUFFER_MARK_RESET) {
+ } else if (count >= BUFFER_MARK_RESET) {
log_warn("Buffer overflow, resetting.");
ret = CAMU_QUEUE_ERR; // Don't use the frame written to out.
buf->queue->reset(buf->queue);
diff --git a/src/buffer/video_null.h b/src/buffer/video_null.h
index 15d2aba..06b284c 100644
--- a/src/buffer/video_null.h
+++ b/src/buffer/video_null.h
@@ -7,6 +7,7 @@
#include "clock.h"
struct camu_video_buffer {
+ struct camu_codec_stream *stream;
bool single_frame;
f64 avg_frame_duration;
#ifdef CAMU_SCREEN_THREADED
@@ -37,9 +38,8 @@ static bool camu_video_buffer_init(struct camu_video_buffer *buf, struct camu_cl
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;
+ buf->stream = stream;
return true;
}
diff --git a/src/cache/backings/memory.c b/src/cache/backings/memory.c
index 6b3da62..063c1c5 100644
--- a/src/cache/backings/memory.c
+++ b/src/cache/backings/memory.c
@@ -7,7 +7,7 @@ static void ensure_allocated(struct cch_backing_memory *mem, off_t amount)
if (amount > mem->expanse) mem->expanse = amount;
if (amount > mem->alloc) {
mem->alloc = al_next_power_of_two(amount);
- mem->data = al_realloc(mem->data, mem->alloc);
+ mem->data = (u8 *)al_realloc(mem->data, mem->alloc);
}
}
@@ -107,7 +107,7 @@ struct cch_backing *cch_backing_memory_create(size_t size)
mem->backing.get_size_if_known = memory_backing_get_size_if_known;
mem->backing.finalize = memory_backing_finalize;
mem->backing.free = memory_backing_free;
- if (size > 0) mem->data = al_malloc(size);
+ if (size > 0) mem->data = (u8 *)al_malloc(size);
else mem->data = NULL;
mem->alloc = size;
nn_mutex_init(&mem->mutex);
diff --git a/src/cache/handlers/cdio.c b/src/cache/handlers/cdio.c
index 82600dc..7c83523 100644
--- a/src/cache/handlers/cdio.c
+++ b/src/cache/handlers/cdio.c
@@ -205,7 +205,7 @@ struct cch_entry *cch_handler_cdio_create(void)
al_atomic_store(s32)(&cdio->running, 0, AL_ATOMIC_RELAXED);
nn_buffer_init(&cdio->buffer);
nn_buffer_ensure_space(&cdio->buffer, BYTES_PER_STEP);
- nn_buffer_set_size(&cdio->buffer, BYTES_PER_STEP);
+ cdio->buffer.size = BYTES_PER_STEP;
cch_threaded_waits_init(&cdio->waits);
return entry;
}
diff --git a/src/codec/codec.h b/src/codec/codec.h
index 9515220..3440e9f 100644
--- a/src/codec/codec.h
+++ b/src/codec/codec.h
@@ -60,6 +60,7 @@ enum {
enum {
CAMU_PIXEL_FORMAT_RGBA = AV_PIX_FMT_RGBA,
CAMU_PIXEL_FORMAT_RGB = AV_PIX_FMT_RGB8,
+ CAMU_PIXEL_FORMAT_RGB32 = AV_PIX_FMT_RGB32,
CAMU_PIXEL_FORMAT_GREYA = AV_PIX_FMT_GRAY8A,
CAMU_PIXEL_FORMAT_GREY = AV_PIX_FMT_GRAY8,
CAMU_PIXEL_FORMAT_PAL8 = AV_PIX_FMT_PAL8
@@ -295,7 +296,7 @@ static inline void camu_audio_format_copy(struct camu_audio_format *dest, struct
static inline const char *camu_audio_format_name(s32 format)
{
#ifdef CAMU_HAVE_FFMPEG
- return av_get_sample_fmt_name(format);
+ return av_get_sample_fmt_name((enum AVSampleFormat)format);
#else
(void)format;
return "(undefined)";
@@ -305,7 +306,7 @@ static inline const char *camu_audio_format_name(s32 format)
static inline size_t camu_audio_format_bytes_per_sample(struct camu_audio_format *fmt)
{
#ifdef CAMU_HAVE_FFMPEG
- return (size_t)av_get_bytes_per_sample(fmt->format);
+ return (size_t)av_get_bytes_per_sample((enum AVSampleFormat)fmt->format);
#else
switch (fmt->format) {
case CAMU_SAMPLE_FORMAT_U8:
@@ -375,7 +376,7 @@ static inline void camu_video_format_copy(struct camu_video_format *dest, struct
static inline const char *camu_pixel_format_name(s32 format)
{
#ifdef CAMU_HAVE_FFMPEG
- return av_get_pix_fmt_name(format);
+ return av_get_pix_fmt_name((enum AVPixelFormat)format);
#else
switch (format) {
case CAMU_PIXEL_FORMAT_RGBA: return "rgba";
diff --git a/src/codec/ffmpeg/decoder.c b/src/codec/ffmpeg/decoder.c
index f201ea3..9693dd9 100644
--- a/src/codec/ffmpeg/decoder.c
+++ b/src/codec/ffmpeg/decoder.c
@@ -7,19 +7,47 @@
#if !defined CAMU_SINK_NO_VIDEO && defined CAMU_FF_DECODER_HWACCEL
#include "../../render/renderer.h"
-#endif
-
-#ifdef CAMU_FF_DECODER_HWACCEL
+static const char *hwdevces[] = {
#if defined STELA_API_VULKAN
-static const char *hwdevces[] = { "vulkan" };
+ "vulkan",
#else
#if defined NAUNET_ON_WINDOWS
-static const char *hwdevces[] = { "d3d11va" };
+ "d3d11va",
#else
-static const char *hwdevces[] = { "vaapi" };
+ "vaapi",
+#endif
#endif
+};
#endif
+static bool alloc_codec_context_internal(struct camu_ff_decoder *av, const AVCodec *codec, AVCodecParameters *codecpar)
+{
+ if (!(av->codec_context = avcodec_alloc_context3(codec))) {
+ log_error("Failed to alloc codec context.");
+ return false;
+ }
+
+ if (avcodec_parameters_to_context(av->codec_context, codecpar) < 0) {
+ log_error("Failed to copy codec parameters.");
+ return false;
+ }
+
+ return true;
+}
+
+static bool open_avcodec_internal(struct camu_ff_decoder *av, const AVCodec *codec)
+{
+ AVDictionary *opts = NULL;
+ s32 ret = avcodec_open2(av->codec_context, codec, &opts);
+ if (ret < 0) {
+ log_error("Failed to open codec %s (%s).", codec->name, av_err2str(ret));
+ return false;
+ }
+ av_dict_free(&opts);
+ return true;
+}
+
+#ifdef CAMU_FF_DECODER_HWACCEL
static s32 get_buffer2(AVCodecContext *context, AVFrame *pic, s32 flags)
{
struct camu_ff_decoder *av = (struct camu_ff_decoder *)context->opaque;
@@ -63,19 +91,36 @@ static enum AVPixelFormat get_hw_format(AVCodecContext *context, const enum AVPi
{
struct camu_ff_decoder *av = (struct camu_ff_decoder *)context->opaque;
+ const AVCodec *codec = av->codec_context->codec;
+ const char *hwdevice_name = av_hwdevice_get_type_name(av->hw_device_type);
+
const enum AVPixelFormat *fmt = pix_fmts;
for (; *fmt != AV_PIX_FMT_NONE; fmt++) {
if (*fmt == av->hw_pix_fmt) {
if (av->use_frames_context) {
init_hwframe_context(av, av->codec_context, av->hw_context);
}
- return *fmt;
+ log_info("Using %s hardware decoding.", hwdevice_name);
+ goto out;
}
}
- log_error("Failed to get HW surface format.");
+ log_error("Failed to get %s HW surface format.", hwdevice_name);
+
+ // Remake AVCodecContext as a software decoder.
+ AVCodecParameters *codecpar = avcodec_parameters_alloc();
+ avcodec_parameters_from_context(codecpar, av->codec_context);
+ av->errored_hw_context = av->codec_context;
+ alloc_codec_context_internal(av, codec, codecpar);
+ // Note about thread_type choice in ff_decoder_init().
+ av->codec_context->thread_type = FF_THREAD_FRAME;
+ av->codec_context->thread_count = av->thread_count;
+ if (open_avcodec_internal(av, codec)) {
+ log_info("Software fallback, using %i threads for decoder.", av->thread_count);
+ }
- return avcodec_default_get_format(context, pix_fmts);
+out:
+ return *fmt;
}
static s32 init_hwdevice_context(struct camu_ff_decoder *av, AVCodecContext *context)
@@ -90,9 +135,7 @@ static s32 init_hwdevice_context(struct camu_ff_decoder *av, AVCodecContext *con
context->hw_device_ctx = av_buffer_ref(av->hw_context);
// Note that context->extra_hw_frames has the ability to cause corruption.
- context->extra_hw_frames = 18;
-
- log_info("Using %s hardware decoding.", av_hwdevice_get_type_name(av->hw_device_type));
+ //context->extra_hw_frames = 18;
return ret;
}
@@ -139,7 +182,7 @@ static bool get_hwdevice_config(struct camu_ff_decoder *av, const AVCodec *codec
return false;
}
-static bool collect_supported_hwaccels(struct camu_ff_decoder *av)
+static void collect_supported_hwaccels(struct camu_ff_decoder *av)
{
const AVCodec *codec;
void *iter = NULL;
@@ -149,15 +192,10 @@ static bool collect_supported_hwaccels(struct camu_ff_decoder *av)
al_array_push(av->supported_hw_codecs, codec);
}
}
-
enum AVHWDeviceType hw_device_type = AV_HWDEVICE_TYPE_NONE;
while ((hw_device_type = av_hwdevice_iterate_types(hw_device_type)) != AV_HWDEVICE_TYPE_NONE) {
al_array_push(av->supported_hw_devices, hw_device_type);
}
-
- av->hw_device_type = AV_HWDEVICE_TYPE_NONE;
-
- return true;
}
#endif
@@ -165,6 +203,7 @@ static void close_internal(struct camu_ff_decoder *av)
{
if (av->codec_context) avcodec_free_context(&av->codec_context);
#ifdef CAMU_FF_DECODER_HWACCEL
+ if (av->errored_hw_context) avcodec_free_context(&av->errored_hw_context);
al_array_free(av->supported_hw_codecs);
al_array_free(av->supported_hw_devices);
#endif
@@ -177,6 +216,7 @@ static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *rend
av->codec_context = NULL;
#ifdef CAMU_FF_DECODER_HWACCEL
+ av->errored_hw_context = NULL;
al_array_init(av->supported_hw_codecs);
al_array_init(av->supported_hw_devices);
#endif
@@ -188,21 +228,21 @@ static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *rend
goto err;
}
-#ifdef CAMU_FF_DECODER_HWACCEL
- bool attempt_hwdec = stream->duration > 0 && codecpar->codec_type == AVMEDIA_TYPE_VIDEO;
+ bool is_video = codecpar->codec_type == AVMEDIA_TYPE_VIDEO && stream->duration > 0;
- if (attempt_hwdec) {
+#ifdef CAMU_FF_DECODER_HWACCEL
+ if (is_video) {
collect_supported_hwaccels(av);
-
+ av->hw_device_type = AV_HWDEVICE_TYPE_NONE;
av->hw_pix_fmt = AV_PIX_FMT_NONE;
-
for (u32 i = 0; i < ARRAY_SIZE(hwdevces); i++) {
av->hw_device_type = hw_device_supported_by_name(av, hwdevces[i]);
- if (get_hwdevice_config(av, codec, av->hw_device_type)) {
+ if (!get_hwdevice_config(av, codec, av->hw_device_type)) {
+ av->hw_device_type = AV_HWDEVICE_TYPE_NONE;
+ } else {
break;
}
}
-
if (av->hw_device_type == AV_HWDEVICE_TYPE_NONE) {
const AVCodec *hw_codec;
al_array_foreach(av->supported_hw_codecs, i, hw_codec) {
@@ -216,27 +256,18 @@ static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *rend
}
}
}
-
if (av->hw_device_type == AV_HWDEVICE_TYPE_NONE) {
log_warn("Hardware accelerated video decoding of %s not supported.", codec->name);
}
}
#endif
- av->codec_context = avcodec_alloc_context3(codec);
- if (!av->codec_context) {
- log_error("Failed to alloc codec context.");
- goto err;
- }
-
- if (avcodec_parameters_to_context(av->codec_context, codecpar) < 0) {
- log_error("Failed to copy codec parameters.");
+ if (!alloc_codec_context_internal(av, codec, codecpar)) {
goto err;
}
#ifdef CAMU_FF_DECODER_HWACCEL
- if (attempt_hwdec && av->hw_device_type != AV_HWDEVICE_TYPE_NONE) {
- //av->codec_context->codec_id = codec->id;
+ if (is_video && av->hw_device_type != AV_HWDEVICE_TYPE_NONE) {
if (init_hwdevice_context(av, av->codec_context) == 0) {
av->renderer = renderer;
av->codec_context->opaque = av;
@@ -250,37 +281,30 @@ static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *rend
(void)renderer;
#endif
+ if (is_video) {
+ av->thread_count = MIN(6, MAX(1, av_cpu_count() / 2));
#ifdef CAMU_FF_DECODER_HWACCEL
- if ((!attempt_hwdec || av->hw_device_type == AV_HWDEVICE_TYPE_NONE) && stream->duration > 0) {
-#else
- if (codecpar->codec_type == AVMEDIA_TYPE_VIDEO && stream->duration > 0) {
+ if (av->hw_device_type == AV_HWDEVICE_TYPE_NONE) {
#endif
- s32 cpus = 0;
- if (av->codec_context->codec_type == AVMEDIA_TYPE_VIDEO) {
- cpus = av_cpu_count();
- cpus = MIN(6, MAX(1, cpus / 2));
- }
- if (cpus > 0) {
- av->codec_context->thread_count = cpus;
// FF_THREAD_FRAME or FF_THREAD_SLICE.
// Both modes can get choked on certain videos, sometimes SLICE can squeeze
// out a little more performance but a higher thread_count with FRAME seems
// like the safest default.
av->codec_context->thread_type = FF_THREAD_FRAME;
- log_info("Using %i threads for decoder.", cpus);
+ av->codec_context->thread_count = av->thread_count;
+ log_info("Using %i threads for decoder.", av->thread_count);
+#ifdef CAMU_FF_DECODER_HWACCEL
}
+#endif
}
- AVDictionary *opts = NULL;
- s32 ret = avcodec_open2(av->codec_context, codec, &opts);
- if (ret < 0) {
- log_error("Failed to open codec %s (%s).", codec->name, av_err2str(ret));
+ if (!open_avcodec_internal(av, codec)) {
goto err;
}
- const char *long_name = codec->long_name ? codec->long_name : codec->name;
- s64 kbps = (codecpar->bit_rate > 0) ? codecpar->bit_rate / 1000 : 0;
- log_info("Codec: %s (%s) %lldkbps.", codec->name, long_name, kbps);
+ log_info("Codec: %s (%s) %lldkbps.", codec->name,
+ codec->long_name ? codec->long_name : codec->name,
+ (av->codec_context->bit_rate > 0) ? av->codec_context->bit_rate / 1000 : 0);
av->callback = callback;
av->userdata = userdata;
diff --git a/src/codec/ffmpeg/decoder.h b/src/codec/ffmpeg/decoder.h
index 4797060..85b88dc 100644
--- a/src/codec/ffmpeg/decoder.h
+++ b/src/codec/ffmpeg/decoder.h
@@ -11,6 +11,7 @@
struct camu_ff_decoder {
struct camu_decoder dec;
AVCodecContext *codec_context;
+ s32 thread_count;
#ifdef CAMU_FF_DECODER_HWACCEL
struct camu_renderer *renderer;
array(const AVCodec *) supported_hw_codecs;
@@ -19,6 +20,7 @@ struct camu_ff_decoder {
enum AVHWDeviceType hw_device_type;
enum AVPixelFormat hw_pix_fmt;
bool use_frames_context;
+ AVCodecContext *errored_hw_context;
#endif
void (*callback)(void *, struct camu_codec_frame *);
void *userdata;
diff --git a/src/codec/ffmpeg/demuxer.c b/src/codec/ffmpeg/demuxer.c
index a5821a2..6560e0e 100644
--- a/src/codec/ffmpeg/demuxer.c
+++ b/src/codec/ffmpeg/demuxer.c
@@ -80,6 +80,7 @@ static bool ff_demuxer_init(struct camu_demuxer *demux, struct cch_handle *handl
s64 default_duration = (av->format_context->duration < 0) ? 0 : av->format_context->duration;
av->duration = 0;
+ // @TODO: Handle no-duration video.
#define GUESS_STREAM_IS_IMAGE(stream) \
((stream->duration >= 0 && stream->nb_frames <= 1 && \
(stream->avg_frame_rate.den == 0 && stream->r_frame_rate.den > 0)) || \
@@ -87,7 +88,6 @@ static bool ff_demuxer_init(struct camu_demuxer *demux, struct cch_handle *handl
for (u32 i = 0; i < av->format_context->nb_streams; i++) {
AVStream *stream = av->format_context->streams[i];
- enum AVMediaType type = stream->codecpar->codec_type;
#ifdef CAMU_OLD_FFMPEG
// This is probably not correct.
if (stream->codecpar->channel_layout == 0) {
@@ -95,6 +95,7 @@ static bool ff_demuxer_init(struct camu_demuxer *demux, struct cch_handle *handl
stream->codecpar->channels = 2;
}
#endif
+ enum AVMediaType type = stream->codecpar->codec_type;
u64 duration = 0;
if (type == AVMEDIA_TYPE_AUDIO || type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_SUBTITLE) {
// Try to detect attached images.
diff --git a/src/codec/ffmpeg/encoder.c b/src/codec/ffmpeg/encoder.c
index 5d90785..641acc5 100644
--- a/src/codec/ffmpeg/encoder.c
+++ b/src/codec/ffmpeg/encoder.c
@@ -39,7 +39,7 @@ bool camu_ff_encoder_init(struct camu_ff_encoder *enc, char *encoder_name, char
enc->codec_context->time_base = (AVRational){ 1, enc->codec_context->sample_rate };
enc->codec_context->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
- enc->output_format = av_guess_format(ext, NULL, NULL);
+ enc->output_format = av_guess_format(encoder_name, ext, NULL);
if (enc->output_format && enc->output_format->flags & AVFMT_GLOBALHEADER) {
enc->codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
@@ -52,21 +52,23 @@ bool camu_ff_encoder_init(struct camu_ff_encoder *enc, char *encoder_name, char
av_dict_set(&opts, "compression_level", "10", 0);
av_dict_set(&opts, "frame_duration", "20", 0);
av_dict_set(&opts, "application", "audio", 0);
-
if (avcodec_open2(enc->codec_context, codec, &opts) < 0) {
log_error("Failed to open codec.");
av_dict_free(&opts);
goto err;
}
-
av_dict_free(&opts);
+ log_info("Encoder: %s (%s) %sbps.", codec->name,
+ codec->long_name ? codec->long_name : codec->name, kbps);
+
AVCodecContext *out_codec = enc->codec_context;
AVFrame *frame = av_frame_alloc();
frame->format = out_codec->sample_fmt;
frame->sample_rate = out_codec->sample_rate;
camu_copy_channel_layout(&AV_CODECPAR_CHANNEL_LAYOUT(frame), &AV_CODECPAR_CHANNEL_LAYOUT(out_codec));
- av_samples_alloc(frame->data, NULL, AV_CODECPAR_CHANNELS(out_codec), SAMPLES_PER_FRAME, frame->format, 1);
+ av_samples_alloc(frame->data, NULL, AV_CODECPAR_CHANNELS(out_codec),
+ SAMPLES_PER_FRAME, (enum AVSampleFormat)frame->format, 1);
frame->nb_samples = SAMPLES_PER_FRAME;
frame->pts = 0;
diff --git a/src/codec/ffmpeg/packet_ext.c b/src/codec/ffmpeg/packet_ext.c
index a1a6a5e..7dcda88 100644
--- a/src/codec/ffmpeg/packet_ext.c
+++ b/src/codec/ffmpeg/packet_ext.c
@@ -114,7 +114,7 @@ void nn_packet_read_av_codec_parameters(struct nn_packet *packet, AVCodecParamet
NNWT_PACKET_READ_TYPE(packet, enum AVCodecID, codecpar->codec_id);
NNWT_PACKET_READ_TYPE(packet, u32, codecpar->codec_tag);
NNWT_PACKET_READ_TYPE(packet, s32, codecpar->extradata_size);
- codecpar->extradata = av_malloc(codecpar->extradata_size);
+ codecpar->extradata = (u8 *)av_malloc(codecpar->extradata_size);
u8 *extradata;
NNWT_PACKET_READ_DATA(packet, codecpar->extradata_size, extradata);
al_memcpy(codecpar->extradata, extradata, codecpar->extradata_size);
diff --git a/src/codec/ffmpeg/resampler.c b/src/codec/ffmpeg/resampler.c
index 48cca8e..b141681 100644
--- a/src/codec/ffmpeg/resampler.c
+++ b/src/codec/ffmpeg/resampler.c
@@ -23,8 +23,8 @@ static bool ff_resampler_init(struct camu_resampler *resamp, struct camu_resampl
#endif
av_opt_set_int(swr, "in_sample_rate", fmt->in.sample_rate, 0);
av_opt_set_int(swr, "out_sample_rate", fmt->req.sample_rate, 0);
- av_opt_set_sample_fmt(swr, "in_sample_fmt", fmt->in.format, 0);
- av_opt_set_sample_fmt(swr, "out_sample_fmt", fmt->req.format, 0);
+ av_opt_set_sample_fmt(swr, "in_sample_fmt", (enum AVSampleFormat)fmt->in.format, 0);
+ av_opt_set_sample_fmt(swr, "out_sample_fmt", (enum AVSampleFormat)fmt->req.format, 0);
#ifdef CAMU_HAVE_SOXR
// Slower but more accurate resampler.
@@ -54,7 +54,7 @@ static inline void alloc_sample_data(struct camu_ff_resampler *av, s32 sample_co
if (av->sample_count >= sample_count) return;
free_sample_data(av);
s32 nb_channels = av->fmt.channel_count;
- av_samples_alloc(av->data, NULL, nb_channels, sample_count, av->fmt.format, 32);
+ av_samples_alloc(av->data, NULL, nb_channels, sample_count, (enum AVSampleFormat)av->fmt.format, 32);
av->sample_count = sample_count;
}
diff --git a/src/codec/ffmpeg/scaler.c b/src/codec/ffmpeg/scaler.c
index 93bbac6..0cd7fcb 100644
--- a/src/codec/ffmpeg/scaler.c
+++ b/src/codec/ffmpeg/scaler.c
@@ -41,16 +41,17 @@ static bool ff_scaler_init(struct camu_scaler *scale, struct camu_scaler_format
av->fmt = *fmt;
- s32 flags = SWS_BITEXACT;
- av->scaler_context = sws_getContext(fmt->in.width, fmt->in.height, fmt->in.format,
- fmt->req.width, fmt->req.height, fmt->req.format, flags, NULL, NULL, NULL);
+ av->scaler_context = sws_getContext(
+ fmt->in.width, fmt->in.height, (enum AVPixelFormat)fmt->in.format,
+ fmt->req.width, fmt->req.height, (enum AVPixelFormat)fmt->req.format,
+ SWS_BITEXACT, NULL, NULL, NULL);
if (!av->scaler_context) {
log_error("Failed to create scaler context.");
return false;
}
struct camu_codec_frame *frame = &av->frame;
- frame->av.frame = alloc_picture(fmt->req.format, fmt->req.width, fmt->req.height);
+ frame->av.frame = alloc_picture((enum AVPixelFormat)fmt->req.format, fmt->req.width, fmt->req.height);
if (!frame->av.frame) {
log_error("Failed to allocate frame.");
return false;
diff --git a/src/fruits/cmc/cmc.c b/src/fruits/cmc/cmc.c
index 0529f81..1baae8f 100644
--- a/src/fruits/cmc/cmc.c
+++ b/src/fruits/cmc/cmc.c
@@ -214,7 +214,7 @@ static void client_callback(void *userdata, u8 op, void *opaque)
u32 size = nn_packet_read_u32(packet);
u8 *ptr;
NNWT_PACKET_READ_DATA(packet, size, ptr);
- entry->raw.data = al_malloc(size);
+ entry->raw.data = (u8 *)al_malloc(size);
al_memcpy(entry->raw.data, ptr, size);
entry->raw.sample_count = nn_packet_read_u32(packet);
cmc_ui_queue_render(&c->ui);
@@ -269,7 +269,7 @@ s32 main(s32 argc, char *argv[])
al_array_init(c.cli.args);
if (!parse_command_line(argc, argv)) {
- return EXIT_FAILURE;
+ goto err;
}
nn_event_loop_init(&c.loop);
@@ -281,12 +281,18 @@ s32 main(s32 argc, char *argv[])
c.client.callback = client_callback;
c.client.userdata = &c;
if (!camu_client_login(&c.client, &al_str_c("andrew"), &c.loop, CAMU_TEST_TYPE, &CAMU_TEST_ADDR, CAMU_PORT)) {
- return EXIT_FAILURE;
+ goto err;
}
nn_event_loop_run(&c.loop);
cmc_ui_close(&c.ui);
+ nn_common_close();
+
return EXIT_SUCCESS;
+err:
+ nn_common_close();
+
+ return EXIT_FAILURE;
}
diff --git a/src/fruits/cmv/cmv.c b/src/fruits/cmv/cmv.c
index 727261b..6875dd1 100644
--- a/src/fruits/cmv/cmv.c
+++ b/src/fruits/cmv/cmv.c
@@ -232,7 +232,9 @@ s32 wmain(s32 argc, wchar_t **argv)
s32 main(s32 argc, char *argv[])
#endif
{
- if (!nn_common_init("cmv_main") || !stl_global_init(false)) {
+ if (!nn_common_init("cmv_main")) return EXIT_FAILURE;
+ if (!stl_global_init(false)) {
+ nn_common_close();
return EXIT_FAILURE;
}
diff --git a/src/liana/common.h b/src/liana/common.h
deleted file mode 100644
index 6f70f09..0000000
--- a/src/liana/common.h
+++ /dev/null
@@ -1 +0,0 @@
-#pragma once
diff --git a/src/liana/handlers/cdio_client.c b/src/liana/handlers/cdio_client.c
index 6d61e59..af93dcc 100644
--- a/src/liana/handlers/cdio_client.c
+++ b/src/liana/handlers/cdio_client.c
@@ -24,7 +24,7 @@ static bool cdio_client_handle_packet(struct lia_client_handler *handler, struct
frame->audio.sample_count = nn_packet_read_s32(packet);
struct nn_buffer buffer;
nn_packet_read_buffer(packet, &buffer);
- frame->data = al_malloc(buffer.size);
+ frame->data = (u8 *)al_malloc(buffer.size);
al_memcpy(frame->data, nn_buffer_get_ptr(&buffer, 0), buffer.size);
cdio->handler.callback(cdio->handler.userdata, LIANA_CLIENT_DATA, cdio->handler.stream, frame);
return true;
diff --git a/src/liana/handlers/cdio_server.c b/src/liana/handlers/cdio_server.c
index e1d574d..0fafae7 100644
--- a/src/liana/handlers/cdio_server.c
+++ b/src/liana/handlers/cdio_server.c
@@ -87,7 +87,7 @@ static void cdio_server_write_packet(struct lia_server_handler *handler, struct
nn_packet_write_f64(packet, cdio->pts);
s32 sample_count = ret / (camu_audio_format_bytes_per_sample(&cdio->fmt) * cdio->fmt.channel_count);
nn_packet_write_s32(packet, sample_count);
- nn_buffer_set_size(&cdio->buffer, ret);
+ cdio->buffer.size = ret;
nn_packet_write_buffer(packet, &cdio->buffer);
cdio->pts += camu_audio_format_samples_to_sec(&cdio->fmt, sample_count);
}
diff --git a/src/liana/handlers/codec.h b/src/liana/handlers/codec.h
index 696cbb8..be43f63 100644
--- a/src/liana/handlers/codec.h
+++ b/src/liana/handlers/codec.h
@@ -5,6 +5,12 @@
#include "../server.h"
#include "../handler.h"
+// FFmpeg Independence:
+// - [ ] CAMU_CODEC_* only for explicitly non-ffmpeg codecs.
+// - [ ] Map between av_guess_format() and CAMU_CODEC_
+// - [ ] Runtime selection.
+// - [ ] Audio support.
+
#ifdef LIANA_SERVER
struct lia_codec_server {
struct lia_server_handler handler;
diff --git a/src/liana/handlers/codec_client.c b/src/liana/handlers/codec_client.c
index b85fc43..267187f 100644
--- a/src/liana/handlers/codec_client.c
+++ b/src/liana/handlers/codec_client.c
@@ -39,8 +39,7 @@ static bool codec_client_init(struct lia_client_handler *handler, struct camu_re
#ifdef CAMU_HAVE_FFMPEG
static bool push_av_packet(struct lia_codec_client *codec, AVPacket *pkt)
{
- s32 ret = codec->dec->push_av_packet(codec->dec, pkt);
- return ret == CAMU_OK;
+ return codec->dec->push_av_packet(codec->dec, pkt) == CAMU_OK;
}
static void passthrough_subtitle(struct lia_codec_client *codec, AVPacket *pkt)
@@ -73,7 +72,7 @@ static bool codec_client_handle_packet(struct lia_client_handler *handler, struc
break;
#ifdef CAMU_HAVE_FFMPEG
case CAMU_FFMPEG_COMPAT:
- codec->dec->push_av_packet(codec->dec, NULL);
+ push_av_packet(codec, NULL);
break;
#endif
}
@@ -117,6 +116,9 @@ static bool codec_client_handle_packet(struct lia_client_handler *handler, struc
}
if (codec->dec) {
success = push_av_packet(codec, pkt);
+ if (!success) {
+ success = push_av_packet(codec, pkt);
+ }
} else {
passthrough_subtitle(codec, pkt);
success = true;
diff --git a/src/liana/handlers/dvd_server.c b/src/liana/handlers/dvd_server.c
index f564325..5a9c793 100644
--- a/src/liana/handlers/dvd_server.c
+++ b/src/liana/handlers/dvd_server.c
@@ -4,7 +4,7 @@
static s32 dvd_stream_read(void *userdata, void *buffer, s32 size)
{
struct lia_dvd_server *dvd = (struct lia_dvd_server *)userdata;
- return cch_handle_read(dvd->handle, buffer, size);
+ return cch_handle_read(dvd->handle, (u8 *)buffer, size);
}
static s32 dvd_stream_seek(void *userdata, u64 pos)
diff --git a/src/liana/list.c b/src/liana/list.c
index 2a1458f..610ee80 100644
--- a/src/liana/list.c
+++ b/src/liana/list.c
@@ -456,8 +456,8 @@ static bool handle_end(struct lia_list *list, u32 id, u32 reset_id)
// Looping:
// - Main issue is rolling back an entry that skipped onto queued
- // before it's looping state was synced.
- // - Maybe we can track which sink END comes from.
+ // before it's looping state was synced. Maybe we can track which
+ // sink END comes from.
if (reset_id != entry->reset_id) {
log_warn("Got end() with out of order or incorrect reset id, ignoring.");
@@ -815,7 +815,8 @@ void lia_list_clear(struct lia_list *list)
void lia_list_close(struct lia_list *list)
{
- // @TODO: Consider sinks being in use. Wait for list->sinks to be empty?
+ // @TODO: Consider sinks being in use.
+ // Wait for list->sinks to be empty?
struct lia_list_entry *entry;
al_array_foreach(list->entries, i, entry) {
entry_unload(list, entry);
diff --git a/src/liana/vcr.c b/src/liana/vcr.c
index 228d758..352fb03 100644
--- a/src/liana/vcr.c
+++ b/src/liana/vcr.c
@@ -66,7 +66,7 @@ static nn_thread_result NNWT_THREADCALL vcr_track_thread(void *userdata)
nn_thread_set_priority(NNWT_THREAD_SCHED_FIFO, 32);
struct lia_vcr_track *track = (struct lia_vcr_track *)userdata;
struct lia_vcr *vcr = track->vcr;
- const char thread_name[16]; // 16 = limit.
+ const char thread_name[16] = "\0"; // 16 = limit.
al_snprintf((char *)thread_name, sizeof(thread_name), "vcr:%hu", vcr->node_id);
nn_thread_set_name(thread_name);
@@ -298,7 +298,7 @@ void lia_vcr_push_packet(struct lia_vcr *vcr, struct nn_packet *packet)
break;
}
case LIANA_PACKET_EOF:
- case LIANA_PACKET_ERROR:
+ case LIANA_PACKET_ERROR: {
al_array_foreach(vcr->tracks, i, track) {
nn_packet_cache_send_packet(&track->cache, NULL);
}
@@ -311,6 +311,7 @@ void lia_vcr_push_packet(struct lia_vcr *vcr, struct nn_packet *packet)
log_warn("Forcing EOF because we got an error packet.");
}
break;
+ }
default:
log_warn("Erroneous packet.");
break;
@@ -332,8 +333,8 @@ void lia_vcr_uncork(struct lia_vcr_track *track)
{
if (al_atomic_load(s32)(&track->state, AL_ATOMIC_RELAXED) != VCR_TRACK_STOPPED) {
// We will get here during normal operation. Early returning is historically
- // tricky in vcr_uncork(). If I'm understanding correctly, asserting that cond_is_waiting()
- // just below means we are safe.
+ // tricky in vcr_uncork(). If I'm understanding correctly, asserting that
+ // cond_is_waiting() just below means we are safe.
return;
}
// Lock before setting track->state to avoid a race with cork().
diff --git a/src/libsink/desktop.c b/src/libsink/desktop.c
index a2d02c9..6ef0562 100644
--- a/src/libsink/desktop.c
+++ b/src/libsink/desktop.c
@@ -239,7 +239,7 @@ bool camu_desktop_connect(struct camu_desktop *c, str *name, struct nn_event_loo
bool camu_desktop_tick(struct camu_desktop *c)
{
bool force;
- if (!c->should_quit && camu_screen_tick(&c->scr, &force)) {
+ if (camu_screen_tick(&c->scr, &force) && !c->should_quit) {
c->renderer->render(c->renderer, &c->scr, force);
}
return !c->should_quit;
@@ -260,6 +260,7 @@ void camu_desktop_stop(struct camu_desktop *c)
void camu_desktop_free(struct camu_desktop *c)
{
camu_sink_free(&c->sink);
+ al_str_free(&c->sink.default_list);
camu_mixer_close(&c->mixer);
c->renderer->free(&c->renderer);
camu_screen_close(&c->scr);
diff --git a/src/libsink/sink.c b/src/libsink/sink.c
index 94ee3c6..6565cf5 100644
--- a/src/libsink/sink.c
+++ b/src/libsink/sink.c
@@ -60,13 +60,15 @@ enum {
#define ENTRY_MAX_AGE 4
// printf format for entries.
-#if defined AL_WE_64BIT
-#define ENTRY_FMT "#%u(0x%llx)"
-#elif defined AL_WE_32BIT
-#define ENTRY_FMT "#%u(0x%lx)"
-#endif
+#ifdef AL_DEBUG
+#define ENTRY_FMT "#%u(%p)"
#define ENTRY_ARG(entry) \
((entry) && (entry) != (struct camu_sink_entry *)0xb00b) ? (entry)->id : 0, (entry) ? (entry) : 0x0
+#else
+#define ENTRY_FMT "#%u"
+#define ENTRY_ARG(entry) \
+ ((entry) && (entry) != (struct camu_sink_entry *)0xb00b) ? (entry)->id : 0
+#endif
#define AUDIO_STATE(entry) ((entry)->audio.state)
#define VIDEO_STATE(entry) ((entry)->video.state)
@@ -85,6 +87,7 @@ enum {
#define AUDIO_ENDED_OR_EMPTY(entry) ((entry)->audio.state == BUFFER_ENDED || BUFFER_EMPTY(&(entry)->audio))
#define VIDEO_ENDED_OR_EMPTY(entry) ((entry)->video.state == BUFFER_ENDED || BUFFER_EMPTY(&(entry)->video))
+#define VIDEO_STREAM(entry) (entry)->video.buf.stream
#define VIDEO_IS_SINGLE_FRAME(entry) (entry)->video.buf.single_frame
#if defined CAMU_SCREEN_THREADED && defined CAMU_MIXER_THREADED
@@ -1079,11 +1082,15 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str
// Remove again for another add in BUFFER_BUFFERED.
remove_entry_audio_buffer(entry);
}
- // Slight optimization. A duplicate frame will still be sent but discarded in the video buffer.
bool skip_video = rec->reconnect && VIDEO_IS_SINGLE_FRAME(entry);
- if (!skip_video && !VIDEO_ENDED_OR_EMPTY(entry)) {
- remove_entry_video_buffer(entry);
- remove_entry_video_buffer(entry);
+ if (!VIDEO_ENDED_OR_EMPTY(entry)) { // Single frames will never be ENDED.
+ if (!skip_video) {
+ remove_entry_video_buffer(entry);
+ remove_entry_video_buffer(entry);
+ } else {
+ // We are keeping the frame, so don't request a duplicate.
+ entry->client.mask &= ~(1 << VIDEO_STREAM(entry)->index);
+ }
}
// Resolve any queued REMOVE_BUFFER requests before blocking.
// If MIXER_THREADED_START_STOP is not set, REMOVE_BUFFER happens from the sink's
@@ -1238,7 +1245,7 @@ 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);
camu_video_buffer_free(&entry->video.buf);
- log_debug("Entry ("ENTRY_FMT") closed by %s.", ENTRY_ARG(entry), removed ? "force" : "cleanup");
+ log_info("Entry ("ENTRY_FMT") closed by %s.", ENTRY_ARG(entry), removed ? "force" : "cleanup");
al_free(entry);
break;
diff --git a/src/mixer/audio_miniaudio.c b/src/mixer/audio_miniaudio.c
index a0db3f3..bc2da0d 100644
--- a/src/mixer/audio_miniaudio.c
+++ b/src/mixer/audio_miniaudio.c
@@ -1,18 +1,17 @@
#define AL_LOG_SECTION "audio_miniaudio"
#include <al/log.h>
#if defined(__clang__) || defined(__GNUC__)
-_Pragma("GCC diagnostic push") \
-_Pragma("GCC diagnostic ignored \"-Wunused-function\"")
-_Pragma("GCC diagnostic ignored \"-Wunused-variable\"")
-_Pragma("GCC diagnostic ignored \"-Wincompatible-pointer-types\"")
-_Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"")
+AL_IGNORE_WARNING("-Wunused-function")
+AL_IGNORE_WARNING("-Wunused-variable")
+AL_IGNORE_WARNING("-Wincompatible-pointer-types")
+AL_IGNORE_WARNING("-Wunknown-pragmas")
#elif _MSC_VER
#pragma warning(push, 0)
#endif
#define MINIAUDIO_IMPLEMENTATION
#include "audio_miniaudio.h"
#if defined(__clang__) || defined(__GNUC__)
-_Pragma("GCC diagnostic pop")
+AL_IGNORE_WARNING_END
#elif _MSC_VER
#pragma warning(pop)
#endif
@@ -155,7 +154,7 @@ static void data_callback(ma_device *device, void *output, const void *input, u3
struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)device->pUserData;
(void)input;
bool silence;
- ma->a.data_callback(ma->a.userdata, output, frame_count, &silence);
+ ma->a.data_callback(ma->a.userdata, (u8 *)output, frame_count, &silence);
}
//#define MINIAUDIO_LOW_LATENCY
diff --git a/src/render/meson.build b/src/render/meson.build
index b7927bb..af68036 100644
--- a/src/render/meson.build
+++ b/src/render/meson.build
@@ -128,7 +128,7 @@ endif
if get_option('subtitles').enabled()
libass = dependency('libass', required: false, allow_fallback: false)
if not libass.found()
- libass_opts = []
+ libass_opts = ['test=disabled']
if not is_windows
libass_opts += ['fontconfig=enabled']
# libass(e46aede):meson.build:126
diff --git a/src/render/queue_libplacebo.c b/src/render/queue_libplacebo.c
index 5932944..488c3b5 100644
--- a/src/render/queue_libplacebo.c
+++ b/src/render/queue_libplacebo.c
@@ -22,6 +22,7 @@ static bool queue_lp_configure_subtitles(struct camu_frame_queue *queue, u32 wid
log_error("Failed to initialize ass renderer.");
return false;
}
+ ass_set_shaper(lq->ass_renderer, ASS_SHAPING_COMPLEX);
lq->ass_track = ass_new_track(lq->ass);
if (!lq->ass_track) {
log_error("Failed to create ass track.");
@@ -49,7 +50,7 @@ static bool queue_lp_configure_subtitles(struct camu_frame_queue *queue, u32 wid
static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src,
struct pl_frame *out_frame)
{
- struct camu_codec_frame *frame = src->frame_data;
+ struct camu_codec_frame *frame = (struct camu_codec_frame *)src->frame_data;
u64 masks[4] = { 0 };
s32 pixel_stride;
@@ -120,7 +121,7 @@ static void unmap_frame(pl_gpu gpu, struct pl_frame *frame, const struct pl_sour
static void discard_frame(const struct pl_source_frame *src)
{
- struct camu_codec_frame *frame = src->frame_data;
+ struct camu_codec_frame *frame = (struct camu_codec_frame *)src->frame_data;
camu_codec_frame_discard(frame);
}
@@ -142,7 +143,6 @@ static void queue_lp_push(struct camu_frame_queue *queue, struct camu_codec_fram
static struct camu_overlay_lp *create_subtitle_overlay(pl_gpu gpu, ASS_Image *ass_frame)
{
struct camu_overlay_lp *overlay = al_alloc_object(struct camu_overlay_lp);
-
for (; ass_frame; ass_frame = ass_frame->next) {
pl_tex tex = pl_tex_create(gpu, pl_tex_params(
.w = ass_frame->w,
@@ -166,14 +166,11 @@ static struct camu_overlay_lp *create_subtitle_overlay(pl_gpu gpu, ASS_Image *as
if (overlay->num >= overlay->alloc) {
s32 old_alloc = overlay->alloc;
overlay->alloc = old_alloc ? al_next_power_of_two(overlay->alloc + 1) : 8;
- if (!overlay->overlays) {
- overlay->overlays = al_malloc(sizeof(struct pl_overlay) * overlay->alloc);
- } else {
- overlay->overlays = al_realloc(overlay->overlays, sizeof(struct pl_overlay) * overlay->alloc);
- }
+ overlay->overlays = (struct pl_overlay *)al_realloc(overlay->overlays,
+ sizeof(struct pl_overlay) * overlay->alloc);
for (s32 i = old_alloc; i < overlay->alloc; i++) {
current = (struct pl_overlay *)&overlay->overlays[i];
- current->parts = al_malloc(sizeof(struct pl_overlay_part) * 1);
+ current->parts = (struct pl_overlay_part *)al_malloc(sizeof(struct pl_overlay_part));
current->num_parts = 0;
}
}
@@ -209,7 +206,6 @@ static struct camu_overlay_lp *create_subtitle_overlay(pl_gpu gpu, ASS_Image *as
current->coords = PL_OVERLAY_COORDS_SRC_FRAME;
current->num_parts = 1;
}
-
return overlay;
}
#endif
@@ -217,7 +213,7 @@ static struct camu_overlay_lp *create_subtitle_overlay(pl_gpu gpu, ASS_Image *as
static bool map_av_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src,
struct pl_frame *out_frame)
{
- AVFrame *frame = src->frame_data;
+ AVFrame *frame = (AVFrame *)src->frame_data;
struct camu_frame_queue_lp *lq = (struct camu_frame_queue_lp *)frame->opaque;
AVStream *stream = lq->q.buf->stream->av.stream;
@@ -316,7 +312,7 @@ static void unmap_av_frame(pl_gpu gpu, struct pl_frame *frame, const struct pl_s
static void discard_av_frame(const struct pl_source_frame *src)
{
- AVFrame *frame = src->frame_data;
+ AVFrame *frame = (AVFrame *)src->frame_data;
av_frame_free(&frame);
log_warn("Dropped frame with PTS %.3f.", src->pts);
}
diff --git a/src/render/renderer_libplacebo.c b/src/render/renderer_libplacebo.c
index 5ef2a34..191c40d 100644
--- a/src/render/renderer_libplacebo.c
+++ b/src/render/renderer_libplacebo.c
@@ -15,6 +15,8 @@
#include <libplacebo/d3d11.h>
#endif
+#include "shaders/vr_video.h"
+
#include "renderer_libplacebo.h"
#include "queue_libplacebo.h"
@@ -276,19 +278,15 @@ static bool renderer_lp_create_renderer(struct camu_renderer *renderer, u32 *wid
lr->params.correct_subpixel_offsets = true;
//lr->params.distort_params = pl_distort_params(.unscaled = false);
-#if 0
+ lr->vr_emulation_hook[0] = pl_mpv_user_shader_parse(lr->gpu, vr_video_shader, sizeof(vr_video_shader) - 1);
+ /*
struct nn_file file;
if (nn_file_open(&file, &al_str_c(""), 0)) {
char *c_str;
size_t length = nn_file_read_as_c_str(&file, &c_str);
const struct pl_hook *hook = pl_mpv_user_shader_parse(lr->gpu, (const char *)c_str, length);
- const struct pl_hook **hooks = al_malloc(sizeof(struct pl_hook *));
- hooks[0] = al_malloc(sizeof(struct pl_hook));
- al_memcpy((void *)hooks[0], (void *)hook, sizeof(struct pl_hook));
- lr->params.hooks = hooks;
- lr->params.num_hooks = 1;
}
-#endif
+ */
#ifdef CAMU_HAVE_FFMPEG
lr->r.get_buffer2 = pl_get_buffer2;
@@ -345,6 +343,19 @@ static u32 renderer_lp_get_latency(struct camu_renderer *renderer)
return (u32)pl_swapchain_latency(lr->swapchain);
}
+// This is a completely broken "hash" of the f64 pts value, but it works for now.
+// https://www.virtualdub.org/blog2/entry_259.html
+static inline intptr_t float_64_hash(f64 value)
+{
+ union { f64 f; u64 u; } fv;
+ fv.f = value;
+ intptr_t hash = (intptr_t)fv.u;
+#ifdef AL_WE_32BIT
+ hash += (intptr_t)(fv.u >> 32);
+#endif
+ return hash;
+}
+
static void renderer_lp_render(struct camu_renderer *renderer, struct camu_screen *scr, bool force)
{
struct camu_renderer_lp *lr = (struct camu_renderer_lp *)renderer;
@@ -368,6 +379,20 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree
lr->have_frame = true;
}
+ f64 mouse_x, mouse_y;
+ bool vr_emulation = scr->vr_emulation && lr->vr_emulation_hook[0];
+ if (vr_emulation) {
+ mouse_x = (scr->last_pointer_x - (scr->calibrate_x - (scr->width / 2.0))) / (f64)scr->width;
+ mouse_y = (scr->last_pointer_y - (scr->calibrate_y - (scr->height / 2.0))) / (f64)scr->height;
+ mouse_y = 1.0 - mouse_y;
+ mouse_x = CLAMP(mouse_x, -0.5, 1.5);
+ mouse_y = CLAMP(mouse_y, -0.5, 1.5);
+ lr->params.hooks = lr->vr_emulation_hook;
+ lr->params.num_hooks = 1;
+ lr->params.hooks[0]->parameters[0].data->f = (f32)mouse_x;
+ lr->params.hooks[0]->parameters[1].data->f = (f32)mouse_y;
+ }
+
struct pl_frame *target = &lr->target;
struct pl_frame_mix mix;
@@ -385,16 +410,15 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree
do_gpu_finish |= weighted;
// Terrible hack. Lets us distinguish single frames with the same dimensions.
// Tied to a libplacebo patch to consider info_priv in the hash.
- // Also, add in reset_pts to account for buffer resets. This is a completely
- // broken "hash" of the f64 pts value but hopefully it works for now.
- // https://www.virtualdub.org/blog2/entry_259.html
+ // Also, add in reset_pts to account for buffer resets.
intptr_t hash = (intptr_t)video->buf;
- union { f64 f; u64 u; } pts_hash;
- pts_hash.f = video->buf->reset_pts;
- hash += (intptr_t)pts_hash.u;
-#ifdef AL_WE_32BIT
- hash += (intptr_t)(pts_hash.u >> 32);
-#endif
+ hash += float_64_hash(video->buf->reset_pts);
+ if (vr_emulation) {
+ hash += float_64_hash(mouse_x);
+ hash += float_64_hash(mouse_y);
+ hash += float_64_hash(video->view.fov);
+ lr->params.hooks[0]->parameters[2].data->f = (f32)video->view.fov;
+ }
lr->params.info_priv = (void *)hash;
target->crop = mix.frames[0]->crop;
target->crop.x1 *= video->view.zoom / video->view.stretch;
@@ -404,9 +428,7 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree
target->crop.x1 += video->view.x_offset;
target->crop.y1 += video->view.y_offset;
target->rotation = video->view.rotation;
- //lr->params.color_adjustment = pl_color_adjustment(
- // .saturation = 0.0
- //);
+ //lr->params.color_adjustment = pl_color_adjustment(.saturation = 0.0);
pl_render_image_mix(lr->renderer, &mix, target, &lr->params);
}
} else {
@@ -424,6 +446,9 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree
}
}
+ lr->params.hooks = NULL;
+ lr->params.num_hooks = 0;
+
if (!scr->videos.count && !force) {
// Don't spin too hard on a potential error state.
nn_thread_sleep(NNWT_TS_FROM_USEC(192));
@@ -462,6 +487,7 @@ void renderer_lp_free(struct camu_renderer **renderer)
if (lr->gl) pl_opengl_destroy(&lr->gl);
#endif
if (lr->logger) pl_log_destroy(&lr->logger);
+ if (lr->vr_emulation_hook[0]) pl_mpv_user_shader_destroy(&lr->vr_emulation_hook[0]);
#ifdef CAMU_HAVE_SUBTITLES
if (lr->ass) ass_library_done(lr->ass);
#endif
diff --git a/src/render/renderer_libplacebo.h b/src/render/renderer_libplacebo.h
index a0125ba..c8962d2 100644
--- a/src/render/renderer_libplacebo.h
+++ b/src/render/renderer_libplacebo.h
@@ -37,6 +37,7 @@ struct camu_renderer_lp {
pl_renderer renderer;
f64 last_render_tick;
struct pl_render_params params;
+ const struct pl_hook *vr_emulation_hook[1];
#ifdef CAMU_HAVE_SUBTITLES
ASS_Library *ass;
#endif
diff --git a/src/render/shaders/vr_video.h b/src/render/shaders/vr_video.h
new file mode 100644
index 0000000..fc2d192
--- /dev/null
+++ b/src/render/shaders/vr_video.h
@@ -0,0 +1,46 @@
+#pragma once
+
+// https://gist.github.com/tesu/196db5421559de3e9555d4f9da9d847d
+// FOV: [0 to M_PI] horizontal field of view, range is exclusive
+// YAW: [any float] polar angle, one full revolution is 2*M_PI
+// PITCH: [any float] vertical tilt, positive is up
+// ROLL: [any float] view rotation, positive is clockwise
+//" const float yaw = M_PI*(-1.0+(mouse_x*2.0));\n" // 360
+static const char vr_video_shader[] = \
+"//!PARAM mouse_x\n"
+"//!TYPE float\n"
+"//!MINIMUM -0.5\n"
+"//!MAXIMUM 1.5\n"
+"0.0\n"
+"//!PARAM mouse_y\n"
+"//!TYPE float\n"
+"//!MINIMUM -0.5\n"
+"//!MAXIMUM 1.5\n"
+"0.5\n"
+"//!PARAM fov\n"
+"//!TYPE float\n"
+"//!MINIMUM 0\n"
+"//!MAXIMUM 3.141592653589793\n"
+"1.5707963267948966\n"
+"//!HOOK MAINPRESUB\n"
+"//!BIND HOOKED\n"
+"//!DESC un360\n"
+"const float M_PI = 3.141592653589793;\n"
+"vec4 hook() {\n"
+" float yaw = M_PI*mouse_x;\n"
+" float pitch = M_PI*(-0.5+mouse_y);\n"
+" float roll = M_PI*0.0;\n"
+" float t = tan(fov/2);\n"
+" float c = cos(pitch);\n"
+" float s = sin(pitch);\n"
+" float r = target_size.y/target_size.x;\n"
+" float sR = sin(roll);\n"
+" float cR = cos(roll);\n"
+" mat3 m = mat3(2*t*cR, 2*sR*t*r, -t*(cR+sR*r), -2*sR*t*c, 2*cR*t*c*r, t*c*(sR-cR*r)-s, -2*sR*t*s, 2*cR*t*s*r, t*s*(sR-cR*r)+c);\n"
+" vec3 p = vec3(HOOKED_pos, 1.0) * m;\n"
+" float theta = atan(p.x, p.z) + yaw;\n"
+" float phi = atan(p.y, length(p.xz)) + M_PI/2;\n"
+" float x = fract(theta / (2*M_PI));\n"
+" float y = phi / M_PI;\n"
+" return HOOKED_tex(vec2(x, y));\n"
+"}\n";
diff --git a/src/screen/screen.c b/src/screen/screen.c
index 09fbe64..965088f 100644
--- a/src/screen/screen.c
+++ b/src/screen/screen.c
@@ -6,8 +6,8 @@
#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_MOD1(scr) ((scr)->window->held_modifiers & (STELA_MOD_LCONTROL | STELA_MOD_RCONTROL))
+#define SCREEN_MOD2(scr) ((scr)->window->held_modifiers & (STELA_MOD_LSHIFT | STELA_MOD_RSHIFT))
#define SCREEN_IS_DRAGGING(scr) ((scr)->flags & CAMU_SCREEN_DRAGGING)
#define SCREEN_IS_ZOOMING(scr) ((scr)->flags & CAMU_SCREEN_ZOOMING)
#define SCREEN_ZOOM_MODE(scr, mode) ((scr)->flags & mode)
@@ -18,6 +18,7 @@
static void should_close_callback(void *userdata)
{
struct camu_screen *scr = (struct camu_screen *)userdata;
+ scr->window->close(scr->window);
scr->callback(scr->userdata, CAMU_SCREEN_CLOSE, NULL);
}
@@ -38,6 +39,8 @@ static void do_resize(struct camu_screen *scr, u32 width, u32 height)
al_assert(width > 0 && height > 0);
scr->width = width;
scr->height = height;
+ scr->calibrate_x = scr->width / 2.0;
+ scr->calibrate_y = scr->height / 2.0;
struct camu_screen_video *video;
al_array_foreach_ptr(scr->videos, i, video) {
camu_view_calculate(&video->view, scr->width, scr->height);
@@ -68,23 +71,40 @@ static struct camu_view *get_view_from_mouse_pos(struct camu_screen *scr)
return view;
}
+static void seek_to_percent_at_pointer(struct camu_screen *scr, f64 x)
+{
+ f64 percent = x / scr->width;
+ percent = CLAMP(percent, 0.0, 100.0);
+ scr->callback(scr->userdata, CAMU_SCREEN_PERCENT_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 (SCREEN_IS_DRAGGING(scr)) {
- if (view && SCREEN_ZOOM_MODE(scr, CAMU_SCREEN_ZOOM_PAN_SIMPLE)) {
- f64 dx = x - scr->last_pointer_x;
- f64 dy = y - scr->last_pointer_y;
- if (camu_view_pan_simple(view, scr->width, scr->height, dx, dy)) {
- view->mode = CAMU_VIEW_DETACHED;
- queue_refresh = true;
- }
- if (fabs(dx) + fabs(dy) > 3.0) {
- scr->last_click_ts = SCREEN_INVALID_TS;
+#ifdef CAMU_SCREEN_DRAG_SEEK
+ u64 now = nn_get_timestamp();
+ if (view && now - scr->last_seek_ts > 110000) {
+ seek_to_percent_at_pointer(scr, x);
+ scr->last_seek_ts = now;
+ }
+#else
+ if (view) {
+ if (SCREEN_ZOOM_MODE(scr, CAMU_SCREEN_ZOOM_PAN_SIMPLE)) {
+ f64 dx = x - scr->last_pointer_x;
+ f64 dy = y - scr->last_pointer_y;
+ if (camu_view_pan_simple(view, scr->width, scr->height, dx, dy)) {
+ view->mode = CAMU_VIEW_DETACHED;
+ queue_refresh = true;
+ }
+ if (fabs(dx) + fabs(dy) > 3.0) {
+ scr->last_click_ts = SCREEN_INVALID_TS;
+ }
}
}
+#endif
}
scr->last_pointer_x = x;
scr->last_pointer_y = y;
@@ -144,13 +164,6 @@ static bool touch_callback(void *userdata, s32 index, u8 phase, f64 x, f64 y)
return false;
}
-static void seek_to_percent_at_pointer(struct camu_screen *scr)
-{
- f64 percent = scr->last_pointer_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;
@@ -159,13 +172,22 @@ static bool mouse_button_callback(void *userdata, u8 state, u8 button)
switch (state) {
case STELA_BUTTON_PRESSED:
scr->flags |= CAMU_SCREEN_DRAGGING;
+ scr->window->set_cursor(scr->window, STELA_CURSOR_CROSSHAIR);
+#ifdef CAMU_SCREEN_DRAG_SEEK
+ //scr->callback(scr->userdata, CAMU_SCREEN_TOGGLE_PAUSE, NULL);
+#endif
scr->last_click_ts = nn_get_timestamp();
break;
case STELA_BUTTON_RELEASED:
scr->flags &= ~CAMU_SCREEN_DRAGGING;
+ scr->window->set_cursor(scr->window, (scr->fullscreen || scr->vr_emulation) ?
+ STELA_CURSOR_HIDDEN : STELA_CURSOR_NORMAL);
+#ifdef CAMU_SCREEN_DRAG_SEEK
+ //scr->callback(scr->userdata, CAMU_SCREEN_TOGGLE_PAUSE, NULL);
+#endif
if (SCREEN_LAST_CLICK_WITHIN(200000)) {
if (SCREEN_MOD1(scr)) {
- seek_to_percent_at_pointer(scr);
+ seek_to_percent_at_pointer(scr, scr->last_pointer_x);
} else {
s32 n = (scr->last_pointer_x >= scr->width / 2.0) ? 1 : -1;
scr->callback(scr->userdata, CAMU_SCREEN_SKIP, &n);
@@ -177,7 +199,7 @@ static bool mouse_button_callback(void *userdata, u8 state, u8 button)
case STELA_MOUSE3: {
switch (state) {
case STELA_BUTTON_RELEASED: {
- seek_to_percent_at_pointer(scr);
+ seek_to_percent_at_pointer(scr, scr->last_pointer_x);
break;
}
}
@@ -205,10 +227,14 @@ bool scroll_callback(void *userdata, f64 y)
y = -y / 20.0;
#endif
struct camu_view *view = get_view_from_mouse_pos(scr);
- if (view && SCREEN_ZOOM_MODE(scr, CAMU_SCREEN_ZOOM_PAN_SIMPLE)) {
- if (camu_view_zoom_simple(view, scr->width, scr->height, scr->last_pointer_x, scr->last_pointer_y, y)) {
- view->mode = CAMU_VIEW_DETACHED;
- return true;
+ if (view) {
+ if (SCREEN_ZOOM_MODE(scr, CAMU_SCREEN_ZOOM_PAN_SIMPLE)) {
+ if (camu_view_zoom_simple(view, scr->width, scr->height, scr->last_pointer_x, scr->last_pointer_y, y)) {
+ view->mode = CAMU_VIEW_DETACHED;
+ return true;
+ }
+ } else if (SCREEN_ZOOM_MODE(scr, CAMU_SCREEN_ZOOM_FOV)) {
+ view->fov -= y;
}
}
}
@@ -221,15 +247,8 @@ static bool key_callback(void *userdata, u8 state, u16 button)
switch (state) {
case STELA_BUTTON_PRESSED:
switch (button) {
- case STELA_KEY_LEFT_SHIFT:
- case STELA_KEY_RIGHT_SHIFT:
- scr->flags |= CAMU_SCREEN_MOD_SHIFT;
- break;
- case STELA_KEY_LEFT_CONTROL:
- case STELA_KEY_RIGHT_CONTROL:
- scr->flags |= CAMU_SCREEN_MOD_CONTROL;
- break;
case STELA_KEY_Q:
+ scr->window->close(scr->window);
scr->callback(scr->userdata, CAMU_SCREEN_CLOSE, NULL);
break;
case STELA_KEY_RIGHT:
@@ -285,6 +304,8 @@ static bool key_callback(void *userdata, u8 state, u16 button)
break;
#endif
case STELA_KEY_R: {
+ scr->calibrate_x = scr->last_pointer_x;
+ scr->calibrate_y = scr->last_pointer_y;
struct camu_view *view = get_view_from_mouse_pos(scr);
if (view) {
if (SCREEN_MOD2(scr)) {
@@ -367,21 +388,29 @@ static bool key_callback(void *userdata, u8 state, u16 button)
// Force refresh for if there's no video buffer.
al_atomic_add(u32)(&scr->force_refresh, 1, AL_ATOMIC_RELAXED);
return true;
+ case STELA_KEY_V: {
+ struct camu_view *view = get_view_from_mouse_pos(scr);
+ if (view) {
+ view->mode = CAMU_DEFAULT_VIEW;
+ camu_view_calculate(view, scr->width, scr->height);
+ }
+ if (!scr->vr_emulation) {
+ scr->flags = (scr->flags & ~CAMU_SCREEN_ZOOM_PAN_SIMPLE) | CAMU_SCREEN_ZOOM_FOV;
+ scr->window->set_cursor(scr->window, STELA_CURSOR_HIDDEN);
+ scr->vr_emulation = true;
+ } else {
+ scr->flags = (scr->flags & ~CAMU_SCREEN_ZOOM_FOV) | CAMU_SCREEN_ZOOM_PAN_SIMPLE;
+ scr->window->set_cursor(scr->window, STELA_CURSOR_NORMAL);
+ scr->vr_emulation = false;
+ }
+ break;
+ }
default:
break;
}
break;
case STELA_BUTTON_RELEASED:
- switch (button) {
- case STELA_KEY_LEFT_SHIFT:
- case STELA_KEY_RIGHT_SHIFT:
- scr->flags &= ~CAMU_SCREEN_MOD_SHIFT;
- break;
- case STELA_KEY_LEFT_CONTROL:
- case STELA_KEY_RIGHT_CONTROL:
- scr->flags &= ~CAMU_SCREEN_MOD_CONTROL;
- break;
- }
+ break;
default:
break;
}
@@ -396,6 +425,7 @@ static void key_immediate_callback(void *userdata, u8 state, u16 button)
switch (button) {
case STELA_KEY_F:
scr->fullscreen = !scr->fullscreen;
+ scr->window->set_cursor(scr->window, scr->fullscreen ? STELA_CURSOR_HIDDEN : STELA_CURSOR_NORMAL);
#ifdef STELA_API_DX11
scr->renderer->set(scr->renderer, CAMU_RENDERER_FULLSCREEN, scr->fullscreen);
#else
@@ -429,6 +459,10 @@ bool camu_screen_init(struct camu_screen *scr)
scr->last_click_ts = SCREEN_INVALID_TS;
scr->last_pointer_x = 0.0;
scr->last_pointer_y = 0.0;
+#ifdef CAMU_SCREEN_DRAG_SEEK
+ scr->last_seek_ts = 0;
+#endif
+ scr->vr_emulation = false;
al_array_init(scr->videos);
#ifdef CAMU_SCREEN_THREADED
al_array_init(scr->add_queue);
@@ -444,7 +478,9 @@ static nn_thread_result NNWT_THREADCALL event_thread(void *userdata)
{
struct camu_screen *scr = (struct camu_screen *)userdata;
nn_thread_set_name("window_events");
- while (scr->window->poll(scr->window, true) && al_atomic_load(s32)(&scr->state, AL_ATOMIC_RELAXED) != CAMU_SCREEN_STOPPED) {
+ // If we decide to close within a poll, we must not expect any
+ // window events after that point.
+ while (scr->window->poll(scr->window, true)) {
scr->window->process_events(scr->window);
}
return 0;
@@ -459,6 +495,8 @@ bool camu_screen_create_window(struct camu_screen *scr, const char *name)
}
scr->width = scr->window->width;
scr->height = scr->window->height;
+ scr->calibrate_x = scr->width / 2.0;
+ scr->calibrate_y = scr->height / 2.0;
scr->fullscreen = false;
scr->scaling_disabled = false;
scr->transparent_background = false;
@@ -510,12 +548,7 @@ static void add_buffer_internal(struct camu_screen *scr, struct camu_video_buffe
video.view.height = fmt->height;
#endif
} else {
- video.view.mode = CAMU_DEFAULT_VIEW;
- video.view.rotation = CAMU_VIEW_ROTATION_0;
- video.view.width = fmt->width;
- video.view.height = fmt->height;
- video.view.zindex = 0;
- video.view.stretch = 1.0;
+ camu_view_init(&video.view, fmt->width, fmt->height);
}
camu_view_calculate(&video.view, scr->width, scr->height);
#ifdef CAMU_SCREEN_THREADED
@@ -644,11 +677,9 @@ void camu_screen_clear(struct camu_screen *scr)
void camu_screen_set_state(struct camu_screen *scr, s32 state)
{
// @TODO: What to do if a buffer is queued for removal and we pause here.
- if (al_atomic_load(s32)(&scr->state, AL_ATOMIC_ACQUIRE) != CAMU_SCREEN_STOPPED) {
- al_atomic_store(s32)(&scr->state, state, AL_ATOMIC_RELEASE);
- if (state == CAMU_SCREEN_PAUSED) {
- al_atomic_add(u32)(&scr->force_refresh, 1, AL_ATOMIC_RELAXED);
- }
+ al_atomic_store(s32)(&scr->state, state, AL_ATOMIC_RELEASE);
+ if (state == CAMU_SCREEN_PAUSED) {
+ al_atomic_add(u32)(&scr->force_refresh, 1, AL_ATOMIC_RELAXED);
}
}
@@ -668,8 +699,9 @@ bool camu_screen_poll(struct camu_screen *scr, bool block)
}
stl_window_read_events(scr->window);
#else
- scr->window->poll(scr->window, block);
- scr->window->process_events(scr->window);
+ if (scr->window->poll(scr->window, block)) {
+ scr->window->process_events(scr->window);
+ }
#endif
#ifdef STELA_PAUSE
return scr->window->should_refresh(scr->window);
@@ -681,9 +713,8 @@ bool camu_screen_poll(struct camu_screen *scr, bool block)
bool camu_screen_tick(struct camu_screen *scr, bool *force)
{
s32 state = al_atomic_load(s32)(&scr->state, AL_ATOMIC_RELAXED);
- al_assert(state != CAMU_SCREEN_STOPPED);
u32 force_refresh = al_atomic_load(u32)(&scr->force_refresh, AL_ATOMIC_ACQUIRE);
- bool paused = state == CAMU_SCREEN_PAUSED && force_refresh == 0;
+ bool paused = !scr->vr_emulation && state == CAMU_SCREEN_PAUSED && force_refresh == 0;
bool do_render = camu_screen_poll(scr, paused) || !paused;
// Should be AL_ATOMIC_ACQ_REL.
force_refresh = al_atomic_load(u32)(&scr->force_refresh, AL_ATOMIC_ACQUIRE);
@@ -712,7 +743,6 @@ void camu_screen_close(struct camu_screen *scr)
nn_mutex_destroy(&scr->mutex);
#endif
al_array_free(scr->videos);
- al_atomic_store(s32)(&scr->state, CAMU_SCREEN_STOPPED, AL_ATOMIC_RELAXED);
#ifdef STELA_EVENT_BUFFER
nn_thread_join(&scr->thread);
#endif
diff --git a/src/screen/screen.h b/src/screen/screen.h
index dacf756..4c6e205 100644
--- a/src/screen/screen.h
+++ b/src/screen/screen.h
@@ -14,6 +14,9 @@
//#define CAMU_SCREEN_DEBUG_KEY
+// Interpret click and drag as seeking. This is mainly a seek performance test.
+//#define CAMU_SCREEN_DRAG_SEEK
+
#define CAMU_SCREEN_WIDTH 700
#define CAMU_SCREEN_HEIGHT 700
@@ -22,13 +25,13 @@ enum {
CAMU_SCREEN_MOD_CONTROL = 1 << 1,
CAMU_SCREEN_DRAGGING = 1 << 2,
CAMU_SCREEN_ZOOMING = 1 << 3,
- CAMU_SCREEN_ZOOM_PAN_SIMPLE = 1 << 4
+ CAMU_SCREEN_ZOOM_PAN_SIMPLE = 1 << 4,
+ CAMU_SCREEN_ZOOM_FOV = 1 << 5
};
enum {
CAMU_SCREEN_PAUSED = 0,
- CAMU_SCREEN_PLAYING,
- CAMU_SCREEN_STOPPED
+ CAMU_SCREEN_PLAYING
};
enum {
@@ -69,6 +72,12 @@ struct camu_screen {
u64 last_click_ts;
f64 last_pointer_y;
f64 last_pointer_x;
+#ifdef CAMU_SCREEN_DRAG_SEEK
+ u64 last_seek_ts;
+#endif
+ f64 calibrate_x;
+ f64 calibrate_y;
+ bool vr_emulation;
array(struct camu_screen_video) videos;
#ifdef CAMU_SCREEN_THREADED
array(struct camu_video_buffer *) add_queue;
diff --git a/src/screen/view.c b/src/screen/view.c
index b5fade2..1676084 100644
--- a/src/screen/view.c
+++ b/src/screen/view.c
@@ -1,4 +1,5 @@
#include <al/lib.h>
+#include <math.h>
#include "view.h"
@@ -15,6 +16,17 @@
#define VIEW_ON_ITS_SIDE(view) \
(view->rotation % CAMU_VIEW_ROTATION_180 == CAMU_VIEW_ROTATION_90)
+void camu_view_init(struct camu_view *view, u32 width, u32 height)
+{
+ view->mode = CAMU_DEFAULT_VIEW;
+ view->rotation = CAMU_VIEW_ROTATION_0;
+ view->width = width;
+ view->height = height;
+ view->zindex = 0;
+ view->stretch = 1.0;
+ view->fov = M_PI / 2.25; // 80 degrees
+}
+
void camu_view_calculate(struct camu_view *view, u32 window_width, u32 window_height)
{
u32 width, height;
diff --git a/src/screen/view.h b/src/screen/view.h
index 30b6f64..b5f948e 100644
--- a/src/screen/view.h
+++ b/src/screen/view.h
@@ -29,8 +29,10 @@ struct camu_view {
f64 x_offset;
f64 y_offset;
f64 stretch;
+ f64 fov;
};
+void camu_view_init(struct camu_view *view, u32 width, u32 height);
void camu_view_calculate(struct camu_view *view, u32 window_width, u32 window_height);
bool camu_view_pan_simple(struct camu_view *view, u32 window_width, u32 window_height, f64 dx, f64 dy);
bool camu_view_zoom_simple(struct camu_view *view, u32 window_width, u32 window_height, f64 x, f64 y, f64 v);
diff --git a/subprojects/freetype2.wrap b/subprojects/freetype2.wrap
index 1993fdd..1604e96 100644
--- a/subprojects/freetype2.wrap
+++ b/subprojects/freetype2.wrap
@@ -1,5 +1,5 @@
[wrap-git]
directory = freetype-2.13.3
-url = https://github.com/freetype/freetype.git
+url = https://gitlab.freedesktop.org/freetype/freetype.git
revision = VER-2-13-3
depth = 1
diff --git a/subprojects/libalabaster.wrap b/subprojects/libalabaster.wrap
index 81771ed..9a9cfc1 100644
--- a/subprojects/libalabaster.wrap
+++ b/subprojects/libalabaster.wrap
@@ -1,4 +1,4 @@
[wrap-git]
url = https://git.akon.city/libalabaster
-revision = b403770e7eb6e0a5a8ca14a24f91dbf214ffe917
+revision = 0470ca3416388e7e58b64f63bfe505d454f150b9
depth = 1
diff --git a/subprojects/libass.wrap b/subprojects/libass.wrap
index f2aa71c..fa50802 100644
--- a/subprojects/libass.wrap
+++ b/subprojects/libass.wrap
@@ -1,6 +1,6 @@
[wrap-git]
-directory = libass-0.17.3
+directory = libass-0.17.4
url = https://github.com/libass/libass.git
-revision = 0.17.3
+revision = 0.17.4
clone-recursive = true
depth = 1
diff --git a/subprojects/libnaunet.wrap b/subprojects/libnaunet.wrap
index 8388a93..15dd478 100644
--- a/subprojects/libnaunet.wrap
+++ b/subprojects/libnaunet.wrap
@@ -1,4 +1,4 @@
[wrap-git]
url = https://git.akon.city/libnaunet
-revision = 393e266f6bfceb25a97cd1cf28dfc72cac9e9f81
+revision = 3ea974dd51b1f01483bad6a4d115189928bbf4db
depth = 1
diff --git a/subprojects/libplacebo.wrap b/subprojects/libplacebo.wrap
index d9a2165..33e3323 100644
--- a/subprojects/libplacebo.wrap
+++ b/subprojects/libplacebo.wrap
@@ -1,7 +1,7 @@
[wrap-git]
-directory = libplacebo-2bd627f
+directory = libplacebo-4ff55e0
url = https://code.videolan.org/videolan/libplacebo.git
-revision = 2bd627f823ba1cedbc51a0ee6eb7a9fb433d912e
+revision = 4ff55e04264e8c650b9de097d91f17bcd820fb31
clone-recursive = true
depth = 1
diff --git a/subprojects/notcurses.wrap b/subprojects/notcurses.wrap
index 31ef4c4..bc58274 100644
--- a/subprojects/notcurses.wrap
+++ b/subprojects/notcurses.wrap
@@ -3,11 +3,10 @@ directory = notcurses-93a4890
url = https://github.com/dankamongmen/notcurses.git
revision = 93a4890f4c2b491cda62fea5110995c6a25691e2
depth = 1
+method = cmake
patch_directory = notcurses
diff_files = notcurses/duration_and_ffmpeg_fix.diff
-method = cmake
-
[provide]
dependency_names = notcurses, notcurses-core
diff --git a/subprojects/packagefiles/ffmpeg-compat/meson_options.txt b/subprojects/packagefiles/ffmpeg-compat/meson_options.txt
new file mode 100644
index 0000000..eb4b08b
--- /dev/null
+++ b/subprojects/packagefiles/ffmpeg-compat/meson_options.txt
@@ -0,0 +1,4 @@
+# Copy-paste from top-level project.
+option('codec-hwaccel', type: 'feature', value: 'disabled', yield: true)
+option('renderer-api', type: 'combo', choices: ['vulkan', 'dx11', 'gl', 'gl-rpi'], value: 'vulkan', yield: true)
+option('sink-only', type: 'boolean', value: false, yield: true)
diff --git a/subprojects/packagefiles/ffmpeg-compat/meson_project.diff b/subprojects/packagefiles/ffmpeg-compat/meson_project.diff
new file mode 100644
index 0000000..5646fe1
--- /dev/null
+++ b/subprojects/packagefiles/ffmpeg-compat/meson_project.diff
@@ -0,0 +1,16 @@
+--- meson.build 2025-05-25 19:32:01.080982098 -0400
++++ meson.build 2025-05-25 19:32:01.080982098 -0400
+@@ -1,4 +1,4 @@
+-project('ffmpeg', 'c', version: '7.1.1', meson_version: '>=0.57.0')
++project('ffmpeg', 'c', version: '3.4.14', meson_version: '>=0.57.0')
+
+ ffmpeg_version_string = meson.project_version()
+
+@@ -39,6 +39,7 @@
+ endif
+
+ if is_windows
++ extra_options += ['--extra-cflags=-D_WIN32_WINNT=0x0502'] # Windows Server 2003 with SP1, Windows XP with SP2.
+ if is_64bit
+ extra_options += ['--target-os=win64']
+ else
diff --git a/subprojects/packagefiles/ffmpeg/meson_options.txt b/subprojects/packagefiles/ffmpeg/meson_options.txt
new file mode 100644
index 0000000..eb4b08b
--- /dev/null
+++ b/subprojects/packagefiles/ffmpeg/meson_options.txt
@@ -0,0 +1,4 @@
+# Copy-paste from top-level project.
+option('codec-hwaccel', type: 'feature', value: 'disabled', yield: true)
+option('renderer-api', type: 'combo', choices: ['vulkan', 'dx11', 'gl', 'gl-rpi'], value: 'vulkan', yield: true)
+option('sink-only', type: 'boolean', value: false, yield: true)
diff --git a/subprojects/packagefiles/libplacebo/oversample_testing.diff b/subprojects/packagefiles/libplacebo/oversample_testing.diff
new file mode 100644
index 0000000..2993809
--- /dev/null
+++ b/subprojects/packagefiles/libplacebo/oversample_testing.diff
@@ -0,0 +1,18 @@
+diff --git a/src/renderer.c b/src/renderer.c
+index 1f67bc3..7df525e 100644
+--- a/src/renderer.c
++++ b/src/renderer.c
+@@ -1761,6 +1761,13 @@ static bool pass_read_image(struct pass_state *pass)
+ ref_rounded.y0 = truncf(ref_rc.y0);
+ ref_rounded.x1 = ref_rounded.x0 + roundf(pl_rect_w(ref_rc));
+ ref_rounded.y1 = ref_rounded.y0 + roundf(pl_rect_h(ref_rc));
++ // Oversample to avoid distortion when really zoomed in.
++ // @TODO: Verbosely log offset/base/stretch/scale to try and understand what's happening here.
++ //const float oversample = 3;
++ //ref_rounded.x0 -= oversample;
++ //ref_rounded.y0 -= oversample;
++ //ref_rounded.x1 += oversample;
++ //ref_rounded.y1 += oversample;
+
+ PL_TRACE(rr, "Rounded reference rect: {%d %d %d %d}",
+ ref_rounded.x0, ref_rounded.y0,
diff --git a/subprojects/stela.wrap b/subprojects/stela.wrap
index 444d851..9f5c62b 100644
--- a/subprojects/stela.wrap
+++ b/subprojects/stela.wrap
@@ -1,4 +1,4 @@
[wrap-git]
url = https://git.akon.city/stela
-revision = 658e476c3d43718a2c9eb2884f16ac37cc3f1da2
+revision = 01637283b33d7fe4921e9347ada867e44b29edba
depth = 1