Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

opus: implement range decoder. (v2) #313

Open
wants to merge 2 commits into
base: dev-0.6
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion symphonia-codec-opus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "0.0.1"
description = "Pure Opus decoder (a part of project Symphonia)."
homepage = "https://github.com/pdeljanov/Symphonia"
repository = "https://github.com/pdeljanov/Symphonia"
authors = ["Philip Deljanov <[email protected]>"]
authors = ["Philip Deljanov <[email protected]>", "Darius Tan <[email protected]>"]
license = "MPL-2.0"
readme = "README.md"
categories = ["multimedia", "multimedia::audio", "multimedia::encoding"]
Expand Down
346 changes: 346 additions & 0 deletions symphonia-codec-opus/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1,347 @@
// Symphonia
// Copyright (c) 2019-2021 The Project Symphonia Developers.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

#![warn(rust_2018_idioms)]
#![forbid(unsafe_code)]
// The following lints are allowed in all Symphonia crates. Please see clippy.toml for their
// justification.
#![allow(clippy::comparison_chain)]
#![allow(clippy::excessive_precision)]
#![allow(clippy::identity_op)]
#![allow(clippy::manual_range_contains)]
// Disable to better express the specification.
#![allow(clippy::collapsible_else_if)]

use symphonia_core::audio::{
AsGenericAudioBufferRef, AudioBuffer, AudioSpec, Channels, GenericAudioBufferRef,
};
use symphonia_core::codecs::audio::well_known::CODEC_ID_OPUS;
use symphonia_core::codecs::audio::{
AudioCodecParameters, AudioDecoder, AudioDecoderOptions, FinalizeResult,
};
use symphonia_core::codecs::registry::{RegisterableAudioDecoder, SupportedAudioCodec};
use symphonia_core::codecs::CodecInfo;
use symphonia_core::errors::{decode_error, unsupported_error, Result};
use symphonia_core::formats::Packet;
use symphonia_core::io::{BufReader, ReadBytes};
use symphonia_core::support_audio_codec;

#[allow(dead_code)]
pub struct OpusDecoder {
ident_header: IdentificationHeader,
params: AudioCodecParameters,
buffer: AudioBuffer<f32>,
}

/// The operating mode for the Opus Decoder.
/// See RFC 6716 Section 3.1, https://tools.ietf.org/pdf/rfc7845.pdf.
enum Mode {
/// SILK-only mode.
Silk,
/// CELT-only mode.
Celt,
/// SILK and CELT mode.
Hybrid,
}

impl OpusDecoder {
fn try_new(params: &AudioCodecParameters, _: &AudioDecoderOptions) -> Result<Self> {
let extra_data = match params.extra_data.as_ref() {
Some(buf) => buf,
_ => return unsupported_error("opus: missing extra data"),
};

let mut reader = BufReader::new(extra_data);

let ident_header = read_ident_header(&mut reader)?;
Ok(OpusDecoder {
ident_header,
params: params.clone(),
buffer: AudioBuffer::new(AudioSpec::new(0, Channels::None), 0),
})
}
}

impl AudioDecoder for OpusDecoder {
fn reset(&mut self) {
unimplemented!()
}

fn codec_info(&self) -> &CodecInfo {
&Self::supported_codecs()[0].info
}

fn codec_params(&self) -> &AudioCodecParameters {
&self.params
}

#[allow(unused_variables)]
fn decode(&mut self, packet: &Packet) -> Result<GenericAudioBufferRef<'_>> {
let mut reader = packet.as_buf_reader();

// Configuring the decoder from the table of contents (toc) byte.
// See RFC 6716 Section 3.1, https://tools.ietf.org/pdf/rfc7845.pdf.
let toc = reader.read_byte()?;
let config = toc >> 3;
let mode = match config {
0..=11 => Mode::Silk,
12..=15 => Mode::Hybrid,
16..=31 => Mode::Celt,
_ => unreachable!(),
};
let stereo_flag = toc & 0b00000100;
let frame_count_code = toc & 0b00000011;

Ok(self.buffer.as_generic_audio_buffer_ref())
}

fn finalize(&mut self) -> FinalizeResult {
unimplemented!()
}

fn last_decoded(&self) -> GenericAudioBufferRef<'_> {
unimplemented!()
}
}

impl RegisterableAudioDecoder for OpusDecoder {
fn try_registry_new(
params: &AudioCodecParameters,
opts: &AudioDecoderOptions,
) -> Result<Box<dyn AudioDecoder>>
where
Self: Sized,
{
Ok(Box::new(OpusDecoder::try_new(params, opts)?))
}

fn supported_codecs() -> &'static [SupportedAudioCodec] {
&[support_audio_codec!(CODEC_ID_OPUS, "opus", "Opus")]
}
}

#[derive(Debug)]
pub struct IdentificationHeader {
pub output_channel_count: u8,
pub pre_skip: u16,
pub input_sample_rate: u32,
pub output_gain: u16,
pub channel_mapping_family: u8,
pub stream_count: u8,
pub coupled_stream_count: u8,
pub channel_mapping: [u8; 8],
}

/// Create an IdentificationHeader from a reader.
///
/// If the header is invalid, a DecodeError is returned.
///
/// See RFC 7845 Section 5.1, https://tools.ietf.org/pdf/rfc7845.pdf.
fn read_ident_header<B: ReadBytes>(reader: &mut B) -> Result<IdentificationHeader> {
// The first 8 bytes are the magic signature ASCII bytes.
const OGG_OPUS_MAGIC_SIGNATURE: &[u8] = b"OpusHead";

let mut magic_signature = [0; 8];
reader.read_buf_exact(&mut magic_signature)?;

if magic_signature != *OGG_OPUS_MAGIC_SIGNATURE {
return decode_error("incorrect opus signature");
}

// The next byte is the OGG Opus encapsulation version.
const OGG_OPUS_VERSION: u8 = 0x01;

let mut version = [0; 1];
reader.read_buf_exact(&mut version)?;

// TODO: Allow version numbers that are < 15 and disallow all > 16.
// See RFC 7845 Section 5.1 (Version).
if version[0] != OGG_OPUS_VERSION {
return decode_error("incorrect opus version");
}

// The next byte is the number of channels/
let output_channel_count = reader.read_byte()?;

if output_channel_count == 0 {
return decode_error("output channel count is 0");
}

// The next 16-bit integer is the pre-skip padding.
let pre_skip = reader.read_u16()?;

// The next 32-bit integer is the sample rate of the original audio.
let input_sample_rate = reader.read_u32()?;

// Next, the 16-bit gain value.
let output_gain = reader.read_u16()?;

// The next byte indicates the channel mapping. Most of these values are reserved.
let channel_mapping_family = reader.read_byte()?;

let (stream_count, coupled_stream_count) = match channel_mapping_family {
// RTP mapping. Supports up-to 2 channels.
0 => {
if output_channel_count > 2 {
return decode_error("invalid output channel count");
}

(1, output_channel_count - 1)
}
// Vorbis mapping. Supports 1 to 8 channels.
1 => {
if output_channel_count > 8 {
return decode_error("invalid output channel count");
}

let stream_count = reader.read_u8()?;
if stream_count == 0 {
return decode_error("stream count is 0");
}

let coupled_stream_count = reader.read_u8()?;
(stream_count, coupled_stream_count)
}
_ => return decode_error("reserved mapping family"),
};

if stream_count.checked_add(coupled_stream_count).is_none() {
return decode_error("stream count + coupled stream count > 255");
}

let mut channel_mapping = [0; 8];

// The channel mapping table is only read if not using the RTP mapping.
if channel_mapping_family != 0 {
for mapping in &mut channel_mapping[..output_channel_count as usize] {
*mapping = reader.read_u8()?;
}
}

Ok(IdentificationHeader {
output_channel_count,
pre_skip,
input_sample_rate,
output_gain,
channel_mapping_family,
stream_count,
coupled_stream_count,
channel_mapping,
})
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn verify_err_if_no_magic_signature() {
let bytes: [u8; 23] = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x09, 0x38, 0x01, 0x80, 0xbb,
0x00, 0x00, 0x00, 0x00, 0x01, 0x4f, 0x67, 0x67, 0x53,
];
let mut reader = BufReader::new(&bytes);
let result = read_ident_header(&mut reader);
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "malformed stream: incorrect opus signature");
}

#[test]
fn verify_err_if_version_number_neq_1() {
let bytes: [u8; 23] = [
0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64, 0x02, 0x09, 0x38, 0x01, 0x80, 0xbb,
0x00, 0x00, 0x00, 0x00, 0x01, 0x4f, 0x67, 0x67, 0x53,
];
let mut reader = BufReader::new(&bytes);
let result = read_ident_header(&mut reader);
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "malformed stream: incorrect opus version");
}

#[test]
fn verify_err_if_channel_count_eq_0() {
let bytes: [u8; 23] = [
0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64, 0x01, 0x00, 0x38, 0x01, 0x80, 0xbb,
0x00, 0x00, 0x00, 0x00, 0x01, 0x4f, 0x67, 0x67, 0x53,
];
let mut reader = BufReader::new(&bytes);
let result = read_ident_header(&mut reader);
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "malformed stream: output channel count is 0");
}

#[test]
fn verify_err_if_channel_family_gt_2() {
let bytes: [u8; 23] = [
0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64, 0x01, 0x02, 0x38, 0x01, 0x80, 0xbb,
0x00, 0x00, 0x00, 0x00, 0x02, 0x4f, 0x67, 0x67, 0x53,
];
let mut reader = BufReader::new(&bytes);
let result = read_ident_header(&mut reader);
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "malformed stream: reserved mapping family");
}

#[test]
fn verify_err_if_channel_family_eq_0_and_channel_count_gt_2() {
let bytes: [u8; 23] = [
0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64, 0x01, 0x03, 0x38, 0x01, 0x80, 0xbb,
0x00, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x67, 0x67, 0x53,
];

let mut reader = BufReader::new(&bytes);
let result = read_ident_header(&mut reader);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"malformed stream: invalid output channel count"
);
}

#[test]
fn verify_err_if_channel_family_eq_1_and_channel_count_gt_8() {
let bytes: [u8; 23] = [
0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64, 0x01, 0x09, 0x38, 0x01, 0x80, 0xbb,
0x00, 0x00, 0x00, 0x00, 0x01, 0x4f, 0x67, 0x67, 0x53,
];

let mut reader = BufReader::new(&bytes);
let result = read_ident_header(&mut reader);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"malformed stream: invalid output channel count"
);
}

#[test]
fn verify_err_if_channel_family_eq_1_and_stream_count_eq_0() {
let bytes: [u8; 23] = [
0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64, 0x01, 0x02, 0x38, 0x01, 0x80, 0xbb,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x67, 0x67, 0x53,
];
let mut reader = BufReader::new(&bytes);
let result = read_ident_header(&mut reader);
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "malformed stream: stream count is 0");
}

#[test]
fn verify_err_if_channel_family_eq_1_and_stream_counts_sum_gt_255() {
let bytes: [u8; 23] = [
0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64, 0x01, 0x02, 0x38, 0x01, 0x80, 0xbb,
0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0xFF, 0x67, 0x53,
];
let mut reader = BufReader::new(&bytes);
let result = read_ident_header(&mut reader);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"malformed stream: stream count + coupled stream count > 255"
);
}
}
6 changes: 6 additions & 0 deletions symphonia/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ wav = ["dep:symphonia-format-riff", "symphonia-format-riff/wav"]
ape = ["symphonia-metadata/ape"]
id3v1 = ["symphonia-metadata/id3v1"]
id3v2 = ["symphonia-metadata/id3v2"]
opus = ["dep:symphonia-codec-opus"]

# MPEG audio codecs.
mpa = ["mp1", "mp2", "mp3"]
Expand Down Expand Up @@ -138,6 +139,11 @@ version = "0.5.4"
path = "../symphonia-codec-vorbis"
optional = true

[dependencies.symphonia-codec-opus]
version = "0.0.1"
path = "../symphonia-codec-opus"
optional = true

[dependencies.symphonia-format-riff]
version = "0.5.4"
path = "../symphonia-format-riff"
Expand Down
5 changes: 5 additions & 0 deletions symphonia/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ pub mod default {
pub use symphonia_codec_adpcm::AdpcmDecoder;
#[cfg(feature = "alac")]
pub use symphonia_codec_alac::AlacDecoder;
#[cfg(feature = "opus")]
pub use symphonia_codec_opus::OpusDecoder;
#[cfg(feature = "pcm")]
pub use symphonia_codec_pcm::PcmDecoder;
#[cfg(feature = "vorbis")]
Expand Down Expand Up @@ -278,6 +280,9 @@ pub mod default {

#[cfg(feature = "vorbis")]
registry.register_audio_decoder::<codecs::VorbisDecoder>();

#[cfg(feature = "opus")]
registry.register_audio_decoder::<codecs::OpusDecoder>();
}

/// Registers all the formats selected by the `feature` flags in the includer's `Cargo.toml` on
Expand Down