├── .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 | --------------------------------------------------------------------------------