Menu: animated WebM thumbnails (Ozone/XMB/MaterialUI)

Let .webm files serve as menu thumbnails, decoded and animated exactly
like animated WebP: the still-image pipeline uploads the first displayed
frame, and gfx_thumbnail_animate() then streams frames under the
existing per-vsync decode budget with durations taken from the
container's block timestamps.

New glue module formats/webm/rwebm_video.c combines the rwebm demuxer
with the rvp8/rvp9 decoders behind the two contracts image_transfer.c
dispatches on:
- rwebm_video_alloc/set_buf_ptr/process_image: rwebp_t-style still
  image (first displayed frame), honouring supports_rgba
- rwebm_video_stream_open/get_info/next/rewind/close: the
  anim-stream contract (ABGR words, per-frame duration in ms,
  loop_count 0 = infinite)

It picks the first video track with a compiled-in codec (VP8 via rvp8;
VP9 via rvp9 when HAVE_RVP9), pre-scans block timestamps so frame
durations need no lookahead decode, splits VP9 superframes, skips
non-shown frames (VP8 show_frame bit / VP9 show_fb), and resets the
decoder on rewind since the stream restarts at a key frame.

Plumbing: IMAGE_TYPE_WEBM through image_texture_get_type and every
image_transfer dispatch, NBIO_TYPE_WEBM through the image load task,
the gfx_thumbnail anim gate, and .webp/.webm appended to the
playlist_allow_non_png extension probe order (webp animation existed
but was previously undiscoverable there). HAVE_RWEBM/HAVE_RVP9 default
to yes in qb builds; console Makefiles are unchanged.

Verified against libvpx via ffmpeg-encoded streams (VP8, VP9,
VP9+altref superframes, odd 317x239 dims): frame counts and container
durations exact, rewind byte-deterministic, final inter frame within
YUV->RGB rounding (max diff 3) of ffmpeg's decode; ASan/UBSan clean
over truncation and random-corruption sweeps. Multi-tile VP9 streams
are rejected gracefully (documented rvp9 limitation). Decode cost in
harness: ~1ms/frame at 320x240, well inside the 8ms anim budget;
oversized streams degrade to a lower frame rate by design.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-07-14 15:06:34 +02:00
1 parent a4c583e01c
commit 1316b60f05
15 files changed
+728 -16

No files matched your search

+6 -1
View File
@@ -2326,7 +2326,12 @@ endif
ifeq ($(HAVE_RWEBM), 1)
DEFINES += -DHAVE_RWEBM
OBJ += $(LIBRETRO_COMM_DIR)/formats/webm/rwebm.o
OBJ += $(LIBRETRO_COMM_DIR)/formats/webm/rwebm.o \
$(LIBRETRO_COMM_DIR)/formats/webm/rwebm_video.o
ifneq ($(HAVE_RWEBP), 1)
# rwebm_video decodes VP8 tracks with rvp8, normally built with RWEBP
OBJ += $(LIBRETRO_COMM_DIR)/formats/vp8/rvp8.o
endif
endif
ifeq ($(HAVE_RVP9), 1)
+12
View File
@@ -416,6 +416,18 @@
#define SUPPORTS_RWEBP false
#endif
#ifdef HAVE_RWEBM
#define SUPPORTS_RWEBM true
#else
#define SUPPORTS_RWEBM false
#endif
#ifdef HAVE_RVP9
#define SUPPORTS_RVP9 true
#else
#define SUPPORTS_RVP9 false
#endif
#ifdef HAVE_RDDS
#define SUPPORTS_RDDS true
#else
+18 -3
View File
@@ -273,7 +273,8 @@ static void gfx_thumbnail_anim_open(gfx_thumbnail_t *thumbnail,
/* Cheap gate: only container types with an animation decoder */
type = image_texture_get_type(path);
if (type != IMAGE_TYPE_WEBP)
if ( (type != IMAGE_TYPE_WEBP)
&& (type != IMAGE_TYPE_WEBM))
return;
if (!filestream_read_file(path, &buf, &len))
@@ -318,7 +319,8 @@ fail:
* thumbnail (menu drivers do this from their main-thread iterate step).
* For a still image the ANIM_ACTIVE flag is clear, so this returns
* immediately after a single flag test - non-animated thumbnails, and
* every non-WebP image type, pay nothing beyond that. */
* every image type without an animation decoder, pay nothing beyond
* that. */
void gfx_thumbnail_animate(gfx_thumbnail_t *thumbnail)
{
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
@@ -2048,8 +2050,21 @@ bool gfx_thumbnail_update_path(
unsigned menu_left_thumbnails = settings->uints.menu_left_thumbnails;
unsigned menu_icon_thumbnails = settings->uints.menu_icon_thumbnails;
/* Thumbnail extension order. The default (i.e. png) is always the first. */
#if defined(HAVE_RWEBP) && defined(HAVE_RWEBM)
#define MAX_SUPPORTED_THUMBNAIL_EXTENSIONS 7
#elif defined(HAVE_RWEBP) || defined(HAVE_RWEBM)
#define MAX_SUPPORTED_THUMBNAIL_EXTENSIONS 6
#else
#define MAX_SUPPORTED_THUMBNAIL_EXTENSIONS 5
const char* const SUPPORTED_THUMBNAIL_EXTENSIONS[] = { ".png", ".jpg", ".jpeg", ".bmp", ".tga", 0 };
#endif
const char* const SUPPORTED_THUMBNAIL_EXTENSIONS[] = { ".png", ".jpg", ".jpeg", ".bmp", ".tga",
#ifdef HAVE_RWEBP
".webp",
#endif
#ifdef HAVE_RWEBM
".webm",
#endif
0 };
if (!path_data)
return false;
+7 -1
View File
@@ -440,9 +440,15 @@ VIDEO IMAGE
#include "../libretro-common/formats/bmp/rbmp.c"
#endif
#ifdef HAVE_RWEBP
#include "../libretro-common/formats/vp8/rvp8.c"
#include "../libretro-common/formats/webp/rwebp.c"
#endif
#if defined(HAVE_RWEBP) || defined(HAVE_RWEBM)
#include "../libretro-common/formats/vp8/rvp8.c"
#endif
#ifdef HAVE_RWEBM
#include "../libretro-common/formats/webm/rwebm.c"
#include "../libretro-common/formats/webm/rwebm_video.c"
#endif
#ifdef HAVE_RVP9
#include "../libretro-common/formats/vp9/rvp9.c"
+7
View File
@@ -101,6 +101,13 @@ enum image_type_enum image_texture_get_type(const char *path)
(ext[2] | 0x20) == 'b' &&
(ext[3] | 0x20) == 'p')
return IMAGE_TYPE_WEBP;
#endif
#ifdef HAVE_RWEBM
if ((ext[0] | 0x20) == 'w' &&
(ext[1] | 0x20) == 'e' &&
(ext[2] | 0x20) == 'b' &&
(ext[3] | 0x20) == 'm')
return IMAGE_TYPE_WEBM;
#endif
break;
}
+67 -3
View File
@@ -41,6 +41,10 @@
#ifdef HAVE_RWEBP
#include <formats/rwebp.h>
#endif
#ifdef HAVE_RWEBM
#include <formats/rwebm_video.h>
#endif
#ifdef HAVE_RDDS
#include <formats/rdds.h>
#endif
@@ -78,6 +82,11 @@ void image_transfer_free(void *data, enum image_type_enum type)
case IMAGE_TYPE_WEBP:
#ifdef HAVE_RWEBP
rwebp_free((rwebp_t*)data);
#endif
break;
case IMAGE_TYPE_WEBM:
#ifdef HAVE_RWEBM
rwebm_video_free((rwebm_video_t*)data);
#endif
break;
case IMAGE_TYPE_DDS:
@@ -123,6 +132,12 @@ void *image_transfer_new(enum image_type_enum type)
return rwebp_alloc();
#else
break;
#endif
case IMAGE_TYPE_WEBM:
#ifdef HAVE_RWEBM
return rwebm_video_alloc();
#else
break;
#endif
case IMAGE_TYPE_DDS:
#ifdef HAVE_RDDS
@@ -168,6 +183,8 @@ bool image_transfer_start(void *data, enum image_type_enum type)
return true;
case IMAGE_TYPE_WEBP:
return true;
case IMAGE_TYPE_WEBM:
return true;
case IMAGE_TYPE_DDS:
#ifdef HAVE_RDDS
return true;
@@ -209,6 +226,8 @@ bool image_transfer_is_valid(
return true;
case IMAGE_TYPE_WEBP:
return true;
case IMAGE_TYPE_WEBM:
return true;
case IMAGE_TYPE_DDS:
#ifdef HAVE_RDDS
return true;
@@ -253,6 +272,11 @@ void image_transfer_set_buffer_ptr(
case IMAGE_TYPE_WEBP:
#ifdef HAVE_RWEBP
rwebp_set_buf_ptr((rwebp_t*)data, (uint8_t*)ptr, len);
#endif
break;
case IMAGE_TYPE_WEBM:
#ifdef HAVE_RWEBM
rwebm_video_set_buf_ptr((rwebm_video_t*)data, (uint8_t*)ptr, len);
#endif
break;
case IMAGE_TYPE_DDS:
@@ -316,6 +340,14 @@ int image_transfer_process(
break;
#else
break;
#endif
case IMAGE_TYPE_WEBM:
#ifdef HAVE_RWEBM
ret = rwebm_video_process_image((rwebm_video_t*)data,
(void**)buf, len, width, height, supports_rgba);
break;
#else
break;
#endif
case IMAGE_TYPE_DDS:
#ifdef HAVE_RDDS
@@ -447,6 +479,8 @@ bool image_transfer_iterate(void *data, enum image_type_enum type)
return false;
case IMAGE_TYPE_WEBP:
return false;
case IMAGE_TYPE_WEBM:
return false;
case IMAGE_TYPE_DDS:
#ifdef HAVE_RDDS
return false;
@@ -461,9 +495,10 @@ bool image_transfer_iterate(void *data, enum image_type_enum type)
}
/* ===== Animation ===== *
* Currently only WEBP supports animation. These helpers return NULL /
* false for every other image type, so callers may attempt animation
* unconditionally and fall back to the still-image path. */
* WEBP (animated) and WEBM (video track) support animation. These
* helpers return NULL / false for every other image type, so callers
* may attempt animation unconditionally and fall back to the
* still-image path. */
void *image_transfer_anim_new(void *buf, size_t len,
enum image_type_enum type)
@@ -558,6 +593,12 @@ void *image_transfer_anim_stream_new(void *buf, size_t len,
return rwebp_anim_stream_open((const uint8_t*)buf, len);
#else
break;
#endif
case IMAGE_TYPE_WEBM:
#ifdef HAVE_RWEBM
return rwebm_video_stream_open((const uint8_t*)buf, len);
#else
break;
#endif
default:
break;
@@ -573,6 +614,11 @@ void image_transfer_anim_stream_free(void *stream,
case IMAGE_TYPE_WEBP:
#ifdef HAVE_RWEBP
rwebp_anim_stream_close((rwebp_anim_stream_t*)stream);
#endif
break;
case IMAGE_TYPE_WEBM:
#ifdef HAVE_RWEBM
rwebm_video_stream_close((rwebm_video_stream_t*)stream);
#endif
break;
default:
@@ -590,6 +636,12 @@ void image_transfer_anim_stream_get_info(void *stream,
#ifdef HAVE_RWEBP
rwebp_anim_stream_get_info((const rwebp_anim_stream_t*)stream,
width, height, num_frames, loop_count);
#endif
break;
case IMAGE_TYPE_WEBM:
#ifdef HAVE_RWEBM
rwebm_video_stream_get_info((const rwebm_video_stream_t*)stream,
width, height, num_frames, loop_count);
#endif
break;
default:
@@ -608,6 +660,13 @@ const uint32_t *image_transfer_anim_stream_next(void *stream,
duration_ms);
#else
break;
#endif
case IMAGE_TYPE_WEBM:
#ifdef HAVE_RWEBM
return rwebm_video_stream_next((rwebm_video_stream_t*)stream,
duration_ms);
#else
break;
#endif
default:
break;
@@ -623,6 +682,11 @@ void image_transfer_anim_stream_rewind(void *stream,
case IMAGE_TYPE_WEBP:
#ifdef HAVE_RWEBP
rwebp_anim_stream_rewind((rwebp_anim_stream_t*)stream);
#endif
break;
case IMAGE_TYPE_WEBM:
#ifdef HAVE_RWEBM
rwebm_video_stream_rewind((rwebm_video_stream_t*)stream);
#endif
break;
default:
+505
View File
@@ -0,0 +1,505 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------
* The following license statement only applies to this file (rwebm_video.c).
* ---------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* WebM video-to-image glue: rwebm demuxer + rvp8/rvp9 decoders exposed
* through the still-image and streaming-animation contracts that
* image_transfer.c dispatches on (see rwebm_video.h). */
#include <stdlib.h>
#include <string.h>
#include <retro_inline.h>
#include <formats/image.h>
#include <formats/rwebm.h>
#include <formats/rvp8.h>
#ifdef HAVE_RVP9
#include <formats/rvp9.h>
#endif
#include <formats/rwebm_video.h>
/* Per-packet timestamps are pre-scanned at open so every frame's display
* duration is known without lookahead decoding; cap the table so a
* pathological stream cannot balloon it. Frames past the cap reuse the
* last stored delta. */
#define RWEBM_VIDEO_MAX_TS 8192
/* A VP9 superframe carries at most 8 sub-frames. */
#define RWEBM_VIDEO_MAX_SUPER 8
struct rwebm_video_stream
{
rwebm_t *demux;
rvp8_video *vp8;
#ifdef HAVE_RVP9
rvp9_dec *vp9;
#endif
uint32_t *frame; /* width * height ABGR words */
int64_t *ts; /* pre-scanned packet timestamps (ns) */
int ts_count; /* entries stored in ts */
int num_frames; /* total video packets in the stream */
int pkt_idx; /* ordinal of the next video packet */
int track; /* index of the chosen video track */
enum rwebm_codec codec;
unsigned width;
unsigned height;
};
struct rwebm_video
{
const uint8_t *buf;
size_t len;
};
/* ------------------------------------------------------------------ */
/* BT.601 limited-range I420 -> ABGR words (memory R,G,B,A on LE), */
/* the packing the animated-WebP stream emits. */
/* ------------------------------------------------------------------ */
static INLINE uint32_t rwebm_video_yuv_px(int y, int u, int v)
{
int c = 298 * (y - 16);
int d = u - 128;
int e = v - 128;
int r = (c + 409 * e + 128) >> 8;
int g = (c - 100 * d - 208 * e + 128) >> 8;
int b = (c + 516 * d + 128) >> 8;
if (r < 0)
r = 0;
else if (r > 255)
r = 255;
if (g < 0)
g = 0;
else if (g > 255)
g = 255;
if (b < 0)
b = 0;
else if (b > 255)
b = 255;
return 0xFF000000u
| ((uint32_t)b << 16)
| ((uint32_t)g << 8)
| (uint32_t)r;
}
static void rwebm_video_blit_i420(uint32_t *dst, unsigned dst_stride,
unsigned w, unsigned h,
const uint8_t *y, int ys,
const uint8_t *u, const uint8_t *v, int uvs)
{
unsigned j;
for (j = 0; j < h; j++)
{
const uint8_t *yr = y + (size_t)j * ys;
const uint8_t *ur = u + (size_t)(j >> 1) * uvs;
const uint8_t *vr = v + (size_t)(j >> 1) * uvs;
uint32_t *dr = dst + (size_t)j * dst_stride;
unsigned i;
for (i = 0; i < w; i++)
dr[i] = rwebm_video_yuv_px(yr[i], ur[i >> 1], vr[i >> 1]);
}
}
/* ------------------------------------------------------------------ */
/* VP9 superframe index (parsed from the trailing marker byte). */
/* Returns the number of sub-frames and their sizes; 1 = whole chunk. */
/* ------------------------------------------------------------------ */
#ifdef HAVE_RVP9
static int rwebm_video_vp9_superframe(const uint8_t *data, size_t size,
size_t *sizes, int max_frames)
{
uint8_t marker;
int frames, bytes_per_size, i, k;
size_t index_size, total;
const uint8_t *x;
if (size < 2)
goto whole;
marker = data[size - 1];
if ((marker & 0xe0) != 0xc0)
goto whole;
frames = (marker & 0x7) + 1;
bytes_per_size = ((marker >> 3) & 0x3) + 1;
index_size = 2 + (size_t)frames * bytes_per_size;
if (size < index_size || data[size - index_size] != marker)
goto whole;
if (frames > max_frames)
goto whole;
x = data + size - index_size + 1;
total = 0;
for (i = 0; i < frames; i++)
{
size_t sz = 0;
for (k = 0; k < bytes_per_size; k++)
sz |= (size_t)*x++ << (k * 8);
sizes[i] = sz;
total += sz;
}
if (total > size - index_size)
goto whole;
return frames;
whole:
sizes[0] = size;
return 1;
}
#endif
/* ------------------------------------------------------------------ */
/* Decoder lifecycle */
/* ------------------------------------------------------------------ */
static bool rwebm_video_stream_open_decoder(rwebm_video_stream_t *s)
{
switch (s->codec)
{
case RWEBM_CODEC_VP8:
if (!(s->vp8 = rvp8_video_open()))
return false;
return true;
#ifdef HAVE_RVP9
case RWEBM_CODEC_VP9:
if (!(s->vp9 = (rvp9_dec*)calloc(1, sizeof(*s->vp9))))
return false;
return true;
#endif
default:
break;
}
return false;
}
static void rwebm_video_stream_close_decoder(rwebm_video_stream_t *s)
{
if (s->vp8)
{
rvp8_video_close(s->vp8);
s->vp8 = NULL;
}
#ifdef HAVE_RVP9
if (s->vp9)
{
rvp9_free(s->vp9);
free(s->vp9);
s->vp9 = NULL;
}
#endif
}
/* ------------------------------------------------------------------ */
/* Streaming animation */
/* ------------------------------------------------------------------ */
rwebm_video_stream_t *rwebm_video_stream_open(const uint8_t *buf,
size_t len)
{
rwebm_video_stream_t *s;
const rwebm_track *trk = NULL;
rwebm_packet pkt;
int i, num_tracks;
if (!buf || !len)
return NULL;
if (!(s = (rwebm_video_stream_t*)calloc(1, sizeof(*s))))
return NULL;
if (!(s->demux = rwebm_open_memory(buf, len)))
goto fail;
/* Pick the first video track whose codec we can decode. */
s->track = -1;
num_tracks = rwebm_num_tracks(s->demux);
for (i = 0; i < num_tracks; i++)
{
const rwebm_track *t = rwebm_get_track(s->demux, i);
if (!t || t->type != RWEBM_TRACK_VIDEO)
continue;
if (t->width < 1 || t->height < 1)
continue;
if (t->codec != RWEBM_CODEC_VP8
#ifdef HAVE_RVP9
&& t->codec != RWEBM_CODEC_VP9
#endif
)
continue;
s->track = i;
s->codec = t->codec;
trk = t;
break;
}
if (s->track < 0)
goto fail;
s->width = trk->width;
s->height = trk->height;
/* Pre-scan: count the track's packets and record their timestamps so
* frame durations come straight from the container. This walks block
* headers only (no decode). */
if (!(s->ts = (int64_t*)malloc(RWEBM_VIDEO_MAX_TS * sizeof(int64_t))))
goto fail;
while (rwebm_read_packet(s->demux, &pkt) == 1)
{
if (pkt.track != s->track)
continue;
if (s->num_frames < RWEBM_VIDEO_MAX_TS)
s->ts[s->ts_count++] = pkt.timestamp;
s->num_frames++;
}
if (s->num_frames < 1)
goto fail;
rwebm_rewind(s->demux);
if (!rwebm_video_stream_open_decoder(s))
goto fail;
if (!(s->frame = (uint32_t*)malloc(
(size_t)s->width * s->height * sizeof(uint32_t))))
goto fail;
return s;
fail:
rwebm_video_stream_close(s);
return NULL;
}
void rwebm_video_stream_close(rwebm_video_stream_t *s)
{
if (!s)
return;
rwebm_video_stream_close_decoder(s);
if (s->demux)
rwebm_close(s->demux);
free(s->ts);
free(s->frame);
free(s);
}
void rwebm_video_stream_get_info(const rwebm_video_stream_t *s,
unsigned *width, unsigned *height, int *num_frames, int *loop_count)
{
if (!s)
return;
if (width)
*width = s->width;
if (height)
*height = s->height;
if (num_frames)
*num_frames = s->num_frames;
if (loop_count)
*loop_count = 0; /* video loops indefinitely */
}
/* Display duration of packet 'idx', in ms, from the pre-scanned
* timestamp table; 0 when unknown (caller applies its default). */
static int rwebm_video_duration_ms(const rwebm_video_stream_t *s, int idx)
{
int64_t delta_ns = 0;
if (idx + 1 < s->ts_count)
delta_ns = s->ts[idx + 1] - s->ts[idx];
else if (s->ts_count >= 2)
delta_ns = s->ts[s->ts_count - 1] - s->ts[s->ts_count - 2];
if (delta_ns <= 0)
return 0;
return (int)(delta_ns / 1000000);
}
/* Decode one demuxed packet into s->frame. Returns 1 when a picture was
* produced, 0 when the packet decoded but is not displayed, -1 on a
* decode error. */
static int rwebm_video_decode_packet(rwebm_video_stream_t *s,
const rwebm_packet *pkt)
{
/* A decoder can be absent if the re-open in rewind hit OOM. */
if (!s->vp8
#ifdef HAVE_RVP9
&& !s->vp9
#endif
)
return -1;
#ifdef HAVE_RVP9
if (s->codec == RWEBM_CODEC_VP9)
{
size_t sizes[RWEBM_VIDEO_MAX_SUPER];
const uint8_t *frame = pkt->data;
int nf = rwebm_video_vp9_superframe(pkt->data, pkt->size,
sizes, RWEBM_VIDEO_MAX_SUPER);
int i, r, show, last_show = -1;
for (i = 0; i < nf; i++)
{
r = rvp9_decode_frame(s->vp9, frame, sizes[i], &show);
if (r < 0)
return -1;
if (show >= 0)
last_show = show;
frame += sizes[i];
}
if (last_show >= 0)
{
const rvp9_fb *fb = &s->vp9->fbs[last_show];
unsigned w = (unsigned)fb->w < s->width ? (unsigned)fb->w : s->width;
unsigned h = (unsigned)fb->h < s->height ? (unsigned)fb->h : s->height;
rwebm_video_blit_i420(s->frame, s->width, w, h,
fb->y, s->vp9->ys, fb->u, fb->v, s->vp9->uvs);
return 1;
}
return 0;
}
#endif
if (s->codec == RWEBM_CODEC_VP8)
{
const uint8_t *y, *u, *v;
int ys, uvs, w, h, cw, ch;
/* VP8 frame tag: bit 4 of byte 0 is show_frame. */
int shown = (pkt->size > 0) && (pkt->data[0] & 0x10);
if (rvp8_video_decode(s->vp8, pkt->data, pkt->size) != 0)
return -1;
if (!shown)
return 0;
y = rvp8_video_plane(s->vp8, 0, &ys, &w, &h);
u = rvp8_video_plane(s->vp8, 1, &uvs, &cw, &ch);
v = rvp8_video_plane(s->vp8, 2, &uvs, &cw, &ch);
if (!y || !u || !v)
return -1;
if ((unsigned)w > s->width)
w = (int)s->width;
if ((unsigned)h > s->height)
h = (int)s->height;
rwebm_video_blit_i420(s->frame, s->width,
(unsigned)w, (unsigned)h, y, ys, u, v, uvs);
return 1;
}
return -1;
}
const uint32_t *rwebm_video_stream_next(rwebm_video_stream_t *s,
int *duration_ms)
{
rwebm_packet pkt;
if (!s)
return NULL;
while (rwebm_read_packet(s->demux, &pkt) == 1)
{
int idx, r;
if (pkt.track != s->track)
continue;
idx = s->pkt_idx++;
r = rwebm_video_decode_packet(s, &pkt);
if (r < 0)
return NULL; /* decode error: end the animation */
if (r == 0)
continue; /* non-shown frame: keep going */
if (duration_ms)
*duration_ms = rwebm_video_duration_ms(s, idx);
return s->frame;
}
return NULL; /* end of one pass */
}
void rwebm_video_stream_rewind(rwebm_video_stream_t *s)
{
if (!s)
return;
rwebm_rewind(s->demux);
s->pkt_idx = 0;
/* The stream restarts at a key frame, so a fresh decoder (empty
* reference chain, default probabilities) is the correct state. */
rwebm_video_stream_close_decoder(s);
rwebm_video_stream_open_decoder(s);
}
/* ------------------------------------------------------------------ */
/* Still image (first displayed frame) */
/* ------------------------------------------------------------------ */
rwebm_video_t *rwebm_video_alloc(void)
{
return (rwebm_video_t*)calloc(1, sizeof(rwebm_video_t));
}
void rwebm_video_free(rwebm_video_t *webm)
{
free(webm);
}
bool rwebm_video_set_buf_ptr(rwebm_video_t *webm, void *data, size_t len)
{
if (!webm)
return false;
webm->buf = (const uint8_t*)data;
webm->len = len;
return true;
}
int rwebm_video_process_image(rwebm_video_t *webm, void **buf,
size_t len, unsigned *width, unsigned *height, bool supports_rgba)
{
rwebm_video_stream_t *s;
const uint32_t *frame;
uint32_t *out;
size_t i, n;
int duration_ms = 0;
(void)len;
if (!webm || !webm->buf || !buf)
return IMAGE_PROCESS_ERROR;
if (!(s = rwebm_video_stream_open(webm->buf, webm->len)))
return IMAGE_PROCESS_ERROR;
if (!(frame = rwebm_video_stream_next(s, &duration_ms)))
{
rwebm_video_stream_close(s);
return IMAGE_PROCESS_ERROR;
}
n = (size_t)s->width * s->height;
if (!(out = (uint32_t*)malloc(n * sizeof(uint32_t))))
{
rwebm_video_stream_close(s);
return IMAGE_PROCESS_ERROR;
}
if (supports_rgba)
memcpy(out, frame, n * sizeof(uint32_t));
else
{
/* ABGR words -> ARGB words (swap R and B channels). */
for (i = 0; i < n; i++)
{
uint32_t px = frame[i];
out[i] = (px & 0xFF00FF00u)
| ((px & 0xFFu) << 16)
| ((px >> 16) & 0xFFu);
}
}
if (width)
*width = s->width;
if (height)
*height = s->height;
*buf = out;
rwebm_video_stream_close(s);
return IMAGE_PROCESS_END;
}
+3 -2
View File
@@ -63,7 +63,8 @@ enum image_type_enum
IMAGE_TYPE_BMP,
IMAGE_TYPE_TGA,
IMAGE_TYPE_WEBP,
IMAGE_TYPE_DDS
IMAGE_TYPE_DDS,
IMAGE_TYPE_WEBM
};
#define IMAGE_MAX_MIPS 16
@@ -176,7 +177,7 @@ bool image_transfer_iterate(void *data, enum image_type_enum type);
bool image_transfer_is_valid(void *data, enum image_type_enum type);
/* Animation (animated images, currently WEBP only).
/* Animation (animated images: WEBP, and the video track of WEBM).
*
* image_transfer_anim_new returns an opaque animation handle, or NULL
* for still images / unsupported types, so a caller can try it first and
@@ -0,0 +1,85 @@
/* rwebm_video -- WebM video-to-image glue for libretro-common.
*
* Combines the rwebm demuxer with the rvp8/rvp9 decoders to present a
* WebM file's video track through the same two interfaces the image
* formats use, so the menu thumbnail pipeline can treat a .webm exactly
* like an animated .webp:
*
* - Still image: rwebm_video_alloc / set_buf_ptr / process_image
* decode the first displayed frame (the rwebp_t-style contract that
* image_transfer.c drives for the initial texture upload).
*
* - Streaming animation: rwebm_video_stream_* decode one displayed
* frame per call, in stream order, with per-frame durations derived
* from the container's block timestamps (the rwebp_anim_stream_t
* contract that gfx_thumbnail_animate() drives).
*
* VP8 tracks require the rvp8 decoder (built with HAVE_RWEBP or
* HAVE_RWEBM); VP9 tracks additionally require HAVE_RVP9. Tracks whose
* codec is not compiled in are skipped. Audio tracks are ignored, and
* alpha (BlockAdditions) is ignored: output is always opaque.
*
* Frames are returned as w*h uint32 pixels. The animation stream emits
* ABGR words (memory order R,G,B,A on little-endian), matching the
* animated WebP stream; the still-image path honours supports_rgba the
* same way rwebp_process_image does (ARGB words when false).
*/
#ifndef __LIBRETRO_SDK_FORMAT_RWEBM_VIDEO_H__
#define __LIBRETRO_SDK_FORMAT_RWEBM_VIDEO_H__
#include <stdint.h>
#include <stddef.h>
#include <boolean.h>
#include <retro_common_api.h>
RETRO_BEGIN_DECLS
/* ---- Still image (first displayed frame) ---- */
typedef struct rwebm_video rwebm_video_t;
rwebm_video_t *rwebm_video_alloc(void);
void rwebm_video_free(rwebm_video_t *webm);
bool rwebm_video_set_buf_ptr(rwebm_video_t *webm, void *data, size_t len);
/* Decodes the first displayed frame of the first supported video track
* into a freshly malloc'd buffer at *buf. Returns IMAGE_PROCESS_END on
* success, IMAGE_PROCESS_ERROR on failure (no supported video track,
* malformed stream, or out of memory). */
int rwebm_video_process_image(rwebm_video_t *webm, void **buf,
size_t len, unsigned *width, unsigned *height, bool supports_rgba);
/* ---- Streaming animation ---- */
typedef struct rwebm_video_stream rwebm_video_stream_t;
/* The buffer is BORROWED and must remain valid and unmodified until
* rwebm_video_stream_close. Returns NULL when the data is not a WebM
* stream, has no video track with a compiled-in codec, or contains no
* video packets. */
rwebm_video_stream_t *rwebm_video_stream_open(const uint8_t *buf,
size_t len);
void rwebm_video_stream_close(rwebm_video_stream_t *stream);
/* num_frames is the number of coded video packets (an upper bound on
* displayed frames when the stream carries non-shown frames).
* loop_count is always 0: video loops indefinitely. */
void rwebm_video_stream_get_info(const rwebm_video_stream_t *stream,
unsigned *width, unsigned *height, int *num_frames, int *loop_count);
/* Decode the next displayed frame. Returns the frame pixels (valid until
* the next call on this stream) and writes its display duration in ms
* (0 when unknown; the caller applies its default), or NULL at the end
* of one pass; call rwebm_video_stream_rewind to loop. */
const uint32_t *rwebm_video_stream_next(rwebm_video_stream_t *stream,
int *duration_ms);
void rwebm_video_stream_rewind(rwebm_video_stream_t *stream);
RETRO_END_DECLS
#endif
+5 -4
View File
@@ -2150,7 +2150,8 @@ static void xmb_selection_pointer_changed(
* eagerly pre-resolve paths for all visible entries here — the
* selection pointer can move many times per second during held-down
* scrolling, and re-resolving up to ~20 paths per step (including
* path_is_valid syscalls, amplified ×5 by playlist_allow_non_png)
* path_is_valid syscalls, amplified up to ×7 by
* playlist_allow_non_png)
* is enough to blow frame budgets. Lazy resolution in the
* dispatcher gets the same visible result for a fraction of the
* work. The is_playlist / title-name gates match the ones the old
@@ -3016,9 +3017,9 @@ static void xmb_populate_dynamic_icons(xmb_handle_t *xmb)
* the render dispatcher will lazily re-resolve and re-load them for
* currently-visible entries, under its per-frame cap. Doing the
* path resolution upfront for every visible entry here would cost
* one path_is_valid syscall per entry (or five per entry with
* one path_is_valid syscall per entry (or up to seven per entry with
* playlist_allow_non_png enabled, due to the .png/.jpg/.jpeg/.bmp/
* .tga fallback chain in gfx_thumbnail_update_path) — enough to
* .tga/.webp/.webm fallback chain in gfx_thumbnail_update_path) — enough to
* blow a frame budget on the populate frame. */
xmb_unload_icon_thumbnail_textures(xmb);
@@ -7816,7 +7817,7 @@ static void xmb_render(void *data,
* xmb_populate_dynamic_icons and xmb_selection_pointer_changed
* used to pay eagerly for every visible entry on every change,
* including on each vertical scroll step — a stat-syscall
* storm multiplied ×5 by playlist_allow_non_png's extension
* storm multiplied up to ×7 by playlist_allow_non_png's extension
* fallback chain. Doing it here, once per entry per list
* population, eliminates the repeat cost on scroll. */
if (!*thumbnail_icon->thumbnail_path_data.icon_path)
+6
View File
@@ -2404,6 +2404,12 @@ static unsigned menu_displaylist_parse_system_info(file_list_t *list)
#ifdef HAVE_RWEBP
{SUPPORTS_RWEBP, "WebP (RWEBP)"},
#endif
#ifdef HAVE_RWEBM
{SUPPORTS_RWEBM, "WebM (RWEBM)"},
#endif
#ifdef HAVE_RVP9
{SUPPORTS_RVP9, "VP9 (RVP9)"},
#endif
#ifdef HAVE_RDDS
{SUPPORTS_RDDS, "DDS (RDDS)"},
#endif
+2 -2
View File
@@ -180,8 +180,8 @@ HAVE_RJPEG=yes # RJPEG support
HAVE_RTGA=yes # RTGA support
HAVE_RDDS=yes # DDS (DXT/BCn) support
HAVE_RWEBP=yes # RWEBP support
HAVE_RWEBM=no # RWEBM (WebM/Matroska demuxer) support
HAVE_RVP9=no # RVP9 (VP9 video decoder) support
HAVE_RWEBM=yes # RWEBM (WebM/Matroska demuxer) support
HAVE_RVP9=yes # RVP9 (VP9 video decoder) support
HAVE_WEBMPLAYER=no # Built-in WebM (VP8/VP9) movie player
HAVE_RWAV=yes # WAV support
HAVE_HID=no # Low-level HID (Human Interface Device) support
+1
View File
@@ -208,6 +208,7 @@ do_transfer_parse:
case NBIO_TYPE_TGA:
case NBIO_TYPE_BMP:
case NBIO_TYPE_WEBP:
case NBIO_TYPE_WEBM:
if (!task_image_load_handler(task))
task_set_flags(task, RETRO_TASK_FLG_FINISHED, true);
break;
+1
View File
@@ -42,6 +42,7 @@ enum nbio_type
NBIO_TYPE_TGA,
NBIO_TYPE_BMP,
NBIO_TYPE_WEBP,
NBIO_TYPE_WEBM,
NBIO_TYPE_OGG,
NBIO_TYPE_FLAC,
NBIO_TYPE_MP3,
+3
View File
@@ -526,6 +526,9 @@ bool task_push_image_load(const char *fullpath,
case IMAGE_TYPE_WEBP:
nbio->type = NBIO_TYPE_WEBP;
break;
case IMAGE_TYPE_WEBM:
nbio->type = NBIO_TYPE_WEBM;
break;
default:
nbio->type = NBIO_TYPE_NONE;
break;