diff options
| author | 2025-06-23 17:49:11 -0400 | |
|---|---|---|
| committer | 2025-06-23 17:49:11 -0400 | |
| commit | e0fcbf0910b52a6e66eb733a2850ec58a53cf0e3 (patch) | |
| tree | a24ff2b9313263666d121536064e01436b480571 | |
| parent | e9475ce94ba69bd437d8cf0cf3f78062be928568 (diff) | |
| download | camu-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>
55 files changed, 533 insertions, 284 deletions
@@ -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); @@ -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": { @@ -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 |