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. :class:`~ruopus.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): .. code-block:: text 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. .. code-block:: python 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: .. code-block:: python 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 :class:`~ruopus.OpusDecoder` is sufficient, since the Ogg container does not use multistream framing for these layouts. .. code-block:: python # 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: .. code-block:: python # 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: .. code-block:: python # 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 :class:`~ruopus.OpusDecoder`, the output rate can be reduced for low-rate playback pipelines: .. code-block:: python 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.