Ogg Opus Files ============== Ogg Opus (`RFC 7845 `_) is the standard container for Opus audio. ruopus provides two convenience functions, :func:`~ruopus.encode_ogg_opus` and :func:`~ruopus.decode_ogg_opus`, that handle the full container round-trip in a single call, plus the :class:`~ruopus.OpusHead` identification-header object returned by the decoder. Encoding to Ogg Opus -------------------- :func:`~ruopus.encode_ogg_opus` takes raw 48 kHz float32 PCM and returns a complete Ogg Opus file as ``bytes``: .. code-block:: python import numpy as np import ruopus # Generate 3 seconds of stereo audio sr = 48_000 t = np.linspace(0, 3, sr * 3, dtype=np.float32) pcm = np.column_stack([ np.sin(2 * np.pi * 440 * t), # left: A4 np.sin(2 * np.pi * 880 * t), # right: A5 ]) # shape (144000, 2) ogg = ruopus.encode_ogg_opus(pcm, channels=2, bitrate=128_000) with open("output.opus", "wb") as f: f.write(ogg) print(f"Ogg Opus file: {len(ogg) / 1024:.1f} KB") Input can also be a 1-D interleaved array (samples interleaved L, R, L, R, …): .. code-block:: python flat_pcm = pcm.ravel() # shape (288000,) ogg = ruopus.encode_ogg_opus(flat_pcm, channels=2, bitrate=128_000) Decoding from Ogg Opus ----------------------- :func:`~ruopus.decode_ogg_opus` handles the complete demuxing pipeline: - Reads the ``OpusHead`` identification header - Applies the :attr:`~ruopus.OpusHead.pre_skip` (discards leading silence) - Trims trailing granule padding - Applies the :attr:`~ruopus.OpusHead.output_gain_q8` to the decoded PCM .. code-block:: python with open("input.opus", "rb") as f: data = f.read() pcm, head = ruopus.decode_ogg_opus(data) print(f"Channels: {head.channel_count}") print(f"Input rate: {head.input_sample_rate} Hz") print(f"Pre-skip: {head.pre_skip} samples") print(f"Output gain: {head.output_gain_q8} (Q7.8 dB)") print(f"Decoded PCM: {pcm.shape}, dtype={pcm.dtype}") The output is always ``float32`` at 48 kHz, shaped ``(frames, channels)``. Reading the OpusHead -------------------- The :class:`~ruopus.OpusHead` object exposes every field of the RFC 7845 identification header: .. code-block:: python pcm, head = ruopus.decode_ogg_opus(data) print(repr(head)) # OpusHead(version=1, channel_count=2, pre_skip=312, # input_sample_rate=44100, mapping_family=0) # Round-trip the header back to bytes (e.g. for remuxing): header_bytes = head.to_bytes() Channel-mapping family 0 (mono and stereo) is the only family currently supported by :func:`~ruopus.decode_ogg_opus`. For surround layouts, see :doc:`multistream`. Round-Trip Fidelity ------------------- A full encode → decode round-trip introduces the encoder lookahead (typically 120 samples) as latency and the codec's lossy compression artifacts: .. code-block:: python import numpy as np import ruopus original = np.random.randn(48_000).astype(np.float32) ogg = ruopus.encode_ogg_opus(original, channels=1, bitrate=256_000) decoded, head = ruopus.decode_ogg_opus(ogg) # The decoded length may differ slightly due to pre_skip and frame alignment min_len = min(len(original), len(decoded)) rms_err = np.sqrt(np.mean((original[:min_len] - decoded[:min_len, 0]) ** 2)) print(f"RMS error vs original: {rms_err:.6f}") Saving Decoded Audio -------------------- The decoded float32 PCM can be saved with `audio_samples `_: .. code-block:: python import numpy as np import audio_samples as aus pcm, head = ruopus.decode_ogg_opus(data) samples = aus.AudioSamples.new_mono(pcm, 48_000) aus.io.save("decoded.wav", samples, as_type=np.float32)