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
- 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).
- If you build initializationData yourself, pass the complete Opus identification header starting with the 'OpusHead' magic, not just the first bytes of CodecPrivate.
- Verify the sample MIME type is audio/opus (MimeTypes.AUDIO_OPUS) so only real Opus tracks reach this decoder.
- 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
- Always verify 'OpusHead' magic and length >= 19 before enabling the Opus extension for a track.
- Never hand-assemble initializationData; pass through what the extractor produced.
- Confirm MimeTypes.AUDIO_OPUS routing so foreign headers never reach OpusDecoder.
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
- Invalid channel count: ${channelCount}
- Invalid header, missing stream map
- Failed to load decoder native libraries
- Failed to initialize decoder
- Error instantiating Opus extension
AI-assisted analysis of google/ExoPlayer@dd430f7053 (2026-08-14).
Data as JSON: /api/errors/6a904c2c15fedc61.
Report an issue: GitHub.