-
Notifications
You must be signed in to change notification settings - Fork 154
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
a1phyr
wants to merge
2
commits into
pdeljanov:dev-0.6
Choose a base branch
from
a1phyr:opus-dev
base: dev-0.6
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,341 @@ | ||
// 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, 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, _opts: &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::default() }) | ||
} | ||
} | ||
|
||
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 { | ||
FinalizeResult::default() | ||
} | ||
|
||
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" | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i dont know the full state of this PR, but shouldnt this also be in
all-codecs
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so, the code doesn't do much as this point.