google/ExoPlayer · error · OpusDecoderException
Invalid channel count: ${channelCount}
Error message
Invalid channel count: ${channelCount} What it means
The Opus identification header declares a channel count greater than 8, which the ExoPlayer Opus extension refuses to initialize. libopus multistream is capped here at 8 channels; anything higher indicates a corrupt or non-Opus header byte being read at offset 9.
Source
Thrown at extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java:117
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;
streamMap[1] = 1;
} else {
if (headerBytes.length < 21 + channelCount) {
throw new OpusDecoderException("Invalid header length");View on GitHub (pinned to dd430f7053)
Solutions
- Inspect initializationData[0]: it must start with 'OpusHead' and byte 9 must be 1-8; fix the producer of that header if not.
- Confirm the track's MIME type is audio/opus so non-Opus streams never reach OpusDecoder.
- Re-transcode or re-mux the source file if the header in the container itself is corrupt.
- Exclude the Opus extension renderer via RenderersFactory filtering if you only meant to play standard MediaCodec audio.
Example fix
// before
// format passed straight from extractor to Opus renderer, header byte 9 = 200
// after
byte[] h = format.initializationData.get(0);
int channels = h[9] & 0xFF;
if (channels < 1 || channels > 8) {
// skip / deselect this track instead of letting the decoder throw
selectionParameters.overrideExclude(audioTrackGroup);
} Defensive patterns
Strategy: validation
Validate before calling
int channels = h[9] & 0xFF;
if (channels < 1 || channels > 8) { /* reject track before renderer init */ } Prevention
- Check the channel byte (offset 9) of the Opus ID header is 1..8 before playback.
- Validate that initializationData[0] really is an Opus header via the 'OpusHead' magic.
- Re-mux suspicious files with ffmpeg to normalize headers.
When it happens
Trigger: getChannelCount(headerBytes) reads the unsigned byte at offset 9 of initializationData[0] and it is > 8. Caused by files whose header is actually not an Opus ID header (wrong initializationData), corrupted headers, or hand-built initializationData with the wrong layout.
Common situations: Misrouted audio track (e.g. a different codec's CodecPrivate passed as Opus header); corrupted downloads or malformed transcodes producing a garbage channel byte; incorrect manual Format construction in IMA/media-source code.
Related errors
- Invalid header length
- 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/34936149f4be8b4c.
Report an issue: GitHub.