Multistream Decoding

Surround-sound Opus files use multistream encoding (RFC 7845 §5.1.1): several independent elementary Opus streams are packed into each Ogg page, with a channel-mapping table that routes decoded channels to the output layout. MultistreamDecoder handles this demultiplexing.

Understanding Multistream Layout

A multistream packet contains streams elementary streams. The first coupled streams are decoded as stereo; the remaining streams - coupled are decoded as mono. The mapping list (one entry per output channel) routes each decoded channel to the output array by index; the sentinel value 255 inserts a silent channel.

For example, a 5.1 surround layout has 4 streams (3 stereo-coupled, 1 mono):

streams = 4, coupled = 2
mapping = [0, 1, 2, 3, 4, 5]
           L  R  C LFE  Ls  Rs

Creating a MultistreamDecoder

The mapping list must satisfy:

  • Every entry is either 255 (silent) or less than streams + coupled.

  • At least one output channel.

import ruopus

# 5.1 surround: 4 streams, 2 stereo-coupled
dec = ruopus.MultistreamDecoder(
    streams=4,
    coupled=2,
    mapping=[0, 1, 2, 3, 4, 5],
    sample_rate=48_000,
)

print(f"Output channels: {dec.channels}")    # 6
print(f"Sample rate:     {dec.sample_rate}") # 48000

Decoding Multistream Packets

Each packet contains all elementary streams in self-delimited framing (the last stream is standard-framed). Pass the raw bytes directly:

for raw_pkt in ogg_pages:
    pcm = dec.decode_packet(raw_pkt)   # (frames, 6) float32

Output is (frames, channels) float32, with channels in the output order defined by mapping.

Stereo-Only (Family 0)

For mono and stereo Ogg Opus files (channel mapping family 0), the simpler OpusDecoder is sufficient, since the Ogg container does not use multistream framing for these layouts.

# mono or stereo: plain OpusDecoder, or decode_ogg_opus()
pcm, head = ruopus.decode_ogg_opus(data)   # handles family-0 automatically

Surround Layouts from RFC 7845

RFC 7845 Appendix A defines the mapping tables for the standard Vorbis- compatible surround layouts. Common configurations:

# Stereo (family 0, use OpusDecoder instead)
#   streams=1, coupled=1, mapping=[0, 1]

# 3.0 (L C R)
dec_30 = ruopus.MultistreamDecoder(
    streams=2, coupled=1, mapping=[0, 2, 1]
)

# 5.1 (L R C LFE Ls Rs, Vorbis channel order)
dec_51 = ruopus.MultistreamDecoder(
    streams=4, coupled=2, mapping=[0, 4, 1, 2, 3, 5]
)

# 7.1 (L R C LFE Ls Rs Lss Rss)
dec_71 = ruopus.MultistreamDecoder(
    streams=5, coupled=3, mapping=[0, 6, 1, 2, 3, 4, 5, 7]
)

Silent Channels

A mapping entry of 255 produces a zero-filled output channel, useful for layouts where one output slot has no source stream:

# 4.0 surround with a silent LFE slot
dec = ruopus.MultistreamDecoder(
    streams=2,
    coupled=2,
    mapping=[0, 1, 255, 2, 3],   # channel 2 = silence
)

Output Sample Rates

Like OpusDecoder, the output rate can be reduced for low-rate playback pipelines:

dec = ruopus.MultistreamDecoder(
    streams=2, coupled=1, mapping=[0, 2, 1],
    sample_rate=16_000,   # 16 kHz output
)

Valid rates: 48000, 24000, 16000, 12000, 8000.