Skip to content

Instantly share code, notes, and snippets.

@Ralith
Created October 17, 2021 03:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Ralith/a27ae71f88b4052fddddb0bb937f30e1 to your computer and use it in GitHub Desktop.
Save Ralith/a27ae71f88b4052fddddb0bb937f30e1 to your computer and use it in GitHub Desktop.
Decoding opus files
fn decode_opus(data: &[u8]) -> Result<Arc<oddio::Frames<f32>>> {
let _span = tracy_client::span!("opus decode");
let mut ogg = ogg::PacketReader::new(std::io::Cursor::new(data));
let mut decoder = opus::Decoder::new(SAMPLE_RATE, opus::Channels::Mono).unwrap();
let mut buffer = Vec::new();
let mut serial = None;
let mut pre_skip = 0;
let mut scale = 1.0;
let mut seen_comment = false;
while let Some(packet) = ogg.read_packet().context("reading ogg packet")? {
if serial.is_none() && packet.first_in_stream() {
// Is this an Opus stream?
if !packet.data.starts_with(ID_MAGIC) || packet.data[8] & 0xF0 != 0 {
continue;
}
// Is it sensible?
let channels = packet.data[9];
if channels != 1 {
bail!("unsupported channel count: {}", channels);
}
serial = Some(packet.stream_serial());
pre_skip = usize::from(u16::from_le_bytes(packet.data[10..12].try_into().unwrap()));
let output_gain = i16::from_le_bytes(packet.data[16..18].try_into().unwrap());
scale = 10.0f32.powf(f32::from(output_gain) / (20.0 * 256.0));
let channel_mapping_family = packet.data[18];
if channel_mapping_family != 0 {
bail!(
"unsupported channel mapping family {}",
channel_mapping_family
);
}
continue;
}
// Is this part of the Opus stream?
if Some(packet.stream_serial()) != serial {
continue;
}
// Have we passed the comment header yet?
if !seen_comment {
seen_comment = true;
if packet.data.starts_with(COMMENT_MAGIC) {
continue;
} else {
error!("missing comment header");
}
}
// Decode samples
let n = decoder
.get_nb_samples(&packet.data)
.context("decoding opus packet")?;
let start = buffer.len();
buffer.resize(start + n, 0.0);
decoder
.decode_float(&packet.data, &mut buffer[start..], false)
.context("decoding opus")?;
if pre_skip > 0 {
let skip = pre_skip.min(buffer.len());
buffer.drain(0..skip);
pre_skip -= skip;
}
}
for sample in &mut buffer {
*sample *= scale;
}
trace!(
"loaded {:.02} seconds of audio",
buffer.len() as f32 / SAMPLE_RATE as f32
);
Ok(oddio::Frames::from_slice(SAMPLE_RATE, &buffer))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment