66 * or MPV -- consoles and other minimal targets -- where it acts as the
77 * built-in media player fallback for .webm content.
88 *
9- * Video only for now: audio tracks are ignored and silence is output
10- * to keep frame pacing driven by the audio path. VP9 superframes and
11- * invisible (non-shown) VP8/VP9 frames are handled.
9+ * Audio tracks (Opus via ropus, Vorbis via rvorbis) play through the
10+ * audio_transfer demuxed path when the decoders are built in; emission
11+ * is paced by the presented video frame's timestamp, which keeps A/V
12+ * in sync without a separate clock. Files without a supported audio
13+ * track fall back to silence to keep frame pacing driven by the audio
14+ * callback. VP9 superframes and invisible (non-shown) VP8/VP9 frames
15+ * are handled.
1216 */
1317
1418#include <stdio.h>
2226#include <formats/rwebm.h>
2327#include <formats/rvp9.h>
2428
29+ #if defined(HAVE_ROPUS ) || defined(HAVE_RVORBIS )
30+ #define WEBM_HAVE_AUDIO 1
31+ #include <formats/audio.h>
32+ #endif
33+
2534#ifdef RARCH_INTERNAL
2635#include "internal_cores.h"
2736#define WEBM_CORE_PREFIX (s ) libretro_webm_##s
3544
3645#define WEBM_AUDIO_RATE 48000
3746
47+ #ifdef WEBM_HAVE_AUDIO
48+ /* Opus packet duration in 48 kHz frames from the TOC (RFC 6716 s3):
49+ * used to compute the exact decodable total so container end trimming
50+ * (DiscardPadding) can clamp emission. */
51+ static int64_t webm_opus_pkt_frames (const uint8_t * d , size_t n )
52+ {
53+ static const int16_t fs [32 ] = {
54+ 480 , 960 , 1920 , 2880 , 480 , 960 , 1920 , 2880 , /* SILK NB/MB */
55+ 480 , 960 , 1920 , 2880 , /* SILK WB */
56+ 480 , 960 , 480 , 960 , /* hybrid */
57+ 120 , 240 , 480 , 960 , 120 , 240 , 480 , 960 , /* CELT NB/WB */
58+ 120 , 240 , 480 , 960 , 120 , 240 , 480 , 960 /* CELT SWB/FB */
59+ };
60+ int count ;
61+ if (!n )
62+ return 0 ;
63+ switch (d [0 ] & 3 )
64+ {
65+ case 0 : count = 1 ; break ;
66+ case 3 : count = n >= 2 ? (d [1 ] & 0x3F ) : 0 ; break ;
67+ default : count = 2 ; break ;
68+ }
69+ return (int64_t )fs [d [0 ] >> 3 ] * count ;
70+ }
71+ #endif
72+
3873static retro_log_printf_t WEBM_CORE_PREFIX (log_cb );
3974static retro_video_refresh_t WEBM_CORE_PREFIX (video_cb );
4075static retro_audio_sample_t WEBM_CORE_PREFIX (audio_cb );
@@ -60,6 +95,19 @@ typedef struct
6095 int eof ;
6196 int16_t * silence ;
6297 size_t silence_frames ;
98+ #ifdef WEBM_HAVE_AUDIO
99+ void * actx ; /* audio_transfer context */
100+ enum audio_type_enum atype ;
101+ int atrack ; /* audio track index, -1 = none */
102+ uint8_t * apkts ; /* concatenated audio packets */
103+ uint32_t * asizes ;
104+ unsigned ach ; /* decoded channels (1 or 2) */
105+ unsigned arate ;
106+ int64_t apos ; /* frames emitted so far */
107+ int64_t atotal ; /* emit clamp; <0 = no clamp */
108+ int aeof ;
109+ int64_t vpts_ns ; /* pts of the last presented frame */
110+ #endif
63111} webm_player_t ;
64112
65113static webm_player_t webm_player ;
@@ -226,6 +274,12 @@ static void webm_free_player(webm_player_t *p)
226274#endif
227275 if (p -> webm )
228276 rwebm_close (p -> webm );
277+ #ifdef WEBM_HAVE_AUDIO
278+ if (p -> actx )
279+ audio_transfer_free (p -> actx , p -> atype );
280+ free (p -> apkts );
281+ free (p -> asizes );
282+ #endif
229283 free (p -> fb );
230284 free (p -> silence );
231285 free (p -> file_buf );
@@ -268,6 +322,10 @@ void WEBM_CORE_PREFIX(retro_get_system_av_info)(
268322 memset (info , 0 , sizeof (* info ));
269323 info -> timing .fps = p -> fps ;
270324 info -> timing .sample_rate = WEBM_AUDIO_RATE ;
325+ #ifdef WEBM_HAVE_AUDIO
326+ if (webm_player .actx )
327+ info -> timing .sample_rate = (double )webm_player .arate ;
328+ #endif
271329 info -> geometry .base_width = p -> width ;
272330 info -> geometry .base_height = p -> height ;
273331 info -> geometry .max_width = p -> width ;
@@ -325,6 +383,13 @@ void WEBM_CORE_PREFIX(retro_reset)(void)
325383 rvp8_video_close (p -> vp8 );
326384 p -> vp8 = rvp8_video_open ();
327385 }
386+ #endif
387+ #ifdef WEBM_HAVE_AUDIO
388+ if (p -> actx )
389+ audio_transfer_seek (p -> actx , p -> atype , 0 );
390+ p -> apos = 0 ;
391+ p -> aeof = 0 ;
392+ p -> vpts_ns = 0 ;
328393#endif
329394 p -> eof = 0 ;
330395}
@@ -355,18 +420,75 @@ void WEBM_CORE_PREFIX(retro_run)(void)
355420 p -> eof = 1 ;
356421 break ;
357422 }
423+ #ifdef WEBM_HAVE_AUDIO
424+ if (presented > 0 && pkt .timestamp > 0 )
425+ p -> vpts_ns = pkt .timestamp ;
426+ #endif
358427 }
359428 if (!presented )
360429 WEBM_CORE_PREFIX (video_cb )(p -> fb , p -> width , p -> height ,
361430 p -> width * sizeof (uint32_t ));
362431 }
363432 else
364433 {
434+ int audio_done = 1 ;
435+ #ifdef WEBM_HAVE_AUDIO
436+ if (p -> actx && !p -> aeof )
437+ audio_done = 0 ;
438+ #endif
365439 WEBM_CORE_PREFIX (video_cb )(p -> fb , p -> width , p -> height ,
366440 p -> width * sizeof (uint32_t ));
367- WEBM_CORE_PREFIX (environ_cb )(RETRO_ENVIRONMENT_SHUTDOWN , NULL );
441+ if (audio_done )
442+ WEBM_CORE_PREFIX (environ_cb )(RETRO_ENVIRONMENT_SHUTDOWN , NULL );
368443 }
369444
445+ #ifdef WEBM_HAVE_AUDIO
446+ if (p -> actx && !p -> aeof )
447+ {
448+ /* Emit decoded audio up to the presented frame's timestamp plus
449+ * one nominal frame of lead; at video EOF, drain about a frame
450+ * interval per run so the frontend's audio pacing continues to
451+ * throttle us through the tail. */
452+ int16_t buf [1024 * 2 ];
453+ int16_t st [1024 * 2 ];
454+ int64_t target = (int64_t )(((double )p -> vpts_ns * 1e-9 + 1.0 / p -> fps )
455+ * (double )p -> arate );
456+ if (p -> eof )
457+ target = p -> apos + (int64_t )((double )p -> arate / p -> fps ) + 1 ;
458+ if (p -> atotal >= 0 && target > p -> atotal )
459+ target = p -> atotal ;
460+ if (p -> atotal >= 0 && p -> apos >= p -> atotal )
461+ p -> aeof = 1 ;
462+ while (p -> apos < target )
463+ {
464+ size_t want = (size_t )(target - p -> apos );
465+ size_t got = 0 ;
466+ int r ;
467+ if (want > 1024 )
468+ want = 1024 ;
469+ r = audio_transfer_read_s16 (p -> actx , p -> atype , buf , want , & got );
470+ if (got )
471+ {
472+ const int16_t * out = buf ;
473+ if (p -> ach == 1 )
474+ {
475+ size_t i2 ;
476+ for (i2 = 0 ; i2 < got ; i2 ++ )
477+ st [2 * i2 ] = st [2 * i2 + 1 ] = buf [i2 ];
478+ out = st ;
479+ }
480+ WEBM_CORE_PREFIX (audio_batch_cb )(out , got );
481+ p -> apos += (int64_t )got ;
482+ }
483+ if (r != AUDIO_PROCESS_NEXT || !got )
484+ {
485+ p -> aeof = 1 ;
486+ break ;
487+ }
488+ }
489+ }
490+ else
491+ #endif
370492 WEBM_CORE_PREFIX (audio_batch_cb )(p -> silence , p -> silence_frames );
371493}
372494
@@ -421,6 +543,30 @@ bool WEBM_CORE_PREFIX(retro_load_game)(const struct retro_game_info *info)
421543 }
422544#endif
423545 }
546+ #ifdef WEBM_HAVE_AUDIO
547+ p -> atrack = -1 ;
548+ for (i = 0 ; i < rwebm_num_tracks (p -> webm ); i ++ )
549+ {
550+ const rwebm_track * t = rwebm_get_track (p -> webm , i );
551+ if (t -> type != RWEBM_TRACK_AUDIO || p -> atrack >= 0 )
552+ continue ;
553+ #ifdef HAVE_ROPUS
554+ if (t -> codec == RWEBM_CODEC_OPUS && t -> codec_private_size )
555+ {
556+ p -> atrack = i ;
557+ p -> atype = AUDIO_TYPE_OPUS ;
558+ }
559+ #endif
560+ #ifdef HAVE_RVORBIS
561+ if (t -> codec == RWEBM_CODEC_VORBIS && t -> codec_private_size
562+ && p -> atrack < 0 )
563+ {
564+ p -> atrack = i ;
565+ p -> atype = AUDIO_TYPE_VORBIS ;
566+ }
567+ #endif
568+ }
569+ #endif
424570 if (p -> vtrack < 0 || !vt || !vt -> width || !vt -> height )
425571 {
426572 if (WEBM_CORE_PREFIX (log_cb ))
@@ -440,8 +586,19 @@ bool WEBM_CORE_PREFIX(retro_load_game)(const struct retro_game_info *info)
440586 nvpkts = 0 ;
441587 {
442588 rwebm_packet pkt ;
589+ #ifdef WEBM_HAVE_AUDIO
590+ size_t napkts = 0 , abytes = 0 ;
591+ #endif
443592 while (rwebm_read_packet (p -> webm , & pkt ) == 1 )
444593 {
594+ #ifdef WEBM_HAVE_AUDIO
595+ if (pkt .track == p -> atrack )
596+ {
597+ napkts ++ ;
598+ abytes += pkt .size ;
599+ continue ;
600+ }
601+ #endif
445602 if (pkt .track != p -> vtrack )
446603 continue ;
447604 if (p -> codec == RWEBM_CODEC_VP8
@@ -450,6 +607,88 @@ bool WEBM_CORE_PREFIX(retro_load_game)(const struct retro_game_info *info)
450607 nvpkts ++ ;
451608 }
452609 rwebm_rewind (p -> webm );
610+ #ifdef WEBM_HAVE_AUDIO
611+ /* Second pass: copy the audio packets into one contiguous blob
612+ * for the audio_transfer demuxed contract (the packets alias the
613+ * file buffer but are interleaved with video, so they are not
614+ * contiguous in place). */
615+ if (p -> atrack >= 0 && napkts )
616+ {
617+ size_t k = 0 , off = 0 ;
618+ p -> apkts = (uint8_t * )malloc (abytes ? abytes : 1 );
619+ p -> asizes = (uint32_t * )malloc (napkts * sizeof (uint32_t ));
620+ if (p -> apkts && p -> asizes )
621+ {
622+ int64_t toc_frames = 0 , discard_ns = 0 ;
623+ while (rwebm_read_packet (p -> webm , & pkt ) == 1 )
624+ {
625+ if (pkt .track != p -> atrack )
626+ continue ;
627+ memcpy (p -> apkts + off , pkt .data , pkt .size );
628+ p -> asizes [k ++ ] = (uint32_t )pkt .size ;
629+ off += pkt .size ;
630+ if (p -> atype == AUDIO_TYPE_OPUS )
631+ toc_frames += webm_opus_pkt_frames (pkt .data , pkt .size );
632+ if (pkt .discard_padding > 0 )
633+ discard_ns += pkt .discard_padding ;
634+ }
635+ rwebm_rewind (p -> webm );
636+ /* Exact playable length: decoded total minus the pre-skip
637+ * (dropped inside the decoder arm) minus container end
638+ * trimming. Only computable for Opus, whose TOC encodes
639+ * packet durations; Vorbis emission is left unclamped. */
640+ p -> atotal = -1 ;
641+ if (p -> atype == AUDIO_TYPE_OPUS && toc_frames > 0 )
642+ {
643+ const rwebm_track * at0 =
644+ rwebm_get_track (p -> webm , p -> atrack );
645+ int64_t preskip = 0 ;
646+ if (at0 -> codec_private_size >= 19 )
647+ preskip = at0 -> codec_private [10 ]
648+ | ((int64_t )at0 -> codec_private [11 ] << 8 );
649+ p -> atotal = toc_frames - preskip
650+ - (discard_ns * 48000 + 500000000 ) / 1000000000 ;
651+ if (p -> atotal < 0 )
652+ p -> atotal = 0 ;
653+ }
654+ {
655+ const rwebm_track * at = rwebm_get_track (p -> webm , p -> atrack );
656+ p -> actx = audio_transfer_new (p -> atype );
657+ if (p -> actx
658+ && audio_transfer_set_demuxed_ptr (p -> actx , p -> atype ,
659+ at -> codec_private , at -> codec_private_size ,
660+ p -> apkts , off , p -> asizes , k )
661+ && audio_transfer_start (p -> actx , p -> atype )
662+ && audio_transfer_info (p -> actx , p -> atype ,
663+ & p -> ach , & p -> arate , NULL )
664+ && p -> ach >= 1 && p -> ach <= 2 && p -> arate )
665+ {
666+ if (WEBM_CORE_PREFIX (log_cb ))
667+ WEBM_CORE_PREFIX (log_cb )(RETRO_LOG_INFO ,
668+ "[webm] audio: %s, %u Hz, %u ch, %u packets.\n" ,
669+ p -> atype == AUDIO_TYPE_OPUS ? "Opus" : "Vorbis" ,
670+ p -> arate , p -> ach , (unsigned )k );
671+ }
672+ else
673+ {
674+ if (WEBM_CORE_PREFIX (log_cb ))
675+ WEBM_CORE_PREFIX (log_cb )(RETRO_LOG_WARN ,
676+ "[webm] audio track unusable; playing silent.\n" );
677+ if (p -> actx )
678+ audio_transfer_free (p -> actx , p -> atype );
679+ p -> actx = NULL ;
680+ }
681+ }
682+ }
683+ if (!p -> actx )
684+ {
685+ free (p -> apkts );
686+ free (p -> asizes );
687+ p -> apkts = NULL ;
688+ p -> asizes = NULL ;
689+ }
690+ }
691+ #endif
453692 }
454693 dur_ns = rwebm_duration_ns (p -> webm );
455694 if (nvpkts > 1 && dur_ns > 0 )
0 commit comments