mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 17:28:49 -10:00
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:
1 parent
a4c583e01c
commit
1316b60f05
15 files changed
+728
-16
No files matched your search
+6
-1
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user