Skip to content

retro_run() ignores the return value of GET_AUDIO_VIDEO_ENABLE, producing no video and no audio on frontends that don't implement it #105

Description

@thoscut

retro_run() queries RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE but does not
check whether the call succeeded. That environment call is optional
(47 | RETRO_ENVIRONMENT_EXPERIMENTAL). On a frontend that does not implement
it, the callback returns false and never writes data, so
audio_video_enable keeps its initial value of 0. Both output paths are then
gated off permanently.

libretro.h states the contract explicitly:

@returns true if the environment call is available, regardless of the
value output to data.
If false, the core should assume that the frontend will not skip any
steps.

The core does the opposite. RetroArch implements the call and returns 3,
which is why this is invisible there.

Affected frontend in my case: Kodi 21.3 "Omega" via the game.libretro
bridge (21.0.7.3). grep AUDIO_VIDEO_ENABLE returns no match in that bridge's
LibretroEnvironment.cpp on the Matrix, Nexus, Omega or Piers branches, so the
call falls through to default: return false;.

Expected behavior

The core delivers video frames and audio to the frontend, as it does under
RetroArch.

Actual behavior

The ScummVM engine runs completely — it loads the game, accepts input, updates
scummvm.ini, and writes autosaves — while the frontend receives no frame and
no audio buffer at all
. The screen stays black and there is no sound. No error
path is taken, so nothing is logged and the failure is silent.

The code

backends/platform/libretro/src/libretro-core.cpp, retro_run(), lines
1062–1087 at commit 7310d4e9f5d11553c6c5499911bd2f9b8ff3db3b (the commit
pinned by LibreELEC 12.0.2 and 12.2.1). Identical in current master at
line 1148.

/* Setting RA's video or audio driver to null will disable video/audio bits */
int audio_video_enable = 0;
environ_cb(RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE, &audio_video_enable);

if (g_system) {
    /* Switch to ScummVM thread */
    retro_switch_to_emu_thread();
    ...
    /* Retrieve audio */
    if (audio_video_enable & 2)
        audio_run();

    /* Retrieve video */
    if (audio_video_enable & 1) {
        if (video_hw_mode & VIDEO_GRAPHIC_MODE_REQUEST_SW) {
            const Graphics::ManagedSurface *screen;
            LIBRETRO_G_SYSTEM->getScreen(screen);
            video_cb(screen->getPixels(), screen->w, screen->h, screen->pitch);
        } else
            video_cb(RETRO_HW_FRAME_BUFFER_VALID, ...);
    }

    poll_cb();
    LIBRETRO_G_SYSTEM->processInputs();
}

These are the only two video_cb() call sites and the only
audio_run() call site in the core; all three are behind the gate.
retro_switch_to_emu_thread() is called before it, which is why the engine
keeps running normally.

Steps to reproduce the bug

  1. Use a frontend that does not implement
    RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE (e.g. Kodi's RetroPlayer via
    game.libretro).
  2. Load any ScummVM game.
  3. Observe: black screen, no audio, no error message. Input is accepted.
  4. Wait ~5 minutes and check the save directory — an autosave appears, proving
    the engine is running.

Evidence that the engine runs while nothing is delivered

Controlled 8-minute run of Day of the Tentacle, sampled every 30 s:

  • tentacle.s00 grew 17,852 → 20,166 bytes at +330 s, gzip signature 1f 8b 08
    (matching ScummVM's 300 s default autosave period).
  • scummvm.ini lastselectedgame changed from sky to tentacle.
  • Two CPU cores at roughly 20 % throughout.
  • ALSA (/proc/asound/card*/pcm*p/sub*/status) reported no stream for the whole
    run, while a control core produced RUNNING.
  • Kodi never logged RetroPlayer[RENDER]: Initializing render manager. That
    line comes from CRPRenderManager::Initialize(), reached only from the
    CRetroPlayerVideo constructor, which is only constructed when the game
    client opens a video stream. No video_cb() call means no stream, so the
    renderer is never configured. Audio is opened lazily on the first
    AddFrames_S16NE, so no audio_run() means no audio stream either — both
    symptoms follow from the single gate.

Control cores on the same device at the same log level — mrboom, 2048 and
dosbox — all render and play audio immediately. DOSBox is the relevant
control: it is likewise a framework core with variable resolution, it simply
calls video_cb() unconditionally.

Proposed fix — built and verified

int audio_video_enable = RETRO_AV_ENABLE_VIDEO | RETRO_AV_ENABLE_AUDIO;
if (!environ_cb(RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE, &audio_video_enable))
    audio_video_enable = RETRO_AV_ENABLE_VIDEO | RETRO_AV_ENABLE_AUDIO;

I cross-compiled commit 7310d4e for aarch64 with exactly this change (using
the literal bitmask 1 | 2, since libretro.h is fetched as a submodule at
build time) and installed it on the affected device. Same 30-second test,
same method:

stock core patched core
RENDER: Renderer configured on first frame absent present
RENDER: Configuring format absent RGB565, 1280x720
AUDIO: Creating audio stream absent 48000 Hz, 2 channels
ALSA over 30 s no stream RUNNING throughout
Screenshot black game renders

Two screenshots taken at different moments show different frames, so this is
live rendering rather than a single stuck frame.

Failed to set up hardware rendering, falling back to software. still appears
with the patched core, as expected — that message comes from
retro_set_environment(), the software fallback works correctly, and it was
never related to this bug.

Not the cause (ruled out)

  • Hardware rendering. scummvm_video_hw_acceleration lives inside the
    & 1 branch and can never take effect while the gate is closed.
  • Core version. Commit 6fa7403b8b1b6e18e3a3d02120b38aad6a73ad26 already
    contains the gate at line 1089, as do v2.8.0, v2.8.1 and v2.9.0. Downgrading
    within the current range cannot help.
  • Game data, file paths, addon API versions, missing libraries, reported
    framerate/geometry
    — all verified identical or unremarkable (60.0 fps,
    48000 Hz, no missing ldd entries, no core linking OpenGL).

For context: LibreELEC ≤ 11.x built this core from the now-deleted
kodi-game/scummvm fork, whose older monolithic backend called video_cb()
unconditionally. LibreELEC 12 switched to libretro/scummvm, which is when the
regression reached Kodi users — the addon version number rose smoothly and gave
no hint that the upstream source had changed.

Version/Commit

  • ScummVM core: 2.10.0.47.3, reported as 8345cda-2.10.0git; also reproduced
    with 2.7.0.38.3
  • Frontend: not RetroArch — Kodi 21.3 "Omega" with game.libretro 21.0.7.3
  • Platform: LibreELEC 12.2.1, Raspberry Pi 4, aarch64

I am happy to test any patched build on this setup.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions