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
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.
This is a combination of two issues, which are masking each other.
libopus
decoder does not care about the headers, both OpusHead
and OpusTags
. Simply do not pass those packets to the decoder.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)