google/ExoPlayer · error · OpusDecoderException

Invalid header length

Error message

Invalid header length

What it means

Thrown by the Opus extension decoder when the first initialization data entry (the Opus identification header copied from the container) is shorter than 19 bytes. The constructor needs at least 19 bytes to read channel count (offset 9), pre-skip/gain (offset 16) and the channel mapping family byte (offset 18). A truncated header means the extractor or upstream metadata producer did not supply a complete Opus ID header.

Source

Thrown at extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java:113

    this.cryptoConfig = cryptoConfig;
    if (cryptoConfig != null && !OpusLibrary.opusIsSecureDecodeSupported()) {
      throw new OpusDecoderException("Opus decoder does not support secure decode");
    }
    int initializationDataSize = initializationData.size();
    if (initializationDataSize != 1 && initializationDataSize != 3) {
      throw new OpusDecoderException("Invalid initialization data size");
    }
    if (initializationDataSize == 3
        && (initializationData.get(1).length != 8 || initializationData.get(2).length != 8)) {
      throw new OpusDecoderException("Invalid pre-skip or seek pre-roll");
    }
    preSkipSamples = getPreSkipSamples(initializationData);
    seekPreRollSamples = getSeekPreRollSamples(initializationData);
    skipSamples = preSkipSamples;

    byte[] headerBytes = initializationData.get(0);
    if (headerBytes.length < 19) {
      throw new OpusDecoderException("Invalid header length");
    }
    channelCount = getChannelCount(headerBytes);
    if (channelCount > 8) {
      throw new OpusDecoderException("Invalid channel count: " + channelCount);
    }
    int gain = readSignedLittleEndian16(headerBytes, 16);

    byte[] streamMap = new byte[8];
    int numStreams;
    int numCoupled;
    if (headerBytes[18] == 0) { // Channel mapping
      // If there is no channel mapping, use the defaults.
      if (channelCount > 2) { // Maximum channel count with default layout.
        throw new OpusDecoderException("Invalid header, missing stream map");
      }
      numStreams = 1;
      numCoupled = (channelCount == 2) ? 1 : 0;
      streamMap[0] = 0;

View on GitHub (pinned to dd430f7053)

Solutions

  1. Log initializationData sizes before decoder construction; entry 0 must be >= 19 bytes (full Opus ID header 'OpusHead' + version + channels + pre-skip + sample rate + gain + mapping family).
  2. If you build initializationData yourself, pass the complete Opus identification header starting with the 'OpusHead' magic, not just the first bytes of CodecPrivate.
  3. Verify the sample MIME type is audio/opus (MimeTypes.AUDIO_OPUS) so only real Opus tracks reach this decoder.
  4. If the stream is genuinely malformed, reject the track at selection time rather than crashing the renderer.

Example fix

// before
List<byte[]> initData = format.initializationData; // entry 0 is only 8 bytes

// after
// Validate the ID header before handing the format to the Opus renderer
byte[] header = format.initializationData.get(0);
if (header.length < 19
    || header[0] != 'O' || header[1] != 'p' || header[2] != 'u' || header[3] != 's') {
  throw new IllegalArgumentException("Track has no complete Opus identification header");
}
Defensive patterns

Strategy: validation

Validate before calling

byte[] h = format.initializationData.get(0);
boolean ok = h != null && h.length >= 19 && h[0]=='O' && h[1]=='p' && h[2]=='u' && h[3]=='s' && h[4]=='H' && h[5]=='e' && h[6]=='a' && h[7]=='d';
if (!ok) { /* deselect track / fail before renderer init */ }

Try / catch

catch (OpusDecoderException e) when constructing the renderer only to log; prefer pre-validating the Format so the decoder is never built with a short header.

Prevention

When it happens

Trigger: Constructing OpusDecoder (via DefaultRenderersFactory building an OpusRenderer, or directly) where initializationData.get(0).length < 19. Happens when an extractor emits a partial Opus header, when initializationData is built by hand, or when a non-Opus track is misrouted to the Opus decoder.

Common situations: Playing WebM/MKV or HLS with Opus audio where the container's CodecPrivate is truncated; constructing the format's initializationData manually from a wrong offset; version mismatches between extractor and extension that change what initializationData[0] contains.

Related errors


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