├── .github
└── workflows
│ ├── ci.yml
│ └── docs.yml
├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Cargo.toml
├── LICENSE.md
├── README.md
└── src
├── coder.rs
├── coder
├── decoder.rs
└── encoder.rs
├── error.rs
├── lib.rs
├── packet.rs
├── repacketizer.rs
└── softclip.rs
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | runs-on: ${{ matrix.os || 'ubuntu-latest' }}
8 |
9 | strategy:
10 | fail-fast: false
11 | matrix:
12 | name:
13 | - stable
14 | - beta
15 | - nightly
16 | - macOS
17 | - Windows
18 |
19 | include:
20 | - name: beta
21 | toolchain: beta
22 | - name: nightly
23 | toolchain: nightly
24 | - name: macOS
25 | os: macOS-latest
26 | - name: Windows
27 | os: windows-latest
28 |
29 | steps:
30 | - name: Checkout sources
31 | uses: actions/checkout@v2
32 |
33 | - name: Install toolchain
34 | id: tc
35 | uses: actions-rs/toolchain@v1
36 | with:
37 | toolchain: ${{ matrix.toolchain || 'stable' }}
38 | profile: minimal
39 | override: true
40 |
41 | - name: Install dependencies
42 | if: runner.os == 'Linux'
43 | run: |
44 | sudo apt-get update
45 | sudo apt-get install -y libopus-dev
46 |
47 | - name: Setup cache
48 | if: runner.os != 'macOS'
49 | uses: actions/cache@v2
50 | with:
51 | path: |
52 | ~/.cargo/registry
53 | ~/.cargo/git
54 | target
55 | key: ${{ matrix.os }}-test-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }}
56 |
57 | - name: Build all features
58 | run: cargo build --all-features
59 |
60 | - name: Test all features
61 | run: cargo test --all-features
62 |
63 | docs:
64 | name: Build docs
65 | runs-on: ubuntu-latest
66 |
67 | steps:
68 | - name: Checkout sources
69 | uses: actions/checkout@v2
70 |
71 | - name: Install toolchain
72 | id: tc
73 | uses: actions-rs/toolchain@v1
74 | with:
75 | toolchain: nightly
76 | profile: minimal
77 | override: true
78 |
79 | - name: Install dependencies
80 | run: |
81 | sudo apt-get update
82 | sudo apt-get install -y libopus-dev
83 |
84 | - name: Setup cache
85 | uses: actions/cache@v2
86 | with:
87 | path: |
88 | ~/.cargo/registry
89 | ~/.cargo/git
90 | key: ${{ runner.os }}-gh-pages-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }}
91 |
92 | - name: Build docs
93 | env:
94 | RUSTDOCFLAGS: -D broken_intra_doc_links
95 | run: |
96 | cargo doc --no-deps
97 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: Publish docs
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - stable-changes
8 | - breaking-changes
9 |
10 | jobs:
11 | docs:
12 | name: Publish docs
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - name: Checkout sources
17 | uses: actions/checkout@v2
18 |
19 | - name: Install toolchain
20 | id: tc
21 | uses: actions-rs/toolchain@v1
22 | with:
23 | toolchain: nightly
24 | profile: minimal
25 | override: true
26 |
27 | - name: Install dependencies
28 | run: |
29 | sudo apt-get update
30 | sudo apt-get install -y libopus-dev
31 |
32 | - name: Setup cache
33 | uses: actions/cache@v2
34 | with:
35 | path: |
36 | ~/.cargo/registry
37 | ~/.cargo/git
38 | target/debug
39 | key: ${{ runner.os }}-gh-pages-${{ steps.tc.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.toml') }}
40 |
41 | - name: Build docs
42 | env:
43 | RUSTDOCFLAGS: -D broken_intra_doc_links
44 | run: |
45 | cargo doc --no-deps
46 |
47 | - name: Prepare docs
48 | shell: bash -e -O extglob {0}
49 | run: |
50 | DIR=${GITHUB_REF/refs\/+(heads|tags)\//}
51 | mkdir -p ./docs/$DIR
52 | touch ./docs/.nojekyll
53 | echo '' > ./docs/$DIR/index.html
54 | mv ./target/doc/* ./docs/$DIR/
55 |
56 | - name: Deploy docs
57 | uses: peaceiris/actions-gh-pages@v3
58 | with:
59 | github_token: ${{ secrets.GITHUB_TOKEN }}
60 | publish_branch: gh-pages
61 | publish_dir: ./docs
62 | allow_empty_commit: false
63 | keep_files: true
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | **/*.rs.bk
3 | Cargo.lock
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | An overview of changes:
4 |
5 | ## [0.3.0]
6 | *These notes include a [little upgrade guide below](##Upgrading).*
7 |
8 | This release updates `audiopus_sys` to `0.3`, bringing following
9 | changes to this high-level crate:
10 |
11 | ### **Changed:**
12 | * **Important**: `cmake` is now required to build Opus.
13 |
14 | * The API now expects you to provide the already converted structures instead of
15 | accepting a type implementing `TryInto` for the structure.
16 |
17 | * Windows will build Opus instead of using a pre-built version.
18 |
19 | ### **Fixed:**
20 |
21 | * Cross-compiling should work now.
22 |
23 | ### **Removed:**
24 |
25 | * Pre-built Windows binaries are no longer provided.
26 |
27 | ### **Upgrading:**
28 | Arguments for the methods in `audiopus` use newtypes verifying whether
29 | constraints are upheld.
30 |
31 | This new update requires the API-user to provide these
32 | structures instead of passing a type that would `TryInto` the structure.
33 | However this can be done by using `TryFrom` or `TryInto`.
34 |
35 | Constructing them via `T::try_from`:
36 |
37 | ```rust
38 | let mut signals = MutSignals::try_from(&mut signals)?;
39 | ```
40 |
41 | or by converting them inside the method via `value.try_into()?`:
42 |
43 | ```rust
44 | soft_clip.apply(&(frames).try_into()?)?;
45 | ```
46 |
47 | Let's look at code on how to use the old `v0.2` and compare it to new `v0.3`
48 |
49 | Old `v0.2`:
50 | ```rust
51 | let mut soft_clip = SoftClip::new(Channels::Stereo);
52 |
53 | let mut signals: Vec = vec![];
54 | /// The raw data is being processed inside the method.
55 | soft_clip.apply(signals)?;
56 | ```
57 |
58 | New `v0.3`:
59 | ```rust
60 | let mut soft_clip = SoftClip::new(Channels::Stereo);
61 |
62 | let mut signals: Vec = vec![];
63 |
64 | soft_clip.apply((&signals).try_into()?)?;
65 | ```
66 | This optimises for compile time – as generics have been eliminated – improves the API clarity, and it allows the
67 | user to create and handle the structure construction errors one-by-one.
68 |
69 | ## [0.2.0]
70 |
71 | This release fixes an API inconsistency by introducing one breaking change.
72 |
73 | The `input` on `Decoder::decode_float` must be `Option`,
74 | to be consistent with the `Decoder::decode`-method.
75 | This allows users to provide a null raw pointer by passing `None`.
76 |
77 | ## [0.1.3]
78 |
79 | The `Decoder` implements `Send` now, to be consistent with `Encoder`.
80 |
81 | ### **Added:**
82 |
83 | * Implement `Send` for `Decoder`.
84 |
85 | ## [0.1.2]
86 |
87 | This release contains a critical fix for `SoftClip::apply`.
88 |
89 | ### **Fixed:**
90 |
91 | * Fixed potential Segfault caused by `SoftClip::apply`.
92 | * A typo.
93 |
94 | ## [0.1.1]
95 |
96 | ### **Added:**
97 |
98 | * Implements `std::error::Error` for `Error` and `ErrorCode`.
99 |
100 |
101 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Everyone is welcome to get involved, may it be a pull request, suggestion, bug
4 | report, or a textual improvement! : )
5 |
6 | The language applied in this repository is British English.
7 |
8 | ## Contributions
9 |
10 | Contributions to `audiopus_sys` should be first discussed up via an issue and then
11 | implemented via pull request.
12 | Issues display development-plans or required brainstorming, feel free to ask,
13 | suggest, and discuss!
14 | The `master`-branch contains the latest release.
15 |
16 | ## Comments & Documentation Style
17 |
18 | - Comments are placed the lines before the related code line, not on the same
19 | line.
20 |
21 | - Write full sentences in British English.
22 |
23 | - `unsafe` must always be reasoned and their soundness must be proven via a
24 | comment.
25 |
26 | - Use Rust intra-doc-links paths to refer Rust items in documentation:
27 | `[name](crate::module::struct::method)`.
28 |
29 | - If code ends up difficult, try to simplify it, if unavoidable, explain code
30 | with comments. Prefer explicit variable naming instead of abbreviations.
31 |
32 | ## Commit Style
33 |
34 | Write full sentences in British English.
35 |
36 | Commits should describe the action being peformed.
37 |
38 | Example:
39 | - *Fix deadlock for events.*
40 | - *Correct grammar in `command`-example.*
41 |
42 | ## Pull Request Checklist
43 |
44 | - Make sure to open an issue prior working on a problem or ask on existing
45 | issue be assigned.
46 |
47 | - If a pull requests breaks the current API, use the `breaking-changes`-branch,
48 | otherwise `stable-changes`.
49 |
50 | - Commits shall be as small as possible, compile, and pass all tests.
51 |
52 | - Make sure your code is formatted with `rustfmt` and free of lints,
53 | run `cargo fmt` and `cargo clippy`.
54 |
55 | - If you fixed a bug, add a test for that bug. Unit tests belong inside the
56 | same file's `mod` named `tests`, integrational tests belong inside the
57 | `tests`-folder.
58 |
59 | - Last but not least, make sure your planned pull request merges cleanly,
60 | if it does not, rebase your changes.
61 |
62 | If you have any questions left, please reach out via the issue system : )
63 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "audiopus"
3 | version = "0.3.0-rc.0"
4 | license = "ISC"
5 | repository = "https://github.com/lakelezz/audiopus.git"
6 | authors = ["Lakelezz "]
7 | keywords = ["audio", "opus", "codec"]
8 | categories = ["api-bindings", "compression", "encoding",
9 | "multimedia::audio", "multimedia::encoding"]
10 | description = "High-level binding of the Opus Codec library."
11 | readme = "README.md"
12 | documentation = "https://docs.rs/audiopus"
13 | edition = "2018"
14 |
15 | [dependencies]
16 | audiopus_sys = "0.2.2"
17 |
18 | [dev-dependencies.matches]
19 | version = "0.1.8"
20 |
21 | [features]
22 | default_features = ["coder"]
23 |
24 | encoder = ["packet"]
25 | decoder = ["packet"]
26 | coder = ["encoder", "decoder"]
27 |
28 | packet = []
29 | repacketizer = ["packet"]
30 | multistream = []
31 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | ISC License
2 |
3 | Copyright (c) 2019, Lakelezz
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted, provided that the above
7 | copyright notice and this permission notice appear in all copies.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [![ci-badge][]][ci] [![docs-badge][]][docs] [![rust version badge]][rust version link] [![crates.io version]][crates.io link]
2 |
3 | # About
4 |
5 | `audiopus` is a high-level binding of [`Opus`] version 1.3.
6 | The crate uses [`audiopus_sys`] underneath.
7 |
8 | Orginally, this crate was made to empower the [`serenity`]-crate to build audio features on Windows, Linux, and Mac.
9 |
10 | Everyone is welcome to contribute,
11 | check out the [`CONTRIBUTING.md`](CONTRIBUTING.md) for further guidance.
12 |
13 | # Building
14 |
15 | ## Requirements
16 | If you want to build Opus, you will need `cmake`.
17 |
18 | If you have `pkg-config`, it will attempt to use that before building.
19 |
20 | You can also link a pre-installed Opus, see [**Pre-installed Opus**](#Pre-installed-Opus)
21 | below.
22 |
23 | This crate provides a pre-built binding. In case you want to generate the
24 | binding yourself, you will need [`Clang`](https://rust-lang.github.io/rust-bindgen/requirements.html#clang),
25 | see [**Pre-installed Opus**](#Generating-The-Binding) below for further
26 | instructions.
27 |
28 | ## Linking
29 | The crate underneath, [`audiopus_sys`], links to Opus 1.3 and supports Windows, Linux, and MacOS
30 | By default, we statically link to Windows, MacOS, and if you use the
31 | `musl`-environment. We will link dynamically for Linux except when using
32 | mentioned `musl`.
33 |
34 | This can be altered by compiling with the `static` or `dynamic` feature having
35 | effects respective to their names. If both features are enabled,
36 | we will pick your system's default.
37 |
38 | Environment variables named `LIBOPUS_STATIC` or `OPUS_STATIC` will take
39 | precedence over features thus overriding the behaviour. The value of these
40 | environment variables have no influence of the result: If one of them is set,
41 | statically linking will be picked.
42 |
43 | ## Pkg-Config
44 | By default, `audiopus_sys` will use `pkg-config` on Unix or GNU.
45 | Setting the environment variable `LIBOPUS_NO_PKG` or `OPUS_NO_PKG` will bypass
46 | probing for Opus via `pkg-config`.
47 |
48 | ## Pre-installed Opus
49 | If you have Opus pre-installed, you can set `LIBOPUS_LIB_DIR` or
50 | `OPUS_LIB_DIR` to the directory containing Opus.
51 |
52 | Be aware that using an Opus other than version 1.3 may not work.
53 |
54 | # Installation
55 | Add this to your `Cargo.toml`:
56 |
57 | ```toml
58 | [dependencies]
59 | audiopus = "0.3"
60 | ```
61 | [`serenity`]: https://crates.io/crates/serenity
62 |
63 | [`Opus`]: https://www.opus-codec.org/
64 |
65 | [`audiopus_sys`]: https://github.com/Lakelezz/audiopus_sys.git
66 |
67 | [ci]: https://github.com/Lakelezz/audiopus/actions
68 | [ci-badge]: https://img.shields.io/github/workflow/status/Lakelezz/audiopus/CI?style=flat-square
69 |
70 | [docs-badge]: https://img.shields.io/badge/docs-online-5023dd.svg?style=flat-square&colorB=32b6b7
71 | [docs]: https://docs.rs/audiopus
72 |
73 | [rust version badge]: https://img.shields.io/badge/rust-1.51+-93450a.svg?style=flat-square&colorB=ff9a0d
74 | [rust version link]: hhttps://blog.rust-lang.org/2021/03/25/Rust-1.51.0.html
75 |
76 | [crates.io link]: https://crates.io/crates/audiopus
77 | [crates.io version]: https://img.shields.io/crates/v/audiopus.svg?style=flat-square&colorB=b73732
78 |
--------------------------------------------------------------------------------
/src/coder.rs:
--------------------------------------------------------------------------------
1 | use crate::{Error, SampleRate};
2 |
3 | pub use self::{
4 | decoder::{size, Decoder},
5 | encoder::Encoder,
6 | };
7 |
8 | mod decoder;
9 | mod encoder;
10 |
11 | /// A set of methods that both `Encoder` and `Decoder` have implemented.
12 | ///
13 | /// **Info**:
14 | /// This does not include `set_sample_rate` as it returns unimplemented on
15 | /// [`Decoder`].
16 | ///
17 | /// [`Decoder`]: decoder/struct.Decoder.html
18 | pub trait GenericCtl {
19 | fn final_range(&self) -> Result;
20 |
21 | fn phase_inversion_disabled(&self) -> Result;
22 | fn set_phase_inversion_disabled(&mut self, disabled: bool) -> Result<(), Error>;
23 |
24 | fn sample_rate(&self) -> Result;
25 |
26 | fn reset_state(&mut self) -> Result<(), Error>;
27 | }
28 |
--------------------------------------------------------------------------------
/src/coder/decoder.rs:
--------------------------------------------------------------------------------
1 | use super::GenericCtl;
2 | use crate::{
3 | error::try_map_opus_error, ffi, packet::Packet, Channels, ErrorCode, MutSignals, Result,
4 | SampleRate,
5 | };
6 | use std::convert::TryFrom;
7 |
8 | /// `Decoder` to decode.
9 | #[derive(Debug)]
10 | pub struct Decoder {
11 | pointer: *mut ffi::OpusDecoder,
12 | channels: Channels,
13 | }
14 |
15 | /// The Opus decoder can be sent between threads unless the Opus library
16 | /// has been compiled with `NONTHREADSAFE_PSEUDOSTACK` to disallow decoding in
17 | /// parallel.
18 | unsafe impl Send for Decoder {}
19 |
20 | impl GenericCtl for Decoder {
21 | fn final_range(&self) -> Result {
22 | self.decoder_ctl_request(ffi::OPUS_GET_FINAL_RANGE_REQUEST)
23 | .map(|v| v as u32)
24 | }
25 |
26 | fn phase_inversion_disabled(&self) -> Result {
27 | self.decoder_ctl_request(ffi::OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST)
28 | .map(|b| b == 1)
29 | }
30 |
31 | fn set_phase_inversion_disabled(&mut self, disabled: bool) -> Result<()> {
32 | let disable_phase_inversion = if disabled { 1 } else { 0 };
33 | self.set_decoder_ctl_request(
34 | ffi::OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST,
35 | disable_phase_inversion,
36 | )
37 | .map(|_| ())
38 | }
39 |
40 | fn sample_rate(&self) -> Result {
41 | self.decoder_ctl_request(ffi::OPUS_GET_SAMPLE_RATE_REQUEST)
42 | .and_then(SampleRate::try_from)
43 | }
44 |
45 | fn reset_state(&mut self) -> Result<()> {
46 | self.decoder_ctl_request(ffi::OPUS_RESET_STATE).map(|_| ())
47 | }
48 | }
49 |
50 | impl Decoder {
51 | /// Creates a new Opus decoder.
52 | pub fn new(sample_rate: SampleRate, channels: Channels) -> Result {
53 | let mut opus_code = 0;
54 |
55 | let pointer = unsafe {
56 | ffi::opus_decoder_create(sample_rate as i32, channels as i32, &mut opus_code)
57 | };
58 |
59 | if opus_code == ffi::OPUS_OK || !pointer.is_null() {
60 | return Ok(Decoder { pointer, channels });
61 | }
62 |
63 | Err(ErrorCode::from(opus_code).into())
64 | }
65 |
66 | /// Decodes an Opus packet as `input` and writes decoded data into `output`.
67 | /// Passing `None` as `input` indicates a packet loss.
68 | ///
69 | /// **Errors**:
70 | /// Returns [Error::Opus] when Opus encountered a problem.
71 | ///
72 | /// [Error::Opus]: crate::error::Error::Opus
73 | pub fn decode(
74 | &mut self,
75 | input: Option>,
76 | mut output: MutSignals<'_, i16>,
77 | fec: bool,
78 | ) -> Result {
79 | let (input_pointer, input_len) = if let Some(value) = input {
80 | (value.as_ptr(), value.i32_len())
81 | } else {
82 | (std::ptr::null(), 0)
83 | };
84 |
85 | try_map_opus_error(unsafe {
86 | ffi::opus_decode(
87 | self.pointer,
88 | input_pointer,
89 | input_len,
90 | output.as_mut_ptr(),
91 | output.i32_len() / self.channels as i32,
92 | fec as i32,
93 | )
94 | })
95 | .map(|n| n as usize)
96 | }
97 |
98 | /// Decodes an Opus frame from floating point input.
99 | ///
100 | /// The `input` signal (interleaved if 2 channels) will be encoded into the
101 | /// `output` payload and on success, returns the length of the
102 | /// encoded packet.
103 | ///
104 | /// **Errors**:
105 | /// Returns [Error::Opus] when Opus encountered a problem.
106 | ///
107 | /// [Error::Opus]: crate::error::Error::Opus
108 | pub fn decode_float(
109 | &mut self,
110 | input: Option>,
111 | mut output: MutSignals<'_, f32>,
112 | fec: bool,
113 | ) -> Result {
114 | let (input_pointer, input_len) = if let Some(value) = input {
115 | (value.as_ptr(), value.i32_len())
116 | } else {
117 | (std::ptr::null(), 0)
118 | };
119 |
120 | try_map_opus_error(unsafe {
121 | ffi::opus_decode_float(
122 | self.pointer,
123 | input_pointer,
124 | input_len,
125 | output.as_mut_ptr(),
126 | output.i32_len() / self.channels as i32,
127 | fec as i32,
128 | )
129 | })
130 | .map(|n| n as usize)
131 | }
132 |
133 | /// Gets the number of samples of an Opus packet.
134 | ///
135 | /// **Errors**:
136 | /// Returns [Error::Opus] when Opus encountered a problem.
137 | ///
138 | /// [Error::Opus]: crate::error::Error::Opus
139 | pub fn nb_samples(&self, input: Packet<'_>) -> Result {
140 | unsafe {
141 | try_map_opus_error(ffi::opus_decoder_get_nb_samples(
142 | self.pointer,
143 | input.as_ptr(),
144 | input.i32_len(),
145 | ))
146 | .map(|n| n as usize)
147 | }
148 | }
149 |
150 | /// Issues a CTL `request` to Opus without argument used to
151 | /// request a value.
152 | /// If Opus returns a value smaller than 0, it indicates an error.
153 | ///
154 | /// **Errors**:
155 | /// Returns [Error::Opus] when Opus encountered a problem
156 | ///
157 | /// [Error::Opus]: crate::error::Error::Opus
158 | fn decoder_ctl_request(&self, request: i32) -> Result {
159 | let mut value = 0;
160 |
161 | let ffi_result = unsafe { ffi::opus_decoder_ctl(self.pointer, request, &mut value) };
162 |
163 | try_map_opus_error(ffi_result)?;
164 |
165 | Ok(value)
166 | }
167 |
168 | /// Issues a CTL `request` to Opus accepting an additional argument used
169 | /// to set the `decoder`'s setting to `value`.
170 | /// If Opus returns a value smaller than 0, it indicates an error.
171 | ///
172 | /// **Errors**:
173 | /// Returns [Error::Opus] when Opus encountered a problem
174 | ///
175 | /// [Error::Opus]: crate::error::Error::Opus
176 | fn set_decoder_ctl_request(&self, request: i32, value: i32) -> Result<()> {
177 | try_map_opus_error(unsafe { ffi::opus_decoder_ctl(self.pointer, request, value) })?;
178 |
179 | Ok(())
180 | }
181 |
182 | /// Gets the duration (in samples) of the last packet successfully decoded
183 | /// or concealed.
184 | pub fn last_packet_duration(&self) -> Result {
185 | self.decoder_ctl_request(ffi::OPUS_GET_LAST_PACKET_DURATION_REQUEST)
186 | .map(|v| v as u32)
187 | }
188 |
189 | /// Gets the pitch period at 48 kHz of the last decoded frame, if available.
190 | ///
191 | /// This can be used for any post-processing algorithm requiring the use of
192 | /// pitch, e.g. time stretching/shortening.
193 | /// If the last frame was not voiced, or if the pitch was not coded in the
194 | /// frame, then zero is returned.
195 | pub fn pitch(&self) -> Result {
196 | self.decoder_ctl_request(ffi::OPUS_GET_PITCH_REQUEST)
197 | }
198 |
199 | /// Gets the decoder's configured amount to scale PCM signal by
200 | /// in Q8 dB units.
201 | pub fn gain(&self) -> Result {
202 | self.decoder_ctl_request(ffi::OPUS_GET_GAIN_REQUEST)
203 | }
204 |
205 | /// Configures decoder gain adjustment.
206 | ///
207 | /// Scales the decoded output by a factor of `gain` specified in
208 | /// Q8 dB units.
209 | ///
210 | /// **Warning**:
211 | /// This has a maximum range of -32768 to 32767 inclusive, and returns
212 | /// [`BadArgument`] otherwise.
213 | /// The default is 0 indicating no adjustment.
214 | ///
215 | /// **Info**:
216 | /// This setting survives decoder reset.
217 | ///
218 | /// [`BadArgument`]: ../error/enum.ErrorCode.html#variant.BadArgument
219 | pub fn set_gain(&self, gain: i32) -> Result<()> {
220 | self.set_decoder_ctl_request(ffi::OPUS_SET_GAIN_REQUEST, gain)
221 | }
222 |
223 | /// Gets size of self's underlying Opus-decoder in bytes.
224 | pub fn size(&self) -> usize {
225 | unsafe { ffi::opus_decoder_get_size(self.channels as i32) as usize }
226 | }
227 | }
228 |
229 | /// Gets size of an Opus-decoder in bytes.
230 | pub fn size(channels: Channels) -> usize {
231 | unsafe { ffi::opus_decoder_get_size(channels as i32) as usize }
232 | }
233 |
234 | impl Drop for Decoder {
235 | /// We have to ensure that the resource our wrapping Opus-struct is pointing
236 | /// to is deallocated properly.
237 | fn drop(&mut self) {
238 | unsafe { ffi::opus_decoder_destroy(self.pointer) }
239 | }
240 | }
241 |
242 | #[cfg(test)]
243 | mod tests {
244 | use super::Decoder;
245 | use crate::{Channels, Error, ErrorCode, SampleRate};
246 | use matches::assert_matches;
247 |
248 | #[test]
249 | fn set_and_get_gain() {
250 | let decoder = Decoder::new(SampleRate::Hz48000, Channels::Stereo).unwrap();
251 |
252 | assert_matches!(decoder.gain(), Ok(0));
253 |
254 | assert_matches!(decoder.set_gain(10), Ok(()));
255 |
256 | assert_matches!(decoder.gain(), Ok(10));
257 |
258 | let lower_limit = -32768;
259 | let upper_limit = 32767;
260 |
261 | assert_matches!(decoder.set_gain(lower_limit), Ok(()));
262 | assert_matches!(
263 | decoder.set_gain(lower_limit - 1),
264 | Err(Error::Opus(ErrorCode::BadArgument))
265 | );
266 |
267 | assert_matches!(decoder.set_gain(upper_limit), Ok(()));
268 | assert_matches!(
269 | decoder.set_gain(upper_limit + 1),
270 | Err(Error::Opus(ErrorCode::BadArgument))
271 | );
272 | }
273 | }
274 |
--------------------------------------------------------------------------------
/src/coder/encoder.rs:
--------------------------------------------------------------------------------
1 | use super::GenericCtl;
2 | use crate::{
3 | error::try_map_opus_error, ffi, Application, Bandwidth, Bitrate, Channels, ErrorCode, Result,
4 | SampleRate, Signal, TryFrom,
5 | };
6 |
7 | /// `Encoder` calls to Opus and offers method to encode and issue
8 | /// requests to Opus.
9 | #[derive(Debug)]
10 | pub struct Encoder {
11 | pointer: *mut ffi::OpusEncoder,
12 | channels: Channels,
13 | }
14 |
15 | /// The Opus encoder can be sent between threads unless the Opus library
16 | /// has been compiled with `NONTHREADSAFE_PSEUDOSTACK` to disallow encoding in
17 | /// parallel.
18 | unsafe impl Send for Encoder {}
19 |
20 | impl GenericCtl for Encoder {
21 | /// Gets the final state of the codec's entropy coder.
22 | ///
23 | /// This is used for testing purposes. The encoder state should
24 | /// be identical after coding a payload, assuming no data corruption or
25 | /// software bugs.
26 | fn final_range(&self) -> Result {
27 | self.encoder_ctl_request(ffi::OPUS_GET_FINAL_RANGE_REQUEST)
28 | .map(|v| v as u32)
29 | }
30 |
31 | /// Gets the encoder's configured phase inversion status.
32 | fn phase_inversion_disabled(&self) -> Result {
33 | self.encoder_ctl_request(ffi::OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST)
34 | .map(|b| b == 1)
35 | }
36 |
37 | /// If set to `true`, disables the use of phase inversion for intensity
38 | /// stereo, improving the quality of mono downmixes, but slightly reducing
39 | /// normal stereo quality.
40 | ///
41 | /// Disabling phase inversion in the decoder does not comply with RFC 6716,
42 | /// although it does not cause any interoperability issue and is expected
43 | /// to become part of the Opus standard once RFC 6716 is updated by
44 | /// draft-ietf-codec-opus-update.
45 | fn set_phase_inversion_disabled(&mut self, disabled: bool) -> Result<()> {
46 | let disable_phase_inversion = if disabled { 1 } else { 0 };
47 | self.set_encoder_ctl_request(
48 | ffi::OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST,
49 | disable_phase_inversion,
50 | )
51 | .map(|_| ())
52 | }
53 |
54 | /// Gets the sampling rate the encoder or decoder was initialized with.
55 | ///
56 | /// This simply returns the Fs value passed to [`Encoder::new`].
57 | ///
58 | /// [`Encoder::new`]: struct.Encoder.html#method.new
59 | fn sample_rate(&self) -> Result {
60 | self.encoder_ctl_request(ffi::OPUS_GET_SAMPLE_RATE_REQUEST)
61 | .and_then(SampleRate::try_from)
62 | }
63 |
64 | /// Resets the codec state to be equivalent to a freshly initialized state.
65 | ///
66 | /// This should be called when switching streams in order to prevent the
67 | /// back to back decoding from giving different results from one at a
68 | /// time decoding.
69 | fn reset_state(&mut self) -> Result<()> {
70 | self.encoder_ctl_request(ffi::OPUS_RESET_STATE).map(|_| ())
71 | }
72 | }
73 |
74 | impl Encoder {
75 | /// Creates a new Opus encoder.
76 | ///
77 | /// **Warning**:
78 | /// If `channels` is set to [`Channels::Auto`] the function will
79 | /// return [`BadArgument`].
80 | ///
81 | /// [`Channels::Auto`]: ../enum.Channels.html#variant.Auto
82 | /// [`BadArgument`]: ../error/enum.ErrorCode.html#variant.BadArgument
83 | pub fn new(sample_rate: SampleRate, channels: Channels, mode: Application) -> Result {
84 | let mut opus_code = 0;
85 |
86 | let pointer = unsafe {
87 | ffi::opus_encoder_create(
88 | sample_rate as i32,
89 | channels as i32,
90 | mode as i32,
91 | &mut opus_code,
92 | )
93 | };
94 |
95 | if opus_code == ffi::OPUS_OK || !pointer.is_null() {
96 | return Ok(Encoder { pointer, channels });
97 | }
98 |
99 | Err(ErrorCode::from(opus_code).into())
100 | }
101 |
102 | /// Issues a CTL get-`request` to Opus.
103 | /// If Opus returns a negative value it indicates an error.
104 | ///
105 | /// **Info**:
106 | /// As [`Encoder`]'s methods cover all possible CTLs, it is recommended
107 | /// to use them instead.
108 | ///
109 | /// [`Encoder`]: struct.Encoder.html
110 | pub fn encoder_ctl_request(&self, request: i32) -> Result {
111 | let mut value = 0;
112 |
113 | let ffi_result = unsafe { ffi::opus_encoder_ctl(self.pointer, request, &mut value) };
114 | try_map_opus_error(ffi_result)?;
115 |
116 | Ok(value)
117 | }
118 |
119 | /// Issues a CTL set-`request` to Opus and sets the `Encoder`'s setting to
120 | /// `value` based on sent `request`.
121 | /// If Opus returns a negative value it indicates an error.
122 | ///
123 | /// **Info**:
124 | /// As [`Encoder`]'s methods cover all possible CTLs, it is recommended
125 | /// to use them instead.
126 | ///
127 | /// [`Encoder`]: struct.Encoder.html
128 | pub fn set_encoder_ctl_request(&mut self, request: i32, value: i32) -> Result<()> {
129 | try_map_opus_error(unsafe { ffi::opus_encoder_ctl(self.pointer, request, value) })?;
130 |
131 | Ok(())
132 | }
133 |
134 | /// Encodes an Opus frame.
135 | ///
136 | /// The `input` signal (interleaved if 2 channels) will be encoded into the
137 | /// `output` payload and on success returns the length of the
138 | /// encoded packet.
139 | pub fn encode(&self, input: &[i16], output: &mut [u8]) -> Result {
140 | try_map_opus_error(unsafe {
141 | ffi::opus_encode(
142 | self.pointer,
143 | input.as_ptr(),
144 | input.len() as i32 / self.channels as i32,
145 | output.as_mut_ptr(),
146 | output.len() as i32,
147 | )
148 | })
149 | .map(|n| n as usize)
150 | }
151 |
152 | /// Encodes an Opus frame from floating point input.
153 | ///
154 | /// The `input` signal (interleaved if 2 channels) will be encoded into the
155 | /// `output` payload and on success, returns the length of the
156 | /// encoded packet.
157 | pub fn encode_float(&self, input: &[f32], output: &mut [u8]) -> Result {
158 | try_map_opus_error(unsafe {
159 | ffi::opus_encode_float(
160 | self.pointer,
161 | input.as_ptr(),
162 | input.len() as i32 / self.channels as i32,
163 | output.as_mut_ptr(),
164 | output.len() as i32,
165 | )
166 | })
167 | .map(|n| n as usize)
168 | }
169 |
170 | /// Gets the encoder's complexity configuration.
171 | pub fn complexity(&self) -> Result {
172 | self.encoder_ctl_request(ffi::OPUS_GET_COMPLEXITY_REQUEST)
173 | .map(|v| v as u8)
174 | }
175 |
176 | /// Configures the encoder's computational complexity.
177 | ///
178 | /// **Warning**:
179 | /// If `complexity` exceeds 10, [`BadArgument`] will be returned.
180 | ///
181 | /// [`BadArgument`]: ../error/enum.ErrorCode.html#variant.BadArgument.html
182 | pub fn set_complexity(&mut self, complexity: u8) -> Result<()> {
183 | self.set_encoder_ctl_request(ffi::OPUS_SET_COMPLEXITY_REQUEST, i32::from(complexity))
184 | }
185 |
186 | /// Gets the encoder's configured application.
187 | pub fn application(&self) -> Result {
188 | self.encoder_ctl_request(ffi::OPUS_GET_APPLICATION_REQUEST)
189 | .and_then(Application::try_from)
190 | }
191 |
192 | /// Configures the encoder's intended application.
193 | ///
194 | /// The initial value is a mandatory argument in the [`new`]-function.
195 | ///
196 | /// [`new`]: struct.Encoder.html#method.new
197 | pub fn set_application(&mut self, application: Application) -> Result<()> {
198 | self.set_encoder_ctl_request(ffi::OPUS_SET_APPLICATION_REQUEST, application as i32)
199 | .map(|_| ())
200 | }
201 |
202 | /// Configures the bitrate in the encoder.
203 | ///
204 | /// Rates from 500 to 512000 bits per second are meaningful,
205 | /// as well as the special values [`Bitrate::Auto`] and [`Bitrate::Max`].
206 | /// [`Bitrate::Max`] can be used to cause the codec to use
207 | /// as much rate as it can, which is useful for controlling the rate by
208 | /// adjusting the output buffer size.
209 | pub fn set_bitrate(&mut self, bitrate: Bitrate) -> Result<()> {
210 | self.set_encoder_ctl_request(ffi::OPUS_SET_BITRATE_REQUEST, bitrate.into())?;
211 |
212 | Ok(())
213 | }
214 |
215 | /// Gets the encoder's configured bandpass.
216 | pub fn bitrate(&self) -> Result {
217 | self.encoder_ctl_request(ffi::OPUS_GET_BITRATE_REQUEST)
218 | .and_then(Bitrate::try_from)
219 | }
220 |
221 | /// Enables variable bitrate (VBR) in the encoder.
222 | ///
223 | /// The configured bitrate may not be met exactly because frames must be an
224 | /// integer number of bytes in length.
225 | ///
226 | /// **Warning**:
227 | /// Only the MDCT mode of Opus currently heeds the constraint.
228 | /// Speech mode ignores it completely,
229 | /// hybrid mode may fail to obey it if the LPC layer uses more bitrate
230 | /// than the constraint would have permitted.
231 | pub fn enable_vbr_constraint(&mut self) -> Result<()> {
232 | self.set_vbr_constraint(true)
233 | }
234 |
235 | /// Disables variable bitrate (VBR) in the encoder.
236 | ///
237 | /// The configured bitrate may not be met exactly because frames must be an
238 | /// integer number of bytes in length.
239 | ///
240 | /// **Warning**:
241 | /// Only the MDCT mode of Opus currently heeds the constraint.
242 | /// Speech mode ignores it completely,
243 | /// hybrid mode may fail to obey it if the LPC layer uses more bitrate
244 | /// than the constraint would have permitted.
245 | pub fn disable_vbr_constraint(&mut self) -> Result<()> {
246 | self.set_vbr_constraint(false)
247 | }
248 |
249 | /// Sets variable bitrate (VBR) in the encoder.
250 | ///
251 | /// The configured bitrate may not be met exactly because frames must be an
252 | /// integer number of bytes in length.
253 | ///
254 | /// **Warning**:
255 | /// Only the MDCT mode of Opus currently heeds the constraint.
256 | /// Speech mode ignores it completely,
257 | /// hybrid mode may fail to obey it if the LPC layer uses more bitrate
258 | /// than the constraint would have permitted.
259 | pub fn set_vbr_constraint(&mut self, enable: bool) -> Result<()> {
260 | let if_vbr_shall_be_enabled = if enable { 1 } else { 0 };
261 |
262 | self.set_encoder_ctl_request(
263 | ffi::OPUS_SET_VBR_CONSTRAINT_REQUEST,
264 | if_vbr_shall_be_enabled,
265 | )
266 | .map(|_| ())
267 | }
268 |
269 | /// Determine if constrained VBR is enabled in the encoder.
270 | pub fn vbr_constraint(&self) -> Result {
271 | self.encoder_ctl_request(ffi::OPUS_GET_VBR_CONSTRAINT_REQUEST)
272 | .map(|b| b == 1)
273 | }
274 |
275 | /// Enables variable bitrate (VBR) in the encoder.
276 | ///
277 | /// The configured bitrate may not be met exactly because frames must be an
278 | /// integer number of bytes in length.
279 | pub fn enable_vbr(&mut self) -> Result<()> {
280 | self.set_vbr(true)
281 | }
282 |
283 | /// Disables variable bitrate (VBR) in the encoder.
284 | ///
285 | /// The configured bitrate may not be met exactly because frames must be an
286 | /// integer number of bytes in length.
287 | pub fn disable_vbr(&mut self) -> Result<()> {
288 | self.set_vbr(false)
289 | }
290 |
291 | /// Sets variable bitrate (VBR) in the encoder.
292 | ///
293 | /// The configured bitrate may not be met exactly because frames must be an
294 | /// integer number of bytes in length.
295 | pub fn set_vbr(&mut self, enable: bool) -> Result<()> {
296 | let if_vbr_shall_be_enabled = if enable { 1 } else { 0 };
297 |
298 | self.set_encoder_ctl_request(ffi::OPUS_SET_VBR_REQUEST, if_vbr_shall_be_enabled)
299 | .map(|_| ())
300 | }
301 |
302 | /// Determine if variable bitrate (VBR) is enabled in the encoder.
303 | pub fn vbr(&self) -> Result {
304 | self.encoder_ctl_request(ffi::OPUS_GET_VBR_REQUEST)
305 | .map(|b| b == 1)
306 | }
307 |
308 | /// Configures the encoder's use of inband forward error correction (FEC).
309 | pub fn set_inband_fec(&mut self, enable: bool) -> Result<()> {
310 | let if_inband_fec_shall_be_enabled = if enable { 1 } else { 0 };
311 |
312 | self.set_encoder_ctl_request(
313 | ffi::OPUS_SET_INBAND_FEC_REQUEST,
314 | if_inband_fec_shall_be_enabled,
315 | )
316 | .map(|_| ())
317 | }
318 |
319 | /// Enables the encoder's use of inband forward error correction (FEC).
320 | pub fn enable_inband_fec(&mut self) -> Result<()> {
321 | self.set_inband_fec(true)
322 | }
323 |
324 | /// Disables the encoder's use of inband forward error correction (FEC).
325 | pub fn disable_inband_fec(&mut self) -> Result<()> {
326 | self.set_inband_fec(false)
327 | }
328 |
329 | /// Gets encoder's configured use of inband forward error correction.
330 | pub fn inband_fec(&self) -> Result {
331 | self.encoder_ctl_request(ffi::OPUS_GET_INBAND_FEC_REQUEST)
332 | .map(|n| n == 1)
333 | }
334 |
335 | /// Gets the encoder's configured packet loss percentage.
336 | pub fn packet_loss_perc(&self) -> Result {
337 | self.encoder_ctl_request(ffi::OPUS_GET_PACKET_LOSS_PERC_REQUEST)
338 | .map(|n| n as u8)
339 | }
340 |
341 | /// Higher values trigger progressively more loss resistant behavior in the
342 | /// encoder at the expense of quality at a given bitrate in the absence of
343 | /// packet loss, but greater quality under loss.
344 | ///
345 | /// Configures the encoder's expected packet loss percentage.
346 | pub fn set_packet_loss_perc(&mut self, percentage: u8) -> Result<()> {
347 | self.set_encoder_ctl_request(
348 | ffi::OPUS_SET_PACKET_LOSS_PERC_REQUEST,
349 | i32::from(percentage),
350 | )
351 | .map(|_| ())
352 | }
353 |
354 | /// Gets the total samples of delay added by the entire codec.
355 | ///
356 | /// This can be queried by the encoder and then the provided number of
357 | /// samples can be skipped on from the start of the decoder's output to
358 | /// provide time aligned input and output. From the perspective of a
359 | /// decoding application the real data begins this many samples late.
360 | ///
361 | /// The decoder contribution to this delay is identical for all decoders,
362 | /// but the encoder portion of the delay may vary from implementation to
363 | /// implementation, version to version, or even depend on the encoder's
364 | /// initial configuration.
365 | /// Applications needing delay compensation should call this method
366 | /// rather than hard-coding a value.
367 | pub fn lookahead(&self) -> Result {
368 | self.encoder_ctl_request(ffi::OPUS_GET_LOOKAHEAD_REQUEST)
369 | .map(|n| n as u32)
370 | }
371 |
372 | /// Configures mono/stereo forcing in the encoder.
373 | ///
374 | /// This can force the encoder to produce packets encoded as either
375 | /// mono or stereo, regardless of the format of the input audio.
376 | /// This is useful when the caller knows that the input signal is
377 | /// currently a mono source embedded in a stereo stream.
378 | pub fn set_force_channels(&mut self, channels: Channels) -> Result<()> {
379 | self.set_encoder_ctl_request(ffi::OPUS_SET_FORCE_CHANNELS_REQUEST, channels as i32)
380 | .map(|_| ())
381 | }
382 |
383 | /// Gets the encoder's forced channel configuration.
384 | pub fn force_channels(&self) -> Result {
385 | self.encoder_ctl_request(ffi::OPUS_GET_FORCE_CHANNELS_REQUEST)
386 | .and_then(Channels::try_from)
387 | }
388 |
389 | /// Gets the encoder's configured maximum allowed bandpass.
390 | pub fn max_bandwidth(&self) -> Result {
391 | self.encoder_ctl_request(ffi::OPUS_GET_MAX_BANDWIDTH_REQUEST)
392 | .and_then(Bandwidth::try_from)
393 | }
394 |
395 | /// Configures the maximum bandpass that the encoder will select automatically.
396 | ///
397 | /// Applications should normally use this instead of [`set_bandwidth`]
398 | /// (leaving that set to the default, [`Bandwidth::Auto`]).
399 | ///
400 | /// This allows the application to set an upper bound based on the type of
401 | /// input it is providing, but still gives the encoder the freedom to reduce
402 | /// the bandpass when the bitrate becomes too low,
403 | /// for better overall quality.
404 | ///
405 | /// **Warning**:
406 | /// [`Bandwidth::Auto`] will return [`BadArgument`] as it is not
407 | /// accepted by Opus as `bandwidth` value.
408 | ///
409 | /// [`set_bandwidth`]: struct.Encoder.html#method.set_bandwidth.html
410 | /// [`Bandwidth::Auto`]: ../enum.Bandwidth.html#variant.Auto
411 | /// [`BadArgument`]: ../error/enum.ErrorCode.html#variant.BadArgument.html
412 | pub fn set_max_bandwidth(&mut self, bandwidth: Bandwidth) -> Result<()> {
413 | self.set_encoder_ctl_request(ffi::OPUS_SET_MAX_BANDWIDTH_REQUEST, bandwidth as i32)
414 | }
415 |
416 | /// Gets the encoder's configured prediction status.
417 | pub fn prediction_disabled(&self) -> Result {
418 | self.encoder_ctl_request(ffi::OPUS_GET_PREDICTION_DISABLED_REQUEST)
419 | .map(|n| n == 1)
420 | }
421 |
422 | /// If set `prediction_disabled` to `true`, disables almost all use of
423 | /// prediction, making frames almost completely independent.
424 | ///
425 | /// This reduces quality.
426 | pub fn set_prediction_disabled(&mut self, prediction_disabled: bool) -> Result<()> {
427 | let prediction_disabled = if prediction_disabled { 1 } else { 0 };
428 |
429 | self.set_encoder_ctl_request(
430 | ffi::OPUS_SET_PREDICTION_DISABLED_REQUEST,
431 | prediction_disabled,
432 | )
433 | .map(|_| ())
434 | }
435 |
436 | /// Gets the encoder's configured signal type.
437 | pub fn signal(&self) -> Result {
438 | self.encoder_ctl_request(ffi::OPUS_GET_SIGNAL_REQUEST)
439 | .and_then(Signal::try_from)
440 | }
441 |
442 | /// Configures the type of signal being encoded.
443 | ///
444 | /// This is a hint which helps the encoder's mode selection.
445 | pub fn set_signal(&mut self, signal: Signal) -> Result<()> {
446 | self.set_encoder_ctl_request(ffi::OPUS_SET_SIGNAL_REQUEST, signal as i32)
447 | .map(|_| ())
448 | }
449 |
450 | /// Gets the encoder's configured bandpass.
451 | pub fn bandwidth(&self) -> Result {
452 | self.encoder_ctl_request(ffi::OPUS_GET_BANDWIDTH_REQUEST)
453 | .and_then(Bandwidth::try_from)
454 | }
455 |
456 | /// Sets the encoder's bandpass to a specific value.
457 | ///
458 | /// This prevents the encoder from automatically selecting the bandpass
459 | /// based on the available bitrate.
460 | /// If an application knows the bandpass of the input audio it is providing,
461 | /// it should normally use [`set_max_bandwidth`] instead,
462 | /// which still gives the encoder the freedom to reduce the bandpass when
463 | /// the bitrate becomes too low, for better overall quality.
464 | ///
465 | /// [`set_max_bandwidth`]: struct.Encoder.html#method.set_max_bandwidth
466 | pub fn set_bandwidth(&mut self, bandwidth: Bandwidth) -> Result<()> {
467 | self.set_encoder_ctl_request(ffi::OPUS_SET_BANDWIDTH_REQUEST, bandwidth as i32)
468 | }
469 |
470 | /// Gets encoder's configured use of discontinuous transmission.
471 | pub fn dtx(&self) -> Result {
472 | self.encoder_ctl_request(ffi::OPUS_GET_DTX_REQUEST)
473 | .map(|n| n == 1)
474 | }
475 |
476 | /// Configures the encoder's use of discontinuous transmission (DTX).
477 | pub fn set_dtx(&mut self, dtx: bool) -> Result<()> {
478 | let dtx_shall_be_enabled = if dtx { 1 } else { 0 };
479 |
480 | self.set_encoder_ctl_request(ffi::OPUS_SET_DTX_REQUEST, dtx_shall_be_enabled)
481 | .map(|_| ())
482 | }
483 |
484 | /// Enables the encoder's use of discontinuous transmission (DTX).
485 | pub fn enable_dtx(&mut self) -> Result<()> {
486 | self.set_dtx(true)
487 | }
488 |
489 | /// Disables the encoder's use of discontinuous transmission (DTX).
490 | pub fn disable_dtx(&mut self) -> Result<()> {
491 | self.set_dtx(false)
492 | }
493 |
494 | /// Gets the encoder's configured signal depth.
495 | pub fn lsb_depth(&self) -> Result {
496 | self.encoder_ctl_request(ffi::OPUS_GET_LSB_DEPTH_REQUEST)
497 | .map(|n| n as u8)
498 | }
499 |
500 | /// Configures the depth of signal being encoded.
501 | ///
502 | /// This is a hint which helps the encoder identify silence and near-silence.
503 | /// It represents the number of significant bits of linear intensity below
504 | /// which the signal contains ignorable quantisation or other noise.
505 | ///
506 | /// For example, a depth of 14 would be an appropriate setting for G.711
507 | /// u-law input. A depth of 16 would be appropriate for 16-bit linear pcm
508 | /// input with `encode_float()`.
509 | ///
510 | /// When using `encode()` instead of `encode_float()`, or when libopus is
511 | /// compiled for fixed-point, the encoder uses the minimum of the value set
512 | /// here and the value 16.
513 | pub fn set_lsb_depth(&mut self, lsb_depth: u8) -> Result<()> {
514 | self.set_encoder_ctl_request(ffi::OPUS_SET_LSB_DEPTH_REQUEST, i32::from(lsb_depth))
515 | .map(|_| ())
516 | }
517 | }
518 |
519 | impl Drop for Encoder {
520 | /// We have to ensure that the resource our wrapping Opus-struct is pointing
521 | /// to is deallocated properly.
522 | fn drop(&mut self) {
523 | unsafe { ffi::opus_encoder_destroy(self.pointer) }
524 | }
525 | }
526 |
527 | #[cfg(test)]
528 | mod tests {
529 | use super::Encoder;
530 | use crate::{Application, Bandwidth, Bitrate, Channels, Error, ErrorCode, SampleRate, Signal};
531 | use matches::assert_matches;
532 |
533 | #[test]
534 | fn set_get_inband_fec() {
535 | let mut encoder =
536 | Encoder::new(SampleRate::Hz48000, Channels::Stereo, Application::Audio).unwrap();
537 |
538 | assert_matches!(encoder.inband_fec(), Ok(false));
539 |
540 | encoder
541 | .set_inband_fec(true)
542 | .expect("Could not set inband FEC to true.");
543 | assert_matches!(encoder.inband_fec(), Ok(true));
544 |
545 | encoder
546 | .set_inband_fec(false)
547 | .expect("Could not set inband FEC to false.");
548 | assert_matches!(encoder.inband_fec(), Ok(false));
549 |
550 | encoder
551 | .enable_inband_fec()
552 | .expect("Could not set inband FEC to true.");
553 | assert_matches!(encoder.inband_fec(), Ok(true));
554 |
555 | encoder
556 | .disable_inband_fec()
557 | .expect("Could not set inband FEC to false.");
558 | assert_matches!(encoder.inband_fec(), Ok(false));
559 | }
560 |
561 | #[test]
562 | fn set_get_vbr_constraint() {
563 | let mut encoder =
564 | Encoder::new(SampleRate::Hz48000, Channels::Stereo, Application::Audio).unwrap();
565 |
566 | assert_matches!(encoder.vbr_constraint(), Ok(true));
567 |
568 | encoder
569 | .set_vbr_constraint(false)
570 | .expect("Could not disable VBR constraint.");
571 | assert_matches!(encoder.vbr_constraint(), Ok(false));
572 |
573 | encoder
574 | .set_vbr_constraint(true)
575 | .expect("Could not enable VBR constraint.");
576 | assert_matches!(encoder.vbr_constraint(), Ok(true));
577 |
578 | encoder
579 | .enable_vbr_constraint()
580 | .expect("Could not enable VBR constraint.");
581 | assert_matches!(encoder.vbr_constraint(), Ok(true));
582 |
583 | encoder
584 | .disable_vbr_constraint()
585 | .expect("Could not disable VBR constraint.");
586 | assert_matches!(encoder.vbr_constraint(), Ok(false));
587 | }
588 |
589 | #[test]
590 | fn set_get_vbr() {
591 | let mut encoder =
592 | Encoder::new(SampleRate::Hz48000, Channels::Stereo, Application::Audio).unwrap();
593 |
594 | assert_matches!(encoder.vbr(), Ok(true));
595 |
596 | encoder.set_vbr(false).expect("Could not disable VBR.");
597 | assert_matches!(encoder.vbr(), Ok(false));
598 |
599 | encoder.set_vbr(true).expect("Could not enable VBR.");
600 | assert_matches!(encoder.vbr(), Ok(true));
601 |
602 | encoder.enable_vbr().expect("Could not enable VBR.");
603 | assert_matches!(encoder.vbr(), Ok(true));
604 |
605 | encoder.disable_vbr().expect("Could not disable VBR.");
606 | assert_matches!(encoder.vbr(), Ok(false));
607 | }
608 |
609 | #[test]
610 | fn set_get_packet_loss_perc() {
611 | let mut encoder =
612 | Encoder::new(SampleRate::Hz48000, Channels::Stereo, Application::Audio).unwrap();
613 |
614 | assert_matches!(encoder.packet_loss_perc(), Ok(0));
615 |
616 | encoder
617 | .set_packet_loss_perc(10)
618 | .expect("Could not set packet loss perc to 10%.");
619 | assert_matches!(encoder.packet_loss_perc(), Ok(10));
620 |
621 | encoder
622 | .set_packet_loss_perc(100)
623 | .expect("Could not set packet loss perc to 100%.");
624 | assert_matches!(encoder.packet_loss_perc(), Ok(100));
625 |
626 | assert_matches!(
627 | encoder.set_packet_loss_perc(101),
628 | Err(Error::Opus(ErrorCode::BadArgument))
629 | );
630 | assert_matches!(encoder.packet_loss_perc(), Ok(100));
631 | }
632 |
633 | #[test]
634 | fn set_get_force_channels() {
635 | let mut encoder =
636 | Encoder::new(SampleRate::Hz48000, Channels::Stereo, Application::Audio).unwrap();
637 |
638 | assert_matches!(encoder.force_channels(), Ok(Channels::Auto));
639 |
640 | encoder
641 | .set_force_channels(Channels::Mono)
642 | .expect("Could not set force channels to mono.");
643 | assert_matches!(encoder.force_channels(), Ok(Channels::Mono));
644 |
645 | encoder
646 | .set_force_channels(Channels::Stereo)
647 | .expect("Could not set force channels to stereo.");
648 | assert_matches!(encoder.force_channels(), Ok(Channels::Stereo));
649 |
650 | encoder
651 | .set_force_channels(Channels::Auto)
652 | .expect("Could not set force channels to mono.");
653 | assert_matches!(encoder.force_channels(), Ok(Channels::Auto));
654 | }
655 |
656 | #[test]
657 | fn set_get_prediction_disabled() {
658 | let mut encoder =
659 | Encoder::new(SampleRate::Hz48000, Channels::Stereo, Application::Audio).unwrap();
660 |
661 | assert_matches!(encoder.prediction_disabled(), Ok(false));
662 |
663 | encoder
664 | .set_prediction_disabled(true)
665 | .expect("Could not set prediction disabled to true.");
666 | assert_matches!(encoder.prediction_disabled(), Ok(true));
667 |
668 | encoder
669 | .set_prediction_disabled(false)
670 | .expect("Could not set prediction disabled to false.");
671 | assert_matches!(encoder.prediction_disabled(), Ok(false));
672 | }
673 |
674 | #[test]
675 | fn set_get_signal() {
676 | let mut encoder =
677 | Encoder::new(SampleRate::Hz48000, Channels::Stereo, Application::Audio).unwrap();
678 |
679 | assert_matches!(encoder.signal(), Ok(Signal::Auto));
680 |
681 | encoder
682 | .set_signal(Signal::Music)
683 | .expect("Could not set signal to music.");
684 | assert_matches!(encoder.signal(), Ok(Signal::Music));
685 |
686 | encoder
687 | .set_signal(Signal::Voice)
688 | .expect("Could not set signal to voice.");
689 | assert_matches!(encoder.signal(), Ok(Signal::Voice));
690 |
691 | encoder
692 | .set_signal(Signal::Auto)
693 | .expect("Could not set signal back to.");
694 | assert_matches!(encoder.signal(), Ok(Signal::Auto));
695 | }
696 |
697 | #[test]
698 | fn encoder_construction() {
699 | assert_matches!(
700 | Encoder::new(SampleRate::Hz48000, Channels::Auto, Application::Audio),
701 | Err(Error::Opus(ErrorCode::BadArgument))
702 | );
703 |
704 | Encoder::new(SampleRate::Hz48000, Channels::Stereo, Application::Audio)
705 | .expect("Could not create stereo audio encoder");
706 |
707 | Encoder::new(SampleRate::Hz48000, Channels::Mono, Application::Audio)
708 | .expect("Could not create mono audio encoder");
709 | }
710 |
711 | #[test]
712 | fn encoding() {
713 | let stereo_encoder =
714 | Encoder::new(SampleRate::Hz48000, Channels::Stereo, Application::Audio).unwrap();
715 |
716 | // 48000Hz * 1 channel * 20 ms / 1000
717 | const STEREO_20MS: usize = 48000 * 2 * 20 / 1000;
718 | let input = [0_i16; STEREO_20MS];
719 | let mut output = [0; 256];
720 |
721 | let len = stereo_encoder.encode(&input, &mut output).unwrap();
722 | assert_eq!(&output[..len], &[252, 255, 254]);
723 |
724 | let mono_encoder =
725 | Encoder::new(SampleRate::Hz48000, Channels::Mono, Application::Audio).unwrap();
726 |
727 | // 48000Hz * 1 channel * 20 ms / 1000
728 | const MONO_20MS: usize = 48000 * 1 * 20 / 1000;
729 | let input = [0_i16; MONO_20MS];
730 | let mut output = [0; 256];
731 |
732 | let len = mono_encoder.encode(&input, &mut output).unwrap();
733 | assert_eq!(&output[..len], &[248, 255, 254]);
734 | }
735 |
736 | #[test]
737 | fn set_max_bandwidth() {
738 | let mut encoder =
739 | Encoder::new(SampleRate::Hz48000, Channels::Stereo, Application::Audio).unwrap();
740 |
741 | assert_matches!(encoder.max_bandwidth(), Ok(Bandwidth::Fullband));
742 |
743 | let bandwidth_to_set = Bandwidth::Narrowband;
744 |
745 | encoder.set_max_bandwidth(bandwidth_to_set).unwrap();
746 | let bandwidth_got = encoder.max_bandwidth().unwrap();
747 |
748 | assert_eq!(bandwidth_to_set, bandwidth_got);
749 | }
750 |
751 | #[test]
752 | fn get_set_complexity() {
753 | let mut encoder =
754 | Encoder::new(SampleRate::Hz48000, Channels::Stereo, Application::Audio).unwrap();
755 |
756 | encoder
757 | .set_complexity(10)
758 | .expect("Could not set complexity to 10.");
759 | assert_matches!(encoder.complexity(), Ok(10));
760 |
761 | encoder
762 | .set_complexity(0)
763 | .expect("Could not set complexity to 0.");
764 | assert_matches!(encoder.complexity(), Ok(0));
765 |
766 | assert_matches!(
767 | encoder.set_complexity(11),
768 | Err(Error::Opus(ErrorCode::BadArgument))
769 | );
770 | }
771 |
772 | #[test]
773 | fn set_get_application() {
774 | let application_to_set = Application::Audio;
775 | let mut encoder =
776 | Encoder::new(SampleRate::Hz48000, Channels::Stereo, application_to_set).unwrap();
777 | let current_application = encoder.application().unwrap();
778 | assert_eq!(current_application, application_to_set);
779 |
780 | let application_to_set = Application::Voip;
781 | encoder.set_application(application_to_set).unwrap();
782 | let current_application = encoder.application().unwrap();
783 | assert_eq!(current_application, application_to_set);
784 |
785 | let application_to_set = Application::LowDelay;
786 | encoder.set_application(application_to_set).unwrap();
787 | let current_application = encoder.application().unwrap();
788 | assert_eq!(current_application, application_to_set);
789 |
790 | let application_to_set = Application::Audio;
791 | encoder.set_application(application_to_set).unwrap();
792 | let current_application = encoder.application().unwrap();
793 | assert_eq!(current_application, application_to_set);
794 | }
795 |
796 | #[test]
797 | fn set_get_bitrate() {
798 | let mut encoder =
799 | Encoder::new(SampleRate::Hz48000, Channels::Mono, Application::Audio).unwrap();
800 |
801 | let bitrate = 512000;
802 |
803 | encoder
804 | .set_bitrate(Bitrate::BitsPerSecond(bitrate))
805 | .expect("Could not set bitrate to 512000.");
806 |
807 | assert_matches!(encoder.bitrate(), _bitrate);
808 | }
809 |
810 | #[test]
811 | fn set_get_dtx() {
812 | let mut encoder =
813 | Encoder::new(SampleRate::Hz48000, Channels::Stereo, Application::Audio).unwrap();
814 |
815 | assert_matches!(encoder.dtx(), Ok(false));
816 |
817 | encoder.enable_dtx().expect("Could not set dtx to true.");
818 | assert_matches!(encoder.dtx(), Ok(true));
819 |
820 | encoder.disable_dtx().expect("Could not set dtx to false.");
821 | assert_matches!(encoder.dtx(), Ok(false));
822 | }
823 |
824 | #[test]
825 | fn set_get_lsb_depth() {
826 | let mut encoder =
827 | Encoder::new(SampleRate::Hz48000, Channels::Stereo, Application::Audio).unwrap();
828 |
829 | assert_matches!(encoder.lsb_depth(), Ok(24));
830 |
831 | encoder
832 | .set_lsb_depth(16)
833 | .expect("Could not set lsb depth to 16.");
834 | assert_matches!(encoder.lsb_depth(), Ok(16));
835 |
836 | encoder
837 | .set_lsb_depth(8)
838 | .expect("Could not set lsb depth to 8.");
839 | assert_matches!(encoder.lsb_depth(), Ok(8));
840 |
841 | assert_matches!(
842 | encoder.set_lsb_depth(7),
843 | Err(Error::Opus(ErrorCode::BadArgument))
844 | );
845 |
846 | assert_matches!(
847 | encoder.set_lsb_depth(25),
848 | Err(Error::Opus(ErrorCode::BadArgument))
849 | );
850 |
851 | assert_matches!(encoder.lsb_depth(), Ok(8));
852 | }
853 | }
854 |
--------------------------------------------------------------------------------
/src/error.rs:
--------------------------------------------------------------------------------
1 | use crate::ffi;
2 | use std::{
3 | error::Error as StdError,
4 | fmt::{Display, Formatter, Result as FmtResult},
5 | };
6 |
7 | pub type Result = std::result::Result;
8 |
9 | #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
10 | pub enum Error {
11 | /// A value failed to match a documented [`Application`].
12 | ///
13 | /// [`Application`]: ../enum.Application.html
14 | InvalidApplication,
15 | /// A value failed to match a documented [`Bandwidth`].
16 | ///
17 | /// [`Bandwidth`]: ../enum.Application.html
18 | InvalidBandwidth(i32),
19 | /// A value failed to match a documented [`Bitrate`],
20 | /// negative values are invalid.
21 | ///
22 | /// [`Bitrate`]: ../enum.Bitrate.html
23 | InvalidBitrate(i32),
24 | /// A value failed to match a documented [`Signal`].
25 | ///
26 | /// [`Signal`]: ../enum.Signal.html
27 | InvalidSignal(i32),
28 | /// Complexity was lower than 1 or higher than 10.
29 | InvalidComplexity(i32),
30 | /// A value failed to match a documented [`SampleRate`].
31 | ///
32 | /// [`SampleRate`]: ../enum.SampleRate.html
33 | InvalidSampleRate(i32),
34 | /// A value failed to match a documented [`Channels`].
35 | ///
36 | /// [`Channels`]: ../enum.Channels.html
37 | InvalidChannels(i32),
38 | /// An error returned from Opus containing an [`ErrorCode`] describing
39 | /// the cause.
40 | Opus(ErrorCode),
41 | /// Opus is not operating with empty packets.
42 | EmptyPacket,
43 | /// Opus' maximum `Vec` or slice length of `std::i32::MAX` has been
44 | /// exceeded.
45 | SignalsTooLarge,
46 | /// Opus' maximum `Vec` or slice length of `std::i32::MAX` has been
47 | /// exceeded.
48 | PacketTooLarge,
49 | /// A `Vec` representing a mapping exceeded the expected value.
50 | MappingExpectedLen(usize),
51 | }
52 |
53 | impl StdError for Error {
54 | fn source(&self) -> Option<&(dyn StdError + 'static)> {
55 | match self {
56 | Error::Opus(err) => Some(err),
57 | _ => None,
58 | }
59 | }
60 | }
61 |
62 | impl Display for Error {
63 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
64 | match self {
65 | Error::InvalidApplication => f.write_str("Invalid Application"),
66 | Error::InvalidBandwidth(bandwidth) => write!(f, "Invalid Bandwitdh: {}", bandwidth),
67 | Error::InvalidSignal(signal) => write!(f, "Invalid Signal: {}", signal),
68 | Error::InvalidComplexity(complexity) => write!(f, "Invalid Complexity: {}", complexity),
69 | Error::InvalidSampleRate(rate) => write!(f, "Invalid Sample Rate: {}", rate),
70 | Error::InvalidChannels(channels) => write!(f, "Invalid Channels: {}", channels),
71 | Error::Opus(error_code) => write!(f, "{}", error_code),
72 | Error::EmptyPacket => f.write_str("Passed packet contained no elements"),
73 | Error::SignalsTooLarge => f.write_str("Signals' length exceeded `i32::MAX`"),
74 | Error::PacketTooLarge => f.write_str("Packet's length exceeded `i32::MAX`"),
75 | Error::InvalidBitrate(rate) => write!(f, "Invalid Bitrate: {}", rate),
76 | Error::MappingExpectedLen(len) => write!(f, "Wrong channel length, expected: {}", len),
77 | }
78 | }
79 | }
80 |
81 | impl From for Error {
82 | fn from(error_code: ErrorCode) -> Error {
83 | Error::Opus(error_code)
84 | }
85 | }
86 |
87 | #[repr(i32)]
88 | #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
89 | pub enum ErrorCode {
90 | BadArgument = ffi::OPUS_BAD_ARG,
91 | BufferTooSmall = ffi::OPUS_BUFFER_TOO_SMALL,
92 | InternalError = ffi::OPUS_INTERNAL_ERROR,
93 | InvalidPacket = ffi::OPUS_INVALID_PACKET,
94 | Unimplemented = ffi::OPUS_UNIMPLEMENTED,
95 | InvalidState = ffi::OPUS_INVALID_STATE,
96 | AllocFail = ffi::OPUS_ALLOC_FAIL,
97 | /// Occurs when Opus sends an error value that is not documented.
98 | /// `0` is unrelated to Opus and just a mere marker by this crate to
99 | /// differentiate between Opus' errors (all of them are negative).
100 | Unknown = 0,
101 | }
102 |
103 | impl Display for ErrorCode {
104 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
105 | let s = match self {
106 | ErrorCode::BadArgument => "Passed argument violated Opus' specified requirements",
107 | ErrorCode::BufferTooSmall => "Passed buffer was too small",
108 | ErrorCode::InternalError => "Internal error inside Opus occured",
109 | ErrorCode::InvalidPacket => "Opus received a packet violating requirements",
110 | ErrorCode::Unimplemented => "Unimplemented code branch was attempted to be executed",
111 | ErrorCode::InvalidState => "Opus-type instance is in an invalid state",
112 | ErrorCode::AllocFail => "Opus was unable to allocate memory",
113 | ErrorCode::Unknown => {
114 | "Opus returned a non-negative error, this might be a Audiopus or Opus bug"
115 | }
116 | };
117 |
118 | write!(f, "{}", s)
119 | }
120 | }
121 |
122 | impl StdError for ErrorCode {}
123 |
124 | impl From for ErrorCode {
125 | fn from(number: i32) -> ErrorCode {
126 | match number {
127 | ffi::OPUS_BAD_ARG => ErrorCode::BadArgument,
128 | ffi::OPUS_BUFFER_TOO_SMALL => ErrorCode::BufferTooSmall,
129 | ffi::OPUS_INTERNAL_ERROR => ErrorCode::InternalError,
130 | ffi::OPUS_INVALID_PACKET => ErrorCode::InvalidPacket,
131 | ffi::OPUS_UNIMPLEMENTED => ErrorCode::Unimplemented,
132 | ffi::OPUS_INVALID_STATE => ErrorCode::InvalidState,
133 | ffi::OPUS_ALLOC_FAIL => ErrorCode::AllocFail,
134 | _ => ErrorCode::Unknown,
135 | }
136 | }
137 | }
138 |
139 | /// Checks if the `ffi_return_value` is documented by Opus.
140 | /// Returns `Error` if value is negative.
141 | pub fn try_map_opus_error(ffi_return_value: i32) -> Result {
142 | match ffi_return_value {
143 | v if v < 0 => Err(Error::from(ErrorCode::from(v))),
144 | _ => Ok(ffi_return_value),
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! Audiopus is a high level abstraction over the Opus library.
2 | //!
3 | //! This crate uses [`TryFrom`] to prevent the incorrect use of Opus.
4 | //! The API accepts newtypes such as [`Packet`], [`MutPacket`],
5 | //! and [`MutSignals`]. The implementation of [`TryFrom`] ensures Opus'
6 | //! restrictions will be kept in mind by checking these on conversion.
7 | //! Without these restrictions, crashes may occur among others, because Opus
8 | //! does not know any types larger than `i32` and does not expect empty packets.
9 | //!
10 | //! [`Packet`], [`MutPacket`], [`MutSignals`] implement conversions from
11 | //! `&Vec[T]` and `&[T]`, they borrow only. The `Mut` notes when the newtype
12 | //! borrows mutably.
13 | //!
14 | //! A [`Packet`] references an underlying buffer of type `&[u8]`, it cannot be
15 | //! empty and not longer than [`std::i32::MAX`].
16 | //!
17 | //! Same goes for [`MutPacket`], except the type mutably borrows the buffer thus
18 | //! the length may change after passing it to Opus. Hence the length of this
19 | //! type will be returned as [`Result`].
20 | //!
21 | //! [`MutSignals`] wraps around a generic buffer and represents Opus' output.
22 | //! E.g. when encoding, Opus will fill the buffer with the encoded data.
23 | //!
24 | //! Audiopus aims to never panic or crash when interacting with Opus,
25 | //! if either occurs, consider this a bug and please report it on the GitHub!
26 | //!
27 | //! [`Packet`]: crate::packet::Packet
28 | //! [`MutPacket`]: crate::packet::MutPacket
29 | //! [`MutSignals`]: crate::MutSignals
30 | //! [`TryFrom`]: std::convert::TryFrom
31 | //! [`Result`]: std::result::Result
32 | //!
33 | #![deny(rust_2018_idioms)]
34 | #![deny(clippy::all)]
35 | #![deny(clippy::pedantic)]
36 | #![deny(clippy::nursery)]
37 | #![deny(clippy::cargo)]
38 | // TODO: Document all public items.
39 | // #![deny(missing_docs)]
40 |
41 | pub mod coder;
42 | pub mod error;
43 | pub mod packet;
44 | pub mod repacketizer;
45 | pub mod softclip;
46 |
47 | use std::{
48 | convert::{TryFrom, TryInto},
49 | ffi::CStr,
50 | };
51 |
52 | pub use crate::error::{Error, ErrorCode, Result};
53 | pub use audiopus_sys as ffi;
54 |
55 | #[repr(i32)]
56 | #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
57 | pub enum Signal {
58 | Auto = ffi::OPUS_AUTO,
59 | Voice = ffi::OPUS_SIGNAL_VOICE,
60 | Music = ffi::OPUS_SIGNAL_MUSIC,
61 | }
62 |
63 | impl TryFrom for Signal {
64 | type Error = Error;
65 |
66 | fn try_from(value: i32) -> Result {
67 | Ok(match value {
68 | ffi::OPUS_AUTO => Signal::Auto,
69 | ffi::OPUS_SIGNAL_VOICE => Signal::Voice,
70 | ffi::OPUS_SIGNAL_MUSIC => Signal::Music,
71 | _ => return Err(Error::InvalidSignal(value)),
72 | })
73 | }
74 | }
75 |
76 | #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
77 | pub enum Bitrate {
78 | /// Explicit bitrate choice (in bits/second).
79 | BitsPerSecond(i32),
80 | /// Maximum bitrate allowed (up to maximum number of bytes for the packet).
81 | Max,
82 | /// Default bitrate decided by the encoder (not recommended).
83 | Auto,
84 | }
85 |
86 | impl From for i32 {
87 | fn from(bitrate: Bitrate) -> i32 {
88 | match bitrate {
89 | Bitrate::Auto => ffi::OPUS_AUTO,
90 | Bitrate::Max => ffi::OPUS_BITRATE_MAX,
91 | Bitrate::BitsPerSecond(bits) => bits,
92 | }
93 | }
94 | }
95 |
96 | impl TryFrom for Bitrate {
97 | type Error = Error;
98 |
99 | fn try_from(value: i32) -> Result {
100 | Ok(match value {
101 | ffi::OPUS_AUTO => Bitrate::Auto,
102 | ffi::OPUS_BITRATE_MAX => Bitrate::Max,
103 | x if x.is_positive() => Bitrate::BitsPerSecond(x),
104 | _ => return Err(Error::InvalidBandwidth(value)),
105 | })
106 | }
107 | }
108 |
109 | /// Represents possible sample rates Opus can use.
110 | /// Values represent Hertz.
111 | #[repr(i32)]
112 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
113 | pub enum SampleRate {
114 | Hz8000 = 8000,
115 | Hz12000 = 12000,
116 | Hz16000 = 16000,
117 | Hz24000 = 24000,
118 | Hz48000 = 48000,
119 | }
120 |
121 | impl TryFrom for SampleRate {
122 | type Error = Error;
123 |
124 | /// Fails if a number does not map a documented Opus sample rate.
125 | fn try_from(value: i32) -> Result {
126 | Ok(match value {
127 | 8000 => SampleRate::Hz8000,
128 | 12000 => SampleRate::Hz12000,
129 | 16000 => SampleRate::Hz16000,
130 | 24000 => SampleRate::Hz24000,
131 | 48000 => SampleRate::Hz48000,
132 | _ => return Err(Error::InvalidSampleRate(value)),
133 | })
134 | }
135 | }
136 |
137 | /// Represents possible application-types for Opus.
138 | #[repr(i32)]
139 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
140 | pub enum Application {
141 | /// Best for most VoIP/videoconference applications where listening quality
142 | /// and intelligibility matter most.
143 | Voip = ffi::OPUS_APPLICATION_VOIP,
144 | /// Best for broadcast/high-fidelity application where the decoded audio
145 | /// should be as close as possible to the input.
146 | Audio = ffi::OPUS_APPLICATION_AUDIO,
147 | /// Only use when lowest-achievable latency is what matters most.
148 | LowDelay = ffi::OPUS_APPLICATION_RESTRICTED_LOWDELAY,
149 | }
150 |
151 | impl TryFrom for Application {
152 | type Error = Error;
153 |
154 | /// Fails if a value does not match Opus' specified application-value.
155 | fn try_from(value: i32) -> Result {
156 | Ok(match value {
157 | ffi::OPUS_APPLICATION_VOIP => Application::Voip,
158 | ffi::OPUS_APPLICATION_AUDIO => Application::Audio,
159 | ffi::OPUS_APPLICATION_RESTRICTED_LOWDELAY => Application::LowDelay,
160 | _ => return Err(Error::InvalidApplication),
161 | })
162 | }
163 | }
164 |
165 | /// Represents possible audio channels Opus can use.
166 | #[repr(i32)]
167 | #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
168 | pub enum Channels {
169 | /// Not supported when constructing encoders or decoders.
170 | Auto = ffi::OPUS_AUTO,
171 | Mono = 1,
172 | Stereo = 2,
173 | }
174 |
175 | impl Channels {
176 | pub fn is_mono(self) -> bool {
177 | if let Channels::Mono = self {
178 | return true;
179 | }
180 |
181 | false
182 | }
183 |
184 | pub fn is_stereo(self) -> bool {
185 | if let Channels::Stereo = self {
186 | return true;
187 | }
188 |
189 | false
190 | }
191 | }
192 |
193 | impl TryFrom for Channels {
194 | type Error = Error;
195 |
196 | // Fails if a value does not match Opus' specified channel-value.
197 | fn try_from(value: i32) -> Result {
198 | Ok(match value {
199 | ffi::OPUS_AUTO => Channels::Auto,
200 | 1 => Channels::Mono,
201 | 2 => Channels::Stereo,
202 | _ => return Err(Error::InvalidChannels(value)),
203 | })
204 | }
205 | }
206 |
207 | impl From for i32 {
208 | fn from(channels: Channels) -> i32 {
209 | channels as i32
210 | }
211 | }
212 |
213 | /// Represents possible bandwidths of an Opus-stream.
214 | #[repr(i32)]
215 | #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
216 | pub enum Bandwidth {
217 | /// Pick the bandwidth automatically.
218 | Auto = ffi::OPUS_AUTO,
219 | /// A 4kHz bandwidth.
220 | Narrowband = ffi::OPUS_BANDWIDTH_NARROWBAND,
221 | /// A 6kHz bandwidth.
222 | Mediumband = ffi::OPUS_BANDWIDTH_MEDIUMBAND,
223 | /// A 8kHz bandwidth.
224 | Wideband = ffi::OPUS_BANDWIDTH_WIDEBAND,
225 | /// A 12kHz bandwidth.
226 | Superwideband = ffi::OPUS_BANDWIDTH_SUPERWIDEBAND,
227 | /// A 20kHz bandwidth.
228 | Fullband = ffi::OPUS_BANDWIDTH_FULLBAND,
229 | }
230 |
231 | impl TryFrom for Bandwidth {
232 | type Error = Error;
233 |
234 | // Fails if a value does not match Opus' specified bandwidth-value.
235 | fn try_from(value: i32) -> Result {
236 | Ok(match value {
237 | ffi::OPUS_AUTO => Bandwidth::Auto,
238 | ffi::OPUS_BANDWIDTH_NARROWBAND => Bandwidth::Narrowband,
239 | ffi::OPUS_BANDWIDTH_MEDIUMBAND => Bandwidth::Mediumband,
240 | ffi::OPUS_BANDWIDTH_WIDEBAND => Bandwidth::Wideband,
241 | ffi::OPUS_BANDWIDTH_SUPERWIDEBAND => Bandwidth::Superwideband,
242 | ffi::OPUS_BANDWIDTH_FULLBAND => Bandwidth::Fullband,
243 | _ => return Err(Error::InvalidBandwidth(value)),
244 | })
245 | }
246 | }
247 |
248 | /// A newtype wrapping around a mutable buffer. They represent mutably borrowed
249 | /// arguments that will be filled by Opus.
250 | /// E.g. you pass this to an encode-method and Opus encodes data into the
251 | /// underlying buffer.
252 | ///
253 | /// **Info**:
254 | /// This type is only verifying that Opus' requirement are not violated.
255 | #[derive(Debug)]
256 | pub struct MutSignals<'a, T>(&'a mut [T]);
257 |
258 | impl<'a, T> TryFrom<&'a mut [T]> for MutSignals<'a, T> {
259 | type Error = Error;
260 |
261 | fn try_from(value: &'a mut [T]) -> Result {
262 | if value.len() > std::i32::MAX as usize {
263 | return Err(Error::SignalsTooLarge);
264 | }
265 |
266 | Ok(Self(value))
267 | }
268 | }
269 |
270 | impl<'a, T> TryFrom<&'a mut Vec> for MutSignals<'a, T> {
271 | type Error = Error;
272 |
273 | fn try_from(value: &'a mut Vec) -> Result {
274 | value.as_mut_slice().try_into()
275 | }
276 | }
277 |
278 | impl<'a, T> MutSignals<'a, T> {
279 | pub fn as_mut_ptr(&mut self) -> *mut T {
280 | self.0.as_mut_ptr()
281 | }
282 |
283 | /// Due to checking the length during construction of this newtype wrapping
284 | /// around a immutably borrowed buffer, we can safely cast `usize` to `i32`
285 | /// without worrying about `usize` being too large for `i32`.
286 | pub fn i32_len(&self) -> i32 {
287 | self.0.len() as i32
288 | }
289 | }
290 |
291 | /// Gets the libopus version string.
292 | ///
293 | /// Applications may look for the substring "-fixed" in the version string to
294 | /// determine whether they have a fixed-point or floating-point build at runtime.
295 | pub fn version() -> &'static str {
296 | // The pointer given from the `opus_get_version_string` function will be valid
297 | // therefore we can create a `CStr` from this pointer.
298 | unsafe { CStr::from_ptr(ffi::opus_get_version_string()) }
299 | .to_str()
300 | .unwrap()
301 | }
302 |
303 | #[cfg(test)]
304 | mod tests {
305 | use super::{ffi, version, Application, Error, Signal, TryFrom};
306 | use matches::assert_matches;
307 |
308 | #[test]
309 | fn try_get_version() {
310 | // We can't actually check the contents of the string, as it will change when the version
311 | // changes. By just calling the function we can ensure that the CStr conversion succeeds.
312 | version();
313 | }
314 |
315 | #[test]
316 | fn signal_try_from() {
317 | assert_matches!(Signal::try_from(ffi::OPUS_SIGNAL_MUSIC), Ok(Signal::Music));
318 | assert_matches!(Signal::try_from(ffi::OPUS_SIGNAL_VOICE), Ok(Signal::Voice));
319 | assert_matches!(Signal::try_from(ffi::OPUS_AUTO), Ok(Signal::Auto));
320 | assert_matches!(Signal::try_from(0), Err(Error::InvalidSignal(0)));
321 | }
322 |
323 | #[test]
324 | fn application_try_from() {
325 | assert_matches!(
326 | Application::try_from(ffi::OPUS_APPLICATION_AUDIO),
327 | Ok(Application::Audio)
328 | );
329 | assert_matches!(
330 | Application::try_from(ffi::OPUS_APPLICATION_VOIP),
331 | Ok(Application::Voip)
332 | );
333 | assert_matches!(
334 | Application::try_from(ffi::OPUS_APPLICATION_RESTRICTED_LOWDELAY),
335 | Ok(Application::LowDelay)
336 | );
337 | assert_matches!(Application::try_from(11), Err(Error::InvalidApplication));
338 | }
339 | }
340 |
--------------------------------------------------------------------------------
/src/packet.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | error::try_map_opus_error, ffi, Bandwidth, Channels, Error, Result, SampleRate, TryFrom,
3 | TryInto,
4 | };
5 |
6 | fn packet_len_check(packet_buffer: &[u8]) -> Result {
7 | match packet_buffer {
8 | // non-empty guarantee:
9 | x if x.is_empty() => Err(Error::EmptyPacket),
10 | // limited size guarantee:
11 | _ if packet_buffer.len() > std::i32::MAX as usize => Err(Error::PacketTooLarge),
12 | _ => Ok(packet_buffer.len() as i32),
13 | }
14 | }
15 |
16 | /// A newtype around `&[u8]` to guarantee:
17 | /// - Minimum one element: A packet cannot be empty.
18 | /// - Limited size: A packet's length may not exceed `std::i32::MAX`.
19 | #[derive(Debug)]
20 | pub struct Packet<'a>(&'a [u8]);
21 |
22 | impl<'a> Packet<'a> {
23 | pub fn as_ptr(&self) -> *const u8 {
24 | self.0.as_ptr()
25 | }
26 |
27 | /// The underlying type is immutably borrowed and has been verified upon
28 | /// construction of `Packet`, thus we know casting `usize` will fit
29 | /// inside `i32`.
30 | pub fn i32_len(&self) -> i32 {
31 | self.0.len() as i32
32 | }
33 | }
34 |
35 | impl<'a> TryFrom<&'a Vec> for Packet<'a> {
36 | type Error = Error;
37 |
38 | fn try_from(value: &'a Vec) -> Result {
39 | value.as_slice().try_into()
40 | }
41 | }
42 |
43 | impl<'a> TryFrom<&'a [u8]> for Packet<'a> {
44 | type Error = Error;
45 |
46 | fn try_from(value: &'a [u8]) -> Result> {
47 | packet_len_check(value).map(|_| Self(value))
48 | }
49 | }
50 |
51 | /// A newtype around `&mut [u8]` to guarantee that accessing length on the
52 | /// underlying buffer is checked each time.
53 | #[derive(Debug)]
54 | pub struct MutPacket<'a>(&'a mut [u8]);
55 |
56 | impl<'a> MutPacket<'a> {
57 | pub fn as_mut_ptr(&mut self) -> *mut u8 {
58 | self.0.as_mut_ptr()
59 | }
60 |
61 | /// Checks if the underlying buffer meets requirements.
62 | pub fn i32_len(&self) -> Result {
63 | packet_len_check(&self.0)
64 | }
65 | }
66 |
67 | impl<'a> TryFrom<&'a mut Vec> for MutPacket<'a> {
68 | type Error = Error;
69 |
70 | fn try_from(value: &'a mut Vec) -> Result {
71 | value.as_mut_slice().try_into()
72 | }
73 | }
74 |
75 | impl<'a> TryFrom<&'a mut [u8]> for MutPacket<'a> {
76 | type Error = Error;
77 |
78 | fn try_from(value: &'a mut [u8]) -> Result {
79 | packet_len_check(value).map(move |_| Self(value))
80 | }
81 | }
82 |
83 | /// Gets bandwidth of an Opus `packet`.
84 | ///
85 | /// **Errors**:
86 | /// Empty `packet` will return `Error::EmptyPacket`.
87 | pub fn bandwidth(packet: Packet<'_>) -> Result {
88 | unsafe { ffi::opus_packet_get_bandwidth(packet.as_ptr()) }.try_into()
89 | }
90 |
91 | /// Gets number of samples per frame of an Opus `packet`.
92 | ///
93 | /// **Errors**:
94 | /// Empty `packet` will return `Error::EmptyPacket`.
95 | pub fn samples_per_frame(packet: Packet<'_>, sample_rate: SampleRate) -> Result {
96 | unsafe {
97 | Ok(ffi::opus_packet_get_samples_per_frame(packet.as_ptr(), sample_rate as i32) as usize)
98 | }
99 | }
100 |
101 | /// Gets number of samples in an Opus `packet`.
102 | ///
103 | /// **Errors**:
104 | /// Empty `packet` will return `Error::EmptyPacket`.
105 | pub fn nb_samples(packet: Packet<'_>, sample_rate: SampleRate) -> Result {
106 | unsafe {
107 | try_map_opus_error(ffi::opus_packet_get_nb_samples(
108 | packet.as_ptr(),
109 | packet.i32_len(),
110 | sample_rate as i32,
111 | ))
112 | .map(|n| n as usize)
113 | }
114 | }
115 |
116 | /// Gets number of channels in an Opus `packet`.
117 | ///
118 | /// **Errors**:
119 | /// Empty `packet` will return `Error::EmptyPacket`.
120 | pub fn nb_channels(packet: Packet<'_>) -> Result {
121 | unsafe {
122 | Ok(Channels::try_from(ffi::opus_packet_get_nb_channels(
123 | packet.as_ptr(),
124 | ))?)
125 | }
126 | }
127 |
128 | /// Gets number of frames in an Opus `packet`.
129 | ///
130 | /// **Errors**:
131 | /// Empty `packet` will return [`Error::EmptyPacket`].
132 | pub fn nb_frames(packet: Packet<'_>) -> Result {
133 | unsafe {
134 | try_map_opus_error(ffi::opus_packet_get_nb_frames(
135 | packet.as_ptr(),
136 | packet.i32_len(),
137 | ))
138 | .map(|n| n as usize)
139 | }
140 | }
141 |
142 | #[cfg(test)]
143 | mod tests {
144 | use super::bandwidth;
145 | use crate::{packet::Packet, Bandwidth, Error};
146 | use matches::assert_matches;
147 |
148 | #[test]
149 | /// We verify the `TryFrom`-impl for `Packet` by creating and then
150 | /// converting from `Vec`s that meet and violate the contract.
151 | fn packet_bandwidth() {
152 | use std::convert::TryFrom;
153 |
154 | let empty_packet = vec![];
155 | let empty_packet_bandwidth = Packet::try_from(&empty_packet);
156 | assert_matches!(empty_packet_bandwidth, Err(Error::EmptyPacket));
157 |
158 | let narrow_packet = vec![1, 2, 3];
159 | let narrow_packet_bandwidth = bandwidth(Packet::try_from(&narrow_packet).unwrap());
160 | assert_matches!(narrow_packet_bandwidth, Ok(Bandwidth::Narrowband));
161 |
162 | let mediumband_packet = vec![50];
163 | let mediumband_packet_bandwidth = bandwidth(Packet::try_from(&mediumband_packet).unwrap());
164 | assert_matches!(mediumband_packet_bandwidth, Ok(Bandwidth::Mediumband));
165 |
166 | let wideband_packet = vec![80];
167 | let wideband_packet_bandwidth = bandwidth(Packet::try_from(&wideband_packet).unwrap());
168 | assert_matches!(wideband_packet_bandwidth, Ok(Bandwidth::Wideband));
169 |
170 | let superwideband_packet = vec![200];
171 | let superwideband_bandwidth = bandwidth(Packet::try_from(&superwideband_packet).unwrap());
172 | assert_matches!(superwideband_bandwidth, Ok(Bandwidth::Superwideband));
173 |
174 | let fullband_packet = vec![255];
175 | let fullband_bandwidth = bandwidth(Packet::try_from(&fullband_packet).unwrap());
176 | assert_matches!(fullband_bandwidth, Ok(Bandwidth::Fullband));
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/src/repacketizer.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | error::try_map_opus_error,
3 | ffi,
4 | packet::{MutPacket, Packet},
5 | Result,
6 | };
7 |
8 | /// Returns Opus' internal `OpusRepacketizer`'s size in bytes.
9 | pub fn repacketizer_size() -> usize {
10 | unsafe { ffi::opus_repacketizer_get_size() as usize }
11 | }
12 |
13 | pub fn multistream_packet_pad(
14 | mut data: MutPacket<'_>,
15 | new_len: usize,
16 | nb_streams: usize,
17 | ) -> Result<()> {
18 | try_map_opus_error(unsafe {
19 | ffi::opus_multistream_packet_pad(
20 | data.as_mut_ptr(),
21 | data.i32_len()?,
22 | new_len as i32,
23 | nb_streams as i32,
24 | )
25 | })
26 | .map(|_| ())
27 | }
28 |
29 | pub fn multistream_packet_unpad(mut data: MutPacket<'_>, nb_streams: usize) -> Result<()> {
30 | try_map_opus_error(unsafe {
31 | ffi::opus_multistream_packet_unpad(data.as_mut_ptr(), data.i32_len()?, nb_streams as i32)
32 | })
33 | .map(|_| ())
34 | }
35 |
36 | pub fn packet_pad(mut data: MutPacket<'_>, new_len: i32) -> Result<()> {
37 | try_map_opus_error(unsafe { ffi::opus_packet_pad(data.as_mut_ptr(), data.i32_len()?, new_len) })
38 | .map(|_| ())
39 | }
40 |
41 | pub fn packet_unpad(mut data: MutPacket<'_>) -> Result<()> {
42 | try_map_opus_error(unsafe { ffi::opus_packet_unpad(data.as_mut_ptr(), data.i32_len()?) })
43 | .map(|_| ())
44 | }
45 |
46 | #[derive(Debug)]
47 | pub struct Repacketizer {
48 | pointer: *mut ffi::OpusRepacketizer,
49 | }
50 |
51 | impl Default for Repacketizer {
52 | fn default() -> Self {
53 | Self::new()
54 | }
55 | }
56 |
57 | impl Drop for Repacketizer {
58 | /// We have to ensure that the resource our wrapping Opus-struct is pointing
59 | /// to is deallocated properly.
60 | fn drop(&mut self) {
61 | unsafe { ffi::opus_repacketizer_destroy(self.pointer) }
62 | }
63 | }
64 |
65 | impl Repacketizer {
66 | pub fn new() -> Self {
67 | let pointer = unsafe { ffi::opus_repacketizer_create() };
68 |
69 | Self { pointer }
70 | }
71 |
72 | pub fn nb_frames(&self) -> usize {
73 | unsafe { ffi::opus_repacketizer_get_nb_frames(self.pointer) as usize }
74 | }
75 |
76 | pub fn repacketizer_out(&self, mut data_out: MutPacket<'_>, max_len: i32) -> Result<()> {
77 | try_map_opus_error(unsafe {
78 | ffi::opus_repacketizer_out(self.pointer, data_out.as_mut_ptr(), max_len)
79 | })
80 | .map(|_| ())
81 | }
82 |
83 | pub fn repacketizer_out_range(
84 | &self,
85 | begin: i32,
86 | end: i32,
87 | mut data_out: MutPacket<'_>,
88 | max_len: i32,
89 | ) -> Result<()> {
90 | try_map_opus_error(unsafe {
91 | ffi::opus_repacketizer_out_range(
92 | self.pointer,
93 | begin,
94 | end,
95 | data_out.as_mut_ptr(),
96 | max_len,
97 | )
98 | })
99 | .map(|_| ())
100 | }
101 |
102 | pub fn repacketizer_cat(&self, data: Packet<'_>) -> Result<()> {
103 | try_map_opus_error(unsafe {
104 | ffi::opus_repacketizer_cat(self.pointer, data.as_ptr(), data.i32_len())
105 | })
106 | .map(|_| ())
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/softclip.rs:
--------------------------------------------------------------------------------
1 | use crate::{ffi, Channels, MutSignals, Result};
2 |
3 | #[derive(Clone, Debug)]
4 | pub struct SoftClip {
5 | channels: Channels,
6 | memory: [f32; 2],
7 | }
8 |
9 | impl SoftClip {
10 | pub fn new(channels: Channels) -> Self {
11 | Self {
12 | channels,
13 | memory: [0.0; 2],
14 | }
15 | }
16 |
17 | /// Opus applies soft-clipping to bring a f32 signal within the
18 | /// [-1,1] range.
19 | pub fn apply(&mut self, mut signals: MutSignals<'_, f32>) -> Result<()> {
20 | unsafe {
21 | ffi::opus_pcm_soft_clip(
22 | signals.as_mut_ptr(),
23 | signals.i32_len() / self.channels as i32,
24 | self.channels as i32,
25 | self.memory.as_mut_ptr(),
26 | )
27 | };
28 |
29 | Ok(())
30 | }
31 | }
32 |
33 | #[cfg(test)]
34 | mod tests {
35 | use super::SoftClip;
36 | use crate::Channels;
37 | use std::convert::TryInto;
38 |
39 | #[test]
40 | fn soft_clip() {
41 | let mut soft_clip = SoftClip::new(Channels::Stereo);
42 |
43 | let mut signals: Vec = vec![];
44 | soft_clip.apply((&mut signals).try_into().unwrap()).unwrap();
45 |
46 | signals.push(5.0);
47 | signals.push(-5000.3453);
48 | soft_clip.apply((&mut signals).try_into().unwrap()).unwrap();
49 |
50 | assert!(signals[0] <= 1.0 && signals[0] >= -1.0);
51 | assert!(signals[1] <= 1.0 && signals[1] >= -1.0);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------