google/ExoPlayer · error · Gav1DecoderException

Failed to render output buffer to surface: decoder is not in

Error message

Failed to render output buffer to surface: decoder is not initialized.

What it means

Gav1DecoderException thrown by Libgav1VideoRenderer.renderOutputBufferToSurface when its decoder field is null, i.e. renderOutputBufferToSurface was called before the renderer initialized the decoder (during init) or after it was released/reset. It is a state/lifecycle misuse guard on the renderer, not a decode failure.

Source

Thrown at extensions/av1/src/main/java/com/google/android/exoplayer2/ext/av1/Libgav1VideoRenderer.java:167

  /** {@inheritDoc} */
  @Override
  protected final Gav1Decoder createDecoder(Format format, @Nullable CryptoConfig cryptoConfig)
      throws Gav1DecoderException {
    TraceUtil.beginSection("createGav1Decoder");
    int initialInputBufferSize =
        format.maxInputSize != Format.NO_VALUE ? format.maxInputSize : DEFAULT_INPUT_BUFFER_SIZE;
    Gav1Decoder decoder =
        new Gav1Decoder(numInputBuffers, numOutputBuffers, initialInputBufferSize, threads);
    this.decoder = decoder;
    TraceUtil.endSection();
    return decoder;
  }

  @Override
  protected void renderOutputBufferToSurface(VideoDecoderOutputBuffer outputBuffer, Surface surface)
      throws Gav1DecoderException {
    if (decoder == null) {
      throw new Gav1DecoderException(
          "Failed to render output buffer to surface: decoder is not initialized.");
    }
    decoder.renderToSurface(outputBuffer, surface);
    outputBuffer.release();
  }

  @Override
  protected void setDecoderOutputMode(@C.VideoOutputMode int outputMode) {
    if (decoder != null) {
      decoder.setOutputMode(outputMode);
    }
  }

  @Override
  protected DecoderReuseEvaluation canReuseDecoder(
      String decoderName, Format oldFormat, Format newFormat) {
    return new DecoderReuseEvaluation(
        decoderName,

View on GitHub (pinned to dd430f7053)

Solutions

  1. Only invoke renderOutputBufferToSurface after the renderer's initialization has produced a decoder (i.e. within the normal renderer callback lifecycle)
  2. Serialize renderer lifecycle operations (init/flush/release) on the playback thread so no render call overlaps release
  3. Null-check the decoder in derived code before calling this protected method

Example fix

// before
renderer.renderOutputBufferToSurface(buffer, surface);

// after: guard in subclasses / callers
if (renderer != null && isRendererInitialized()) {
  renderer.renderOutputBufferToSurface(buffer, surface);
}
Defensive patterns

Strategy: validation

Validate before calling

// In subclasses/callers of Libgav1VideoRenderer: confirm init ran before render
// (decoder is created in createDecoder during renderer initialization)
if (renderer.isInitialized() && !renderer.isReleased()) { // your own lifecycle flags
  renderer.renderOutputBufferToSurface(buffer, surface);
}

Try / catch

try {
  renderer.renderOutputBufferToSurface(outputBuffer, surface);
} catch (Gav1DecoderException e) {
  if (e.getMessage() != null && e.getMessage().contains("not initialized")) {
    // lifecycle race: renderer was released/reset mid-render; drop the buffer
    outputBuffer.release();
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Calling renderOutputBufferToSurface from outside the normal playback pipeline before onEnabled/onInit completed; concurrently releasing the renderer while a render call is in flight; renderer.reset/disable set decoder = null between operations.

Common situations: Custom player code driving renderer methods directly on the wrong thread; races during stopPlayback where release nulls the decoder while a queued buffer is being rendered.

Related errors


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