Search code examples
pythonaudioopus

opuslib decoding fails with "corrupt stream" on the opus header packet


I have a valid ogg audio file (works in several players including opus-tools opusdec; ffmpeg correctly identifies the file and reports no problems). The audio stream has two channels, a sample rate of 48kHz, and a 20ms frame duration.

You can recreate a very similar file using ffmpeg

ffmpeg -i "a media file with audio" \
-f opus -frame_duration 20 -ar 48000 -ac 2 \
audio.ogg

In a hex editor, I was able to extract the first Opus packet/segment/frame according to the Ogg file format.

Here it is: 4F 70 75 73 48 65 61 64 01 02 38 01 80 BB 00 00 00 00 00

hex editor screenshot of opus packet in ogg file

Then, I attempt to use libopus v1.4 (via the opuslib Python bindings) to begin decoding the stream:

from opuslib import Decoder

SAMPLE_RATE = 48000  # hertz
FRAME_DURATION = 20  # milliseconds
FRAMES_PER_SECOND = 1000 // FRAME_DURATION
SAMPLES_PER_FRAME = SAMPLE_RATE // FRAMES_PER_SECOND
CHANNELS = 2
SAMPLES_PER_CHANNEL_FRAME = SAMPLES_PER_FRAME // CHANNELS

packet = bytes.fromhex("4F 70 75 73 48 65 61 64 01 02 38 01 80 BB 00 00 00 00 00")

decoder = Decoder(SAMPLE_RATE, CHANNELS)
result = decoder.decode(packet, SAMPLES_PER_CHANNEL_FRAME)

print(result)

Which produces this error:

Traceback (most recent call last):
  File "C:\...\test.py", line 50, in <module>
    result = decoder.decode(packet, SAMPLES_PER_CHANNEL_FRAME)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\...\site-packages\opuslib\classes.py", line 56, in decode
    return opuslib.api.decoder.decode(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\...\site-packages\opuslib\api\decoder.py", line 246, in decode
    raise opuslib.exceptions.OpusError(result)
opuslib.exceptions.OpusError: b'corrupted stream'

I have tried multiple variations of the CONSTANT calculations. I have tried decoding the second packet, the third packet, the first and second packet concatenated, etc. All to no avail.

I get the same error for every ogg file I test. What am I doing wrong?

I'm on Windows 11 and built libopus from source using the included VS .sln files with preset ReleaseDLL_fixed x64.


Solution

  • This is a combination of two issues, which are masking each other.

    1. The libopus decoder does not care about the headers, both OpusHead and OpusTags. Simply do not pass those packets to the decoder.
    2. The frame_size parameter is not per-channel.

    Overall, you decode an Opus packet like so:

    for packet in opus_packet_iterator():
      if packet.startswith((b"OpusHead", b"OpusTags")):
        process_header_packet(packet)
      else:
        pcm_data = decoder.decode(packet, SAMPLES_PER_FRAME)
        process_pcm_data(pcm_data)