DrKLO/Telegram · error · OpusDecoderException

Opus decoder does not support secure decode

Error message

Opus decoder does not support secure decode

What it means

Thrown by the OpusDecoder constructor when a non-null CryptoConfig is supplied (i.e. the stream is encrypted/DRM-protected) but the loaded native libopus does not advertise secure-decode support. The check is OpusLibrary.opusIsSecureDecodeSupported(), a JNI call into the bundled native library; it only returns true if that native build was compiled with the secure-decode entry point. The decoder refuses to start because it cannot decrypt samples without it.

Source

Thrown at TMessagesProj/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java:89

   *     May be null and can be ignored if decoder does not handle encrypted content.
   * @param outputFloat Forces the decoder to output float PCM samples when set
   * @throws OpusDecoderException Thrown if an exception occurs when initializing the decoder.
   */
  public OpusDecoder(
      int numInputBuffers,
      int numOutputBuffers,
      int initialInputBufferSize,
      List<byte[]> initializationData,
      @Nullable CryptoConfig cryptoConfig,
      boolean outputFloat)
      throws OpusDecoderException {
    super(new DecoderInputBuffer[numInputBuffers], new SimpleDecoderOutputBuffer[numOutputBuffers]);
    if (!OpusLibrary.isAvailable()) {
      throw new OpusDecoderException("Failed to load decoder native libraries");
    }
    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);

View on GitHub (pinned to 45ab8f4308)

Solutions

  1. Use a libopus native build compiled with the secure-decode JNI entry point so OpusLibrary.opusIsSecureDecodeSupported() returns true.
  2. If the content is not actually encrypted, ensure cryptoConfig passed to the decoder is null (do not forward a CryptoConfig for clear content).
  3. Fall back to another audio renderer/decoder whose native build supports the required C.CryptoType; gate playback on OpusLibrary.supportsCryptoType(format.cryptoType).

Example fix

// before
OpusDecoder decoder = new OpusDecoder(
    NUM_BUFFERS, NUM_BUFFERS, initialInputBufferSize,
    format.initializationData, cryptoConfig, outputFloat);

// after
if (cryptoConfig != null && !OpusLibrary.opusIsSecureDecodeSupported()) {
  throw new OpusDecoderException("Secure decode unavailable; pick another renderer");
}
OpusDecoder decoder = new OpusDecoder(
    NUM_BUFFERS, NUM_BUFFERS, initialInputBufferSize,
    format.initializationData, cryptoConfig, outputFloat);
Defensive patterns

Strategy: validation

Validate before calling

// Before constructing the OpusDecoder for encrypted content
CryptoConfig cfg = (formatIsEncrypted) ? cryptoConfig : null;
if (cfg != null
    && !(OpusLibrary.isAvailable() && OpusLibrary.opusIsSecureDecodeSupported())) {
  // Do not pass a CryptoConfig: route to a renderer whose native lib supports it.
  throw new OpusDecoderException("Secure Opus decode not available in this native build");
}
new OpusDecoder(NUM_BUFFERS, NUM_BUFFERS, initSize, format.initializationData, cfg, outputFloat);

Try / catch

try {
  decoder = new OpusDecoder(..., cryptoConfig, outputFloat);
} catch (OpusDecoderException e) {
  if (e.getMessage().contains("secure decode")) {
    // switch renderer / report DRM unsupported for Opus
  } else { throw e; }
}

Prevention

When it happens

Trigger: LibopusAudioRenderer.createDecoder (LibopusAudioRenderer.java:110) forwards the Format's cryptoConfig into `new OpusDecoder(...)`. When the stream is DRM-protected (Widevine/ClearKey) cryptoConfig is non-null and the constructor at OpusDecoder.java:88 aborts if the native opusIsSecureDecodeSupported() returns false.

Common situations: Shipping the standard non-DRM libopus extension build and then attempting to play ClearKey/Widevine-protected Opus content; swapping in a different decoder extension variant that dropped secure decode; DRM scheme mismatch where OpusLibrary.cryptoType was never configured via OpusLibrary.setLibraries(...).

Related errors


AI-assisted analysis of DrKLO/Telegram@45ab8f4308 (2026-08-14). Data as JSON: /api/errors/11ab05af30cfb9dc. Report an issue: GitHub.