Skip to content

OpusDecoder: celt_accum always uses the FIXED_POINT-only formula, producing silent output for Hybrid-mode packets decoded below 48kHz #67

Description

@samposoft

Summary

OpusDecoder.Decode() returns the full requested sample count (no exception, no error) but the output PCM is completely silent — exactly zero amplitude — for any Opus packet encoded in Hybrid mode (TOC config 12–15: SILK+CELT combined to reconstruct SWB/Fullband audio) when the decoder is configured to output at a sample rate below the native 48kHz (e.g. 16kHz).

This reproduces with essentially any real-world WebRTC encoder (Chrome/Firefox encode Hybrid/Fullband by default), so any application that decodes Opus at a reduced output rate — a very common case for telephony/VoIP use — hits it consistently.

Root cause

In OpusDecoder.cs, inside opus_decode_frame:

celt_accum = ((mode != OpusMode.MODE_CELT_ONLY) && (frame_size >= F10)) ? 1 : 0;

This is the formula from the #ifdef FIXED_POINT branch of the reference C decoder (opus_decoder.c):

/* In fixed-point, we can tell CELT to do the accumulation on top of the
   SILK PCM buffer. This saves some stack space. */
#ifdef FIXED_POINT
   celt_accum = (mode != MODE_CELT_ONLY) && (frame_size >= F10);
#else
   celt_accum = 0;
#endif

celt_accum=1 is a FIXED_POINT-only optimization (CELT writes its output directly on top of the buffer SILK already wrote, instead of a separate buffer that gets added afterward) intended to save stack space on embedded targets. Every floating-point build of libopus — i.e. virtually every real desktop/server deployment — always takes the #else branch, so the accum=1 path is rarely exercised or tested upstream in practice.

Concentus, being a fixed-point-style port, always computes the FIXED_POINT formula with no equivalent #else — unconditionally taking the accum=1 path on every Hybrid/SILK+CELT frame with frame_size >= 10ms. That path has a bug (not further isolated beyond this point) when combined with output-rate downsampling (downsample > 1, i.e. any decode Fs below the native 48kHz).

How this was isolated

  1. Captured raw bytes of real Opus packets from a live WebRTC call.
  2. Decoded them in isolation with Concentus: at 16kHz output → peak amplitude 0; the same exact bytes at 48kHz (native) → valid audio. Confirmed the bug is in the decoder, not in any surrounding pipeline.
  3. Built the official reference libopus 1.6.1 (float build) from source and decoded the same exact bytes: works correctly at both 16kHz and 48kHz — confirming this isn't an inherent Opus/packet limitation, but specific to this C# port.
  4. Instrumented both the reference C decoder (opus_decoder.c/celt_decoder.c) and this port (OpusDecoder.cs/CeltDecoder.cs/CeltCommon.cs) with trace logging of every intermediate value (mode, frame_size, downsample, N, Nd, celt_accum, …) for the same packet, side by side. Every value matched exactly except celt_accum (0 in the reference, 1 in this port).
  5. Forcing celt_accum = 0 in a local copy made the same packet decode correctly again (peak amplitude ~511, vs. ~515 in the reference — a normal fixed/float rounding difference, not a discrepancy).

Minimal repro

using Concentus;

// Real Hybrid-mode (TOC config 15, Fullband, 20ms), mono Opus packet captured from a live WebRTC call.
var packet = Convert.FromBase64String(
    "eAQPIzMOKgxLnJI+goxXFNRlSWLRdqvLJUtfncLBYzBzkBMB3sREp2Tb9E0M8uIz+FtWp/N+078z6ZBsSLmP1XJ/IJgOwBHut34=");

var decoder = OpusCodecFactory.CreateDecoder(16000, 1); // any Fs < 48000 triggers it
var pcm = new short[320];
decoder.Decode(packet, pcm, 320, false);

Console.WriteLine(pcm.Max(Math.Abs)); // prints 0 — should be a few hundred

Suggested fix

celt_accum = 0;

i.e. simply mirror the #else branch of the reference decoder — this is exactly the behavior every real float-build deployment of libopus already relies on, so it carries no new/untested behavior, just removes a code path that appears to have never been correctly ported.

Environment

  • Concentus commit/version: latest master
  • .NET runtime: .NET 10 (also affects other TFMs sharing this decoder code)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions