Libopus Decoder State Internal Data Structure
This article provides a technical overview of the internal data structure used to maintain decoder state in the libopus reference implementation of the Opus audio codec. We will identify the primary state structure, examine its layout, and explain how it manages the dual-engine nature of the hybrid SILK and CELT codecs.
The primary internal data structure that holds the decoder state in
libopus is struct OpusDecoder (commonly
referred to by its typedef
OpusDecoder).
Because libopus is designed with a highly encapsulated API, the
definition of struct OpusDecoder is kept private and
resides within the library’s source code (specifically in
src/opus_decoder.c). This encapsulation ensures binary
compatibility (ABI stability), preventing user applications from
directly accessing or modifying the structure’s fields. Instead,
applications interact with the state using a pointer to this opaque
structure.
Layout of the OpusDecoder Structure
The OpusDecoder structure acts as a master controller
that orchestrates the two underlying decoding technologies used by Opus:
SILK (optimized for speech) and CELT (optimized for music and general
audio).
Internally, struct OpusDecoder contains the following
key components:
- State Metadata: Fields that track the basic
configuration of the decoder, such as the sampling rate
(
Fs), the number of channels, the stream layout, and the last used mode (SILK, CELT, or Hybrid). - Memory Offsets: Variables named
silk_dec_offsetandcelt_dec_offset. These integer offsets define exactly where the sub-decoder states begin in memory relative to the start of the allocatedOpusDecodermemory block. - Sub-Decoder States:
- SILK Decoder State: The memory block that holds the state for the speech-mode decoder.
- CELT Decoder State: The memory block containing the
state (specifically
struct CELTDecoder) for the high-bandwidth audio decoder.
- Entropy Decoder State: Internal variables (such as
rangeDec_state) used by the range coder to track the bitstream decoding process.
Memory Allocation and Initialization
Because the structure is opaque, client applications cannot instantiate it directly on the stack using standard C declarations. Libopus provides specific functions to handle its lifecycle:
opus_decoder_get_size: This function returns the exact size in bytes required for theOpusDecoderstructure based on the requested number of channels.opus_decoder_create: Dynamically allocates memory for theOpusDecoderstructure on the heap and initializes it.opus_decoder_init: Initialises a pre-allocated memory block (which can reside on the stack or a custom memory pool) to act as theOpusDecoderstate.