google/ExoPlayer · error · Gav1DecoderException

Buffer render error: ${gav1GetErrorMessage(gav1DecoderContex

Error message

Buffer render error: ${gav1GetErrorMessage(gav1DecoderContext)}

What it means

Gav1DecoderException thrown when the native gav1RenderFrame call returns GAV1_ERROR while pushing a decoded frame to the output surface. The appended gav1GetErrorMessage() text carries the native reason, typically an unusable surface, a bad frame, or an errored decoder context.

Source

Thrown at extensions/av1/src/main/java/com/google/android/exoplayer2/ext/av1/Gav1Decoder.java:180

    this.outputMode = outputMode;
  }

  /**
   * Renders output buffer to the given surface. Must only be called when in {@link
   * C#VIDEO_OUTPUT_MODE_SURFACE_YUV} mode.
   *
   * @param outputBuffer Output buffer.
   * @param surface Output surface.
   * @throws Gav1DecoderException Thrown if called with invalid output mode or frame rendering
   *     fails.
   */
  public void renderToSurface(VideoDecoderOutputBuffer outputBuffer, Surface surface)
      throws Gav1DecoderException {
    if (outputBuffer.mode != C.VIDEO_OUTPUT_MODE_SURFACE_YUV) {
      throw new Gav1DecoderException("Invalid output mode.");
    }
    if (gav1RenderFrame(gav1DecoderContext, surface, outputBuffer) == GAV1_ERROR) {
      throw new Gav1DecoderException(
          "Buffer render error: " + gav1GetErrorMessage(gav1DecoderContext));
    }
  }

  /**
   * Initializes a libgav1 decoder.
   *
   * @param threads Number of threads to be used by a libgav1 decoder.
   * @return The address of the decoder context or {@link #GAV1_ERROR} if there was an error.
   */
  private native long gav1Init(int threads);

  /**
   * Deallocates the decoder context.
   *
   * @param context Decoder context.
   */
  private native void gav1Close(long context);

View on GitHub (pinned to dd430f7053)

Solutions

  1. Read the native message text to distinguish 'surface invalid' from 'decoder errored'
  2. Guarantee the surface stays valid while rendering: only release it after the renderer has been stopped/flushed
  3. Recreate/flush the renderer (and re-init the decoder) when the surface changes instead of reusing the old context
  4. Catch Gav1DecoderException in the render path and trigger renderer recovery rather than crashing the playback thread

Example fix

// before
decoder.renderToSurface(outputBuffer, surface);
outputBuffer.release();

// after: detect dead surfaces and recover
try {
  decoder.renderToSurface(outputBuffer, surface);
} catch (Gav1DecoderException e) {
  Log.e(TAG, "render failed: " + e.getMessage(), e);
  // signal the player to disable/re-enable the renderer with a fresh surface
} finally {
  outputBuffer.release();
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (surface == null || !surface.isValid()) {
  // do not render into a released/invalid surface
  outputBuffer.release();
  return;
}

Try / catch

try {
  decoder.renderToSurface(outputBuffer, surface);
} catch (Gav1DecoderException e) {
  // native message via gav1GetErrorMessage() identifies surface vs decoder failure;
  // signal renderer disable/re-enable to rebuild the decoder+surface pair
  onRendererRecoveryNeeded(e);
} finally {
  outputBuffer.release();
}

Prevention

When it happens

Trigger: The Surface passed to renderToSurface was released or its producer disconnected between renderer setup and frame render; the decoder context is in an error state from an earlier failed decode; EGL/SurfaceTexture backing the surface destroyed on a different thread.

Common situations: Activity teardown or surface destroyed mid-playback; switching surfaces while frames are in flight; codec surface detached by the UI layer concurrently with rendering.

Related errors


AI-assisted analysis of google/ExoPlayer@dd430f7053 (2026-08-14). Data as JSON: /api/errors/7645304dd19c75a4. Report an issue: GitHub.