├── .editorconfig ├── .github └── workflows │ └── build.yml ├── .gitignore ├── AUTHORS ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── pulse-ffi ├── Cargo.toml └── src │ ├── ffi_funcs.rs │ ├── ffi_types.rs │ └── lib.rs ├── pulse-rs ├── Cargo.toml └── src │ ├── context.rs │ ├── error.rs │ ├── lib.rs │ ├── mainloop_api.rs │ ├── operation.rs │ ├── proplist.rs │ ├── stream.rs │ ├── threaded_mainloop.rs │ └── util.rs └── src ├── backend ├── context.rs ├── cork_state.rs ├── intern.rs ├── mod.rs └── stream.rs ├── capi.rs └── lib.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 4 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-22.04 8 | continue-on-error: ${{ matrix.experimental }} 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | rust: [stable] 13 | experimental: [false] 14 | include: 15 | - rust: nightly 16 | experimental: true 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | with: 21 | submodules: recursive 22 | 23 | - name: Install Rust 24 | run: rustup toolchain install ${{ matrix.rust }} --profile minimal --component rustfmt,clippy 25 | 26 | - name: Install Dependencies (Linux) 27 | run: sudo apt-get update && sudo apt-get install libpulse-dev 28 | 29 | - name: Check format 30 | shell: bash 31 | run: rustup run ${{ matrix.rust }} cargo fmt --all -- --check 32 | 33 | - name: Clippy 34 | shell: bash 35 | run: rustup run ${{ matrix.rust }} cargo clippy --all -- -D warnings 36 | 37 | - name: Build 38 | shell: bash 39 | run: rustup run ${{ matrix.rust }} cargo build --all 40 | 41 | - name: Test 42 | shell: bash 43 | run: rustup run ${{ matrix.rust }} cargo test --all 44 | 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Dan Glastonbury 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "bitflags" 7 | version = "1.3.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "2.6.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 16 | 17 | [[package]] 18 | name = "cache-padded" 19 | version = "1.3.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "981520c98f422fcc584dc1a95c334e6953900b9106bc47a9839b81790009eb21" 22 | 23 | [[package]] 24 | name = "cc" 25 | version = "1.1.21" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" 28 | dependencies = [ 29 | "shlex", 30 | ] 31 | 32 | [[package]] 33 | name = "cmake" 34 | version = "0.1.51" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a" 37 | dependencies = [ 38 | "cc", 39 | ] 40 | 41 | [[package]] 42 | name = "cubeb-backend" 43 | version = "0.13.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "67361fe9b49b4599e2a230ce322529b6ddd91df14897c872dcede716f8fbca81" 46 | dependencies = [ 47 | "cubeb-core", 48 | ] 49 | 50 | [[package]] 51 | name = "cubeb-core" 52 | version = "0.13.0" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "ac08d314dd1ec6d41d9ccdeec70899c98ed3b89845367000dd6096099481bc73" 55 | dependencies = [ 56 | "bitflags 1.3.2", 57 | "cubeb-sys", 58 | ] 59 | 60 | [[package]] 61 | name = "cubeb-pulse" 62 | version = "0.5.0" 63 | dependencies = [ 64 | "cubeb-backend", 65 | "pulse", 66 | "pulse-ffi", 67 | "ringbuf", 68 | "semver", 69 | ] 70 | 71 | [[package]] 72 | name = "cubeb-sys" 73 | version = "0.13.0" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "26073cd50c7b6ba4272204839f56921557609a0d67e092882cbb903df94cab39" 76 | dependencies = [ 77 | "cmake", 78 | "pkg-config", 79 | ] 80 | 81 | [[package]] 82 | name = "libc" 83 | version = "0.2.159" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" 86 | 87 | [[package]] 88 | name = "pkg-config" 89 | version = "0.3.31" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 92 | 93 | [[package]] 94 | name = "pulse" 95 | version = "0.3.0" 96 | dependencies = [ 97 | "bitflags 2.6.0", 98 | "pulse-ffi", 99 | ] 100 | 101 | [[package]] 102 | name = "pulse-ffi" 103 | version = "0.1.0" 104 | dependencies = [ 105 | "libc", 106 | ] 107 | 108 | [[package]] 109 | name = "ringbuf" 110 | version = "0.2.8" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "f65af18d50f789e74aaf23bbb3f65dcd22a3cb6e029b5bced149f6bd57c5c2a2" 113 | dependencies = [ 114 | "cache-padded", 115 | ] 116 | 117 | [[package]] 118 | name = "semver" 119 | version = "1.0.23" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" 122 | 123 | [[package]] 124 | name = "shlex" 125 | version = "1.3.0" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 128 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cubeb-pulse" 3 | version = "0.5.0" 4 | authors = ["Dan Glastonbury "] 5 | description = "Cubeb backed for PulseAudio written in Rust" 6 | license = "ISC" 7 | 8 | [features] 9 | pulse-dlopen = ["pulse-ffi/dlopen"] 10 | gecko-in-tree = ["cubeb-backend/gecko-in-tree"] 11 | 12 | [lib] 13 | crate-type = ["staticlib", "rlib"] 14 | 15 | [dependencies] 16 | cubeb-backend = "0.13" 17 | pulse-ffi = { path = "pulse-ffi" } 18 | pulse = { path = "pulse-rs" } 19 | semver = "1.0" 20 | ringbuf = "0.2" 21 | 22 | # Workaround for https://github.com/rust-lang/cargo/issues/6745 to allow this 23 | # Cargo.toml file to appear under a subdirectory of a workspace without being in 24 | # that workspace (e.g. in cubeb-rs). 25 | [workspace] 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2011 Mozilla Foundation 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cubeb-pulse-rs 2 | 3 | Implementation of PulseAudio backend for Cubeb written in Rust. 4 | 5 | [![Build Status](https://github.com/mozilla/cubeb-pulse-rs/actions/workflows/build.yml/badge.svg)](https://github.com/mozilla/cubeb-pulse-rs/actions/workflows/build.yml) 6 | -------------------------------------------------------------------------------- /pulse-ffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pulse-ffi" 3 | version = "0.1.0" 4 | authors = ["Dan Glastonbury "] 5 | description = "FFI for libpulse.so supporting static linking and dynamic loading." 6 | license = "ISC" 7 | 8 | [features] 9 | dlopen = [] 10 | 11 | [dependencies] 12 | libc = "^0.2.20" 13 | -------------------------------------------------------------------------------- /pulse-ffi/src/ffi_types.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | 3 | use std::os::raw::{c_char, c_int, c_long, c_uint, c_ulong, c_void}; 4 | 5 | /* automatically generated by rust-bindgen */ 6 | pub const PA_RATE_MAX: u32 = 48000 * 8; 7 | 8 | pub const PA_SAMPLE_U8: c_int = 0; 9 | pub const PA_SAMPLE_ALAW: c_int = 1; 10 | pub const PA_SAMPLE_ULAW: c_int = 2; 11 | pub const PA_SAMPLE_S16LE: c_int = 3; 12 | pub const PA_SAMPLE_S16BE: c_int = 4; 13 | pub const PA_SAMPLE_FLOAT32LE: c_int = 5; 14 | pub const PA_SAMPLE_FLOAT32BE: c_int = 6; 15 | pub const PA_SAMPLE_S32LE: c_int = 7; 16 | pub const PA_SAMPLE_S32BE: c_int = 8; 17 | pub const PA_SAMPLE_S24LE: c_int = 9; 18 | pub const PA_SAMPLE_S24BE: c_int = 10; 19 | pub const PA_SAMPLE_S24_32LE: c_int = 11; 20 | pub const PA_SAMPLE_S24_32BE: c_int = 12; 21 | pub const PA_SAMPLE_MAX: c_int = 13; 22 | pub const PA_SAMPLE_INVALID: c_int = -1; 23 | pub type pa_sample_format_t = c_int; 24 | 25 | #[repr(C)] 26 | #[derive(Copy, Clone, Debug)] 27 | pub struct Struct_pa_sample_spec { 28 | pub format: pa_sample_format_t, 29 | pub rate: u32, 30 | pub channels: u8, 31 | } 32 | 33 | impl ::std::default::Default for Struct_pa_sample_spec { 34 | fn default() -> Self { 35 | unsafe { ::std::mem::zeroed() } 36 | } 37 | } 38 | 39 | pub type pa_sample_spec = Struct_pa_sample_spec; 40 | pub type pa_usec_t = u64; 41 | 42 | // From pulse/timeval.h 43 | pub const PA_USEC_PER_MSEC: pa_usec_t = 1_000; 44 | pub const PA_USEC_PER_SEC: pa_usec_t = 1_000_000; 45 | 46 | pub const PA_CONTEXT_UNCONNECTED: c_int = 0; 47 | pub const PA_CONTEXT_CONNECTING: c_int = 1; 48 | pub const PA_CONTEXT_AUTHORIZING: c_int = 2; 49 | pub const PA_CONTEXT_SETTING_NAME: c_int = 3; 50 | pub const PA_CONTEXT_READY: c_int = 4; 51 | pub const PA_CONTEXT_FAILED: c_int = 5; 52 | pub const PA_CONTEXT_TERMINATED: c_int = 6; 53 | pub type pa_context_state_t = c_int; 54 | 55 | #[allow(non_snake_case)] 56 | pub fn PA_CONTEXT_IS_GOOD(x: pa_context_state_t) -> bool { 57 | x == PA_CONTEXT_CONNECTING 58 | || x == PA_CONTEXT_AUTHORIZING 59 | || x == PA_CONTEXT_SETTING_NAME 60 | || x == PA_CONTEXT_READY 61 | } 62 | 63 | pub const PA_STREAM_UNCONNECTED: c_int = 0; 64 | pub const PA_STREAM_CREATING: c_int = 1; 65 | pub const PA_STREAM_READY: c_int = 2; 66 | pub const PA_STREAM_FAILED: c_int = 3; 67 | pub const PA_STREAM_TERMINATED: c_int = 4; 68 | pub type pa_stream_state_t = c_int; 69 | 70 | #[allow(non_snake_case)] 71 | pub fn PA_STREAM_IS_GOOD(x: pa_stream_state_t) -> bool { 72 | x == PA_STREAM_CREATING || x == PA_STREAM_READY 73 | } 74 | 75 | pub const PA_OPERATION_RUNNING: c_int = 0; 76 | pub const PA_OPERATION_DONE: c_int = 1; 77 | pub const PA_OPERATION_CANCELLED: c_int = 2; 78 | pub type pa_operation_state_t = c_int; 79 | 80 | pub const PA_CONTEXT_NOFLAGS: c_uint = 0; 81 | pub const PA_CONTEXT_NOAUTOSPAWN: c_uint = 1; 82 | pub const PA_CONTEXT_NOFAIL: c_uint = 2; 83 | pub type pa_context_flags_t = c_uint; 84 | 85 | pub const PA_DIRECTION_OUTPUT: c_int = 1; 86 | pub const PA_DIRECTION_INPUT: c_int = 2; 87 | pub type pa_direction_t = c_int; 88 | 89 | pub const PA_DEVICE_TYPE_SINK: c_int = 0; 90 | pub const PA_DEVICE_TYPE_SOURCE: c_int = 1; 91 | pub type pa_device_type_t = c_int; 92 | 93 | pub const PA_STREAM_NODIRECTION: c_int = 0; 94 | pub const PA_STREAM_PLAYBACK: c_int = 1; 95 | pub const PA_STREAM_RECORD: c_int = 2; 96 | pub const PA_STREAM_UPLOAD: c_int = 3; 97 | pub type pa_stream_direction_t = c_int; 98 | 99 | pub const PA_STREAM_NOFLAGS: c_uint = 0x0_0000; 100 | pub const PA_STREAM_START_CORKED: c_uint = 0x0_0001; 101 | pub const PA_STREAM_INTERPOLATE_TIMING: c_uint = 0x0_0002; 102 | pub const PA_STREAM_NOT_MONOTONIC: c_uint = 0x0_0004; 103 | pub const PA_STREAM_AUTO_TIMING_UPDATE: c_uint = 0x0_0008; 104 | pub const PA_STREAM_NO_REMAP_CHANNELS: c_uint = 0x0_0010; 105 | pub const PA_STREAM_NO_REMIX_CHANNELS: c_uint = 0x0_0020; 106 | pub const PA_STREAM_FIX_FORMAT: c_uint = 0x0_0040; 107 | pub const PA_STREAM_FIX_RATE: c_uint = 0x0_0080; 108 | pub const PA_STREAM_FIX_CHANNELS: c_uint = 0x0_0100; 109 | pub const PA_STREAM_DONT_MOVE: c_uint = 0x0_0200; 110 | pub const PA_STREAM_VARIABLE_RATE: c_uint = 0x0_0400; 111 | pub const PA_STREAM_PEAK_DETECT: c_uint = 0x0_0800; 112 | pub const PA_STREAM_START_MUTED: c_uint = 0x0_1000; 113 | pub const PA_STREAM_ADJUST_LATENCY: c_uint = 0x0_2000; 114 | pub const PA_STREAM_EARLY_REQUESTS: c_uint = 0x0_4000; 115 | pub const PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND: c_uint = 0x0_8000; 116 | pub const PA_STREAM_START_UNMUTED: c_uint = 0x1_0000; 117 | pub const PA_STREAM_FAIL_ON_SUSPEND: c_uint = 0x2_0000; 118 | pub const PA_STREAM_RELATIVE_VOLUME: c_uint = 0x4_0000; 119 | pub const PA_STREAM_PASSTHROUGH: c_uint = 0x8_0000; 120 | pub type pa_stream_flags_t = c_uint; 121 | 122 | #[repr(C)] 123 | #[derive(Clone, Copy, Debug)] 124 | pub struct pa_buffer_attr { 125 | pub maxlength: u32, 126 | pub tlength: u32, 127 | pub prebuf: u32, 128 | pub minreq: u32, 129 | pub fragsize: u32, 130 | } 131 | 132 | impl ::std::default::Default for pa_buffer_attr { 133 | fn default() -> Self { 134 | unsafe { ::std::mem::zeroed() } 135 | } 136 | } 137 | 138 | pub const PA_OK: c_int = 0; 139 | pub const PA_ERR_ACCESS: c_int = 1; 140 | pub const PA_ERR_COMMAND: c_int = 2; 141 | pub const PA_ERR_INVALID: c_int = 3; 142 | pub const PA_ERR_EXIST: c_int = 4; 143 | pub const PA_ERR_NOENTITY: c_int = 5; 144 | pub const PA_ERR_CONNECTIONREFUSED: c_int = 6; 145 | pub const PA_ERR_PROTOCOL: c_int = 7; 146 | pub const PA_ERR_TIMEOUT: c_int = 8; 147 | pub const PA_ERR_AUTHKEY: c_int = 9; 148 | pub const PA_ERR_INTERNAL: c_int = 10; 149 | pub const PA_ERR_CONNECTIONTERMINATED: c_int = 11; 150 | pub const PA_ERR_KILLED: c_int = 12; 151 | pub const PA_ERR_INVALIDSERVER: c_int = 13; 152 | pub const PA_ERR_MODINITFAILED: c_int = 14; 153 | pub const PA_ERR_BADSTATE: c_int = 15; 154 | pub const PA_ERR_NODATA: c_int = 16; 155 | pub const PA_ERR_VERSION: c_int = 17; 156 | pub const PA_ERR_TOOLARGE: c_int = 18; 157 | pub const PA_ERR_NOTSUPPORTED: c_int = 19; 158 | pub const PA_ERR_UNKNOWN: c_int = 20; 159 | pub const PA_ERR_NOEXTENSION: c_int = 21; 160 | pub const PA_ERR_OBSOLETE: c_int = 22; 161 | pub const PA_ERR_NOTIMPLEMENTED: c_int = 23; 162 | pub const PA_ERR_FORKED: c_int = 24; 163 | pub const PA_ERR_IO: c_int = 25; 164 | pub const PA_ERR_BUSY: c_int = 26; 165 | pub const PA_ERR_MAX: c_int = 27; 166 | pub type pa_error_code_t = c_int; 167 | 168 | pub const PA_SUBSCRIPTION_MASK_NULL: c_uint = 0x0; 169 | pub const PA_SUBSCRIPTION_MASK_SINK: c_uint = 0x1; 170 | pub const PA_SUBSCRIPTION_MASK_SOURCE: c_uint = 0x2; 171 | pub const PA_SUBSCRIPTION_MASK_SINK_INPUT: c_uint = 0x4; 172 | pub const PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT: c_uint = 0x8; 173 | pub const PA_SUBSCRIPTION_MASK_MODULE: c_uint = 0x10; 174 | pub const PA_SUBSCRIPTION_MASK_CLIENT: c_uint = 0x20; 175 | pub const PA_SUBSCRIPTION_MASK_SAMPLE_CACHE: c_uint = 0x40; 176 | pub const PA_SUBSCRIPTION_MASK_SERVER: c_uint = 0x80; 177 | pub const PA_SUBSCRIPTION_MASK_AUTOLOAD: c_uint = 0x100; 178 | pub const PA_SUBSCRIPTION_MASK_CARD: c_uint = 0x200; 179 | pub const PA_SUBSCRIPTION_MASK_ALL: c_uint = 0x3FF; 180 | pub type pa_subscription_mask_t = c_uint; 181 | 182 | pub const PA_SUBSCRIPTION_EVENT_SINK: c_int = 0; 183 | pub const PA_SUBSCRIPTION_EVENT_SOURCE: c_int = 1; 184 | pub const PA_SUBSCRIPTION_EVENT_SINK_INPUT: c_int = 2; 185 | pub const PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT: c_int = 3; 186 | pub const PA_SUBSCRIPTION_EVENT_MODULE: c_int = 4; 187 | pub const PA_SUBSCRIPTION_EVENT_CLIENT: c_int = 5; 188 | pub const PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE: c_int = 6; 189 | pub const PA_SUBSCRIPTION_EVENT_SERVER: c_int = 7; 190 | pub const PA_SUBSCRIPTION_EVENT_AUTOLOAD: c_int = 8; 191 | pub const PA_SUBSCRIPTION_EVENT_CARD: c_int = 9; 192 | pub const PA_SUBSCRIPTION_EVENT_FACILITY_MASK: c_int = 15; 193 | pub const PA_SUBSCRIPTION_EVENT_NEW: c_int = 0; 194 | pub const PA_SUBSCRIPTION_EVENT_CHANGE: c_int = 16; 195 | pub const PA_SUBSCRIPTION_EVENT_REMOVE: c_int = 32; 196 | pub const PA_SUBSCRIPTION_EVENT_TYPE_MASK: c_int = 48; 197 | pub type pa_subscription_event_type_t = c_int; 198 | 199 | #[repr(C)] 200 | #[derive(Clone, Copy, Debug)] 201 | pub struct timeval { 202 | pub tv_sec: c_long, 203 | pub tv_usec: c_long, 204 | } 205 | 206 | #[repr(C)] 207 | #[derive(Clone, Copy, Debug)] 208 | pub struct pa_timing_info { 209 | pub timestamp: timeval, 210 | pub synchronized_clocks: c_int, 211 | pub sink_usec: pa_usec_t, 212 | pub source_usec: pa_usec_t, 213 | pub transport_usec: pa_usec_t, 214 | pub playing: c_int, 215 | pub write_index_corrupt: c_int, 216 | pub write_index: i64, 217 | pub read_index_corrupt: c_int, 218 | pub read_index: i64, 219 | pub configured_sink_usec: pa_usec_t, 220 | pub configured_source_usec: pa_usec_t, 221 | pub since_underrun: i64, 222 | } 223 | 224 | impl ::std::default::Default for pa_timing_info { 225 | fn default() -> Self { 226 | unsafe { ::std::mem::zeroed() } 227 | } 228 | } 229 | 230 | #[repr(C)] 231 | #[derive(Clone, Copy, Debug)] 232 | pub struct pa_spawn_api { 233 | pub prefork: Option ()>, 234 | pub postfork: Option ()>, 235 | pub atfork: Option ()>, 236 | } 237 | 238 | impl ::std::default::Default for pa_spawn_api { 239 | fn default() -> Self { 240 | unsafe { ::std::mem::zeroed() } 241 | } 242 | } 243 | 244 | pub const PA_SEEK_RELATIVE: c_int = 0; 245 | pub const PA_SEEK_ABSOLUTE: c_int = 1; 246 | pub const PA_SEEK_RELATIVE_ON_READ: c_int = 2; 247 | pub const PA_SEEK_RELATIVE_END: c_int = 3; 248 | pub type pa_seek_mode_t = c_int; 249 | 250 | pub const PA_SINK_NOFLAGS: c_uint = 0x000; 251 | pub const PA_SINK_HW_VOLUME_CTRL: c_uint = 0x001; 252 | pub const PA_SINK_LATENCY: c_uint = 0x002; 253 | pub const PA_SINK_HARDWARE: c_uint = 0x004; 254 | pub const PA_SINK_NETWORK: c_uint = 0x008; 255 | pub const PA_SINK_HW_MUTE_CTRL: c_uint = 0x010; 256 | pub const PA_SINK_DECIBEL_VOLUME: c_uint = 0x020; 257 | pub const PA_SINK_FLAT_VOLUME: c_uint = 0x040; 258 | pub const PA_SINK_DYNAMIC_LATENCY: c_uint = 0x080; 259 | pub const PA_SINK_SET_FORMATS: c_uint = 0x100; 260 | pub type pa_sink_flags_t = c_uint; 261 | 262 | pub const PA_SINK_INVALID_STATE: c_int = -1; 263 | pub const PA_SINK_RUNNING: c_int = 0; 264 | pub const PA_SINK_IDLE: c_int = 1; 265 | pub const PA_SINK_SUSPENDED: c_int = 2; 266 | pub const PA_SINK_INIT: c_int = -2; 267 | pub const PA_SINK_UNLINKED: c_int = -3; 268 | pub type pa_sink_state_t = c_int; 269 | 270 | pub const PA_SOURCE_NOFLAGS: c_uint = 0x00; 271 | pub const PA_SOURCE_HW_VOLUME_CTRL: c_uint = 0x01; 272 | pub const PA_SOURCE_LATENCY: c_uint = 0x02; 273 | pub const PA_SOURCE_HARDWARE: c_uint = 0x04; 274 | pub const PA_SOURCE_NETWORK: c_uint = 0x08; 275 | pub const PA_SOURCE_HW_MUTE_CTRL: c_uint = 0x10; 276 | pub const PA_SOURCE_DECIBEL_VOLUME: c_uint = 0x20; 277 | pub const PA_SOURCE_DYNAMIC_LATENCY: c_uint = 0x40; 278 | pub const PA_SOURCE_FLAT_VOLUME: c_uint = 0x80; 279 | pub type pa_source_flags_t = c_uint; 280 | 281 | pub const PA_SOURCE_INVALID_STATE: c_int = -1; 282 | pub const PA_SOURCE_RUNNING: c_int = 0; 283 | pub const PA_SOURCE_IDLE: c_int = 1; 284 | pub const PA_SOURCE_SUSPENDED: c_int = 2; 285 | pub const PA_SOURCE_INIT: c_int = -2; 286 | pub const PA_SOURCE_UNLINKED: c_int = -3; 287 | pub type pa_source_state_t = c_int; 288 | 289 | pub type pa_free_cb_t = Option ()>; 290 | 291 | pub const PA_PORT_AVAILABLE_UNKNOWN: c_int = 0; 292 | pub const PA_PORT_AVAILABLE_NO: c_int = 1; 293 | pub const PA_PORT_AVAILABLE_YES: c_int = 2; 294 | pub type pa_port_available_t = c_int; 295 | 296 | pub const PA_IO_EVENT_NULL: c_int = 0; 297 | pub const PA_IO_EVENT_INPUT: c_int = 1; 298 | pub const PA_IO_EVENT_OUTPUT: c_int = 2; 299 | pub const PA_IO_EVENT_HANGUP: c_int = 4; 300 | pub const PA_IO_EVENT_ERROR: c_int = 8; 301 | pub type pa_io_event_flags_t = c_int; 302 | 303 | pub enum pa_io_event {} 304 | pub type pa_io_event_cb_t = Option< 305 | unsafe extern "C" fn( 306 | ea: *mut pa_mainloop_api, 307 | e: *mut pa_io_event, 308 | fd: c_int, 309 | events: pa_io_event_flags_t, 310 | userdata: *mut c_void, 311 | ), 312 | >; 313 | pub type pa_io_event_destroy_cb_t = Option< 314 | unsafe extern "C" fn(a: *mut pa_mainloop_api, e: *mut pa_io_event, userdata: *mut c_void), 315 | >; 316 | pub enum pa_time_event {} 317 | pub type pa_time_event_cb_t = Option< 318 | unsafe extern "C" fn( 319 | a: *mut pa_mainloop_api, 320 | e: *mut pa_time_event, 321 | tv: *const timeval, 322 | userdata: *mut c_void, 323 | ), 324 | >; 325 | pub type pa_time_event_destroy_cb_t = Option< 326 | unsafe extern "C" fn(a: *mut pa_mainloop_api, e: *mut pa_time_event, userdata: *mut c_void), 327 | >; 328 | 329 | pub enum pa_defer_event {} 330 | pub type pa_defer_event_cb_t = Option< 331 | unsafe extern "C" fn(a: *mut pa_mainloop_api, e: *mut pa_defer_event, userdata: *mut c_void), 332 | >; 333 | pub type pa_defer_event_destroy_cb_t = Option< 334 | unsafe extern "C" fn(a: *mut pa_mainloop_api, e: *mut pa_defer_event, userdata: *mut c_void), 335 | >; 336 | pub type IoNewFn = Option< 337 | unsafe extern "C" fn( 338 | a: *mut pa_mainloop_api, 339 | fd: c_int, 340 | events: pa_io_event_flags_t, 341 | cb: pa_io_event_cb_t, 342 | userdata: *mut c_void, 343 | ) -> *mut pa_io_event, 344 | >; 345 | pub type TimeNewFn = Option< 346 | unsafe extern "C" fn( 347 | a: *mut pa_mainloop_api, 348 | tv: *const timeval, 349 | cb: pa_time_event_cb_t, 350 | userdata: *mut c_void, 351 | ) -> *mut pa_time_event, 352 | >; 353 | pub type DeferNewFn = Option< 354 | unsafe extern "C" fn( 355 | a: *mut pa_mainloop_api, 356 | cb: pa_defer_event_cb_t, 357 | userdata: *mut c_void, 358 | ) -> *mut pa_defer_event, 359 | >; 360 | 361 | #[repr(C)] 362 | #[derive(Clone, Copy, Debug)] 363 | pub struct pa_mainloop_api { 364 | pub userdata: *mut c_void, 365 | pub io_new: IoNewFn, 366 | pub io_enable: Option, 367 | pub io_free: Option, 368 | pub io_set_destroy: 369 | Option, 370 | pub time_new: TimeNewFn, 371 | pub time_restart: Option, 372 | pub time_free: Option, 373 | pub time_set_destroy: 374 | Option, 375 | pub defer_new: DeferNewFn, 376 | pub defer_enable: Option, 377 | pub defer_free: Option, 378 | pub defer_set_destroy: 379 | Option, 380 | pub quit: Option, 381 | } 382 | 383 | impl ::std::default::Default for pa_mainloop_api { 384 | fn default() -> Self { 385 | unsafe { ::std::mem::zeroed() } 386 | } 387 | } 388 | 389 | pub type pa_mainloop_api_once_cb_t = 390 | Option; 391 | 392 | pub enum pa_proplist {} 393 | 394 | pub const PA_UPDATE_SET: c_int = 0; 395 | pub const PA_UPDATE_MERGE: c_int = 1; 396 | pub const PA_UPDATE_REPLACE: c_int = 2; 397 | pub type pa_update_mode_t = c_int; 398 | 399 | pub const PA_CHANNEL_POSITION_INVALID: c_int = -1; 400 | pub const PA_CHANNEL_POSITION_MONO: c_int = 0; 401 | pub const PA_CHANNEL_POSITION_FRONT_LEFT: c_int = 1; 402 | pub const PA_CHANNEL_POSITION_FRONT_RIGHT: c_int = 2; 403 | pub const PA_CHANNEL_POSITION_FRONT_CENTER: c_int = 3; 404 | pub const PA_CHANNEL_POSITION_LEFT: c_int = 1; 405 | pub const PA_CHANNEL_POSITION_RIGHT: c_int = 2; 406 | pub const PA_CHANNEL_POSITION_CENTER: c_int = 3; 407 | pub const PA_CHANNEL_POSITION_REAR_CENTER: c_int = 4; 408 | pub const PA_CHANNEL_POSITION_REAR_LEFT: c_int = 5; 409 | pub const PA_CHANNEL_POSITION_REAR_RIGHT: c_int = 6; 410 | pub const PA_CHANNEL_POSITION_LFE: c_int = 7; 411 | pub const PA_CHANNEL_POSITION_SUBWOOFER: c_int = 7; 412 | pub const PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: c_int = 8; 413 | pub const PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: c_int = 9; 414 | pub const PA_CHANNEL_POSITION_SIDE_LEFT: c_int = 10; 415 | pub const PA_CHANNEL_POSITION_SIDE_RIGHT: c_int = 11; 416 | pub const PA_CHANNEL_POSITION_AUX0: c_int = 12; 417 | pub const PA_CHANNEL_POSITION_AUX1: c_int = 13; 418 | pub const PA_CHANNEL_POSITION_AUX2: c_int = 14; 419 | pub const PA_CHANNEL_POSITION_AUX3: c_int = 15; 420 | pub const PA_CHANNEL_POSITION_AUX4: c_int = 16; 421 | pub const PA_CHANNEL_POSITION_AUX5: c_int = 17; 422 | pub const PA_CHANNEL_POSITION_AUX6: c_int = 18; 423 | pub const PA_CHANNEL_POSITION_AUX7: c_int = 19; 424 | pub const PA_CHANNEL_POSITION_AUX8: c_int = 20; 425 | pub const PA_CHANNEL_POSITION_AUX9: c_int = 21; 426 | pub const PA_CHANNEL_POSITION_AUX10: c_int = 22; 427 | pub const PA_CHANNEL_POSITION_AUX11: c_int = 23; 428 | pub const PA_CHANNEL_POSITION_AUX12: c_int = 24; 429 | pub const PA_CHANNEL_POSITION_AUX13: c_int = 25; 430 | pub const PA_CHANNEL_POSITION_AUX14: c_int = 26; 431 | pub const PA_CHANNEL_POSITION_AUX15: c_int = 27; 432 | pub const PA_CHANNEL_POSITION_AUX16: c_int = 28; 433 | pub const PA_CHANNEL_POSITION_AUX17: c_int = 29; 434 | pub const PA_CHANNEL_POSITION_AUX18: c_int = 30; 435 | pub const PA_CHANNEL_POSITION_AUX19: c_int = 31; 436 | pub const PA_CHANNEL_POSITION_AUX20: c_int = 32; 437 | pub const PA_CHANNEL_POSITION_AUX21: c_int = 33; 438 | pub const PA_CHANNEL_POSITION_AUX22: c_int = 34; 439 | pub const PA_CHANNEL_POSITION_AUX23: c_int = 35; 440 | pub const PA_CHANNEL_POSITION_AUX24: c_int = 36; 441 | pub const PA_CHANNEL_POSITION_AUX25: c_int = 37; 442 | pub const PA_CHANNEL_POSITION_AUX26: c_int = 38; 443 | pub const PA_CHANNEL_POSITION_AUX27: c_int = 39; 444 | pub const PA_CHANNEL_POSITION_AUX28: c_int = 40; 445 | pub const PA_CHANNEL_POSITION_AUX29: c_int = 41; 446 | pub const PA_CHANNEL_POSITION_AUX30: c_int = 42; 447 | pub const PA_CHANNEL_POSITION_AUX31: c_int = 43; 448 | pub const PA_CHANNEL_POSITION_TOP_CENTER: c_int = 44; 449 | pub const PA_CHANNEL_POSITION_TOP_FRONT_LEFT: c_int = 45; 450 | pub const PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: c_int = 46; 451 | pub const PA_CHANNEL_POSITION_TOP_FRONT_CENTER: c_int = 47; 452 | pub const PA_CHANNEL_POSITION_TOP_REAR_LEFT: c_int = 48; 453 | pub const PA_CHANNEL_POSITION_TOP_REAR_RIGHT: c_int = 49; 454 | pub const PA_CHANNEL_POSITION_TOP_REAR_CENTER: c_int = 50; 455 | pub const PA_CHANNEL_POSITION_MAX: c_int = 51; 456 | pub type pa_channel_position_t = c_int; 457 | pub type pa_channel_position_mask_t = u64; 458 | 459 | pub const PA_CHANNEL_MAP_AIFF: c_int = 0; 460 | pub const PA_CHANNEL_MAP_ALSA: c_int = 1; 461 | pub const PA_CHANNEL_MAP_AUX: c_int = 2; 462 | pub const PA_CHANNEL_MAP_WAVEEX: c_int = 3; 463 | pub const PA_CHANNEL_MAP_OSS: c_int = 4; 464 | pub const PA_CHANNEL_MAP_DEF_MAX: c_int = 5; 465 | pub const PA_CHANNEL_MAP_DEFAULT: c_int = 0; 466 | pub type pa_channel_map_def_t = c_int; 467 | 468 | #[repr(C)] 469 | #[derive(Clone, Copy, Debug)] 470 | pub struct pa_channel_map { 471 | pub channels: u8, 472 | pub map: [pa_channel_position_t; 32usize], 473 | } 474 | 475 | impl ::std::default::Default for pa_channel_map { 476 | fn default() -> Self { 477 | unsafe { ::std::mem::zeroed() } 478 | } 479 | } 480 | 481 | pub const PA_ENCODING_ANY: c_int = 0; 482 | pub const PA_ENCODING_PCM: c_int = 1; 483 | pub const PA_ENCODING_AC3_IEC61937: c_int = 2; 484 | pub const PA_ENCODING_EAC3_IEC61937: c_int = 3; 485 | pub const PA_ENCODING_MPEG_IEC61937: c_int = 4; 486 | pub const PA_ENCODING_DTS_IEC61937: c_int = 5; 487 | pub const PA_ENCODING_MPEG2_AAC_IEC61937: c_int = 6; 488 | pub const PA_ENCODING_MAX: c_int = 7; 489 | pub const PA_ENCODING_INVALID: c_int = -1; 490 | pub type pa_encoding_t = c_int; 491 | 492 | #[repr(C)] 493 | #[derive(Clone, Copy, Debug)] 494 | pub struct pa_format_info { 495 | pub encoding: pa_encoding_t, 496 | pub plist: *mut pa_proplist, 497 | } 498 | 499 | impl ::std::default::Default for pa_format_info { 500 | fn default() -> Self { 501 | unsafe { ::std::mem::zeroed() } 502 | } 503 | } 504 | 505 | pub const PA_PROP_TYPE_INT: c_int = 0; 506 | pub const PA_PROP_TYPE_INT_RANGE: c_int = 1; 507 | pub const PA_PROP_TYPE_INT_ARRAY: c_int = 2; 508 | pub const PA_PROP_TYPE_STRING: c_int = 3; 509 | pub const PA_PROP_TYPE_STRING_ARRAY: c_int = 4; 510 | pub const PA_PROP_TYPE_INVALID: c_int = -1; 511 | pub type pa_prop_type_t = c_int; 512 | 513 | pub enum pa_operation {} 514 | pub type pa_operation_notify_cb_t = 515 | Option; 516 | 517 | pub enum pa_context {} 518 | pub type pa_context_notify_cb_t = 519 | Option; 520 | pub type pa_context_success_cb_t = 521 | Option; 522 | pub type pa_context_event_cb_t = Option< 523 | unsafe extern "C" fn( 524 | c: *mut pa_context, 525 | name: *const c_char, 526 | p: *mut pa_proplist, 527 | userdata: *mut c_void, 528 | ), 529 | >; 530 | 531 | pub type pa_volume_t = u32; 532 | 533 | #[repr(C)] 534 | #[derive(Clone, Copy, Debug)] 535 | pub struct pa_cvolume { 536 | pub channels: u8, 537 | pub values: [pa_volume_t; 32usize], 538 | } 539 | 540 | impl ::std::default::Default for pa_cvolume { 541 | fn default() -> Self { 542 | unsafe { ::std::mem::zeroed() } 543 | } 544 | } 545 | 546 | pub enum pa_stream {} 547 | pub type pa_stream_success_cb_t = 548 | Option; 549 | pub type pa_stream_request_cb_t = 550 | Option; 551 | pub type pa_stream_notify_cb_t = 552 | Option; 553 | pub type pa_stream_event_cb_t = Option< 554 | unsafe extern "C" fn( 555 | p: *mut pa_stream, 556 | name: *const c_char, 557 | pl: *mut pa_proplist, 558 | userdata: *mut c_void, 559 | ), 560 | >; 561 | 562 | #[repr(C)] 563 | #[derive(Clone, Copy, Debug)] 564 | pub struct pa_port_info { 565 | pub name: *const c_char, 566 | pub description: *const c_char, 567 | pub priority: u32, 568 | pub available: c_int, 569 | } 570 | 571 | impl ::std::default::Default for pa_port_info { 572 | fn default() -> Self { 573 | unsafe { ::std::mem::zeroed() } 574 | } 575 | } 576 | 577 | #[repr(C)] 578 | #[derive(Clone, Copy, Debug)] 579 | pub struct pa_sink_info { 580 | pub name: *const c_char, 581 | pub index: u32, 582 | pub description: *const c_char, 583 | pub sample_spec: pa_sample_spec, 584 | pub channel_map: pa_channel_map, 585 | pub owner_module: u32, 586 | pub volume: pa_cvolume, 587 | pub mute: c_int, 588 | pub monitor_source: u32, 589 | pub monitor_source_name: *const c_char, 590 | pub latency: pa_usec_t, 591 | pub driver: *const c_char, 592 | pub flags: pa_sink_flags_t, 593 | pub proplist: *mut pa_proplist, 594 | pub configured_latency: pa_usec_t, 595 | pub base_volume: pa_volume_t, 596 | pub state: pa_sink_state_t, 597 | pub n_volume_steps: u32, 598 | pub card: u32, 599 | pub n_ports: u32, 600 | pub ports: *mut *mut pa_port_info, 601 | pub active_port: *mut pa_port_info, 602 | pub n_formats: u8, 603 | pub formats: *mut *mut pa_format_info, 604 | } 605 | 606 | impl ::std::default::Default for pa_sink_info { 607 | fn default() -> Self { 608 | unsafe { ::std::mem::zeroed() } 609 | } 610 | } 611 | 612 | pub type pa_sink_info_cb_t = Option< 613 | unsafe extern "C" fn( 614 | c: *mut pa_context, 615 | i: *const pa_sink_info, 616 | eol: c_int, 617 | userdata: *mut c_void, 618 | ), 619 | >; 620 | 621 | #[repr(C)] 622 | #[derive(Clone, Copy, Debug)] 623 | pub struct pa_source_info { 624 | pub name: *const c_char, 625 | pub index: u32, 626 | pub description: *const c_char, 627 | pub sample_spec: pa_sample_spec, 628 | pub channel_map: pa_channel_map, 629 | pub owner_module: u32, 630 | pub volume: pa_cvolume, 631 | pub mute: c_int, 632 | pub monitor_of_sink: u32, 633 | pub monitor_of_sink_name: *const c_char, 634 | pub latency: pa_usec_t, 635 | pub driver: *const c_char, 636 | pub flags: pa_source_flags_t, 637 | pub proplist: *mut pa_proplist, 638 | pub configured_latency: pa_usec_t, 639 | pub base_volume: pa_volume_t, 640 | pub state: pa_source_state_t, 641 | pub n_volume_steps: u32, 642 | pub card: u32, 643 | pub n_ports: u32, 644 | pub ports: *mut *mut pa_port_info, 645 | pub active_port: *mut pa_port_info, 646 | pub n_formats: u8, 647 | pub formats: *mut *mut pa_format_info, 648 | } 649 | 650 | impl ::std::default::Default for pa_source_info { 651 | fn default() -> Self { 652 | unsafe { ::std::mem::zeroed() } 653 | } 654 | } 655 | 656 | pub type pa_source_info_cb_t = Option< 657 | unsafe extern "C" fn( 658 | c: *mut pa_context, 659 | i: *const pa_source_info, 660 | eol: c_int, 661 | userdata: *mut c_void, 662 | ), 663 | >; 664 | 665 | #[repr(C)] 666 | #[derive(Clone, Copy, Debug)] 667 | pub struct pa_server_info { 668 | pub user_name: *const c_char, 669 | pub host_name: *const c_char, 670 | pub server_version: *const c_char, 671 | pub server_name: *const c_char, 672 | pub sample_spec: pa_sample_spec, 673 | pub default_sink_name: *const c_char, 674 | pub default_source_name: *const c_char, 675 | pub cookie: u32, 676 | pub channel_map: pa_channel_map, 677 | } 678 | 679 | impl ::std::default::Default for pa_server_info { 680 | fn default() -> Self { 681 | unsafe { ::std::mem::zeroed() } 682 | } 683 | } 684 | 685 | pub type pa_server_info_cb_t = Option< 686 | unsafe extern "C" fn(c: *mut pa_context, i: *const pa_server_info, userdata: *mut c_void), 687 | >; 688 | 689 | #[repr(C)] 690 | #[derive(Clone, Copy, Debug)] 691 | pub struct pa_module_info { 692 | pub index: u32, 693 | pub name: *const c_char, 694 | pub argument: *const c_char, 695 | pub n_used: u32, 696 | pub auto_unload: c_int, 697 | pub proplist: *mut pa_proplist, 698 | } 699 | 700 | impl ::std::default::Default for pa_module_info { 701 | fn default() -> Self { 702 | unsafe { ::std::mem::zeroed() } 703 | } 704 | } 705 | 706 | pub type pa_module_info_cb_t = Option< 707 | unsafe extern "C" fn( 708 | c: *mut pa_context, 709 | i: *const pa_module_info, 710 | eol: c_int, 711 | userdata: *mut c_void, 712 | ), 713 | >; 714 | pub type pa_context_index_cb_t = 715 | Option; 716 | 717 | #[repr(C)] 718 | #[derive(Clone, Copy, Debug)] 719 | pub struct pa_client_info { 720 | pub index: u32, 721 | pub name: *const c_char, 722 | pub owner_module: u32, 723 | pub driver: *const c_char, 724 | pub proplist: *mut pa_proplist, 725 | } 726 | 727 | impl ::std::default::Default for pa_client_info { 728 | fn default() -> Self { 729 | unsafe { ::std::mem::zeroed() } 730 | } 731 | } 732 | 733 | pub type pa_client_info_cb_t = Option< 734 | unsafe extern "C" fn( 735 | c: *mut pa_context, 736 | i: *const pa_client_info, 737 | eol: c_int, 738 | userdata: *mut c_void, 739 | ), 740 | >; 741 | 742 | #[repr(C)] 743 | #[derive(Clone, Copy, Debug)] 744 | pub struct pa_card_profile_info { 745 | pub name: *const c_char, 746 | pub description: *const c_char, 747 | pub n_sinks: u32, 748 | pub n_sources: u32, 749 | pub priority: u32, 750 | } 751 | 752 | impl ::std::default::Default for pa_card_profile_info { 753 | fn default() -> Self { 754 | unsafe { ::std::mem::zeroed() } 755 | } 756 | } 757 | 758 | #[repr(C)] 759 | #[derive(Clone, Copy, Debug)] 760 | pub struct pa_card_profile_info2 { 761 | pub name: *const c_char, 762 | pub description: *const c_char, 763 | pub n_sinks: u32, 764 | pub n_sources: u32, 765 | pub priority: u32, 766 | pub available: c_int, 767 | } 768 | 769 | impl ::std::default::Default for pa_card_profile_info2 { 770 | fn default() -> Self { 771 | unsafe { ::std::mem::zeroed() } 772 | } 773 | } 774 | 775 | #[repr(C)] 776 | #[derive(Clone, Copy, Debug)] 777 | pub struct pa_card_port_info { 778 | pub name: *const c_char, 779 | pub description: *const c_char, 780 | pub priority: u32, 781 | pub available: c_int, 782 | pub direction: c_int, 783 | pub n_profiles: u32, 784 | pub profiles: *mut *mut pa_card_profile_info, 785 | pub proplist: *mut pa_proplist, 786 | pub latency_offset: i64, 787 | pub profiles2: *mut *mut pa_card_profile_info2, 788 | } 789 | 790 | impl ::std::default::Default for pa_card_port_info { 791 | fn default() -> Self { 792 | unsafe { ::std::mem::zeroed() } 793 | } 794 | } 795 | 796 | #[repr(C)] 797 | #[derive(Clone, Copy, Debug)] 798 | pub struct pa_card_info { 799 | pub index: u32, 800 | pub name: *const c_char, 801 | pub owner_module: u32, 802 | pub driver: *const c_char, 803 | pub n_profiles: u32, 804 | pub profiles: *mut pa_card_profile_info, 805 | pub active_profile: *mut pa_card_profile_info, 806 | pub proplist: *mut pa_proplist, 807 | pub n_ports: u32, 808 | pub ports: *mut *mut pa_card_port_info, 809 | pub profiles2: *mut *mut pa_card_profile_info2, 810 | pub active_profile2: *mut pa_card_profile_info2, 811 | } 812 | 813 | impl ::std::default::Default for pa_card_info { 814 | fn default() -> Self { 815 | unsafe { ::std::mem::zeroed() } 816 | } 817 | } 818 | 819 | pub type pa_card_info_cb_t = Option< 820 | unsafe extern "C" fn( 821 | c: *mut pa_context, 822 | i: *const pa_card_info, 823 | eol: c_int, 824 | userdata: *mut c_void, 825 | ), 826 | >; 827 | 828 | #[repr(C)] 829 | #[derive(Clone, Copy, Debug)] 830 | pub struct pa_sink_input_info { 831 | pub index: u32, 832 | pub name: *const c_char, 833 | pub owner_module: u32, 834 | pub client: u32, 835 | pub sink: u32, 836 | pub sample_spec: pa_sample_spec, 837 | pub channel_map: pa_channel_map, 838 | pub volume: pa_cvolume, 839 | pub buffer_usec: pa_usec_t, 840 | pub sink_usec: pa_usec_t, 841 | pub resample_method: *const c_char, 842 | pub driver: *const c_char, 843 | pub mute: c_int, 844 | pub proplist: *mut pa_proplist, 845 | pub corked: c_int, 846 | pub has_volume: c_int, 847 | pub volume_writable: c_int, 848 | pub format: *mut pa_format_info, 849 | } 850 | 851 | impl ::std::default::Default for pa_sink_input_info { 852 | fn default() -> Self { 853 | unsafe { ::std::mem::zeroed() } 854 | } 855 | } 856 | 857 | pub type pa_sink_input_info_cb_t = Option< 858 | unsafe extern "C" fn( 859 | c: *mut pa_context, 860 | i: *const pa_sink_input_info, 861 | eol: c_int, 862 | userdata: *mut c_void, 863 | ), 864 | >; 865 | 866 | #[repr(C)] 867 | #[derive(Clone, Copy, Debug)] 868 | pub struct pa_source_output_info { 869 | pub index: u32, 870 | pub name: *const c_char, 871 | pub owner_module: u32, 872 | pub client: u32, 873 | pub source: u32, 874 | pub sample_spec: pa_sample_spec, 875 | pub channel_map: pa_channel_map, 876 | pub buffer_usec: pa_usec_t, 877 | pub source_usec: pa_usec_t, 878 | pub resample_method: *const c_char, 879 | pub driver: *const c_char, 880 | pub proplist: *mut pa_proplist, 881 | pub corked: c_int, 882 | pub volume: pa_cvolume, 883 | pub mute: c_int, 884 | pub has_volume: c_int, 885 | pub volume_writable: c_int, 886 | pub format: *mut pa_format_info, 887 | } 888 | 889 | impl ::std::default::Default for pa_source_output_info { 890 | fn default() -> Self { 891 | unsafe { ::std::mem::zeroed() } 892 | } 893 | } 894 | 895 | pub type pa_source_output_info_cb_t = Option< 896 | unsafe extern "C" fn( 897 | c: *mut pa_context, 898 | i: *const pa_source_output_info, 899 | eol: c_int, 900 | userdata: *mut c_void, 901 | ), 902 | >; 903 | 904 | #[repr(C)] 905 | #[derive(Clone, Copy, Debug)] 906 | pub struct pa_stat_info { 907 | pub memblock_total: u32, 908 | pub memblock_total_size: u32, 909 | pub memblock_allocated: u32, 910 | pub memblock_allocated_size: u32, 911 | pub scache_size: u32, 912 | } 913 | 914 | impl ::std::default::Default for pa_stat_info { 915 | fn default() -> Self { 916 | unsafe { ::std::mem::zeroed() } 917 | } 918 | } 919 | 920 | pub type pa_stat_info_cb_t = 921 | Option; 922 | 923 | #[repr(C)] 924 | #[derive(Clone, Copy, Debug)] 925 | pub struct pa_sample_info { 926 | pub index: u32, 927 | pub name: *const c_char, 928 | pub volume: pa_cvolume, 929 | pub sample_spec: pa_sample_spec, 930 | pub channel_map: pa_channel_map, 931 | pub duration: pa_usec_t, 932 | pub bytes: u32, 933 | pub lazy: c_int, 934 | pub filename: *const c_char, 935 | pub proplist: *mut pa_proplist, 936 | } 937 | 938 | impl ::std::default::Default for pa_sample_info { 939 | fn default() -> Self { 940 | unsafe { ::std::mem::zeroed() } 941 | } 942 | } 943 | 944 | pub type pa_sample_info_cb_t = Option< 945 | unsafe extern "C" fn( 946 | c: *mut pa_context, 947 | i: *const pa_sample_info, 948 | eol: c_int, 949 | userdata: *mut c_void, 950 | ), 951 | >; 952 | 953 | pub const PA_AUTOLOAD_SINK: c_int = 0; 954 | pub const PA_AUTOLOAD_SOURCE: c_int = 1; 955 | pub type pa_autoload_type_t = c_int; 956 | 957 | #[repr(C)] 958 | #[derive(Clone, Copy, Debug)] 959 | pub struct pa_autoload_info { 960 | pub index: u32, 961 | pub name: *const c_char, 962 | pub _type: pa_autoload_type_t, 963 | pub module: *const c_char, 964 | pub argument: *const c_char, 965 | } 966 | 967 | impl ::std::default::Default for pa_autoload_info { 968 | fn default() -> Self { 969 | unsafe { ::std::mem::zeroed() } 970 | } 971 | } 972 | 973 | pub type pa_autoload_info_cb_t = Option< 974 | unsafe extern "C" fn( 975 | c: *mut pa_context, 976 | i: *const pa_autoload_info, 977 | eol: c_int, 978 | userdata: *mut c_void, 979 | ), 980 | >; 981 | pub type pa_context_subscribe_cb_t = Option< 982 | unsafe extern "C" fn( 983 | c: *mut pa_context, 984 | t: pa_subscription_event_type_t, 985 | idx: u32, 986 | userdata: *mut c_void, 987 | ), 988 | >; 989 | pub type pa_context_play_sample_cb_t = 990 | Option; 991 | 992 | pub enum pa_threaded_mainloop {} 993 | pub enum pollfd {} 994 | pub enum pa_mainloop {} 995 | 996 | pub type pa_poll_func = Option< 997 | unsafe extern "C" fn( 998 | ufds: *mut pollfd, 999 | nfds: c_ulong, 1000 | timeout: c_int, 1001 | userdata: *mut c_void, 1002 | ) -> c_int, 1003 | >; 1004 | pub enum pa_signal_event {} 1005 | 1006 | pub type pa_signal_cb_t = Option< 1007 | unsafe extern "C" fn( 1008 | api: *mut pa_mainloop_api, 1009 | e: *mut pa_signal_event, 1010 | sig: c_int, 1011 | userdata: *mut c_void, 1012 | ), 1013 | >; 1014 | pub type pa_signal_destroy_cb_t = Option< 1015 | unsafe extern "C" fn(api: *mut pa_mainloop_api, e: *mut pa_signal_event, userdata: *mut c_void), 1016 | >; 1017 | -------------------------------------------------------------------------------- /pulse-ffi/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Required for dlopen, et al. 2 | #[cfg(feature = "dlopen")] 3 | extern crate libc; 4 | 5 | mod ffi_funcs; 6 | mod ffi_types; 7 | 8 | pub use ffi_funcs::*; 9 | pub use ffi_types::*; 10 | 11 | #[cfg(feature = "dlopen")] 12 | pub unsafe fn open() -> Option { 13 | return LibLoader::open(); 14 | } 15 | -------------------------------------------------------------------------------- /pulse-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pulse" 3 | version = "0.3.0" 4 | authors = ["Dan Glastonbury "] 5 | license = "ISC" 6 | 7 | [dependencies] 8 | bitflags = "2" 9 | pulse-ffi = { path = "../pulse-ffi" } 10 | -------------------------------------------------------------------------------- /pulse-rs/src/context.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use ffi; 7 | use std::ffi::CStr; 8 | use std::mem::{forget, MaybeUninit}; 9 | use std::os::raw::{c_int, c_void}; 10 | use std::ptr; 11 | use util::UnwrapCStr; 12 | use *; 13 | 14 | // A note about `wrapped` functions 15 | // 16 | // C FFI demands `unsafe extern fn(*mut pa_context, ...) -> i32`, etc, 17 | // but we want to allow such callbacks to be safe. This means no 18 | // `unsafe` or `extern`, and callbacks should be called with a safe 19 | // wrapper of `*mut pa_context`. Since the callback doesn't take 20 | // ownership, this is `&Context`. `fn wrapped(...)` defines a 21 | // function that converts from our safe signature to the unsafe 22 | // signature. 23 | // 24 | // Currently, we use a property of Rust, namely that each function 25 | // gets its own unique type. These unique types can't be written 26 | // directly, so we use generic and a type parameter, and let the Rust 27 | // compiler fill in the name for us: 28 | // 29 | // fn get_sink_input_info(&self, ..., _: CB, ...) -> ... 30 | // where CB: Fn(&Context, *const SinkInputInfo, i32, *mut c_void) 31 | // 32 | // Because we aren't storing or passing any state, we assert, at run-time :-(, 33 | // that our functions are zero-sized: 34 | // 35 | // assert!(mem::size_of::() == 0); 36 | // 37 | // We need to obtain a value of type F in order to call it. Since we 38 | // can't name the function, we have to unsafely construct that value 39 | // somehow - we do this using mem::uninitialized. Then, we call that 40 | // function with a reference to the Context, and save the result: 41 | // 42 | // | generate value || call it | 43 | // let result = ::std::mem::uninitialized::()(&mut object); 44 | // 45 | // Lastly, since our Object is an owned type, we need to avoid 46 | // dropping it, then return the result we just generated. 47 | // 48 | // mem::forget(object); 49 | // result 50 | 51 | // For all clippy-ignored warnings in this file, see 52 | // https://github.com/mozilla/cubeb-pulse-rs/issues/95 for the effort to fix. 53 | 54 | // Aid in returning Operation from callbacks 55 | macro_rules! op_or_err { 56 | ($self_:ident, $e:expr) => {{ 57 | let o = unsafe { $e }; 58 | if o.is_null() { 59 | Err(ErrorCode::from_error_code($self_.errno())) 60 | } else { 61 | Ok(unsafe { operation::from_raw_ptr(o) }) 62 | } 63 | }}; 64 | } 65 | 66 | #[repr(C)] 67 | #[derive(Debug)] 68 | pub struct Context(*mut ffi::pa_context); 69 | 70 | impl Context { 71 | pub fn new<'a, OPT>(api: &MainloopApi, name: OPT) -> Option 72 | where 73 | OPT: Into>, 74 | { 75 | let ptr = unsafe { ffi::pa_context_new(api.raw_mut(), name.unwrap_cstr()) }; 76 | if ptr.is_null() { 77 | None 78 | } else { 79 | Some(Context(ptr)) 80 | } 81 | } 82 | 83 | #[doc(hidden)] 84 | #[allow(clippy::mut_from_ref)] 85 | pub fn raw_mut(&self) -> &mut ffi::pa_context { 86 | unsafe { &mut *self.0 } 87 | } 88 | 89 | pub fn unref(self) { 90 | unsafe { 91 | ffi::pa_context_unref(self.raw_mut()); 92 | } 93 | } 94 | 95 | pub fn clear_state_callback(&self) { 96 | unsafe { 97 | ffi::pa_context_set_state_callback(self.raw_mut(), None, ptr::null_mut()); 98 | } 99 | } 100 | 101 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 102 | pub fn set_state_callback(&self, _: CB, userdata: *mut c_void) 103 | where 104 | CB: Fn(&Context, *mut c_void), 105 | { 106 | assert_eq!(::std::mem::size_of::(), 0); 107 | 108 | // See: A note about `wrapped` functions 109 | unsafe extern "C" fn wrapped(c: *mut ffi::pa_context, userdata: *mut c_void) 110 | where 111 | F: Fn(&Context, *mut c_void), 112 | { 113 | let ctx = context::from_raw_ptr(c); 114 | let cb = MaybeUninit::::uninit(); 115 | (*cb.as_ptr())(&ctx, userdata); 116 | #[allow(clippy::forget_non_drop)] 117 | forget(ctx); 118 | } 119 | 120 | unsafe { 121 | ffi::pa_context_set_state_callback(self.raw_mut(), Some(wrapped::), userdata); 122 | } 123 | } 124 | 125 | pub fn errno(&self) -> ffi::pa_error_code_t { 126 | unsafe { ffi::pa_context_errno(self.raw_mut()) } 127 | } 128 | 129 | pub fn get_state(&self) -> ContextState { 130 | ContextState::try_from(unsafe { ffi::pa_context_get_state(self.raw_mut()) }) 131 | .expect("pa_context_get_state returned invalid ContextState") 132 | } 133 | 134 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 135 | pub fn connect<'a, OPT>( 136 | &self, 137 | server: OPT, 138 | flags: ContextFlags, 139 | api: *const ffi::pa_spawn_api, 140 | ) -> Result<()> 141 | where 142 | OPT: Into>, 143 | { 144 | let r = unsafe { 145 | ffi::pa_context_connect( 146 | self.raw_mut(), 147 | server.into().unwrap_cstr(), 148 | flags.into(), 149 | api, 150 | ) 151 | }; 152 | error_result!((), r) 153 | } 154 | 155 | pub fn disconnect(&self) { 156 | unsafe { 157 | ffi::pa_context_disconnect(self.raw_mut()); 158 | } 159 | } 160 | 161 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 162 | pub fn drain(&self, _: CB, userdata: *mut c_void) -> Result 163 | where 164 | CB: Fn(&Context, *mut c_void), 165 | { 166 | assert_eq!(::std::mem::size_of::(), 0); 167 | 168 | // See: A note about `wrapped` functions 169 | unsafe extern "C" fn wrapped(c: *mut ffi::pa_context, userdata: *mut c_void) 170 | where 171 | F: Fn(&Context, *mut c_void), 172 | { 173 | let ctx = context::from_raw_ptr(c); 174 | let cb = MaybeUninit::::uninit(); 175 | (*cb.as_ptr())(&ctx, userdata); 176 | #[allow(clippy::forget_non_drop)] 177 | forget(ctx); 178 | } 179 | 180 | op_or_err!( 181 | self, 182 | ffi::pa_context_drain(self.raw_mut(), Some(wrapped::), userdata) 183 | ) 184 | } 185 | 186 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 187 | pub fn rttime_new( 188 | &self, 189 | usec: USec, 190 | _: CB, 191 | userdata: *mut c_void, 192 | ) -> *mut ffi::pa_time_event 193 | where 194 | CB: Fn(&MainloopApi, *mut ffi::pa_time_event, &TimeVal, *mut c_void), 195 | { 196 | assert_eq!(::std::mem::size_of::(), 0); 197 | 198 | // See: A note about `wrapped` functions 199 | unsafe extern "C" fn wrapped( 200 | a: *mut ffi::pa_mainloop_api, 201 | e: *mut ffi::pa_time_event, 202 | tv: *const TimeVal, 203 | userdata: *mut c_void, 204 | ) where 205 | F: Fn(&MainloopApi, *mut ffi::pa_time_event, &TimeVal, *mut c_void), 206 | { 207 | let api = mainloop_api::from_raw_ptr(a); 208 | let timeval = &*tv; 209 | let cb = MaybeUninit::::uninit(); 210 | (*cb.as_ptr())(&api, e, timeval, userdata); 211 | #[allow(clippy::forget_non_drop)] 212 | forget(api); 213 | } 214 | 215 | unsafe { ffi::pa_context_rttime_new(self.raw_mut(), usec, Some(wrapped::), userdata) } 216 | } 217 | 218 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 219 | pub fn get_server_info(&self, _: CB, userdata: *mut c_void) -> Result 220 | where 221 | CB: Fn(&Context, Option<&ServerInfo>, *mut c_void), 222 | { 223 | assert_eq!(::std::mem::size_of::(), 0); 224 | 225 | // See: A note about `wrapped` functions 226 | unsafe extern "C" fn wrapped( 227 | c: *mut ffi::pa_context, 228 | i: *const ffi::pa_server_info, 229 | userdata: *mut c_void, 230 | ) where 231 | F: Fn(&Context, Option<&ServerInfo>, *mut c_void), 232 | { 233 | let info = if i.is_null() { None } else { Some(&*i) }; 234 | let ctx = context::from_raw_ptr(c); 235 | let cb = MaybeUninit::::uninit(); 236 | (*cb.as_ptr())(&ctx, info, userdata); 237 | #[allow(clippy::forget_non_drop)] 238 | forget(ctx); 239 | } 240 | 241 | op_or_err!( 242 | self, 243 | ffi::pa_context_get_server_info(self.raw_mut(), Some(wrapped::), userdata) 244 | ) 245 | } 246 | 247 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 248 | pub fn get_sink_info_by_name<'str, CS, CB>( 249 | &self, 250 | name: CS, 251 | _: CB, 252 | userdata: *mut c_void, 253 | ) -> Result 254 | where 255 | CB: Fn(&Context, *const SinkInfo, i32, *mut c_void), 256 | CS: Into>, 257 | { 258 | assert_eq!(::std::mem::size_of::(), 0); 259 | 260 | // See: A note about `wrapped` functions 261 | unsafe extern "C" fn wrapped( 262 | c: *mut ffi::pa_context, 263 | info: *const ffi::pa_sink_info, 264 | eol: c_int, 265 | userdata: *mut c_void, 266 | ) where 267 | F: Fn(&Context, *const SinkInfo, i32, *mut c_void), 268 | { 269 | let ctx = context::from_raw_ptr(c); 270 | let cb = MaybeUninit::::uninit(); 271 | (*cb.as_ptr())(&ctx, info, eol, userdata); 272 | #[allow(clippy::forget_non_drop)] 273 | forget(ctx); 274 | } 275 | 276 | op_or_err!( 277 | self, 278 | ffi::pa_context_get_sink_info_by_name( 279 | self.raw_mut(), 280 | name.into().unwrap_cstr(), 281 | Some(wrapped::), 282 | userdata 283 | ) 284 | ) 285 | } 286 | 287 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 288 | pub fn get_sink_info_list(&self, _: CB, userdata: *mut c_void) -> Result 289 | where 290 | CB: Fn(&Context, *const SinkInfo, i32, *mut c_void), 291 | { 292 | assert_eq!(::std::mem::size_of::(), 0); 293 | 294 | // See: A note about `wrapped` functions 295 | unsafe extern "C" fn wrapped( 296 | c: *mut ffi::pa_context, 297 | info: *const ffi::pa_sink_info, 298 | eol: c_int, 299 | userdata: *mut c_void, 300 | ) where 301 | F: Fn(&Context, *const SinkInfo, i32, *mut c_void), 302 | { 303 | let ctx = context::from_raw_ptr(c); 304 | let cb = MaybeUninit::::uninit(); 305 | (*cb.as_ptr())(&ctx, info, eol, userdata); 306 | #[allow(clippy::forget_non_drop)] 307 | forget(ctx); 308 | } 309 | 310 | op_or_err!( 311 | self, 312 | ffi::pa_context_get_sink_info_list(self.raw_mut(), Some(wrapped::), userdata) 313 | ) 314 | } 315 | 316 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 317 | pub fn get_sink_input_info( 318 | &self, 319 | idx: u32, 320 | _: CB, 321 | userdata: *mut c_void, 322 | ) -> Result 323 | where 324 | CB: Fn(&Context, *const SinkInputInfo, i32, *mut c_void), 325 | { 326 | assert_eq!(::std::mem::size_of::(), 0); 327 | 328 | // See: A note about `wrapped` functions 329 | unsafe extern "C" fn wrapped( 330 | c: *mut ffi::pa_context, 331 | info: *const ffi::pa_sink_input_info, 332 | eol: c_int, 333 | userdata: *mut c_void, 334 | ) where 335 | F: Fn(&Context, *const SinkInputInfo, i32, *mut c_void), 336 | { 337 | let ctx = context::from_raw_ptr(c); 338 | let cb = MaybeUninit::::uninit(); 339 | (*cb.as_ptr())(&ctx, info, eol, userdata); 340 | #[allow(clippy::forget_non_drop)] 341 | forget(ctx); 342 | } 343 | 344 | op_or_err!( 345 | self, 346 | ffi::pa_context_get_sink_input_info(self.raw_mut(), idx, Some(wrapped::), userdata) 347 | ) 348 | } 349 | 350 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 351 | pub fn get_source_info_list(&self, _: CB, userdata: *mut c_void) -> Result 352 | where 353 | CB: Fn(&Context, *const SourceInfo, i32, *mut c_void), 354 | { 355 | assert_eq!(::std::mem::size_of::(), 0); 356 | 357 | // See: A note about `wrapped` functions 358 | unsafe extern "C" fn wrapped( 359 | c: *mut ffi::pa_context, 360 | info: *const ffi::pa_source_info, 361 | eol: c_int, 362 | userdata: *mut c_void, 363 | ) where 364 | F: Fn(&Context, *const SourceInfo, i32, *mut c_void), 365 | { 366 | let ctx = context::from_raw_ptr(c); 367 | let cb = MaybeUninit::::uninit(); 368 | (*cb.as_ptr())(&ctx, info, eol, userdata); 369 | #[allow(clippy::forget_non_drop)] 370 | forget(ctx); 371 | } 372 | 373 | op_or_err!( 374 | self, 375 | ffi::pa_context_get_source_info_list(self.raw_mut(), Some(wrapped::), userdata) 376 | ) 377 | } 378 | 379 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 380 | pub fn set_sink_input_volume( 381 | &self, 382 | idx: u32, 383 | volume: &CVolume, 384 | _: CB, 385 | userdata: *mut c_void, 386 | ) -> Result 387 | where 388 | CB: Fn(&Context, i32, *mut c_void), 389 | { 390 | assert_eq!(::std::mem::size_of::(), 0); 391 | 392 | // See: A note about `wrapped` functions 393 | unsafe extern "C" fn wrapped( 394 | c: *mut ffi::pa_context, 395 | success: c_int, 396 | userdata: *mut c_void, 397 | ) where 398 | F: Fn(&Context, i32, *mut c_void), 399 | { 400 | let ctx = context::from_raw_ptr(c); 401 | let cb = MaybeUninit::::uninit(); 402 | (*cb.as_ptr())(&ctx, success, userdata); 403 | #[allow(clippy::forget_non_drop)] 404 | forget(ctx); 405 | } 406 | 407 | op_or_err!( 408 | self, 409 | ffi::pa_context_set_sink_input_volume( 410 | self.raw_mut(), 411 | idx, 412 | volume, 413 | Some(wrapped::), 414 | userdata 415 | ) 416 | ) 417 | } 418 | 419 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 420 | pub fn subscribe( 421 | &self, 422 | m: SubscriptionMask, 423 | _: CB, 424 | userdata: *mut c_void, 425 | ) -> Result 426 | where 427 | CB: Fn(&Context, i32, *mut c_void), 428 | { 429 | assert_eq!(::std::mem::size_of::(), 0); 430 | 431 | // See: A note about `wrapped` functions 432 | unsafe extern "C" fn wrapped( 433 | c: *mut ffi::pa_context, 434 | success: c_int, 435 | userdata: *mut c_void, 436 | ) where 437 | F: Fn(&Context, i32, *mut c_void), 438 | { 439 | let ctx = context::from_raw_ptr(c); 440 | let cb = MaybeUninit::::uninit(); 441 | (*cb.as_ptr())(&ctx, success, userdata); 442 | #[allow(clippy::forget_non_drop)] 443 | forget(ctx); 444 | } 445 | 446 | op_or_err!( 447 | self, 448 | ffi::pa_context_subscribe(self.raw_mut(), m.into(), Some(wrapped::), userdata) 449 | ) 450 | } 451 | 452 | pub fn clear_subscribe_callback(&self) { 453 | unsafe { 454 | ffi::pa_context_set_subscribe_callback(self.raw_mut(), None, ptr::null_mut()); 455 | } 456 | } 457 | 458 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 459 | pub fn set_subscribe_callback(&self, _: CB, userdata: *mut c_void) 460 | where 461 | CB: Fn(&Context, SubscriptionEvent, u32, *mut c_void), 462 | { 463 | assert_eq!(::std::mem::size_of::(), 0); 464 | 465 | // See: A note about `wrapped` functions 466 | unsafe extern "C" fn wrapped( 467 | c: *mut ffi::pa_context, 468 | t: ffi::pa_subscription_event_type_t, 469 | idx: u32, 470 | userdata: *mut c_void, 471 | ) where 472 | F: Fn(&Context, SubscriptionEvent, u32, *mut c_void), 473 | { 474 | let ctx = context::from_raw_ptr(c); 475 | let event = SubscriptionEvent::try_from(t) 476 | .expect("pa_context_subscribe_cb_t passed invalid pa_subscription_event_type_t"); 477 | let cb = MaybeUninit::::uninit(); 478 | (*cb.as_ptr())(&ctx, event, idx, userdata); 479 | #[allow(clippy::forget_non_drop)] 480 | forget(ctx); 481 | } 482 | 483 | unsafe { 484 | ffi::pa_context_set_subscribe_callback(self.raw_mut(), Some(wrapped::), userdata); 485 | } 486 | } 487 | } 488 | 489 | #[doc(hidden)] 490 | pub unsafe fn from_raw_ptr(ptr: *mut ffi::pa_context) -> Context { 491 | Context(ptr) 492 | } 493 | -------------------------------------------------------------------------------- /pulse-rs/src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use ffi; 7 | use std::ffi::CStr; 8 | 9 | #[macro_export] 10 | macro_rules! error_result { 11 | ($t:expr, $err:expr) => { 12 | if $err >= 0 { 13 | Ok($t) 14 | } else { 15 | Err(ErrorCode::from_error_result($err)) 16 | } 17 | }; 18 | } 19 | 20 | #[derive(Debug, PartialEq)] 21 | pub struct ErrorCode { 22 | err: ffi::pa_error_code_t, 23 | } 24 | 25 | impl ErrorCode { 26 | pub fn from_error_result(err: i32) -> Self { 27 | debug_assert!(err < 0); 28 | ErrorCode { 29 | err: (-err) as ffi::pa_error_code_t, 30 | } 31 | } 32 | 33 | pub fn from_error_code(err: ffi::pa_error_code_t) -> Self { 34 | debug_assert!(err > 0); 35 | ErrorCode { err } 36 | } 37 | 38 | fn desc(&self) -> &'static str { 39 | let cstr = unsafe { CStr::from_ptr(ffi::pa_strerror(self.err)) }; 40 | cstr.to_str().unwrap() 41 | } 42 | } 43 | 44 | impl ::std::error::Error for ErrorCode { 45 | fn description(&self) -> &str { 46 | self.desc() 47 | } 48 | } 49 | 50 | impl ::std::fmt::Display for ErrorCode { 51 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 52 | write!(f, "{:?}: {}", self, self.desc()) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /pulse-rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | #[macro_use] 7 | extern crate bitflags; 8 | extern crate pulse_ffi as ffi; 9 | 10 | #[macro_use] 11 | mod error; 12 | mod context; 13 | mod mainloop_api; 14 | mod operation; 15 | mod proplist; 16 | mod stream; 17 | mod threaded_mainloop; 18 | mod util; 19 | 20 | pub use context::Context; 21 | pub use error::ErrorCode; 22 | pub use ffi::pa_buffer_attr as BufferAttr; 23 | pub use ffi::pa_channel_map as ChannelMap; 24 | pub use ffi::pa_cvolume as CVolume; 25 | pub use ffi::pa_sample_spec as SampleSpec; 26 | pub use ffi::pa_server_info as ServerInfo; 27 | pub use ffi::pa_sink_info as SinkInfo; 28 | pub use ffi::pa_sink_input_info as SinkInputInfo; 29 | pub use ffi::pa_source_info as SourceInfo; 30 | pub use ffi::pa_usec_t as USec; 31 | pub use ffi::pa_volume_t as Volume; 32 | pub use ffi::timeval as TimeVal; 33 | pub use mainloop_api::MainloopApi; 34 | pub use operation::Operation; 35 | pub use proplist::Proplist; 36 | use std::os::raw::{c_char, c_uint}; 37 | pub use stream::Stream; 38 | pub use threaded_mainloop::ThreadedMainloop; 39 | 40 | #[allow(non_camel_case_types)] 41 | #[repr(i32)] 42 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] 43 | pub enum SampleFormat { 44 | #[default] 45 | Invalid = ffi::PA_SAMPLE_INVALID, 46 | U8 = ffi::PA_SAMPLE_U8, 47 | Alaw = ffi::PA_SAMPLE_ALAW, 48 | Ulaw = ffi::PA_SAMPLE_ULAW, 49 | Signed16LE = ffi::PA_SAMPLE_S16LE, 50 | Signed16BE = ffi::PA_SAMPLE_S16BE, 51 | Float32LE = ffi::PA_SAMPLE_FLOAT32LE, 52 | Float32BE = ffi::PA_SAMPLE_FLOAT32BE, 53 | Signed32LE = ffi::PA_SAMPLE_S32LE, 54 | Signed32BE = ffi::PA_SAMPLE_S32BE, 55 | Signed24LE = ffi::PA_SAMPLE_S24LE, 56 | Signed24BE = ffi::PA_SAMPLE_S24BE, 57 | Signed24_32LE = ffi::PA_SAMPLE_S24_32LE, 58 | Signed23_32BE = ffi::PA_SAMPLE_S24_32BE, 59 | } 60 | 61 | impl From for ffi::pa_sample_format_t { 62 | fn from(val: SampleFormat) -> Self { 63 | val as ffi::pa_sample_format_t 64 | } 65 | } 66 | 67 | #[repr(i32)] 68 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] 69 | pub enum ContextState { 70 | #[default] 71 | Unconnected = ffi::PA_CONTEXT_UNCONNECTED, 72 | Connecting = ffi::PA_CONTEXT_CONNECTING, 73 | Authorizing = ffi::PA_CONTEXT_AUTHORIZING, 74 | SettingName = ffi::PA_CONTEXT_SETTING_NAME, 75 | Ready = ffi::PA_CONTEXT_READY, 76 | Failed = ffi::PA_CONTEXT_FAILED, 77 | Terminated = ffi::PA_CONTEXT_TERMINATED, 78 | } 79 | 80 | impl ContextState { 81 | // This function implements the PA_CONTENT_IS_GOOD macro from pulse/def.h 82 | // It must match the version from PA headers. 83 | pub fn is_good(self) -> bool { 84 | matches!( 85 | self, 86 | ContextState::Connecting 87 | | ContextState::Authorizing 88 | | ContextState::SettingName 89 | | ContextState::Ready 90 | ) 91 | } 92 | 93 | pub fn try_from(x: ffi::pa_context_state_t) -> Option { 94 | if (ffi::PA_CONTEXT_UNCONNECTED..=ffi::PA_CONTEXT_TERMINATED).contains(&x) { 95 | Some(unsafe { ::std::mem::transmute::(x) }) 96 | } else { 97 | None 98 | } 99 | } 100 | } 101 | 102 | impl From for ffi::pa_context_state_t { 103 | fn from(val: ContextState) -> Self { 104 | val as ffi::pa_context_state_t 105 | } 106 | } 107 | 108 | #[repr(i32)] 109 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] 110 | pub enum StreamState { 111 | #[default] 112 | Unconnected = ffi::PA_STREAM_UNCONNECTED, 113 | Creating = ffi::PA_STREAM_CREATING, 114 | Ready = ffi::PA_STREAM_READY, 115 | Failed = ffi::PA_STREAM_FAILED, 116 | Terminated = ffi::PA_STREAM_TERMINATED, 117 | } 118 | 119 | impl StreamState { 120 | // This function implements the PA_STREAM_IS_GOOD macro from pulse/def.h 121 | // It must match the version from PA headers. 122 | pub fn is_good(self) -> bool { 123 | matches!(self, StreamState::Creating | StreamState::Ready) 124 | } 125 | 126 | pub fn try_from(x: ffi::pa_stream_state_t) -> Option { 127 | if (ffi::PA_STREAM_UNCONNECTED..=ffi::PA_STREAM_TERMINATED).contains(&x) { 128 | Some(unsafe { ::std::mem::transmute::(x) }) 129 | } else { 130 | None 131 | } 132 | } 133 | } 134 | 135 | impl From for ffi::pa_stream_state_t { 136 | fn from(val: StreamState) -> Self { 137 | val as ffi::pa_stream_state_t 138 | } 139 | } 140 | 141 | #[repr(i32)] 142 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 143 | pub enum OperationState { 144 | Running = ffi::PA_OPERATION_RUNNING, 145 | Done = ffi::PA_OPERATION_DONE, 146 | Cancelled = ffi::PA_OPERATION_CANCELLED, 147 | } 148 | 149 | impl OperationState { 150 | pub fn try_from(x: ffi::pa_operation_state_t) -> Option { 151 | if (ffi::PA_OPERATION_RUNNING..=ffi::PA_OPERATION_CANCELLED).contains(&x) { 152 | Some(unsafe { ::std::mem::transmute::(x) }) 153 | } else { 154 | None 155 | } 156 | } 157 | } 158 | 159 | impl From for ffi::pa_operation_state_t { 160 | fn from(val: OperationState) -> Self { 161 | val as ffi::pa_operation_state_t 162 | } 163 | } 164 | 165 | bitflags! { 166 | pub struct ContextFlags: u32 { 167 | const NOAUTOSPAWN = ffi::PA_CONTEXT_NOAUTOSPAWN; 168 | const NOFAIL = ffi::PA_CONTEXT_NOFAIL; 169 | } 170 | } 171 | 172 | impl From for ffi::pa_context_flags_t { 173 | fn from(val: ContextFlags) -> Self { 174 | val.bits() as ffi::pa_context_flags_t 175 | } 176 | } 177 | 178 | #[repr(i32)] 179 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 180 | pub enum DeviceType { 181 | Sink = ffi::PA_DEVICE_TYPE_SINK, 182 | Source = ffi::PA_DEVICE_TYPE_SOURCE, 183 | } 184 | 185 | impl DeviceType { 186 | pub fn try_from(x: ffi::pa_device_type_t) -> Option { 187 | if (ffi::PA_DEVICE_TYPE_SINK..=ffi::PA_DEVICE_TYPE_SOURCE).contains(&x) { 188 | Some(unsafe { ::std::mem::transmute::(x) }) 189 | } else { 190 | None 191 | } 192 | } 193 | } 194 | 195 | impl From for ffi::pa_device_type_t { 196 | fn from(val: DeviceType) -> Self { 197 | val as ffi::pa_device_type_t 198 | } 199 | } 200 | 201 | #[repr(i32)] 202 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 203 | pub enum StreamDirection { 204 | NoDirection = ffi::PA_STREAM_NODIRECTION, 205 | Playback = ffi::PA_STREAM_PLAYBACK, 206 | Record = ffi::PA_STREAM_RECORD, 207 | StreamUpload = ffi::PA_STREAM_UPLOAD, 208 | } 209 | 210 | impl StreamDirection { 211 | pub fn try_from(x: ffi::pa_stream_direction_t) -> Option { 212 | if (ffi::PA_STREAM_NODIRECTION..=ffi::PA_STREAM_UPLOAD).contains(&x) { 213 | Some(unsafe { ::std::mem::transmute::(x) }) 214 | } else { 215 | None 216 | } 217 | } 218 | } 219 | 220 | impl From for ffi::pa_stream_direction_t { 221 | fn from(val: StreamDirection) -> Self { 222 | val as ffi::pa_stream_direction_t 223 | } 224 | } 225 | 226 | bitflags! { 227 | pub struct StreamFlags : u32 { 228 | const START_CORKED = ffi::PA_STREAM_START_CORKED; 229 | const INTERPOLATE_TIMING = ffi::PA_STREAM_INTERPOLATE_TIMING; 230 | const NOT_MONOTONIC = ffi::PA_STREAM_NOT_MONOTONIC; 231 | const AUTO_TIMING_UPDATE = ffi::PA_STREAM_AUTO_TIMING_UPDATE; 232 | const NO_REMAP_CHANNELS = ffi::PA_STREAM_NO_REMAP_CHANNELS; 233 | const NO_REMIX_CHANNELS = ffi::PA_STREAM_NO_REMIX_CHANNELS; 234 | const FIX_FORMAT = ffi::PA_STREAM_FIX_FORMAT; 235 | const FIX_RATE = ffi::PA_STREAM_FIX_RATE; 236 | const FIX_CHANNELS = ffi::PA_STREAM_FIX_CHANNELS; 237 | const DONT_MOVE = ffi::PA_STREAM_DONT_MOVE; 238 | const VARIABLE_RATE = ffi::PA_STREAM_VARIABLE_RATE; 239 | const PEAK_DETECT = ffi::PA_STREAM_PEAK_DETECT; 240 | const START_MUTED = ffi::PA_STREAM_START_MUTED; 241 | const ADJUST_LATENCY = ffi::PA_STREAM_ADJUST_LATENCY; 242 | const EARLY_REQUESTS = ffi::PA_STREAM_EARLY_REQUESTS; 243 | const DONT_INHIBIT_AUTO_SUSPEND = ffi::PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND; 244 | const START_UNMUTED = ffi::PA_STREAM_START_UNMUTED; 245 | const FAIL_ON_SUSPEND = ffi::PA_STREAM_FAIL_ON_SUSPEND; 246 | const RELATIVE_VOLUME = ffi::PA_STREAM_RELATIVE_VOLUME; 247 | const PASSTHROUGH = ffi::PA_STREAM_PASSTHROUGH; 248 | } 249 | } 250 | 251 | impl StreamFlags { 252 | pub fn try_from(x: ffi::pa_stream_flags_t) -> Option { 253 | if (x & !(ffi::PA_STREAM_NOFLAGS 254 | | ffi::PA_STREAM_START_CORKED 255 | | ffi::PA_STREAM_INTERPOLATE_TIMING 256 | | ffi::PA_STREAM_NOT_MONOTONIC 257 | | ffi::PA_STREAM_AUTO_TIMING_UPDATE 258 | | ffi::PA_STREAM_NO_REMAP_CHANNELS 259 | | ffi::PA_STREAM_NO_REMIX_CHANNELS 260 | | ffi::PA_STREAM_FIX_FORMAT 261 | | ffi::PA_STREAM_FIX_RATE 262 | | ffi::PA_STREAM_FIX_CHANNELS 263 | | ffi::PA_STREAM_DONT_MOVE 264 | | ffi::PA_STREAM_VARIABLE_RATE 265 | | ffi::PA_STREAM_PEAK_DETECT 266 | | ffi::PA_STREAM_START_MUTED 267 | | ffi::PA_STREAM_ADJUST_LATENCY 268 | | ffi::PA_STREAM_EARLY_REQUESTS 269 | | ffi::PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND 270 | | ffi::PA_STREAM_START_UNMUTED 271 | | ffi::PA_STREAM_FAIL_ON_SUSPEND 272 | | ffi::PA_STREAM_RELATIVE_VOLUME 273 | | ffi::PA_STREAM_PASSTHROUGH)) 274 | == 0 275 | { 276 | Some(unsafe { ::std::mem::transmute::(x) }) 277 | } else { 278 | None 279 | } 280 | } 281 | } 282 | 283 | impl From for ffi::pa_stream_flags_t { 284 | fn from(val: StreamFlags) -> Self { 285 | val.bits() as ffi::pa_stream_flags_t 286 | } 287 | } 288 | 289 | pub enum StreamLatency { 290 | Positive(u64), 291 | Negative(u64), 292 | } 293 | 294 | bitflags! { 295 | pub struct SubscriptionMask : u32 { 296 | const SINK = ffi::PA_SUBSCRIPTION_MASK_SINK; 297 | const SOURCE = ffi::PA_SUBSCRIPTION_MASK_SOURCE; 298 | const SINK_INPUT = ffi::PA_SUBSCRIPTION_MASK_SINK_INPUT; 299 | const SOURCE_OUTPUT = ffi::PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT; 300 | const MODULE = ffi::PA_SUBSCRIPTION_MASK_MODULE; 301 | const CLIENT = ffi::PA_SUBSCRIPTION_MASK_CLIENT; 302 | const SAMPLE_CACHE = ffi::PA_SUBSCRIPTION_MASK_SAMPLE_CACHE; 303 | const SERVER = ffi::PA_SUBSCRIPTION_MASK_SERVER; 304 | const AUTOLOAD = ffi::PA_SUBSCRIPTION_MASK_AUTOLOAD; 305 | const CARD = ffi::PA_SUBSCRIPTION_MASK_CARD; 306 | } 307 | } 308 | 309 | impl SubscriptionMask { 310 | pub fn try_from(x: ffi::pa_subscription_mask_t) -> Option { 311 | if (x & !ffi::PA_SUBSCRIPTION_MASK_ALL) == 0 { 312 | Some(unsafe { ::std::mem::transmute::(x) }) 313 | } else { 314 | None 315 | } 316 | } 317 | } 318 | 319 | impl From for ffi::pa_subscription_mask_t { 320 | fn from(val: SubscriptionMask) -> Self { 321 | val.bits() as ffi::pa_subscription_mask_t 322 | } 323 | } 324 | 325 | #[repr(i32)] 326 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 327 | pub enum SubscriptionEventFacility { 328 | Sink = ffi::PA_SUBSCRIPTION_EVENT_SINK, 329 | Source = ffi::PA_SUBSCRIPTION_EVENT_SOURCE, 330 | SinkInput = ffi::PA_SUBSCRIPTION_EVENT_SINK_INPUT, 331 | SourceOutput = ffi::PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT, 332 | Module = ffi::PA_SUBSCRIPTION_EVENT_MODULE, 333 | Client = ffi::PA_SUBSCRIPTION_EVENT_CLIENT, 334 | SampleCache = ffi::PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE, 335 | Server = ffi::PA_SUBSCRIPTION_EVENT_SERVER, 336 | Autoload = ffi::PA_SUBSCRIPTION_EVENT_AUTOLOAD, 337 | Card = ffi::PA_SUBSCRIPTION_EVENT_CARD, 338 | } 339 | 340 | #[repr(C)] 341 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 342 | pub enum SubscriptionEventType { 343 | New, 344 | Change, 345 | Remove, 346 | } 347 | 348 | #[repr(C)] 349 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 350 | pub struct SubscriptionEvent(ffi::pa_subscription_event_type_t); 351 | impl SubscriptionEvent { 352 | pub fn try_from(x: ffi::pa_subscription_event_type_t) -> Option { 353 | if (x & !(ffi::PA_SUBSCRIPTION_EVENT_TYPE_MASK | ffi::PA_SUBSCRIPTION_EVENT_FACILITY_MASK)) 354 | == 0 355 | { 356 | Some(SubscriptionEvent(x)) 357 | } else { 358 | None 359 | } 360 | } 361 | 362 | pub fn event_facility(self) -> SubscriptionEventFacility { 363 | unsafe { ::std::mem::transmute(self.0 & ffi::PA_SUBSCRIPTION_EVENT_FACILITY_MASK) } 364 | } 365 | 366 | pub fn event_type(self) -> SubscriptionEventType { 367 | unsafe { ::std::mem::transmute((self.0 & ffi::PA_SUBSCRIPTION_EVENT_TYPE_MASK) >> 4) } 368 | } 369 | } 370 | 371 | #[repr(i32)] 372 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 373 | pub enum SeekMode { 374 | Relative = ffi::PA_SEEK_RELATIVE, 375 | Absolute = ffi::PA_SEEK_ABSOLUTE, 376 | RelativeOnRead = ffi::PA_SEEK_RELATIVE_ON_READ, 377 | RelativeEnd = ffi::PA_SEEK_RELATIVE_END, 378 | } 379 | 380 | impl SeekMode { 381 | pub fn try_from(x: ffi::pa_seek_mode_t) -> Option { 382 | if (ffi::PA_SEEK_RELATIVE..=ffi::PA_SEEK_RELATIVE_END).contains(&x) { 383 | Some(unsafe { ::std::mem::transmute::(x) }) 384 | } else { 385 | None 386 | } 387 | } 388 | } 389 | 390 | impl From for ffi::pa_seek_mode_t { 391 | fn from(val: SeekMode) -> Self { 392 | val as ffi::pa_seek_mode_t 393 | } 394 | } 395 | 396 | bitflags! { 397 | #[derive(Debug, Clone, Copy)] 398 | pub struct SinkFlags: u32 { 399 | const HW_VOLUME_CTRL = ffi::PA_SINK_HW_VOLUME_CTRL; 400 | const LATENCY = ffi::PA_SINK_LATENCY; 401 | const HARDWARE = ffi::PA_SINK_HARDWARE; 402 | const NETWORK = ffi::PA_SINK_NETWORK; 403 | const HW_MUTE_CTRL = ffi::PA_SINK_HW_MUTE_CTRL; 404 | const DECIBEL_VOLUME = ffi::PA_SINK_DECIBEL_VOLUME; 405 | const FLAT_VOLUME = ffi::PA_SINK_FLAT_VOLUME; 406 | const DYNAMIC_LATENCY = ffi::PA_SINK_DYNAMIC_LATENCY; 407 | const SET_FORMATS = ffi::PA_SINK_SET_FORMATS; 408 | } 409 | } 410 | 411 | impl SinkFlags { 412 | pub fn try_from(x: ffi::pa_sink_flags_t) -> Option { 413 | if (x & !(ffi::PA_SINK_NOFLAGS 414 | | ffi::PA_SINK_HW_VOLUME_CTRL 415 | | ffi::PA_SINK_LATENCY 416 | | ffi::PA_SINK_HARDWARE 417 | | ffi::PA_SINK_NETWORK 418 | | ffi::PA_SINK_HW_MUTE_CTRL 419 | | ffi::PA_SINK_DECIBEL_VOLUME 420 | | ffi::PA_SINK_DYNAMIC_LATENCY 421 | | ffi::PA_SINK_FLAT_VOLUME 422 | | ffi::PA_SINK_SET_FORMATS)) 423 | == 0 424 | { 425 | Some(unsafe { ::std::mem::transmute::(x) }) 426 | } else { 427 | None 428 | } 429 | } 430 | } 431 | 432 | #[repr(i32)] 433 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 434 | pub enum SinkState { 435 | InvalidState = ffi::PA_SINK_INVALID_STATE, 436 | Running = ffi::PA_SINK_RUNNING, 437 | Idle = ffi::PA_SINK_IDLE, 438 | Suspended = ffi::PA_SINK_SUSPENDED, 439 | Init = ffi::PA_SINK_INIT, 440 | Unlinked = ffi::PA_SINK_UNLINKED, 441 | } 442 | 443 | bitflags! { 444 | pub struct SourceFlags: u32 { 445 | const HW_VOLUME_CTRL = ffi::PA_SOURCE_HW_VOLUME_CTRL; 446 | const LATENCY = ffi::PA_SOURCE_LATENCY; 447 | const HARDWARE = ffi::PA_SOURCE_HARDWARE; 448 | const NETWORK = ffi::PA_SOURCE_NETWORK; 449 | const HW_MUTE_CTRL = ffi::PA_SOURCE_HW_MUTE_CTRL; 450 | const DECIBEL_VOLUME = ffi::PA_SOURCE_DECIBEL_VOLUME; 451 | const DYNAMIC_LATENCY = ffi::PA_SOURCE_DYNAMIC_LATENCY; 452 | const FLAT_VOLUME = ffi::PA_SOURCE_FLAT_VOLUME; 453 | } 454 | } 455 | 456 | impl From for ffi::pa_source_flags_t { 457 | fn from(val: SourceFlags) -> Self { 458 | val.bits() as ffi::pa_source_flags_t 459 | } 460 | } 461 | 462 | #[repr(i32)] 463 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 464 | pub enum SourceState { 465 | InvalidState = ffi::PA_SOURCE_INVALID_STATE, 466 | Running = ffi::PA_SOURCE_RUNNING, 467 | Idle = ffi::PA_SOURCE_IDLE, 468 | Suspended = ffi::PA_SOURCE_SUSPENDED, 469 | Init = ffi::PA_SOURCE_INIT, 470 | Unlinked = ffi::PA_SOURCE_UNLINKED, 471 | } 472 | 473 | #[repr(i32)] 474 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 475 | pub enum PortAvailable { 476 | Unknown = ffi::PA_PORT_AVAILABLE_UNKNOWN, 477 | No = ffi::PA_PORT_AVAILABLE_NO, 478 | Yes = ffi::PA_PORT_AVAILABLE_YES, 479 | } 480 | 481 | impl PortAvailable { 482 | pub fn try_from(x: ffi::pa_port_available_t) -> Option { 483 | if (ffi::PA_PORT_AVAILABLE_UNKNOWN..=ffi::PA_PORT_AVAILABLE_YES).contains(&x) { 484 | Some(unsafe { ::std::mem::transmute::(x) }) 485 | } else { 486 | None 487 | } 488 | } 489 | } 490 | 491 | impl From for ffi::pa_port_available_t { 492 | fn from(val: PortAvailable) -> Self { 493 | val as ffi::pa_port_available_t 494 | } 495 | } 496 | 497 | #[repr(i32)] 498 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] 499 | pub enum ChannelPosition { 500 | #[default] 501 | Invalid = ffi::PA_CHANNEL_POSITION_INVALID, 502 | Mono = ffi::PA_CHANNEL_POSITION_MONO, 503 | FrontLeft = ffi::PA_CHANNEL_POSITION_FRONT_LEFT, 504 | FrontRight = ffi::PA_CHANNEL_POSITION_FRONT_RIGHT, 505 | FrontCenter = ffi::PA_CHANNEL_POSITION_FRONT_CENTER, 506 | RearCenter = ffi::PA_CHANNEL_POSITION_REAR_CENTER, 507 | RearLeft = ffi::PA_CHANNEL_POSITION_REAR_LEFT, 508 | RearRight = ffi::PA_CHANNEL_POSITION_REAR_RIGHT, 509 | LowFreqEffects = ffi::PA_CHANNEL_POSITION_LFE, 510 | FrontLeftOfCenter = ffi::PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, 511 | FrontRightOfCenter = ffi::PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, 512 | SideLeft = ffi::PA_CHANNEL_POSITION_SIDE_LEFT, 513 | SideRight = ffi::PA_CHANNEL_POSITION_SIDE_RIGHT, 514 | Aux0 = ffi::PA_CHANNEL_POSITION_AUX0, 515 | Aux1 = ffi::PA_CHANNEL_POSITION_AUX1, 516 | Aux2 = ffi::PA_CHANNEL_POSITION_AUX2, 517 | Aux3 = ffi::PA_CHANNEL_POSITION_AUX3, 518 | Aux4 = ffi::PA_CHANNEL_POSITION_AUX4, 519 | Aux5 = ffi::PA_CHANNEL_POSITION_AUX5, 520 | Aux6 = ffi::PA_CHANNEL_POSITION_AUX6, 521 | Aux7 = ffi::PA_CHANNEL_POSITION_AUX7, 522 | Aux8 = ffi::PA_CHANNEL_POSITION_AUX8, 523 | Aux9 = ffi::PA_CHANNEL_POSITION_AUX9, 524 | Aux10 = ffi::PA_CHANNEL_POSITION_AUX10, 525 | Aux11 = ffi::PA_CHANNEL_POSITION_AUX11, 526 | Aux12 = ffi::PA_CHANNEL_POSITION_AUX12, 527 | Aux13 = ffi::PA_CHANNEL_POSITION_AUX13, 528 | Aux14 = ffi::PA_CHANNEL_POSITION_AUX14, 529 | Aux15 = ffi::PA_CHANNEL_POSITION_AUX15, 530 | Aux16 = ffi::PA_CHANNEL_POSITION_AUX16, 531 | Aux17 = ffi::PA_CHANNEL_POSITION_AUX17, 532 | Aux18 = ffi::PA_CHANNEL_POSITION_AUX18, 533 | Aux19 = ffi::PA_CHANNEL_POSITION_AUX19, 534 | Aux20 = ffi::PA_CHANNEL_POSITION_AUX20, 535 | Aux21 = ffi::PA_CHANNEL_POSITION_AUX21, 536 | Aux22 = ffi::PA_CHANNEL_POSITION_AUX22, 537 | Aux23 = ffi::PA_CHANNEL_POSITION_AUX23, 538 | Aux24 = ffi::PA_CHANNEL_POSITION_AUX24, 539 | Aux25 = ffi::PA_CHANNEL_POSITION_AUX25, 540 | Aux26 = ffi::PA_CHANNEL_POSITION_AUX26, 541 | Aux27 = ffi::PA_CHANNEL_POSITION_AUX27, 542 | Aux28 = ffi::PA_CHANNEL_POSITION_AUX28, 543 | Aux29 = ffi::PA_CHANNEL_POSITION_AUX29, 544 | Aux30 = ffi::PA_CHANNEL_POSITION_AUX30, 545 | Aux31 = ffi::PA_CHANNEL_POSITION_AUX31, 546 | TopCenter = ffi::PA_CHANNEL_POSITION_TOP_CENTER, 547 | TopFrontLeft = ffi::PA_CHANNEL_POSITION_TOP_FRONT_LEFT, 548 | TopFrontRight = ffi::PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, 549 | TopFrontCenter = ffi::PA_CHANNEL_POSITION_TOP_FRONT_CENTER, 550 | TopRearLeft = ffi::PA_CHANNEL_POSITION_TOP_REAR_LEFT, 551 | TopRearRight = ffi::PA_CHANNEL_POSITION_TOP_REAR_RIGHT, 552 | TopRearCenter = ffi::PA_CHANNEL_POSITION_TOP_REAR_CENTER, 553 | } 554 | 555 | impl ChannelPosition { 556 | pub fn try_from(x: ffi::pa_channel_position_t) -> Option { 557 | if (ffi::PA_CHANNEL_POSITION_INVALID..ffi::PA_CHANNEL_POSITION_MAX).contains(&x) { 558 | Some(unsafe { ::std::mem::transmute::(x) }) 559 | } else { 560 | None 561 | } 562 | } 563 | } 564 | 565 | impl From for ffi::pa_channel_position_t { 566 | fn from(val: ChannelPosition) -> Self { 567 | val as ffi::pa_channel_position_t 568 | } 569 | } 570 | pub type Result = ::std::result::Result; 571 | 572 | pub trait CVolumeExt { 573 | fn set(&mut self, channels: c_uint, v: Volume); 574 | fn set_balance(&mut self, map: &ChannelMap, new_balance: f32); 575 | } 576 | 577 | impl CVolumeExt for CVolume { 578 | fn set(&mut self, channels: c_uint, v: Volume) { 579 | unsafe { 580 | ffi::pa_cvolume_set(self, channels, v); 581 | } 582 | } 583 | 584 | fn set_balance(&mut self, map: &ChannelMap, new_balance: f32) { 585 | unsafe { 586 | ffi::pa_cvolume_set_balance(self, map, new_balance); 587 | } 588 | } 589 | } 590 | 591 | pub trait ChannelMapExt { 592 | fn init() -> ChannelMap; 593 | fn init_auto(ch: u32, def: ffi::pa_channel_map_def_t) -> Option; 594 | fn can_balance(&self) -> bool; 595 | } 596 | 597 | impl ChannelMapExt for ChannelMap { 598 | fn init() -> ChannelMap { 599 | let mut cm = ChannelMap::default(); 600 | unsafe { 601 | ffi::pa_channel_map_init(&mut cm); 602 | } 603 | cm 604 | } 605 | fn init_auto(ch: u32, def: ffi::pa_channel_map_def_t) -> Option { 606 | let mut cm = ChannelMap::default(); 607 | let r: *mut ffi::pa_channel_map = 608 | unsafe { ffi::pa_channel_map_init_auto(&mut cm, ch, def) }; 609 | if r.is_null() { 610 | None 611 | } else { 612 | Some(cm) 613 | } 614 | } 615 | fn can_balance(&self) -> bool { 616 | unsafe { ffi::pa_channel_map_can_balance(self) > 0 } 617 | } 618 | } 619 | 620 | pub trait ProplistExt { 621 | fn proplist(&self) -> Proplist; 622 | } 623 | 624 | impl ProplistExt for SinkInfo { 625 | fn proplist(&self) -> Proplist { 626 | unsafe { proplist::from_raw_ptr(self.proplist) } 627 | } 628 | } 629 | 630 | impl ProplistExt for SourceInfo { 631 | fn proplist(&self) -> Proplist { 632 | unsafe { proplist::from_raw_ptr(self.proplist) } 633 | } 634 | } 635 | 636 | pub trait SampleSpecExt { 637 | fn frame_size(&self) -> usize; 638 | fn sample_size(&self) -> usize; 639 | } 640 | 641 | impl SampleSpecExt for SampleSpec { 642 | fn frame_size(&self) -> usize { 643 | unsafe { ffi::pa_frame_size(self) } 644 | } 645 | fn sample_size(&self) -> usize { 646 | unsafe { ffi::pa_sample_size(self) } 647 | } 648 | } 649 | 650 | pub trait USecExt { 651 | fn to_bytes(self, spec: &SampleSpec) -> usize; 652 | } 653 | 654 | impl USecExt for USec { 655 | fn to_bytes(self, spec: &SampleSpec) -> usize { 656 | unsafe { ffi::pa_usec_to_bytes(self, spec) } 657 | } 658 | } 659 | 660 | pub fn library_version() -> *const c_char { 661 | unsafe { ffi::pa_get_library_version() } 662 | } 663 | 664 | pub fn sw_volume_from_linear(vol: f64) -> Volume { 665 | unsafe { ffi::pa_sw_volume_from_linear(vol) } 666 | } 667 | 668 | pub fn rtclock_now() -> USec { 669 | unsafe { ffi::pa_rtclock_now() } 670 | } 671 | -------------------------------------------------------------------------------- /pulse-rs/src/mainloop_api.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use ffi; 7 | use std::mem; 8 | use std::os::raw::c_void; 9 | 10 | // Note: For all clippy allowed warnings, see https://github.com/mozilla/cubeb-pulse-rs/issues/95 11 | // for the effort to fix them. 12 | 13 | #[allow(non_camel_case_types)] 14 | type pa_once_cb_t = 15 | Option; 16 | fn wrap_once_cb(_: F) -> pa_once_cb_t 17 | where 18 | F: Fn(&MainloopApi, *mut c_void), 19 | { 20 | assert!(mem::size_of::() == 0); 21 | 22 | unsafe extern "C" fn wrapped(m: *mut ffi::pa_mainloop_api, userdata: *mut c_void) 23 | where 24 | F: Fn(&MainloopApi, *mut c_void), 25 | { 26 | let api = from_raw_ptr(m); 27 | #[allow(clippy::missing_transmute_annotations)] 28 | mem::transmute::<_, &F>(&())(&api, userdata); 29 | #[allow(clippy::forget_non_drop)] 30 | mem::forget(api); 31 | } 32 | 33 | Some(wrapped::) 34 | } 35 | 36 | pub struct MainloopApi(*mut ffi::pa_mainloop_api); 37 | 38 | impl MainloopApi { 39 | #[allow(clippy::mut_from_ref)] 40 | pub fn raw_mut(&self) -> &mut ffi::pa_mainloop_api { 41 | unsafe { &mut *self.0 } 42 | } 43 | 44 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 45 | pub fn once(&self, cb: CB, userdata: *mut c_void) 46 | where 47 | CB: Fn(&MainloopApi, *mut c_void), 48 | { 49 | let wrapped = wrap_once_cb(cb); 50 | unsafe { 51 | ffi::pa_mainloop_api_once(self.raw_mut(), wrapped, userdata); 52 | } 53 | } 54 | 55 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 56 | pub fn time_free(&self, e: *mut ffi::pa_time_event) { 57 | unsafe { 58 | if let Some(f) = self.raw_mut().time_free { 59 | f(e); 60 | } 61 | } 62 | } 63 | } 64 | 65 | pub unsafe fn from_raw_ptr(raw: *mut ffi::pa_mainloop_api) -> MainloopApi { 66 | MainloopApi(raw) 67 | } 68 | -------------------------------------------------------------------------------- /pulse-rs/src/operation.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use ffi; 7 | 8 | #[derive(Debug)] 9 | pub struct Operation(*mut ffi::pa_operation); 10 | 11 | impl Operation { 12 | // See https://github.com/mozilla/cubeb-pulse-rs/issues/95 13 | #[allow(clippy::missing_safety_doc)] 14 | pub unsafe fn from_raw_ptr(raw: *mut ffi::pa_operation) -> Operation { 15 | Operation(raw) 16 | } 17 | 18 | pub fn cancel(&mut self) { 19 | unsafe { 20 | ffi::pa_operation_cancel(self.0); 21 | } 22 | } 23 | 24 | pub fn get_state(&self) -> ffi::pa_operation_state_t { 25 | unsafe { ffi::pa_operation_get_state(self.0) } 26 | } 27 | } 28 | 29 | impl Clone for Operation { 30 | fn clone(&self) -> Self { 31 | Operation(unsafe { ffi::pa_operation_ref(self.0) }) 32 | } 33 | } 34 | 35 | impl Drop for Operation { 36 | fn drop(&mut self) { 37 | unsafe { 38 | ffi::pa_operation_unref(self.0); 39 | } 40 | } 41 | } 42 | 43 | pub unsafe fn from_raw_ptr(raw: *mut ffi::pa_operation) -> Operation { 44 | Operation::from_raw_ptr(raw) 45 | } 46 | -------------------------------------------------------------------------------- /pulse-rs/src/proplist.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use ffi; 7 | use std::ffi::{CStr, CString}; 8 | 9 | #[derive(Debug)] 10 | pub struct Proplist(*mut ffi::pa_proplist); 11 | 12 | impl Proplist { 13 | pub fn gets(&self, key: T) -> Option<&CStr> 14 | where 15 | T: Into>, 16 | { 17 | let key = match CString::new(key) { 18 | Ok(k) => k, 19 | _ => return None, 20 | }; 21 | let r = unsafe { ffi::pa_proplist_gets(self.0, key.as_ptr()) }; 22 | if r.is_null() { 23 | None 24 | } else { 25 | Some(unsafe { CStr::from_ptr(r) }) 26 | } 27 | } 28 | } 29 | 30 | pub unsafe fn from_raw_ptr(raw: *mut ffi::pa_proplist) -> Proplist { 31 | Proplist(raw) 32 | } 33 | -------------------------------------------------------------------------------- /pulse-rs/src/stream.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use context; 7 | use ffi; 8 | use operation; 9 | use std::ffi::CStr; 10 | use std::mem::{self, forget, MaybeUninit}; 11 | use std::os::raw::{c_int, c_void}; 12 | use std::ptr; 13 | use util::*; 14 | use *; 15 | 16 | #[derive(Debug)] 17 | pub struct Stream(*mut ffi::pa_stream); 18 | 19 | // Note: For all clippy allowed warnings, see https://github.com/mozilla/cubeb-pulse-rs/issues/95 20 | // for the effort to fix them. 21 | 22 | impl Stream { 23 | pub fn new<'a, CM>( 24 | c: &Context, 25 | name: &::std::ffi::CStr, 26 | ss: &SampleSpec, 27 | map: CM, 28 | ) -> Option 29 | where 30 | CM: Into>, 31 | { 32 | let ptr = unsafe { 33 | ffi::pa_stream_new( 34 | c.raw_mut(), 35 | name.as_ptr(), 36 | ss as *const _, 37 | to_ptr(map.into()), 38 | ) 39 | }; 40 | if ptr.is_null() { 41 | None 42 | } else { 43 | Some(Stream(ptr)) 44 | } 45 | } 46 | 47 | #[doc(hidden)] 48 | #[allow(clippy::mut_from_ref)] 49 | pub fn raw_mut(&self) -> &mut ffi::pa_stream { 50 | unsafe { &mut *self.0 } 51 | } 52 | 53 | pub fn unref(self) { 54 | unsafe { 55 | ffi::pa_stream_unref(self.raw_mut()); 56 | } 57 | } 58 | 59 | pub fn get_state(&self) -> StreamState { 60 | StreamState::try_from(unsafe { ffi::pa_stream_get_state(self.raw_mut()) }) 61 | .expect("pa_stream_get_state returned invalid StreamState") 62 | } 63 | 64 | pub fn get_context(&self) -> Option { 65 | let ptr = unsafe { ffi::pa_stream_get_context(self.raw_mut()) }; 66 | if ptr.is_null() { 67 | return None; 68 | } 69 | 70 | let ctx = unsafe { context::from_raw_ptr(ptr) }; 71 | Some(ctx) 72 | } 73 | 74 | pub fn get_index(&self) -> u32 { 75 | unsafe { ffi::pa_stream_get_index(self.raw_mut()) } 76 | } 77 | 78 | pub fn get_device_name(&self) -> Result<&CStr> { 79 | let r = unsafe { ffi::pa_stream_get_device_name(self.raw_mut()) }; 80 | if r.is_null() { 81 | let err = if let Some(c) = self.get_context() { 82 | c.errno() 83 | } else { 84 | ffi::PA_ERR_UNKNOWN 85 | }; 86 | return Err(ErrorCode::from_error_code(err)); 87 | } 88 | Ok(unsafe { CStr::from_ptr(r) }) 89 | } 90 | 91 | pub fn is_suspended(&self) -> Result { 92 | let r = unsafe { ffi::pa_stream_is_suspended(self.raw_mut()) }; 93 | error_result!(r != 0, r) 94 | } 95 | 96 | pub fn is_corked(&self) -> Result { 97 | let r = unsafe { ffi::pa_stream_is_corked(self.raw_mut()) }; 98 | error_result!(r != 0, r) 99 | } 100 | 101 | pub fn connect_playback<'a, D, A, V, S>( 102 | &self, 103 | dev: D, 104 | attr: A, 105 | flags: StreamFlags, 106 | volume: V, 107 | sync_stream: S, 108 | ) -> Result<()> 109 | where 110 | D: Into>, 111 | A: Into>, 112 | V: Into>, 113 | S: Into>, 114 | { 115 | let r = unsafe { 116 | ffi::pa_stream_connect_playback( 117 | self.raw_mut(), 118 | str_to_ptr(dev.into()), 119 | to_ptr(attr.into()), 120 | flags.into(), 121 | to_ptr(volume.into()), 122 | map_to_mut_ptr(sync_stream.into(), |p| p.0), 123 | ) 124 | }; 125 | error_result!((), r) 126 | } 127 | 128 | pub fn connect_record<'a, D, A>(&self, dev: D, attr: A, flags: StreamFlags) -> Result<()> 129 | where 130 | D: Into>, 131 | A: Into>, 132 | { 133 | let r = unsafe { 134 | ffi::pa_stream_connect_record( 135 | self.raw_mut(), 136 | str_to_ptr(dev.into()), 137 | to_ptr(attr.into()), 138 | flags.into(), 139 | ) 140 | }; 141 | error_result!((), r) 142 | } 143 | 144 | pub fn disconnect(&self) -> Result<()> { 145 | let r = unsafe { ffi::pa_stream_disconnect(self.raw_mut()) }; 146 | error_result!((), r) 147 | } 148 | 149 | pub fn begin_write(&self, req_bytes: usize) -> Result<(*mut c_void, usize)> { 150 | let mut data: *mut c_void = ptr::null_mut(); 151 | let mut nbytes = req_bytes; 152 | let r = unsafe { ffi::pa_stream_begin_write(self.raw_mut(), &mut data, &mut nbytes) }; 153 | error_result!((data, nbytes), r) 154 | } 155 | 156 | pub fn cancel_write(&self) -> Result<()> { 157 | let r = unsafe { ffi::pa_stream_cancel_write(self.raw_mut()) }; 158 | error_result!((), r) 159 | } 160 | 161 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 162 | pub fn write( 163 | &self, 164 | data: *const c_void, 165 | nbytes: usize, 166 | offset: i64, 167 | seek: SeekMode, 168 | ) -> Result<()> { 169 | let r = unsafe { 170 | ffi::pa_stream_write(self.raw_mut(), data, nbytes, None, offset, seek.into()) 171 | }; 172 | error_result!((), r) 173 | } 174 | 175 | #[allow(clippy::missing_safety_doc)] 176 | pub unsafe fn peek(&self, data: *mut *const c_void, length: *mut usize) -> Result<()> { 177 | let r = ffi::pa_stream_peek(self.raw_mut(), data, length); 178 | error_result!((), r) 179 | } 180 | 181 | pub fn drop(&self) -> Result<()> { 182 | let r = unsafe { ffi::pa_stream_drop(self.raw_mut()) }; 183 | error_result!((), r) 184 | } 185 | 186 | pub fn writable_size(&self) -> Result { 187 | let r = unsafe { ffi::pa_stream_writable_size(self.raw_mut()) }; 188 | if r == usize::MAX { 189 | let err = if let Some(c) = self.get_context() { 190 | c.errno() 191 | } else { 192 | ffi::PA_ERR_UNKNOWN 193 | }; 194 | return Err(ErrorCode::from_error_code(err)); 195 | } 196 | Ok(r) 197 | } 198 | 199 | pub fn readable_size(&self) -> Result { 200 | let r = unsafe { ffi::pa_stream_readable_size(self.raw_mut()) }; 201 | if r == usize::MAX { 202 | let err = if let Some(c) = self.get_context() { 203 | c.errno() 204 | } else { 205 | ffi::PA_ERR_UNKNOWN 206 | }; 207 | return Err(ErrorCode::from_error_code(err)); 208 | } 209 | Ok(r) 210 | } 211 | 212 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 213 | pub fn update_timing_info(&self, _: CB, userdata: *mut c_void) -> Result 214 | where 215 | CB: Fn(&Stream, i32, *mut c_void), 216 | { 217 | assert_eq!(mem::size_of::(), 0); 218 | 219 | // See: A note about `wrapped` functions 220 | unsafe extern "C" fn wrapped( 221 | s: *mut ffi::pa_stream, 222 | success: c_int, 223 | userdata: *mut c_void, 224 | ) where 225 | F: Fn(&Stream, i32, *mut c_void), 226 | { 227 | let mut stm = stream::from_raw_ptr(s); 228 | let cb = MaybeUninit::::uninit(); 229 | (*cb.as_ptr())(&mut stm, success, userdata); 230 | #[allow(clippy::forget_non_drop)] 231 | forget(stm); 232 | } 233 | 234 | let r = unsafe { 235 | ffi::pa_stream_update_timing_info(self.raw_mut(), Some(wrapped::), userdata) 236 | }; 237 | if r.is_null() { 238 | let err = if let Some(c) = self.get_context() { 239 | c.errno() 240 | } else { 241 | ffi::PA_ERR_UNKNOWN 242 | }; 243 | return Err(ErrorCode::from_error_code(err)); 244 | } 245 | Ok(unsafe { operation::from_raw_ptr(r) }) 246 | } 247 | 248 | pub fn clear_state_callback(&self) { 249 | unsafe { 250 | ffi::pa_stream_set_state_callback(self.raw_mut(), None, ptr::null_mut()); 251 | } 252 | } 253 | 254 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 255 | pub fn set_state_callback(&self, _: CB, userdata: *mut c_void) 256 | where 257 | CB: Fn(&Stream, *mut c_void), 258 | { 259 | assert_eq!(mem::size_of::(), 0); 260 | 261 | // See: A note about `wrapped` functions 262 | unsafe extern "C" fn wrapped(s: *mut ffi::pa_stream, userdata: *mut c_void) 263 | where 264 | F: Fn(&Stream, *mut c_void), 265 | { 266 | let mut stm = stream::from_raw_ptr(s); 267 | let cb = MaybeUninit::::uninit(); 268 | (*cb.as_ptr())(&mut stm, userdata); 269 | #[allow(clippy::forget_non_drop)] 270 | forget(stm); 271 | } 272 | 273 | unsafe { 274 | ffi::pa_stream_set_state_callback(self.raw_mut(), Some(wrapped::), userdata); 275 | } 276 | } 277 | 278 | pub fn clear_write_callback(&self) { 279 | unsafe { 280 | ffi::pa_stream_set_write_callback(self.raw_mut(), None, ptr::null_mut()); 281 | } 282 | } 283 | 284 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 285 | pub fn set_write_callback(&self, _: CB, userdata: *mut c_void) 286 | where 287 | CB: Fn(&Stream, usize, *mut c_void), 288 | { 289 | assert_eq!(mem::size_of::(), 0); 290 | 291 | // See: A note about `wrapped` functions 292 | unsafe extern "C" fn wrapped( 293 | s: *mut ffi::pa_stream, 294 | nbytes: usize, 295 | userdata: *mut c_void, 296 | ) where 297 | F: Fn(&Stream, usize, *mut c_void), 298 | { 299 | let mut stm = stream::from_raw_ptr(s); 300 | let cb = MaybeUninit::::uninit(); 301 | (*cb.as_ptr())(&mut stm, nbytes, userdata); 302 | #[allow(clippy::forget_non_drop)] 303 | forget(stm); 304 | } 305 | 306 | unsafe { 307 | ffi::pa_stream_set_write_callback(self.raw_mut(), Some(wrapped::), userdata); 308 | } 309 | } 310 | 311 | pub fn clear_read_callback(&self) { 312 | unsafe { 313 | ffi::pa_stream_set_read_callback(self.raw_mut(), None, ptr::null_mut()); 314 | } 315 | } 316 | 317 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 318 | pub fn set_read_callback(&self, _: CB, userdata: *mut c_void) 319 | where 320 | CB: Fn(&Stream, usize, *mut c_void), 321 | { 322 | assert_eq!(mem::size_of::(), 0); 323 | 324 | // See: A note about `wrapped` functions 325 | unsafe extern "C" fn wrapped( 326 | s: *mut ffi::pa_stream, 327 | nbytes: usize, 328 | userdata: *mut c_void, 329 | ) where 330 | F: Fn(&Stream, usize, *mut c_void), 331 | { 332 | let mut stm = stream::from_raw_ptr(s); 333 | let cb = MaybeUninit::::uninit(); 334 | (*cb.as_ptr())(&mut stm, nbytes, userdata); 335 | #[allow(clippy::forget_non_drop)] 336 | forget(stm); 337 | } 338 | 339 | unsafe { 340 | ffi::pa_stream_set_read_callback(self.raw_mut(), Some(wrapped::), userdata); 341 | } 342 | } 343 | 344 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 345 | pub fn cork(&self, b: i32, _: CB, userdata: *mut c_void) -> Result 346 | where 347 | CB: Fn(&Stream, i32, *mut c_void), 348 | { 349 | assert_eq!(mem::size_of::(), 0); 350 | 351 | // See: A note about `wrapped` functions 352 | unsafe extern "C" fn wrapped( 353 | s: *mut ffi::pa_stream, 354 | success: c_int, 355 | userdata: *mut c_void, 356 | ) where 357 | F: Fn(&Stream, i32, *mut c_void), 358 | { 359 | let mut stm = stream::from_raw_ptr(s); 360 | let cb = MaybeUninit::::uninit(); 361 | (*cb.as_ptr())(&mut stm, success, userdata); 362 | #[allow(clippy::forget_non_drop)] 363 | forget(stm); 364 | } 365 | 366 | let r = unsafe { ffi::pa_stream_cork(self.raw_mut(), b, Some(wrapped::), userdata) }; 367 | if r.is_null() { 368 | let err = if let Some(c) = self.get_context() { 369 | c.errno() 370 | } else { 371 | ffi::PA_ERR_UNKNOWN 372 | }; 373 | return Err(ErrorCode::from_error_code(err)); 374 | } 375 | Ok(unsafe { operation::from_raw_ptr(r) }) 376 | } 377 | 378 | pub fn get_time(&self) -> Result { 379 | let mut usec: USec = 0; 380 | let r = unsafe { ffi::pa_stream_get_time(self.raw_mut(), &mut usec) }; 381 | error_result!(usec, r) 382 | } 383 | 384 | pub fn get_latency(&self) -> Result { 385 | let mut usec: u64 = 0; 386 | let mut negative: i32 = 0; 387 | let r = unsafe { ffi::pa_stream_get_latency(self.raw_mut(), &mut usec, &mut negative) }; 388 | error_result!( 389 | if negative == 0 { 390 | StreamLatency::Positive(usec) 391 | } else { 392 | StreamLatency::Negative(usec) 393 | }, 394 | r 395 | ) 396 | } 397 | 398 | pub fn get_sample_spec(&self) -> &SampleSpec { 399 | unsafe { 400 | let ptr = ffi::pa_stream_get_sample_spec(self.raw_mut()); 401 | debug_assert!(!ptr.is_null()); 402 | &*ptr 403 | } 404 | } 405 | 406 | pub fn get_channel_map(&self) -> &ChannelMap { 407 | unsafe { 408 | let ptr = ffi::pa_stream_get_channel_map(self.raw_mut()); 409 | debug_assert!(!ptr.is_null()); 410 | &*ptr 411 | } 412 | } 413 | 414 | pub fn get_buffer_attr(&self) -> &BufferAttr { 415 | unsafe { 416 | let ptr = ffi::pa_stream_get_buffer_attr(self.raw_mut()); 417 | debug_assert!(!ptr.is_null()); 418 | &*ptr 419 | } 420 | } 421 | 422 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 423 | pub fn set_name(&self, name: &CStr, _: CB, userdata: *mut c_void) -> Result 424 | where 425 | CB: Fn(&Stream, i32, *mut c_void), 426 | { 427 | assert_eq!(mem::size_of::(), 0); 428 | 429 | // See: A note about `wrapped` functions 430 | unsafe extern "C" fn wrapped( 431 | s: *mut ffi::pa_stream, 432 | success: c_int, 433 | userdata: *mut c_void, 434 | ) where 435 | F: Fn(&Stream, i32, *mut c_void), 436 | { 437 | let mut stm = stream::from_raw_ptr(s); 438 | let cb = MaybeUninit::::uninit(); 439 | (*cb.as_ptr())(&mut stm, success, userdata); 440 | #[allow(clippy::forget_non_drop)] 441 | forget(stm); 442 | } 443 | 444 | let r = unsafe { 445 | ffi::pa_stream_set_name(self.raw_mut(), name.as_ptr(), Some(wrapped::), userdata) 446 | }; 447 | if r.is_null() { 448 | let err = if let Some(c) = self.get_context() { 449 | c.errno() 450 | } else { 451 | ffi::PA_ERR_UNKNOWN 452 | }; 453 | return Err(ErrorCode::from_error_code(err)); 454 | } 455 | Ok(unsafe { operation::from_raw_ptr(r) }) 456 | } 457 | } 458 | 459 | #[doc(hidden)] 460 | pub unsafe fn from_raw_ptr(ptr: *mut ffi::pa_stream) -> Stream { 461 | Stream(ptr) 462 | } 463 | -------------------------------------------------------------------------------- /pulse-rs/src/threaded_mainloop.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use ffi; 7 | use mainloop_api; 8 | use mainloop_api::MainloopApi; 9 | use ErrorCode; 10 | use Result; 11 | 12 | #[derive(Debug)] 13 | pub struct ThreadedMainloop(*mut ffi::pa_threaded_mainloop); 14 | 15 | impl ThreadedMainloop { 16 | // see https://github.com/mozilla/cubeb-pulse-rs/issues/95 17 | #[allow(clippy::missing_safety_doc)] 18 | pub unsafe fn from_raw_ptr(raw: *mut ffi::pa_threaded_mainloop) -> Self { 19 | ThreadedMainloop(raw) 20 | } 21 | 22 | pub fn new() -> Self { 23 | unsafe { ThreadedMainloop::from_raw_ptr(ffi::pa_threaded_mainloop_new()) } 24 | } 25 | 26 | // see https://github.com/mozilla/cubeb-pulse-rs/issues/95 27 | #[allow(clippy::mut_from_ref)] 28 | pub fn raw_mut(&self) -> &mut ffi::pa_threaded_mainloop { 29 | unsafe { &mut *self.0 } 30 | } 31 | 32 | pub fn is_null(&self) -> bool { 33 | self.0.is_null() 34 | } 35 | 36 | pub fn start(&self) -> Result<()> { 37 | match unsafe { ffi::pa_threaded_mainloop_start(self.raw_mut()) } { 38 | 0 => Ok(()), 39 | _ => Err(ErrorCode::from_error_code(ffi::PA_ERR_UNKNOWN)), 40 | } 41 | } 42 | 43 | pub fn stop(&self) { 44 | unsafe { 45 | ffi::pa_threaded_mainloop_stop(self.raw_mut()); 46 | } 47 | } 48 | 49 | pub fn lock(&self) { 50 | unsafe { 51 | ffi::pa_threaded_mainloop_lock(self.raw_mut()); 52 | } 53 | } 54 | 55 | pub fn unlock(&self) { 56 | unsafe { 57 | ffi::pa_threaded_mainloop_unlock(self.raw_mut()); 58 | } 59 | } 60 | 61 | pub fn wait(&self) { 62 | unsafe { 63 | ffi::pa_threaded_mainloop_wait(self.raw_mut()); 64 | } 65 | } 66 | 67 | pub fn signal(&self) { 68 | unsafe { 69 | ffi::pa_threaded_mainloop_signal(self.raw_mut(), 0); 70 | } 71 | } 72 | 73 | pub fn get_api(&self) -> MainloopApi { 74 | unsafe { mainloop_api::from_raw_ptr(ffi::pa_threaded_mainloop_get_api(self.raw_mut())) } 75 | } 76 | 77 | pub fn in_thread(&self) -> bool { 78 | unsafe { ffi::pa_threaded_mainloop_in_thread(self.raw_mut()) != 0 } 79 | } 80 | } 81 | 82 | impl ::std::default::Default for ThreadedMainloop { 83 | fn default() -> Self { 84 | ThreadedMainloop(::std::ptr::null_mut()) 85 | } 86 | } 87 | 88 | impl ::std::ops::Drop for ThreadedMainloop { 89 | fn drop(&mut self) { 90 | if !self.is_null() { 91 | unsafe { 92 | ffi::pa_threaded_mainloop_free(self.raw_mut()); 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /pulse-rs/src/util.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use std::ffi::CStr; 7 | use std::os::raw::c_char; 8 | use std::ptr; 9 | 10 | pub trait UnwrapCStr { 11 | fn unwrap_cstr(self) -> *const c_char; 12 | } 13 | 14 | impl<'a, U> UnwrapCStr for U 15 | where 16 | U: Into>, 17 | { 18 | fn unwrap_cstr(self) -> *const c_char { 19 | self.into().map(|o| o.as_ptr()).unwrap_or(std::ptr::null()) 20 | } 21 | } 22 | 23 | pub fn map_to_mut_ptr *mut U>(t: Option<&mut T>, f: F) -> *mut U { 24 | match t { 25 | Some(x) => f(x), 26 | None => ptr::null_mut(), 27 | } 28 | } 29 | 30 | pub fn str_to_ptr(s: Option<&CStr>) -> *const c_char { 31 | match s { 32 | Some(x) => x.as_ptr(), 33 | None => ptr::null(), 34 | } 35 | } 36 | 37 | pub fn to_ptr(t: Option<&T>) -> *const T { 38 | match t { 39 | Some(x) => x as *const T, 40 | None => ptr::null(), 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/backend/context.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use backend::*; 7 | use cubeb_backend::{ 8 | ffi, log_enabled, Context, ContextOps, DeviceCollectionRef, DeviceId, DeviceType, Error, 9 | InputProcessingParams, Ops, Result, Stream, StreamParams, StreamParamsRef, 10 | }; 11 | use pulse::{self, ProplistExt}; 12 | use pulse_ffi::*; 13 | use semver; 14 | use std::cell::RefCell; 15 | use std::default::Default; 16 | use std::ffi::{CStr, CString}; 17 | use std::mem; 18 | use std::os::raw::c_void; 19 | use std::ptr; 20 | 21 | #[derive(Debug)] 22 | pub struct DefaultInfo { 23 | pub sample_spec: pulse::SampleSpec, 24 | pub channel_map: pulse::ChannelMap, 25 | pub flags: pulse::SinkFlags, 26 | } 27 | 28 | pub const PULSE_OPS: Ops = capi_new!(PulseContext, PulseStream); 29 | 30 | #[repr(C)] 31 | #[derive(Debug)] 32 | pub struct PulseContext { 33 | _ops: *const Ops, 34 | pub mainloop: pulse::ThreadedMainloop, 35 | pub context: Option, 36 | pub default_sink_info: Option, 37 | pub context_name: Option, 38 | pub input_collection_changed_callback: ffi::cubeb_device_collection_changed_callback, 39 | pub input_collection_changed_user_ptr: *mut c_void, 40 | pub output_collection_changed_callback: ffi::cubeb_device_collection_changed_callback, 41 | pub output_collection_changed_user_ptr: *mut c_void, 42 | pub error: bool, 43 | pub version_2_0_0: bool, 44 | pub version_0_9_8: bool, 45 | #[cfg(feature = "pulse-dlopen")] 46 | pub libpulse: LibLoader, 47 | devids: RefCell, 48 | } 49 | 50 | impl PulseContext { 51 | #[cfg(feature = "pulse-dlopen")] 52 | fn _new(name: Option) -> Result> { 53 | let libpulse = unsafe { open() }; 54 | if libpulse.is_none() { 55 | cubeb_log!("libpulse not found"); 56 | return Err(Error::error()); 57 | } 58 | 59 | let ctx = Box::new(PulseContext { 60 | _ops: &PULSE_OPS, 61 | libpulse: libpulse.unwrap(), 62 | mainloop: pulse::ThreadedMainloop::new(), 63 | context: None, 64 | default_sink_info: None, 65 | context_name: name, 66 | input_collection_changed_callback: None, 67 | input_collection_changed_user_ptr: ptr::null_mut(), 68 | output_collection_changed_callback: None, 69 | output_collection_changed_user_ptr: ptr::null_mut(), 70 | error: true, 71 | version_0_9_8: false, 72 | version_2_0_0: false, 73 | devids: RefCell::new(Intern::new()), 74 | }); 75 | 76 | Ok(ctx) 77 | } 78 | 79 | #[cfg(not(feature = "pulse-dlopen"))] 80 | fn _new(name: Option) -> Result> { 81 | Ok(Box::new(PulseContext { 82 | _ops: &PULSE_OPS, 83 | mainloop: pulse::ThreadedMainloop::new(), 84 | context: None, 85 | default_sink_info: None, 86 | context_name: name, 87 | input_collection_changed_callback: None, 88 | input_collection_changed_user_ptr: ptr::null_mut(), 89 | output_collection_changed_callback: None, 90 | output_collection_changed_user_ptr: ptr::null_mut(), 91 | error: true, 92 | version_0_9_8: false, 93 | version_2_0_0: false, 94 | devids: RefCell::new(Intern::new()), 95 | })) 96 | } 97 | 98 | fn server_info_cb(context: &pulse::Context, info: Option<&pulse::ServerInfo>, u: *mut c_void) { 99 | fn sink_info_cb(_: &pulse::Context, i: *const pulse::SinkInfo, eol: i32, u: *mut c_void) { 100 | let ctx = unsafe { &mut *(u as *mut PulseContext) }; 101 | if eol == 0 { 102 | let info = unsafe { &*i }; 103 | let flags = pulse::SinkFlags::from_bits_truncate(info.flags); 104 | ctx.default_sink_info = Some(DefaultInfo { 105 | sample_spec: info.sample_spec, 106 | channel_map: info.channel_map, 107 | flags, 108 | }); 109 | } 110 | ctx.mainloop.signal(); 111 | } 112 | 113 | if let Some(info) = info { 114 | let _ = context.get_sink_info_by_name( 115 | try_cstr_from(info.default_sink_name), 116 | sink_info_cb, 117 | u, 118 | ); 119 | } else { 120 | // If info is None, then an error occured. 121 | let ctx = unsafe { &mut *(u as *mut PulseContext) }; 122 | ctx.mainloop.signal(); 123 | } 124 | } 125 | 126 | fn new(name: Option<&CStr>) -> Result> { 127 | let name = name.map(|s| s.to_owned()); 128 | let mut ctx = PulseContext::_new(name)?; 129 | 130 | if ctx.mainloop.start().is_err() { 131 | ctx.destroy(); 132 | cubeb_log!("Error: couldn't start pulse's mainloop"); 133 | return Err(Error::error()); 134 | } 135 | 136 | if ctx.context_init().is_err() { 137 | ctx.destroy(); 138 | cubeb_log!("Error: couldn't init pulse's context"); 139 | return Err(Error::error()); 140 | } 141 | 142 | ctx.mainloop.lock(); 143 | /* server_info_callback performs a second async query, 144 | * which is responsible for initializing default_sink_info 145 | * and signalling the mainloop to end the wait. */ 146 | let user_data: *mut c_void = ctx.as_mut() as *mut _ as *mut _; 147 | if let Some(ref context) = ctx.context { 148 | if let Ok(o) = context.get_server_info(PulseContext::server_info_cb, user_data) { 149 | ctx.operation_wait(None, &o); 150 | } 151 | } 152 | ctx.mainloop.unlock(); 153 | 154 | /* Update `default_sink_info` when the default device changes. */ 155 | if let Err(e) = ctx.subscribe_notifications(pulse::SubscriptionMask::SERVER) { 156 | cubeb_log!("subscribe_notifications ignored failure: {}", e); 157 | } 158 | 159 | // Return the result. 160 | Ok(ctx) 161 | } 162 | 163 | pub fn destroy(&mut self) { 164 | self.context_destroy(); 165 | 166 | assert!( 167 | self.input_collection_changed_callback.is_none() 168 | && self.output_collection_changed_callback.is_none() 169 | ); 170 | 171 | if !self.mainloop.is_null() { 172 | self.mainloop.stop(); 173 | } 174 | } 175 | 176 | fn subscribe_notifications(&mut self, mask: pulse::SubscriptionMask) -> Result<()> { 177 | fn update_collection( 178 | _: &pulse::Context, 179 | event: pulse::SubscriptionEvent, 180 | index: u32, 181 | user_data: *mut c_void, 182 | ) { 183 | let ctx = unsafe { &mut *(user_data as *mut PulseContext) }; 184 | 185 | let (f, t) = (event.event_facility(), event.event_type()); 186 | if (f == pulse::SubscriptionEventFacility::Source) 187 | | (f == pulse::SubscriptionEventFacility::Sink) 188 | { 189 | if (t == pulse::SubscriptionEventType::Remove) 190 | | (t == pulse::SubscriptionEventType::New) 191 | { 192 | if log_enabled() { 193 | let op = if t == pulse::SubscriptionEventType::New { 194 | "Adding" 195 | } else { 196 | "Removing" 197 | }; 198 | let dev = if f == pulse::SubscriptionEventFacility::Sink { 199 | "sink" 200 | } else { 201 | "source " 202 | }; 203 | cubeb_log!("{} {} index {}", op, dev, index); 204 | } 205 | 206 | if f == pulse::SubscriptionEventFacility::Source { 207 | unsafe { 208 | ctx.input_collection_changed_callback.unwrap()( 209 | ctx as *mut _ as *mut _, 210 | ctx.input_collection_changed_user_ptr, 211 | ); 212 | } 213 | } 214 | if f == pulse::SubscriptionEventFacility::Sink { 215 | unsafe { 216 | ctx.output_collection_changed_callback.unwrap()( 217 | ctx as *mut _ as *mut _, 218 | ctx.output_collection_changed_user_ptr, 219 | ); 220 | } 221 | } 222 | } 223 | } else if (f == pulse::SubscriptionEventFacility::Server) 224 | && (t == pulse::SubscriptionEventType::Change) 225 | { 226 | cubeb_log!("Server changed {}", index as i32); 227 | let user_data: *mut c_void = ctx as *mut _ as *mut _; 228 | if let Some(ref context) = ctx.context { 229 | if let Err(e) = context.get_server_info(PulseContext::server_info_cb, user_data) 230 | { 231 | cubeb_log!("Error: get_server_info ignored failure: {}", e); 232 | } 233 | } 234 | } 235 | } 236 | 237 | fn success(_: &pulse::Context, success: i32, user_data: *mut c_void) { 238 | let ctx = unsafe { &*(user_data as *mut PulseContext) }; 239 | if success != 1 { 240 | cubeb_log!("subscribe_success ignored failure: {}", success); 241 | } 242 | ctx.mainloop.signal(); 243 | } 244 | 245 | let user_data: *mut c_void = self as *const _ as *mut _; 246 | if let Some(ref context) = self.context { 247 | self.mainloop.lock(); 248 | 249 | context.set_subscribe_callback(update_collection, user_data); 250 | 251 | if let Ok(o) = context.subscribe(mask, success, self as *const _ as *mut _) { 252 | self.operation_wait(None, &o); 253 | } else { 254 | self.mainloop.unlock(); 255 | cubeb_log!("Error: context subscribe failed"); 256 | return Err(Error::error()); 257 | } 258 | 259 | self.mainloop.unlock(); 260 | } 261 | 262 | Ok(()) 263 | } 264 | } 265 | 266 | impl ContextOps for PulseContext { 267 | fn init(context_name: Option<&CStr>) -> Result { 268 | let ctx = PulseContext::new(context_name)?; 269 | Ok(unsafe { Context::from_ptr(Box::into_raw(ctx) as *mut _) }) 270 | } 271 | 272 | fn backend_id(&mut self) -> &'static CStr { 273 | // https://github.com/rust-lang/rust-clippy/issues/13531 274 | #[allow(clippy::manual_c_str_literals)] 275 | unsafe { 276 | CStr::from_ptr(b"pulse-rust\0".as_ptr() as *const _) 277 | } 278 | } 279 | 280 | fn max_channel_count(&mut self) -> Result { 281 | match self.default_sink_info { 282 | Some(ref info) => Ok(u32::from(info.channel_map.channels)), 283 | None => { 284 | cubeb_log!("Error: couldn't get the max channel count"); 285 | Err(Error::error()) 286 | } 287 | } 288 | } 289 | 290 | fn min_latency(&mut self, params: StreamParams) -> Result { 291 | // According to PulseAudio developers, this is a safe minimum. 292 | Ok(25 * params.rate() / 1000) 293 | } 294 | 295 | fn preferred_sample_rate(&mut self) -> Result { 296 | match self.default_sink_info { 297 | Some(ref info) => Ok(info.sample_spec.rate), 298 | None => { 299 | cubeb_log!("Error: Couldn't get the preferred sample rate"); 300 | Err(Error::error()) 301 | } 302 | } 303 | } 304 | 305 | fn supported_input_processing_params(&mut self) -> Result { 306 | Ok(InputProcessingParams::NONE) 307 | } 308 | 309 | fn enumerate_devices( 310 | &mut self, 311 | devtype: DeviceType, 312 | collection: &DeviceCollectionRef, 313 | ) -> Result<()> { 314 | fn add_output_device( 315 | _: &pulse::Context, 316 | i: *const pulse::SinkInfo, 317 | eol: i32, 318 | user_data: *mut c_void, 319 | ) { 320 | let list_data = unsafe { &mut *(user_data as *mut PulseDevListData) }; 321 | let ctx = list_data.context; 322 | 323 | if eol != 0 { 324 | ctx.mainloop.signal(); 325 | return; 326 | } 327 | 328 | debug_assert!(!i.is_null()); 329 | debug_assert!(!user_data.is_null()); 330 | 331 | let info = unsafe { &*i }; 332 | 333 | let group_id = match info.proplist().gets("sysfs.path") { 334 | Some(p) => p.to_owned().into_raw(), 335 | _ => ptr::null_mut(), 336 | }; 337 | 338 | let vendor_name = match info.proplist().gets("device.vendor.name") { 339 | Some(p) => p.to_owned().into_raw(), 340 | _ => ptr::null_mut(), 341 | }; 342 | 343 | let info_name = unsafe { CStr::from_ptr(info.name) }; 344 | let info_description = unsafe { CStr::from_ptr(info.description) }.to_owned(); 345 | 346 | let preferred = if *info_name == *list_data.default_sink_name { 347 | ffi::CUBEB_DEVICE_PREF_ALL 348 | } else { 349 | ffi::CUBEB_DEVICE_PREF_NONE 350 | }; 351 | 352 | let device_id = ctx.devids.borrow_mut().add(info_name); 353 | let friendly_name = info_description.into_raw(); 354 | let devinfo = ffi::cubeb_device_info { 355 | device_id, 356 | devid: device_id as ffi::cubeb_devid, 357 | friendly_name, 358 | group_id, 359 | vendor_name, 360 | device_type: ffi::CUBEB_DEVICE_TYPE_OUTPUT, 361 | state: ctx.state_from_port(info.active_port), 362 | preferred, 363 | format: ffi::CUBEB_DEVICE_FMT_ALL, 364 | default_format: pulse_format_to_cubeb_format(info.sample_spec.format), 365 | max_channels: u32::from(info.channel_map.channels), 366 | min_rate: 1, 367 | max_rate: PA_RATE_MAX, 368 | default_rate: info.sample_spec.rate, 369 | latency_lo: 0, 370 | latency_hi: 0, 371 | }; 372 | list_data.devinfo.push(devinfo); 373 | } 374 | 375 | fn add_input_device( 376 | _: &pulse::Context, 377 | i: *const pulse::SourceInfo, 378 | eol: i32, 379 | user_data: *mut c_void, 380 | ) { 381 | let list_data = unsafe { &mut *(user_data as *mut PulseDevListData) }; 382 | let ctx = list_data.context; 383 | 384 | if eol != 0 { 385 | ctx.mainloop.signal(); 386 | return; 387 | } 388 | 389 | debug_assert!(!user_data.is_null()); 390 | debug_assert!(!i.is_null()); 391 | 392 | let info = unsafe { &*i }; 393 | 394 | let group_id = match info.proplist().gets("sysfs.path") { 395 | Some(p) => p.to_owned().into_raw(), 396 | _ => ptr::null_mut(), 397 | }; 398 | 399 | let vendor_name = match info.proplist().gets("device.vendor.name") { 400 | Some(p) => p.to_owned().into_raw(), 401 | _ => ptr::null_mut(), 402 | }; 403 | 404 | let info_name = unsafe { CStr::from_ptr(info.name) }; 405 | let info_description = unsafe { CStr::from_ptr(info.description) }.to_owned(); 406 | 407 | let preferred = if *info_name == *list_data.default_source_name { 408 | ffi::CUBEB_DEVICE_PREF_ALL 409 | } else { 410 | ffi::CUBEB_DEVICE_PREF_NONE 411 | }; 412 | 413 | let device_id = ctx.devids.borrow_mut().add(info_name); 414 | let friendly_name = info_description.into_raw(); 415 | let devinfo = ffi::cubeb_device_info { 416 | device_id, 417 | devid: device_id as ffi::cubeb_devid, 418 | friendly_name, 419 | group_id, 420 | vendor_name, 421 | device_type: ffi::CUBEB_DEVICE_TYPE_INPUT, 422 | state: ctx.state_from_port(info.active_port), 423 | preferred, 424 | format: ffi::CUBEB_DEVICE_FMT_ALL, 425 | default_format: pulse_format_to_cubeb_format(info.sample_spec.format), 426 | max_channels: u32::from(info.channel_map.channels), 427 | min_rate: 1, 428 | max_rate: PA_RATE_MAX, 429 | default_rate: info.sample_spec.rate, 430 | latency_lo: 0, 431 | latency_hi: 0, 432 | }; 433 | 434 | list_data.devinfo.push(devinfo); 435 | } 436 | 437 | fn default_device_names( 438 | _: &pulse::Context, 439 | info: Option<&pulse::ServerInfo>, 440 | user_data: *mut c_void, 441 | ) { 442 | let list_data = unsafe { &mut *(user_data as *mut PulseDevListData) }; 443 | 444 | if let Some(info) = info { 445 | list_data.default_sink_name = super::try_cstr_from(info.default_sink_name) 446 | .map(|s| s.to_owned()) 447 | .unwrap_or_default(); 448 | list_data.default_source_name = super::try_cstr_from(info.default_source_name) 449 | .map(|s| s.to_owned()) 450 | .unwrap_or_default(); 451 | } 452 | 453 | list_data.context.mainloop.signal(); 454 | } 455 | 456 | let mut user_data = PulseDevListData::new(self); 457 | 458 | if let Some(ref context) = self.context { 459 | self.mainloop.lock(); 460 | 461 | if let Ok(o) = 462 | context.get_server_info(default_device_names, &mut user_data as *mut _ as *mut _) 463 | { 464 | self.operation_wait(None, &o); 465 | } 466 | 467 | if devtype.contains(DeviceType::OUTPUT) { 468 | if let Ok(o) = context 469 | .get_sink_info_list(add_output_device, &mut user_data as *mut _ as *mut _) 470 | { 471 | self.operation_wait(None, &o); 472 | } 473 | } 474 | 475 | if devtype.contains(DeviceType::INPUT) { 476 | if let Ok(o) = context 477 | .get_source_info_list(add_input_device, &mut user_data as *mut _ as *mut _) 478 | { 479 | self.operation_wait(None, &o); 480 | } 481 | } 482 | 483 | self.mainloop.unlock(); 484 | } 485 | 486 | // Extract the array of cubeb_device_info from 487 | // PulseDevListData and convert it into C representation. 488 | let mut tmp = Vec::new(); 489 | mem::swap(&mut user_data.devinfo, &mut tmp); 490 | let mut devices = tmp.into_boxed_slice(); 491 | let coll = unsafe { &mut *collection.as_ptr() }; 492 | coll.device = devices.as_mut_ptr(); 493 | coll.count = devices.len(); 494 | 495 | // Giving away the memory owned by devices. Don't free it! 496 | mem::forget(devices); 497 | Ok(()) 498 | } 499 | 500 | fn device_collection_destroy(&mut self, collection: &mut DeviceCollectionRef) -> Result<()> { 501 | debug_assert!(!collection.as_ptr().is_null()); 502 | unsafe { 503 | let coll = &mut *collection.as_ptr(); 504 | let mut devices = Vec::from_raw_parts(coll.device, coll.count, coll.count); 505 | for dev in &mut devices { 506 | if !dev.group_id.is_null() { 507 | let _ = CString::from_raw(dev.group_id as *mut _); 508 | } 509 | if !dev.vendor_name.is_null() { 510 | let _ = CString::from_raw(dev.vendor_name as *mut _); 511 | } 512 | if !dev.friendly_name.is_null() { 513 | let _ = CString::from_raw(dev.friendly_name as *mut _); 514 | } 515 | } 516 | coll.device = ptr::null_mut(); 517 | coll.count = 0; 518 | } 519 | Ok(()) 520 | } 521 | 522 | #[allow(clippy::too_many_arguments)] 523 | fn stream_init( 524 | &mut self, 525 | stream_name: Option<&CStr>, 526 | input_device: DeviceId, 527 | input_stream_params: Option<&StreamParamsRef>, 528 | output_device: DeviceId, 529 | output_stream_params: Option<&StreamParamsRef>, 530 | latency_frames: u32, 531 | data_callback: ffi::cubeb_data_callback, 532 | state_callback: ffi::cubeb_state_callback, 533 | user_ptr: *mut c_void, 534 | ) -> Result { 535 | if self.error { 536 | self.context_init()?; 537 | } 538 | 539 | let stm = PulseStream::new( 540 | self, 541 | stream_name, 542 | input_device, 543 | input_stream_params, 544 | output_device, 545 | output_stream_params, 546 | latency_frames, 547 | data_callback, 548 | state_callback, 549 | user_ptr, 550 | )?; 551 | Ok(unsafe { Stream::from_ptr(Box::into_raw(stm) as *mut _) }) 552 | } 553 | 554 | fn register_device_collection_changed( 555 | &mut self, 556 | devtype: DeviceType, 557 | cb: ffi::cubeb_device_collection_changed_callback, 558 | user_ptr: *mut c_void, 559 | ) -> Result<()> { 560 | if devtype.contains(DeviceType::INPUT) { 561 | self.input_collection_changed_callback = cb; 562 | self.input_collection_changed_user_ptr = user_ptr; 563 | } 564 | if devtype.contains(DeviceType::OUTPUT) { 565 | self.output_collection_changed_callback = cb; 566 | self.output_collection_changed_user_ptr = user_ptr; 567 | } 568 | 569 | let mut mask = pulse::SubscriptionMask::empty(); 570 | if self.input_collection_changed_callback.is_some() { 571 | mask |= pulse::SubscriptionMask::SOURCE; 572 | } 573 | if self.output_collection_changed_callback.is_some() { 574 | mask |= pulse::SubscriptionMask::SINK; 575 | } 576 | /* Default device changed, this is always registered in order to update the 577 | * `default_sink_info` when the default device changes. */ 578 | mask |= pulse::SubscriptionMask::SERVER; 579 | 580 | self.subscribe_notifications(mask) 581 | } 582 | } 583 | 584 | impl Drop for PulseContext { 585 | fn drop(&mut self) { 586 | self.destroy(); 587 | } 588 | } 589 | 590 | impl PulseContext { 591 | /* Initialize PulseAudio Context */ 592 | fn context_init(&mut self) -> Result<()> { 593 | fn error_state(c: &pulse::Context, u: *mut c_void) { 594 | let ctx = unsafe { &mut *(u as *mut PulseContext) }; 595 | if !c.get_state().is_good() { 596 | ctx.error = true; 597 | } 598 | ctx.mainloop.signal(); 599 | } 600 | 601 | if self.context.is_some() { 602 | debug_assert!(self.error); 603 | self.context_destroy(); 604 | } 605 | 606 | self.context = { 607 | let name = self.context_name.as_ref().map(|s| s.as_ref()); 608 | pulse::Context::new(&self.mainloop.get_api(), name) 609 | }; 610 | 611 | let context_ptr: *mut c_void = self as *mut _ as *mut _; 612 | if self.context.is_none() { 613 | cubeb_log!("Error: couldn't create pulse's context"); 614 | return Err(Error::error()); 615 | } 616 | 617 | self.mainloop.lock(); 618 | let connected = if let Some(ref context) = self.context { 619 | context.set_state_callback(error_state, context_ptr); 620 | context 621 | .connect(None, pulse::ContextFlags::empty(), ptr::null()) 622 | .is_ok() 623 | } else { 624 | false 625 | }; 626 | 627 | if !connected || !self.wait_until_context_ready() { 628 | self.mainloop.unlock(); 629 | self.context_destroy(); 630 | cubeb_log!("Error: error while waiting for pulse's context to be ready"); 631 | return Err(Error::error()); 632 | } 633 | 634 | self.mainloop.unlock(); 635 | 636 | let version_str = unsafe { CStr::from_ptr(pulse::library_version()) }; 637 | if let Ok(version) = semver::Version::parse(&version_str.to_string_lossy()) { 638 | self.version_0_9_8 = 639 | version >= semver::Version::parse("0.9.8").expect("Failed to parse version"); 640 | self.version_2_0_0 = 641 | version >= semver::Version::parse("2.0.0").expect("Failed to parse version"); 642 | } 643 | 644 | self.error = false; 645 | 646 | Ok(()) 647 | } 648 | 649 | fn context_destroy(&mut self) { 650 | fn drain_complete(_: &pulse::Context, u: *mut c_void) { 651 | let ctx = unsafe { &*(u as *mut PulseContext) }; 652 | ctx.mainloop.signal(); 653 | } 654 | 655 | let context_ptr: *mut c_void = self as *mut _ as *mut _; 656 | if let Some(ctx) = self.context.take() { 657 | self.mainloop.lock(); 658 | if let Ok(o) = ctx.drain(drain_complete, context_ptr) { 659 | self.operation_wait(None, &o); 660 | } 661 | ctx.clear_state_callback(); 662 | ctx.disconnect(); 663 | ctx.unref(); 664 | self.mainloop.unlock(); 665 | } 666 | } 667 | 668 | pub fn operation_wait<'a, S>(&self, s: S, o: &pulse::Operation) -> bool 669 | where 670 | S: Into>, 671 | { 672 | let stream = s.into(); 673 | while o.get_state() == PA_OPERATION_RUNNING { 674 | self.mainloop.wait(); 675 | if let Some(ref context) = self.context { 676 | if !context.get_state().is_good() { 677 | return false; 678 | } 679 | } 680 | 681 | if let Some(stm) = stream { 682 | if !stm.get_state().is_good() { 683 | return false; 684 | } 685 | } 686 | } 687 | 688 | true 689 | } 690 | 691 | pub fn wait_until_context_ready(&self) -> bool { 692 | if let Some(ref context) = self.context { 693 | loop { 694 | let state = context.get_state(); 695 | if !state.is_good() { 696 | return false; 697 | } 698 | if state == pulse::ContextState::Ready { 699 | break; 700 | } 701 | self.mainloop.wait(); 702 | } 703 | } 704 | 705 | true 706 | } 707 | 708 | fn state_from_port(&self, i: *const pa_port_info) -> ffi::cubeb_device_state { 709 | if !i.is_null() { 710 | let info = unsafe { *i }; 711 | if self.version_2_0_0 && info.available == PA_PORT_AVAILABLE_NO { 712 | ffi::CUBEB_DEVICE_STATE_UNPLUGGED 713 | } else { 714 | ffi::CUBEB_DEVICE_STATE_ENABLED 715 | } 716 | } else { 717 | ffi::CUBEB_DEVICE_STATE_ENABLED 718 | } 719 | } 720 | } 721 | 722 | struct PulseDevListData<'a> { 723 | default_sink_name: CString, 724 | default_source_name: CString, 725 | devinfo: Vec, 726 | context: &'a PulseContext, 727 | } 728 | 729 | impl<'a> PulseDevListData<'a> { 730 | pub fn new<'b>(context: &'b PulseContext) -> Self 731 | where 732 | 'b: 'a, 733 | { 734 | PulseDevListData { 735 | default_sink_name: CString::default(), 736 | default_source_name: CString::default(), 737 | devinfo: Vec::new(), 738 | context, 739 | } 740 | } 741 | } 742 | 743 | impl Drop for PulseDevListData<'_> { 744 | fn drop(&mut self) { 745 | for elem in &mut self.devinfo { 746 | let _ = unsafe { Box::from_raw(elem) }; 747 | } 748 | } 749 | } 750 | 751 | fn pulse_format_to_cubeb_format(format: pa_sample_format_t) -> ffi::cubeb_device_fmt { 752 | match format { 753 | PA_SAMPLE_S16LE => ffi::CUBEB_DEVICE_FMT_S16LE, 754 | PA_SAMPLE_S16BE => ffi::CUBEB_DEVICE_FMT_S16BE, 755 | PA_SAMPLE_FLOAT32LE => ffi::CUBEB_DEVICE_FMT_F32LE, 756 | PA_SAMPLE_FLOAT32BE => ffi::CUBEB_DEVICE_FMT_F32BE, 757 | // Unsupported format, return F32NE 758 | _ => ffi::CUBEB_DEVICE_FMT_F32NE, 759 | } 760 | } 761 | -------------------------------------------------------------------------------- /src/backend/cork_state.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use std::ops; 7 | 8 | #[repr(C)] 9 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 10 | pub struct CorkState(u32); 11 | 12 | const UNCORK: u32 = 0b00; 13 | const CORK: u32 = 0b01; 14 | const NOTIFY: u32 = 0b10; 15 | const ALL: u32 = 0b11; 16 | 17 | impl CorkState { 18 | #[inline] 19 | pub fn uncork() -> Self { 20 | CorkState(UNCORK) 21 | } 22 | #[inline] 23 | pub fn cork() -> Self { 24 | CorkState(CORK) 25 | } 26 | #[inline] 27 | pub fn notify() -> Self { 28 | CorkState(NOTIFY) 29 | } 30 | 31 | #[inline] 32 | pub fn is_cork(&self) -> bool { 33 | self.contains(CorkState::cork()) 34 | } 35 | #[inline] 36 | pub fn is_notify(&self) -> bool { 37 | self.contains(CorkState::notify()) 38 | } 39 | 40 | #[inline] 41 | pub fn contains(&self, other: Self) -> bool { 42 | (*self & other) == other 43 | } 44 | } 45 | 46 | impl ops::BitOr for CorkState { 47 | type Output = CorkState; 48 | 49 | #[inline] 50 | fn bitor(self, other: Self) -> Self { 51 | CorkState(self.0 | other.0) 52 | } 53 | } 54 | 55 | impl ops::BitXor for CorkState { 56 | type Output = CorkState; 57 | 58 | #[inline] 59 | fn bitxor(self, other: Self) -> Self { 60 | CorkState(self.0 ^ other.0) 61 | } 62 | } 63 | 64 | impl ops::BitAnd for CorkState { 65 | type Output = CorkState; 66 | 67 | #[inline] 68 | fn bitand(self, other: Self) -> Self { 69 | CorkState(self.0 & other.0) 70 | } 71 | } 72 | 73 | impl ops::Sub for CorkState { 74 | type Output = CorkState; 75 | 76 | #[inline] 77 | fn sub(self, other: Self) -> Self { 78 | CorkState(self.0 & !other.0) 79 | } 80 | } 81 | 82 | impl ops::Not for CorkState { 83 | type Output = CorkState; 84 | 85 | #[inline] 86 | fn not(self) -> Self { 87 | CorkState(!self.0 & ALL) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/backend/intern.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use std::ffi::{CStr, CString}; 7 | use std::os::raw::c_char; 8 | 9 | #[derive(Debug)] 10 | pub struct Intern { 11 | vec: Vec, 12 | } 13 | 14 | impl Intern { 15 | pub fn new() -> Intern { 16 | Intern { vec: Vec::new() } 17 | } 18 | 19 | pub fn add(&mut self, string: &CStr) -> *const c_char { 20 | fn eq(s1: &CStr, s2: &CStr) -> bool { 21 | s1 == s2 22 | } 23 | for s in &self.vec { 24 | if eq(s, string) { 25 | return s.as_ptr(); 26 | } 27 | } 28 | 29 | self.vec.push(string.to_owned()); 30 | self.vec.last().unwrap().as_ptr() 31 | } 32 | } 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use super::Intern; 37 | use std::ffi::CStr; 38 | 39 | #[test] 40 | fn intern() { 41 | fn cstr(str: &[u8]) -> &CStr { 42 | CStr::from_bytes_with_nul(str).unwrap() 43 | } 44 | 45 | let mut intern = Intern::new(); 46 | 47 | let foo_ptr = intern.add(cstr(b"foo\0")); 48 | let bar_ptr = intern.add(cstr(b"bar\0")); 49 | assert!(!foo_ptr.is_null()); 50 | assert!(!bar_ptr.is_null()); 51 | assert!(foo_ptr != bar_ptr); 52 | 53 | assert!(foo_ptr == intern.add(cstr(b"foo\0"))); 54 | assert!(foo_ptr != intern.add(cstr(b"fo\0"))); 55 | assert!(foo_ptr != intern.add(cstr(b"fool\0"))); 56 | assert!(foo_ptr != intern.add(cstr(b"not foo\0"))); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/backend/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | mod context; 7 | mod cork_state; 8 | mod intern; 9 | mod stream; 10 | 11 | pub use self::context::PulseContext; 12 | use self::intern::Intern; 13 | pub use self::stream::PulseStream; 14 | use std::ffi::CStr; 15 | use std::os::raw::c_char; 16 | 17 | // helper to convert *const c_char to Option 18 | fn try_cstr_from<'str>(s: *const c_char) -> Option<&'str CStr> { 19 | if s.is_null() { 20 | None 21 | } else { 22 | Some(unsafe { CStr::from_ptr(s) }) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/backend/stream.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use backend::cork_state::CorkState; 7 | use backend::*; 8 | use cubeb_backend::{ 9 | ffi, log_enabled, ChannelLayout, DeviceId, DeviceRef, Error, InputProcessingParams, Result, 10 | SampleFormat, StreamOps, StreamParamsRef, StreamPrefs, 11 | }; 12 | use pulse::{self, CVolumeExt, ChannelMapExt, SampleSpecExt, StreamLatency, USecExt}; 13 | use pulse_ffi::*; 14 | use ringbuf::RingBuffer; 15 | use std::ffi::{CStr, CString}; 16 | use std::os::raw::{c_long, c_void}; 17 | use std::slice; 18 | use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; 19 | use std::{mem, ptr}; 20 | 21 | use self::LinearInputBuffer::*; 22 | use self::RingBufferConsumer::*; 23 | use self::RingBufferProducer::*; 24 | 25 | const PULSE_NO_GAIN: f32 = -1.0; 26 | 27 | /// Iterator interface to `ChannelLayout`. 28 | /// 29 | /// Iterates each channel in the set represented by `ChannelLayout`. 30 | struct ChannelLayoutIter { 31 | /// The layout set being iterated 32 | layout: ChannelLayout, 33 | /// The next flag to test 34 | index: u8, 35 | } 36 | 37 | fn channel_layout_iter(layout: ChannelLayout) -> ChannelLayoutIter { 38 | let index = 0; 39 | ChannelLayoutIter { layout, index } 40 | } 41 | 42 | impl Iterator for ChannelLayoutIter { 43 | type Item = ChannelLayout; 44 | 45 | fn next(&mut self) -> Option { 46 | while !self.layout.is_empty() { 47 | let test = Self::Item::from_bits_truncate(1 << self.index); 48 | self.index += 1; 49 | if self.layout.contains(test) { 50 | self.layout.remove(test); 51 | return Some(test); 52 | } 53 | } 54 | None 55 | } 56 | } 57 | 58 | fn cubeb_channel_to_pa_channel(channel: ffi::cubeb_channel) -> pa_channel_position_t { 59 | match channel { 60 | ffi::CHANNEL_FRONT_LEFT => PA_CHANNEL_POSITION_FRONT_LEFT, 61 | ffi::CHANNEL_FRONT_RIGHT => PA_CHANNEL_POSITION_FRONT_RIGHT, 62 | ffi::CHANNEL_FRONT_CENTER => PA_CHANNEL_POSITION_FRONT_CENTER, 63 | ffi::CHANNEL_LOW_FREQUENCY => PA_CHANNEL_POSITION_LFE, 64 | ffi::CHANNEL_BACK_LEFT => PA_CHANNEL_POSITION_REAR_LEFT, 65 | ffi::CHANNEL_BACK_RIGHT => PA_CHANNEL_POSITION_REAR_RIGHT, 66 | ffi::CHANNEL_FRONT_LEFT_OF_CENTER => PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, 67 | ffi::CHANNEL_FRONT_RIGHT_OF_CENTER => PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, 68 | ffi::CHANNEL_BACK_CENTER => PA_CHANNEL_POSITION_REAR_CENTER, 69 | ffi::CHANNEL_SIDE_LEFT => PA_CHANNEL_POSITION_SIDE_LEFT, 70 | ffi::CHANNEL_SIDE_RIGHT => PA_CHANNEL_POSITION_SIDE_RIGHT, 71 | ffi::CHANNEL_TOP_CENTER => PA_CHANNEL_POSITION_TOP_CENTER, 72 | ffi::CHANNEL_TOP_FRONT_LEFT => PA_CHANNEL_POSITION_TOP_FRONT_LEFT, 73 | ffi::CHANNEL_TOP_FRONT_CENTER => PA_CHANNEL_POSITION_TOP_FRONT_CENTER, 74 | ffi::CHANNEL_TOP_FRONT_RIGHT => PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, 75 | ffi::CHANNEL_TOP_BACK_LEFT => PA_CHANNEL_POSITION_TOP_REAR_LEFT, 76 | ffi::CHANNEL_TOP_BACK_CENTER => PA_CHANNEL_POSITION_TOP_REAR_CENTER, 77 | ffi::CHANNEL_TOP_BACK_RIGHT => PA_CHANNEL_POSITION_TOP_REAR_RIGHT, 78 | _ => PA_CHANNEL_POSITION_INVALID, 79 | } 80 | } 81 | 82 | fn layout_to_channel_map(layout: ChannelLayout) -> pulse::ChannelMap { 83 | assert_ne!(layout, ChannelLayout::UNDEFINED); 84 | 85 | let mut cm = pulse::ChannelMap::init(); 86 | for (i, channel) in channel_layout_iter(layout).enumerate() { 87 | cm.map[i] = cubeb_channel_to_pa_channel(channel.into()); 88 | } 89 | cm.channels = layout.num_channels() as _; 90 | 91 | // Special case single channel center mapping as mono. 92 | if cm.channels == 1 && cm.map[0] == PA_CHANNEL_POSITION_FRONT_CENTER { 93 | cm.map[0] = PA_CHANNEL_POSITION_MONO; 94 | } 95 | 96 | cm 97 | } 98 | 99 | fn default_layout_for_channels(ch: u32) -> ChannelLayout { 100 | match ch { 101 | 1 => ChannelLayout::MONO, 102 | 2 => ChannelLayout::STEREO, 103 | 3 => ChannelLayout::_3F, 104 | 4 => ChannelLayout::QUAD, 105 | 5 => ChannelLayout::_3F2, 106 | 6 => ChannelLayout::_3F_LFE | ChannelLayout::SIDE_LEFT | ChannelLayout::SIDE_RIGHT, 107 | 7 => ChannelLayout::_3F3R_LFE, 108 | 8 => ChannelLayout::_3F4_LFE, 109 | _ => panic!("channel must be between 1 to 8."), 110 | } 111 | } 112 | 113 | pub struct Device(ffi::cubeb_device); 114 | 115 | impl Drop for Device { 116 | fn drop(&mut self) { 117 | unsafe { 118 | if !self.0.input_name.is_null() { 119 | let _ = CString::from_raw(self.0.input_name as *mut _); 120 | } 121 | if !self.0.output_name.is_null() { 122 | let _ = CString::from_raw(self.0.output_name as *mut _); 123 | } 124 | } 125 | } 126 | } 127 | 128 | enum RingBufferConsumer { 129 | IntegerRingBufferConsumer(ringbuf::Consumer), 130 | FloatRingBufferConsumer(ringbuf::Consumer), 131 | } 132 | 133 | enum RingBufferProducer { 134 | IntegerRingBufferProducer(ringbuf::Producer), 135 | FloatRingBufferProducer(ringbuf::Producer), 136 | } 137 | 138 | enum LinearInputBuffer { 139 | IntegerLinearInputBuffer(Vec), 140 | FloatLinearInputBuffer(Vec), 141 | } 142 | 143 | struct BufferManager { 144 | consumer: RingBufferConsumer, 145 | producer: RingBufferProducer, 146 | linear_input_buffer: LinearInputBuffer, 147 | } 148 | 149 | impl BufferManager { 150 | // When opening a duplex stream, the sample-spec are guaranteed to match. It's ok to have 151 | // either the input or output sample-spec here. 152 | fn new(input_buffer_size: usize, sample_spec: &pulse::SampleSpec) -> BufferManager { 153 | if sample_spec.format == PA_SAMPLE_S16BE || sample_spec.format == PA_SAMPLE_S16LE { 154 | let ring = RingBuffer::::new(input_buffer_size); 155 | let (prod, cons) = ring.split(); 156 | BufferManager { 157 | producer: IntegerRingBufferProducer(prod), 158 | consumer: IntegerRingBufferConsumer(cons), 159 | linear_input_buffer: IntegerLinearInputBuffer(Vec::::with_capacity( 160 | input_buffer_size, 161 | )), 162 | } 163 | } else { 164 | let ring = RingBuffer::::new(input_buffer_size); 165 | let (prod, cons) = ring.split(); 166 | BufferManager { 167 | producer: FloatRingBufferProducer(prod), 168 | consumer: FloatRingBufferConsumer(cons), 169 | linear_input_buffer: FloatLinearInputBuffer(Vec::::with_capacity( 170 | input_buffer_size, 171 | )), 172 | } 173 | } 174 | } 175 | 176 | fn push_input_data(&mut self, input_data: *const c_void, read_samples: usize) { 177 | match &mut self.producer { 178 | RingBufferProducer::FloatRingBufferProducer(p) => { 179 | let input_data = 180 | unsafe { slice::from_raw_parts::(input_data as *const f32, read_samples) }; 181 | // we don't do anything in particular if we can't push everything 182 | p.push_slice(input_data); 183 | } 184 | RingBufferProducer::IntegerRingBufferProducer(p) => { 185 | let input_data = 186 | unsafe { slice::from_raw_parts::(input_data as *const i16, read_samples) }; 187 | p.push_slice(input_data); 188 | } 189 | } 190 | } 191 | 192 | fn pull_input_data(&mut self, input_data: *mut c_void, needed_samples: usize) { 193 | match &mut self.consumer { 194 | IntegerRingBufferConsumer(p) => { 195 | let input: &mut [i16] = unsafe { 196 | slice::from_raw_parts_mut::(input_data as *mut i16, needed_samples) 197 | }; 198 | let read = p.pop_slice(input); 199 | if read < needed_samples { 200 | for i in 0..(needed_samples - read) { 201 | input[read + i] = 0; 202 | } 203 | } 204 | } 205 | FloatRingBufferConsumer(p) => { 206 | let input: &mut [f32] = unsafe { 207 | slice::from_raw_parts_mut::(input_data as *mut f32, needed_samples) 208 | }; 209 | let read = p.pop_slice(input); 210 | if read < needed_samples { 211 | for i in 0..(needed_samples - read) { 212 | input[read + i] = 0.; 213 | } 214 | } 215 | } 216 | } 217 | } 218 | 219 | fn get_linear_input_data(&mut self, nsamples: usize) -> *const c_void { 220 | let p = match &mut self.linear_input_buffer { 221 | LinearInputBuffer::IntegerLinearInputBuffer(b) => { 222 | b.resize(nsamples, 0); 223 | b.as_mut_ptr() as *mut c_void 224 | } 225 | LinearInputBuffer::FloatLinearInputBuffer(b) => { 226 | b.resize(nsamples, 0.); 227 | b.as_mut_ptr() as *mut c_void 228 | } 229 | }; 230 | self.pull_input_data(p, nsamples); 231 | 232 | p 233 | } 234 | 235 | pub fn trim(&mut self, final_size: usize) { 236 | match &self.linear_input_buffer { 237 | LinearInputBuffer::IntegerLinearInputBuffer(b) => { 238 | let length = b.len(); 239 | assert!(final_size <= length); 240 | let nframes_to_pop = length - final_size; 241 | self.get_linear_input_data(nframes_to_pop); 242 | } 243 | LinearInputBuffer::FloatLinearInputBuffer(b) => { 244 | let length = b.len(); 245 | assert!(final_size <= length); 246 | let nframes_to_pop = length - final_size; 247 | self.get_linear_input_data(nframes_to_pop); 248 | } 249 | } 250 | } 251 | pub fn available_samples(&mut self) -> usize { 252 | match &self.linear_input_buffer { 253 | LinearInputBuffer::IntegerLinearInputBuffer(b) => b.len(), 254 | LinearInputBuffer::FloatLinearInputBuffer(b) => b.len(), 255 | } 256 | } 257 | } 258 | 259 | impl std::fmt::Debug for BufferManager { 260 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 261 | write!(f, "") 262 | } 263 | } 264 | 265 | #[repr(C)] 266 | #[derive(Debug)] 267 | pub struct PulseStream<'ctx> { 268 | context: &'ctx PulseContext, 269 | user_ptr: *mut c_void, 270 | output_stream: Option, 271 | input_stream: Option, 272 | data_callback: ffi::cubeb_data_callback, 273 | state_callback: ffi::cubeb_state_callback, 274 | drain_timer: AtomicPtr, 275 | output_sample_spec: pulse::SampleSpec, 276 | input_sample_spec: pulse::SampleSpec, 277 | // output frames count excluding pre-buffering 278 | output_frame_count: AtomicUsize, 279 | shutdown: bool, 280 | volume: f32, 281 | state: ffi::cubeb_state, 282 | input_buffer_manager: Option, 283 | } 284 | 285 | impl<'ctx> PulseStream<'ctx> { 286 | #[allow(clippy::too_many_arguments)] 287 | pub fn new( 288 | context: &'ctx PulseContext, 289 | stream_name: Option<&CStr>, 290 | input_device: DeviceId, 291 | input_stream_params: Option<&StreamParamsRef>, 292 | output_device: DeviceId, 293 | output_stream_params: Option<&StreamParamsRef>, 294 | latency_frames: u32, 295 | data_callback: ffi::cubeb_data_callback, 296 | state_callback: ffi::cubeb_state_callback, 297 | user_ptr: *mut c_void, 298 | ) -> Result> { 299 | fn check_error(s: &pulse::Stream, u: *mut c_void) { 300 | let stm = unsafe { &mut *(u as *mut PulseStream) }; 301 | if !s.get_state().is_good() { 302 | cubeb_alog!("Calling error callback"); 303 | stm.state_change_callback(ffi::CUBEB_STATE_ERROR); 304 | } 305 | stm.context.mainloop.signal(); 306 | } 307 | 308 | fn read_data(s: &pulse::Stream, nbytes: usize, u: *mut c_void) { 309 | fn read_from_input( 310 | s: &pulse::Stream, 311 | buffer: *mut *const c_void, 312 | size: *mut usize, 313 | ) -> i32 { 314 | let readable_size = s.readable_size().map(|s| s as i32).unwrap_or(-1); 315 | if readable_size > 0 && unsafe { s.peek(buffer, size).is_err() } { 316 | cubeb_logv!("Error while peeking the input stream"); 317 | return -1; 318 | } 319 | readable_size 320 | } 321 | 322 | cubeb_alogv!("Input callback buffer size {}", nbytes); 323 | let stm = unsafe { &mut *(u as *mut PulseStream) }; 324 | if stm.shutdown { 325 | return; 326 | } 327 | 328 | let mut read_data: *const c_void = ptr::null(); 329 | let mut read_size: usize = 0; 330 | while read_from_input(s, &mut read_data, &mut read_size) > 0 { 331 | /* read_data can be NULL in case of a hole. */ 332 | if !read_data.is_null() { 333 | let in_frame_size = stm.input_sample_spec.frame_size(); 334 | let read_frames = read_size / in_frame_size; 335 | let read_samples = read_size / stm.input_sample_spec.sample_size(); 336 | 337 | if stm.output_stream.is_some() { 338 | // duplex stream: push the input data to the ring buffer. 339 | stm.input_buffer_manager 340 | .as_mut() 341 | .unwrap() 342 | .push_input_data(read_data, read_samples); 343 | } else { 344 | // input/capture only operation. Call callback directly 345 | let got = unsafe { 346 | stm.data_callback.unwrap()( 347 | stm as *mut _ as *mut _, 348 | stm.user_ptr, 349 | read_data, 350 | ptr::null_mut(), 351 | read_frames as c_long, 352 | ) 353 | }; 354 | 355 | if got < 0 || got as usize != read_frames { 356 | let _ = s.cancel_write(); 357 | stm.shutdown = true; 358 | if got < 0 { 359 | unsafe { 360 | stm.state_callback.unwrap()( 361 | stm as *mut _ as *mut _, 362 | stm.user_ptr, 363 | ffi::CUBEB_STATE_ERROR, 364 | ); 365 | } 366 | } 367 | break; 368 | } 369 | } 370 | } 371 | 372 | if read_size > 0 { 373 | let _ = s.drop(); 374 | } 375 | 376 | if stm.shutdown { 377 | return; 378 | } 379 | } 380 | } 381 | 382 | fn write_data(_: &pulse::Stream, nbytes: usize, u: *mut c_void) { 383 | cubeb_alogv!("Output callback to be written buffer size {}", nbytes); 384 | let stm = unsafe { &mut *(u as *mut PulseStream) }; 385 | if stm.shutdown || stm.state != ffi::CUBEB_STATE_STARTED { 386 | return; 387 | } 388 | 389 | let nframes = nbytes / stm.output_sample_spec.frame_size(); 390 | let first_callback = stm.output_frame_count.fetch_add(nframes, Ordering::SeqCst) == 0; 391 | if stm.input_stream.is_some() { 392 | let nsamples_input = nframes * stm.input_sample_spec.channels as usize; 393 | let input_buffer_manager = stm.input_buffer_manager.as_mut().unwrap(); 394 | 395 | if first_callback { 396 | let buffered_input_frames = input_buffer_manager.available_samples() 397 | / stm.input_sample_spec.channels as usize; 398 | if buffered_input_frames > nframes { 399 | // Trim the buffer to ensure minimal roundtrip latency 400 | let popped_frames = buffered_input_frames - nframes; 401 | input_buffer_manager 402 | .trim(nframes * stm.input_sample_spec.channels as usize); 403 | cubeb_alog!("Dropping {} frames in input buffer.", popped_frames); 404 | } 405 | } 406 | 407 | let p = input_buffer_manager.get_linear_input_data(nsamples_input); 408 | stm.trigger_user_callback(p, nbytes); 409 | } else { 410 | // Output/playback only operation. 411 | // Write directly to output 412 | debug_assert!(stm.output_stream.is_some()); 413 | stm.trigger_user_callback(ptr::null(), nbytes); 414 | } 415 | } 416 | 417 | let mut stm = Box::new(PulseStream { 418 | context, 419 | output_stream: None, 420 | input_stream: None, 421 | data_callback, 422 | state_callback, 423 | user_ptr, 424 | drain_timer: AtomicPtr::new(ptr::null_mut()), 425 | output_sample_spec: pulse::SampleSpec::default(), 426 | input_sample_spec: pulse::SampleSpec::default(), 427 | output_frame_count: AtomicUsize::new(0), 428 | shutdown: false, 429 | volume: PULSE_NO_GAIN, 430 | state: ffi::CUBEB_STATE_ERROR, 431 | input_buffer_manager: None, 432 | }); 433 | 434 | if let Some(ref context) = stm.context.context { 435 | stm.context.mainloop.lock(); 436 | 437 | // Setup output stream 438 | if let Some(stream_params) = output_stream_params { 439 | match PulseStream::stream_init(context, stream_params, stream_name) { 440 | Ok(s) => { 441 | stm.output_sample_spec = *s.get_sample_spec(); 442 | 443 | s.set_state_callback(check_error, stm.as_mut() as *mut _ as *mut _); 444 | s.set_write_callback(write_data, stm.as_mut() as *mut _ as *mut _); 445 | 446 | let buffer_size_bytes = 447 | latency_frames * stm.output_sample_spec.frame_size() as u32; 448 | 449 | let battr = pa_buffer_attr { 450 | maxlength: u32::MAX, 451 | prebuf: u32::MAX, 452 | fragsize: u32::MAX, 453 | tlength: buffer_size_bytes * 2, 454 | minreq: buffer_size_bytes / 4, 455 | }; 456 | let device_name = super::try_cstr_from(output_device as *const _); 457 | let mut stream_flags = pulse::StreamFlags::AUTO_TIMING_UPDATE 458 | | pulse::StreamFlags::INTERPOLATE_TIMING 459 | | pulse::StreamFlags::START_CORKED 460 | | pulse::StreamFlags::ADJUST_LATENCY; 461 | if device_name.is_some() 462 | || stream_params 463 | .prefs() 464 | .contains(StreamPrefs::DISABLE_DEVICE_SWITCHING) 465 | { 466 | stream_flags |= pulse::StreamFlags::DONT_MOVE; 467 | } 468 | let _ = s.connect_playback(device_name, &battr, stream_flags, None, None); 469 | 470 | stm.output_stream = Some(s); 471 | } 472 | Err(e) => { 473 | cubeb_log!("Output stream initialization error"); 474 | stm.context.mainloop.unlock(); 475 | stm.destroy(); 476 | return Err(e); 477 | } 478 | } 479 | } 480 | 481 | // Set up input stream 482 | if let Some(stream_params) = input_stream_params { 483 | match PulseStream::stream_init(context, stream_params, stream_name) { 484 | Ok(s) => { 485 | stm.input_sample_spec = *s.get_sample_spec(); 486 | 487 | s.set_state_callback(check_error, stm.as_mut() as *mut _ as *mut _); 488 | s.set_read_callback(read_data, stm.as_mut() as *mut _ as *mut _); 489 | 490 | let buffer_size_bytes = 491 | latency_frames * stm.input_sample_spec.frame_size() as u32; 492 | let battr = pa_buffer_attr { 493 | maxlength: u32::MAX, 494 | prebuf: u32::MAX, 495 | fragsize: buffer_size_bytes, 496 | tlength: buffer_size_bytes, 497 | minreq: buffer_size_bytes, 498 | }; 499 | let device_name = super::try_cstr_from(input_device as *const _); 500 | let mut stream_flags = pulse::StreamFlags::AUTO_TIMING_UPDATE 501 | | pulse::StreamFlags::INTERPOLATE_TIMING 502 | | pulse::StreamFlags::START_CORKED 503 | | pulse::StreamFlags::ADJUST_LATENCY; 504 | if device_name.is_some() 505 | || stream_params 506 | .prefs() 507 | .contains(StreamPrefs::DISABLE_DEVICE_SWITCHING) 508 | { 509 | stream_flags |= pulse::StreamFlags::DONT_MOVE; 510 | } 511 | let _ = s.connect_record(device_name, &battr, stream_flags); 512 | 513 | stm.input_stream = Some(s); 514 | } 515 | Err(e) => { 516 | cubeb_log!("Input stream initialization error"); 517 | stm.context.mainloop.unlock(); 518 | stm.destroy(); 519 | return Err(e); 520 | } 521 | } 522 | } 523 | 524 | // Duplex, set up the ringbuffer 525 | if input_stream_params.is_some() && output_stream_params.is_some() { 526 | // A bit more room in case of output underrun. 527 | let buffer_size_bytes = 528 | 2 * latency_frames * stm.input_sample_spec.frame_size() as u32; 529 | stm.input_buffer_manager = Some(BufferManager::new( 530 | buffer_size_bytes as usize, 531 | &stm.input_sample_spec, 532 | )) 533 | } 534 | 535 | let r = if stm.wait_until_ready() { 536 | /* force a timing update now, otherwise timing info does not become valid 537 | until some point after initialization has completed. */ 538 | stm.update_timing_info() 539 | } else { 540 | false 541 | }; 542 | 543 | stm.context.mainloop.unlock(); 544 | 545 | if !r { 546 | stm.destroy(); 547 | cubeb_log!("Error while waiting for the stream to be ready"); 548 | return Err(Error::error()); 549 | } 550 | 551 | // TODO: 552 | if log_enabled() { 553 | if let Some(ref output_stream) = stm.output_stream { 554 | let output_att = output_stream.get_buffer_attr(); 555 | cubeb_log!( 556 | "Output buffer attributes maxlength {}, tlength {}, \ 557 | prebuf {}, minreq {}, fragsize {}", 558 | output_att.maxlength, 559 | output_att.tlength, 560 | output_att.prebuf, 561 | output_att.minreq, 562 | output_att.fragsize 563 | ); 564 | } 565 | 566 | if let Some(ref input_stream) = stm.input_stream { 567 | let input_att = input_stream.get_buffer_attr(); 568 | cubeb_log!( 569 | "Input buffer attributes maxlength {}, tlength {}, \ 570 | prebuf {}, minreq {}, fragsize {}", 571 | input_att.maxlength, 572 | input_att.tlength, 573 | input_att.prebuf, 574 | input_att.minreq, 575 | input_att.fragsize 576 | ); 577 | } 578 | } 579 | } 580 | 581 | Ok(stm) 582 | } 583 | 584 | fn destroy(&mut self) { 585 | self.cork(CorkState::cork()); 586 | 587 | self.context.mainloop.lock(); 588 | { 589 | if let Some(stm) = self.output_stream.take() { 590 | let drain_timer = self.drain_timer.load(Ordering::Acquire); 591 | if !drain_timer.is_null() { 592 | /* there's no pa_rttime_free, so use this instead. */ 593 | self.context.mainloop.get_api().time_free(drain_timer); 594 | } 595 | stm.clear_state_callback(); 596 | stm.clear_write_callback(); 597 | let _ = stm.disconnect(); 598 | stm.unref(); 599 | } 600 | 601 | if let Some(stm) = self.input_stream.take() { 602 | stm.clear_state_callback(); 603 | stm.clear_read_callback(); 604 | let _ = stm.disconnect(); 605 | stm.unref(); 606 | } 607 | } 608 | self.context.mainloop.unlock(); 609 | } 610 | } 611 | 612 | impl Drop for PulseStream<'_> { 613 | fn drop(&mut self) { 614 | self.destroy(); 615 | } 616 | } 617 | 618 | impl StreamOps for PulseStream<'_> { 619 | fn start(&mut self) -> Result<()> { 620 | fn output_preroll(_: &pulse::MainloopApi, u: *mut c_void) { 621 | let stm = unsafe { &mut *(u as *mut PulseStream) }; 622 | if !stm.shutdown { 623 | let size = stm 624 | .output_stream 625 | .as_ref() 626 | .map_or(0, |s| s.writable_size().unwrap_or(0)); 627 | stm.trigger_user_callback(std::ptr::null(), size); 628 | } 629 | } 630 | self.shutdown = false; 631 | self.cork(CorkState::uncork() | CorkState::notify()); 632 | 633 | if self.output_stream.is_some() { 634 | /* When doing output-only or duplex, we need to manually call user cb once in order to 635 | * make things roll. This is done via a defer event in order to execute it from PA 636 | * server thread. */ 637 | self.context.mainloop.lock(); 638 | self.context 639 | .mainloop 640 | .get_api() 641 | .once(output_preroll, self as *const _ as *mut _); 642 | self.context.mainloop.unlock(); 643 | } 644 | 645 | Ok(()) 646 | } 647 | 648 | fn stop(&mut self) -> Result<()> { 649 | { 650 | self.context.mainloop.lock(); 651 | self.shutdown = true; 652 | // If draining is taking place wait to finish 653 | cubeb_log!("Stream stop: waiting for drain"); 654 | while !self.drain_timer.load(Ordering::Acquire).is_null() { 655 | self.context.mainloop.wait(); 656 | } 657 | cubeb_log!("Stream stop: waited for drain"); 658 | self.context.mainloop.unlock(); 659 | } 660 | self.cork(CorkState::cork() | CorkState::notify()); 661 | 662 | Ok(()) 663 | } 664 | 665 | fn position(&mut self) -> Result { 666 | let in_thread = self.context.mainloop.in_thread(); 667 | 668 | if !in_thread { 669 | self.context.mainloop.lock(); 670 | } 671 | 672 | if self.output_stream.is_none() { 673 | cubeb_log!("Calling position() on an input-only stream"); 674 | return Err(Error::error()); 675 | } 676 | 677 | let stm = self.output_stream.as_ref().unwrap(); 678 | let r = match stm.get_time() { 679 | Ok(r_usec) => { 680 | let bytes = USecExt::to_bytes(r_usec, &self.output_sample_spec); 681 | Ok((bytes / self.output_sample_spec.frame_size()) as u64) 682 | } 683 | Err(_) => { 684 | cubeb_log!("Error: stm.get_time failed"); 685 | Err(Error::error()) 686 | } 687 | }; 688 | 689 | if !in_thread { 690 | self.context.mainloop.unlock(); 691 | } 692 | 693 | r 694 | } 695 | 696 | fn latency(&mut self) -> Result { 697 | match self.output_stream { 698 | None => { 699 | cubeb_log!("Error: calling latency() on an input-only stream"); 700 | Err(Error::error()) 701 | } 702 | Some(ref stm) => match stm.get_latency() { 703 | Ok(StreamLatency::Positive(r_usec)) => { 704 | let latency = (r_usec * pa_usec_t::from(self.output_sample_spec.rate) 705 | / PA_USEC_PER_SEC) as u32; 706 | Ok(latency) 707 | } 708 | Ok(_) => { 709 | panic!("Can not handle negative latency values."); 710 | } 711 | Err(_) => { 712 | cubeb_log!("Error: get_latency() failed for an output stream"); 713 | Err(Error::error()) 714 | } 715 | }, 716 | } 717 | } 718 | 719 | fn input_latency(&mut self) -> Result { 720 | match self.input_stream { 721 | None => { 722 | cubeb_log!("Error: calling input_latency() on an output-only stream"); 723 | Err(Error::error()) 724 | } 725 | Some(ref stm) => match stm.get_latency() { 726 | Ok(StreamLatency::Positive(w_usec)) => { 727 | let latency = (w_usec * pa_usec_t::from(self.input_sample_spec.rate) 728 | / PA_USEC_PER_SEC) as u32; 729 | Ok(latency) 730 | } 731 | // Input stream can be negative only if it is attached to a 732 | // monitor source device 733 | Ok(StreamLatency::Negative(_)) => Ok(0), 734 | Err(_) => { 735 | cubeb_log!("Error: stm.get_latency() failed for an input stream"); 736 | Err(Error::error()) 737 | } 738 | }, 739 | } 740 | } 741 | 742 | fn set_volume(&mut self, volume: f32) -> Result<()> { 743 | match self.output_stream { 744 | None => { 745 | cubeb_log!("Error: can't set volume on an input-only stream"); 746 | Err(Error::error()) 747 | } 748 | Some(ref stm) => { 749 | if let Some(ref context) = self.context.context { 750 | self.context.mainloop.lock(); 751 | 752 | let mut cvol: pa_cvolume = Default::default(); 753 | 754 | /* if the pulse daemon is configured to use flat 755 | * volumes, apply our own gain instead of changing 756 | * the input volume on the sink. */ 757 | let flags = { 758 | match self.context.default_sink_info { 759 | Some(ref info) => info.flags, 760 | _ => pulse::SinkFlags::empty(), 761 | } 762 | }; 763 | 764 | if flags.contains(pulse::SinkFlags::FLAT_VOLUME) { 765 | self.volume = volume; 766 | } else { 767 | let channels = stm.get_sample_spec().channels; 768 | let vol = pulse::sw_volume_from_linear(f64::from(volume)); 769 | cvol.set(u32::from(channels), vol); 770 | 771 | let index = stm.get_index(); 772 | 773 | let context_ptr = self.context as *const _ as *mut _; 774 | if let Ok(o) = context.set_sink_input_volume( 775 | index, 776 | &cvol, 777 | context_success, 778 | context_ptr, 779 | ) { 780 | self.context.operation_wait(stm, &o); 781 | } 782 | } 783 | 784 | self.context.mainloop.unlock(); 785 | Ok(()) 786 | } else { 787 | cubeb_log!("Error: set_volume: no context?"); 788 | Err(Error::error()) 789 | } 790 | } 791 | } 792 | } 793 | 794 | fn set_name(&mut self, name: &CStr) -> Result<()> { 795 | match self.output_stream { 796 | None => { 797 | cubeb_log!("Error: can't set the name on a input-only stream."); 798 | Err(Error::error()) 799 | } 800 | Some(ref stm) => { 801 | self.context.mainloop.lock(); 802 | if let Ok(o) = stm.set_name(name, stream_success, self as *const _ as *mut _) { 803 | self.context.operation_wait(stm, &o); 804 | } 805 | self.context.mainloop.unlock(); 806 | Ok(()) 807 | } 808 | } 809 | } 810 | 811 | fn current_device(&mut self) -> Result<&DeviceRef> { 812 | if self.context.version_0_9_8 { 813 | let mut dev: Box = Box::new(unsafe { mem::zeroed() }); 814 | 815 | if let Some(ref stm) = self.input_stream { 816 | dev.input_name = match stm.get_device_name() { 817 | Ok(name) => name.to_owned().into_raw(), 818 | Err(_) => { 819 | cubeb_log!("Error: couldn't get the input stream's device name"); 820 | return Err(Error::error()); 821 | } 822 | } 823 | } 824 | 825 | if let Some(ref stm) = self.output_stream { 826 | dev.output_name = match stm.get_device_name() { 827 | Ok(name) => name.to_owned().into_raw(), 828 | Err(_) => { 829 | cubeb_log!("Error: couldn't get the output stream's device name"); 830 | return Err(Error::error()); 831 | } 832 | } 833 | } 834 | 835 | Ok(unsafe { DeviceRef::from_ptr(Box::into_raw(dev) as *mut _) }) 836 | } else { 837 | cubeb_log!("Error: PulseAudio context too old"); 838 | Err(not_supported()) 839 | } 840 | } 841 | 842 | fn set_input_mute(&mut self, _mute: bool) -> Result<()> { 843 | Err(not_supported()) 844 | } 845 | 846 | fn set_input_processing_params(&mut self, _params: InputProcessingParams) -> Result<()> { 847 | Err(not_supported()) 848 | } 849 | 850 | fn device_destroy(&mut self, device: &DeviceRef) -> Result<()> { 851 | if device.as_ptr().is_null() { 852 | cubeb_log!("Error: can't destroy null device"); 853 | Err(Error::error()) 854 | } else { 855 | unsafe { 856 | let _: Box = Box::from_raw(device.as_ptr() as *mut _); 857 | } 858 | Ok(()) 859 | } 860 | } 861 | 862 | fn register_device_changed_callback( 863 | &mut self, 864 | _: ffi::cubeb_device_changed_callback, 865 | ) -> Result<()> { 866 | cubeb_log!("Error: register_device_change_callback unimplemented"); 867 | Err(Error::error()) 868 | } 869 | } 870 | 871 | impl PulseStream<'_> { 872 | fn stream_init( 873 | context: &pulse::Context, 874 | stream_params: &StreamParamsRef, 875 | stream_name: Option<&CStr>, 876 | ) -> Result { 877 | if stream_params.prefs() == StreamPrefs::LOOPBACK { 878 | cubeb_log!("Error: StreamPref::LOOPBACK unimplemented"); 879 | return Err(not_supported()); 880 | } 881 | 882 | fn to_pulse_format(format: SampleFormat) -> pulse::SampleFormat { 883 | match format { 884 | SampleFormat::S16LE => pulse::SampleFormat::Signed16LE, 885 | SampleFormat::S16BE => pulse::SampleFormat::Signed16BE, 886 | SampleFormat::Float32LE => pulse::SampleFormat::Float32LE, 887 | SampleFormat::Float32BE => pulse::SampleFormat::Float32BE, 888 | _ => pulse::SampleFormat::Invalid, 889 | } 890 | } 891 | 892 | let fmt = to_pulse_format(stream_params.format()); 893 | if fmt == pulse::SampleFormat::Invalid { 894 | cubeb_log!("Error: invalid sample format"); 895 | return Err(invalid_format()); 896 | } 897 | 898 | let ss = pulse::SampleSpec { 899 | channels: stream_params.channels() as u8, 900 | format: fmt.into(), 901 | rate: stream_params.rate(), 902 | }; 903 | 904 | let cm: Option = match stream_params.layout() { 905 | ChannelLayout::UNDEFINED => { 906 | if stream_params.channels() <= 8 907 | && pulse::ChannelMap::init_auto( 908 | stream_params.channels(), 909 | PA_CHANNEL_MAP_DEFAULT, 910 | ) 911 | .is_none() 912 | { 913 | cubeb_log!("Layout undefined and PulseAudio's default layout has not been configured, guess one."); 914 | Some(layout_to_channel_map(default_layout_for_channels( 915 | stream_params.channels(), 916 | ))) 917 | } else { 918 | cubeb_log!("Layout undefined, PulseAudio will use its default."); 919 | None 920 | } 921 | } 922 | _ => Some(layout_to_channel_map(stream_params.layout())), 923 | }; 924 | 925 | let stream = pulse::Stream::new(context, stream_name.unwrap(), &ss, cm.as_ref()); 926 | 927 | match stream { 928 | None => { 929 | cubeb_log!("Error: pulse::Stream::new failure"); 930 | Err(Error::error()) 931 | } 932 | Some(stm) => Ok(stm), 933 | } 934 | } 935 | 936 | pub fn cork_stream(&self, stream: Option<&pulse::Stream>, state: CorkState) { 937 | if let Some(stm) = stream { 938 | if let Ok(o) = stm.cork( 939 | state.is_cork() as i32, 940 | stream_success, 941 | self as *const _ as *mut _, 942 | ) { 943 | self.context.operation_wait(stream, &o); 944 | } 945 | } 946 | } 947 | 948 | fn cork(&mut self, state: CorkState) { 949 | { 950 | self.context.mainloop.lock(); 951 | self.cork_stream(self.output_stream.as_ref(), state); 952 | self.cork_stream(self.input_stream.as_ref(), state); 953 | self.context.mainloop.unlock() 954 | } 955 | 956 | if state.is_notify() { 957 | self.state_change_callback(if state.is_cork() { 958 | ffi::CUBEB_STATE_STOPPED 959 | } else { 960 | ffi::CUBEB_STATE_STARTED 961 | }); 962 | } 963 | } 964 | 965 | fn update_timing_info(&self) -> bool { 966 | let mut r = false; 967 | 968 | if let Some(ref stm) = self.output_stream { 969 | if let Ok(o) = stm.update_timing_info(stream_success, self as *const _ as *mut _) { 970 | r = self.context.operation_wait(stm, &o); 971 | } 972 | 973 | if !r { 974 | return r; 975 | } 976 | } 977 | 978 | if let Some(ref stm) = self.input_stream { 979 | if let Ok(o) = stm.update_timing_info(stream_success, self as *const _ as *mut _) { 980 | r = self.context.operation_wait(stm, &o); 981 | } 982 | } 983 | 984 | r 985 | } 986 | 987 | pub fn state_change_callback(&mut self, s: ffi::cubeb_state) { 988 | self.state = s; 989 | unsafe { 990 | (self.state_callback.unwrap())( 991 | self as *mut PulseStream as *mut ffi::cubeb_stream, 992 | self.user_ptr, 993 | s, 994 | ) 995 | }; 996 | } 997 | 998 | fn wait_until_ready(&self) -> bool { 999 | fn wait_until_io_stream_ready( 1000 | stm: &pulse::Stream, 1001 | mainloop: &pulse::ThreadedMainloop, 1002 | ) -> bool { 1003 | if mainloop.is_null() { 1004 | return false; 1005 | } 1006 | 1007 | loop { 1008 | let state = stm.get_state(); 1009 | if !state.is_good() { 1010 | return false; 1011 | } 1012 | if state == pulse::StreamState::Ready { 1013 | break; 1014 | } 1015 | mainloop.wait(); 1016 | } 1017 | 1018 | true 1019 | } 1020 | 1021 | if let Some(ref stm) = self.output_stream { 1022 | if !wait_until_io_stream_ready(stm, &self.context.mainloop) { 1023 | return false; 1024 | } 1025 | } 1026 | 1027 | if let Some(ref stm) = self.input_stream { 1028 | if !wait_until_io_stream_ready(stm, &self.context.mainloop) { 1029 | return false; 1030 | } 1031 | } 1032 | 1033 | true 1034 | } 1035 | 1036 | #[allow(clippy::cognitive_complexity)] 1037 | fn trigger_user_callback(&mut self, input_data: *const c_void, nbytes: usize) { 1038 | fn drained_cb( 1039 | a: &pulse::MainloopApi, 1040 | e: *mut pa_time_event, 1041 | _tv: &pulse::TimeVal, 1042 | u: *mut c_void, 1043 | ) { 1044 | cubeb_logv!("Drain finished callback."); 1045 | let stm = unsafe { &mut *(u as *mut PulseStream) }; 1046 | let drain_timer = stm.drain_timer.load(Ordering::Acquire); 1047 | debug_assert_eq!(drain_timer, e); 1048 | stm.state_change_callback(ffi::CUBEB_STATE_DRAINED); 1049 | /* there's no pa_rttime_free, so use this instead. */ 1050 | a.time_free(drain_timer); 1051 | stm.drain_timer.store(ptr::null_mut(), Ordering::Release); 1052 | stm.context.mainloop.signal(); 1053 | } 1054 | 1055 | if let Some(ref stm) = self.output_stream { 1056 | let frame_size = self.output_sample_spec.frame_size(); 1057 | debug_assert_eq!(nbytes % frame_size, 0); 1058 | 1059 | let mut towrite = nbytes; 1060 | let mut read_offset = 0usize; 1061 | while towrite > 0 { 1062 | match stm.begin_write(towrite) { 1063 | Err(e) => { 1064 | cubeb_logv!("Error: failure to write data"); 1065 | panic!("Failed to write data: {}", e); 1066 | } 1067 | Ok((buffer, size)) => { 1068 | debug_assert!(size > 0); 1069 | debug_assert_eq!(size % frame_size, 0); 1070 | 1071 | cubeb_logv!( 1072 | "Trigger user callback with output buffer size={}, read_offset={}", 1073 | size, 1074 | read_offset 1075 | ); 1076 | let read_ptr = unsafe { (input_data as *const u8).add(read_offset) }; 1077 | #[allow(clippy::unnecessary_cast)] 1078 | let mut got = unsafe { 1079 | self.data_callback.unwrap()( 1080 | self as *const _ as *mut _, 1081 | self.user_ptr, 1082 | read_ptr as *const _ as *mut _, 1083 | buffer, 1084 | (size / frame_size) as c_long, 1085 | ) as i64 1086 | }; 1087 | if got < 0 { 1088 | let _ = stm.cancel_write(); 1089 | self.shutdown = true; 1090 | unsafe { 1091 | self.state_callback.unwrap()( 1092 | self as *const _ as *mut _, 1093 | self.user_ptr, 1094 | ffi::CUBEB_STATE_ERROR, 1095 | ); 1096 | } 1097 | return; 1098 | } 1099 | 1100 | // If more iterations move offset of read buffer 1101 | if !input_data.is_null() { 1102 | let in_frame_size = self.input_sample_spec.frame_size(); 1103 | read_offset += (size / frame_size) * in_frame_size; 1104 | } 1105 | 1106 | if self.volume != PULSE_NO_GAIN { 1107 | let samples = (self.output_sample_spec.channels as usize * size 1108 | / frame_size) as isize; 1109 | 1110 | if self.output_sample_spec.format == PA_SAMPLE_S16BE 1111 | || self.output_sample_spec.format == PA_SAMPLE_S16LE 1112 | { 1113 | let b = buffer as *mut i16; 1114 | for i in 0..samples { 1115 | unsafe { *b.offset(i) *= self.volume as i16 }; 1116 | } 1117 | } else { 1118 | let b = buffer as *mut f32; 1119 | for i in 0..samples { 1120 | unsafe { *b.offset(i) *= self.volume }; 1121 | } 1122 | } 1123 | } 1124 | 1125 | let should_drain = (got as usize) < size / frame_size; 1126 | 1127 | if should_drain && self.output_frame_count.load(Ordering::SeqCst) == 0 { 1128 | // Draining during preroll, ensure `prebuf` frames are written so 1129 | // the stream starts. If not, pad with a bit of silence. 1130 | let prebuf_size_bytes = stm.get_buffer_attr().prebuf as usize; 1131 | let got_bytes = got as usize * frame_size; 1132 | if prebuf_size_bytes > got_bytes { 1133 | let padding_bytes = prebuf_size_bytes - got_bytes; 1134 | if padding_bytes + got_bytes <= size { 1135 | // A slice that starts after the data provided by the callback, 1136 | // with just enough room to provide a final buffer big enough. 1137 | let padding_buf: &mut [u8] = unsafe { 1138 | slice::from_raw_parts_mut::( 1139 | buffer.add(got_bytes) as *mut u8, 1140 | padding_bytes, 1141 | ) 1142 | }; 1143 | padding_buf.fill(0); 1144 | got += (padding_bytes / frame_size) as i64; 1145 | } 1146 | } else { 1147 | cubeb_logv!( 1148 | "Not enough room to pad up to prebuf when prebuffering." 1149 | ) 1150 | } 1151 | } 1152 | 1153 | let r = stm.write( 1154 | buffer, 1155 | got as usize * frame_size, 1156 | 0, 1157 | pulse::SeekMode::Relative, 1158 | ); 1159 | 1160 | if should_drain { 1161 | cubeb_logv!("Draining {} < {}", got, size / frame_size); 1162 | let latency = match stm.get_latency() { 1163 | Ok(StreamLatency::Positive(l)) => l, 1164 | Ok(_) => { 1165 | panic!("Can not handle negative latency values."); 1166 | } 1167 | Err(e) => { 1168 | debug_assert_eq!( 1169 | e, 1170 | pulse::ErrorCode::from_error_code(PA_ERR_NODATA) 1171 | ); 1172 | /* this needs a better guess. */ 1173 | 100 * PA_USEC_PER_MSEC 1174 | } 1175 | }; 1176 | 1177 | /* pa_stream_drain is useless, see PA bug# 866. this is a workaround. */ 1178 | /* arbitrary safety margin: double the current latency. */ 1179 | debug_assert!(self.drain_timer.load(Ordering::Acquire).is_null()); 1180 | let stream_ptr = self as *const _ as *mut _; 1181 | if let Some(ref context) = self.context.context { 1182 | self.drain_timer.store( 1183 | context.rttime_new( 1184 | pulse::rtclock_now() + 2 * latency, 1185 | drained_cb, 1186 | stream_ptr, 1187 | ), 1188 | Ordering::Release, 1189 | ); 1190 | } 1191 | self.shutdown = true; 1192 | return; 1193 | } 1194 | 1195 | debug_assert!(r.is_ok()); 1196 | 1197 | towrite -= size; 1198 | } 1199 | } 1200 | } 1201 | debug_assert_eq!(towrite, 0); 1202 | } 1203 | } 1204 | } 1205 | 1206 | fn stream_success(_: &pulse::Stream, success: i32, u: *mut c_void) { 1207 | let stm = unsafe { &*(u as *mut PulseStream) }; 1208 | if success != 1 { 1209 | cubeb_log!("stream_success ignored failure: {}", success); 1210 | } 1211 | stm.context.mainloop.signal(); 1212 | } 1213 | 1214 | fn context_success(_: &pulse::Context, success: i32, u: *mut c_void) { 1215 | let ctx = unsafe { &*(u as *mut PulseContext) }; 1216 | if success != 1 { 1217 | cubeb_log!("context_success ignored failure: {}", success); 1218 | } 1219 | ctx.mainloop.signal(); 1220 | } 1221 | 1222 | fn invalid_format() -> Error { 1223 | Error::from_raw(ffi::CUBEB_ERROR_INVALID_FORMAT) 1224 | } 1225 | 1226 | fn not_supported() -> Error { 1227 | Error::from_raw(ffi::CUBEB_ERROR_NOT_SUPPORTED) 1228 | } 1229 | 1230 | #[cfg(all(test, not(feature = "pulse-dlopen")))] 1231 | mod test { 1232 | use super::layout_to_channel_map; 1233 | use cubeb_backend::ChannelLayout; 1234 | use pulse_ffi::*; 1235 | 1236 | macro_rules! channel_tests { 1237 | {$($name: ident, $layout: ident => [ $($channels: ident),* ]),+} => { 1238 | $( 1239 | #[test] 1240 | fn $name() { 1241 | let layout = ChannelLayout::$layout; 1242 | let mut iter = super::channel_layout_iter(layout); 1243 | $( 1244 | assert_eq!(Some(ChannelLayout::$channels), iter.next()); 1245 | )* 1246 | assert_eq!(None, iter.next()); 1247 | } 1248 | 1249 | )* 1250 | } 1251 | } 1252 | 1253 | channel_tests! { 1254 | channels_unknown, UNDEFINED => [ ], 1255 | channels_mono, MONO => [ 1256 | FRONT_CENTER 1257 | ], 1258 | channels_mono_lfe, MONO_LFE => [ 1259 | FRONT_CENTER, 1260 | LOW_FREQUENCY 1261 | ], 1262 | channels_stereo, STEREO => [ 1263 | FRONT_LEFT, 1264 | FRONT_RIGHT 1265 | ], 1266 | channels_stereo_lfe, STEREO_LFE => [ 1267 | FRONT_LEFT, 1268 | FRONT_RIGHT, 1269 | LOW_FREQUENCY 1270 | ], 1271 | channels_3f, _3F => [ 1272 | FRONT_LEFT, 1273 | FRONT_RIGHT, 1274 | FRONT_CENTER 1275 | ], 1276 | channels_3f_lfe, _3F_LFE => [ 1277 | FRONT_LEFT, 1278 | FRONT_RIGHT, 1279 | FRONT_CENTER, 1280 | LOW_FREQUENCY 1281 | ], 1282 | channels_2f1, _2F1 => [ 1283 | FRONT_LEFT, 1284 | FRONT_RIGHT, 1285 | BACK_CENTER 1286 | ], 1287 | channels_2f1_lfe, _2F1_LFE => [ 1288 | FRONT_LEFT, 1289 | FRONT_RIGHT, 1290 | LOW_FREQUENCY, 1291 | BACK_CENTER 1292 | ], 1293 | channels_3f1, _3F1 => [ 1294 | FRONT_LEFT, 1295 | FRONT_RIGHT, 1296 | FRONT_CENTER, 1297 | BACK_CENTER 1298 | ], 1299 | channels_3f1_lfe, _3F1_LFE => [ 1300 | FRONT_LEFT, 1301 | FRONT_RIGHT, 1302 | FRONT_CENTER, 1303 | LOW_FREQUENCY, 1304 | BACK_CENTER 1305 | ], 1306 | channels_2f2, _2F2 => [ 1307 | FRONT_LEFT, 1308 | FRONT_RIGHT, 1309 | SIDE_LEFT, 1310 | SIDE_RIGHT 1311 | ], 1312 | channels_2f2_lfe, _2F2_LFE => [ 1313 | FRONT_LEFT, 1314 | FRONT_RIGHT, 1315 | LOW_FREQUENCY, 1316 | SIDE_LEFT, 1317 | SIDE_RIGHT 1318 | ], 1319 | channels_quad, QUAD => [ 1320 | FRONT_LEFT, 1321 | FRONT_RIGHT, 1322 | BACK_LEFT, 1323 | BACK_RIGHT 1324 | ], 1325 | channels_quad_lfe, QUAD_LFE => [ 1326 | FRONT_LEFT, 1327 | FRONT_RIGHT, 1328 | LOW_FREQUENCY, 1329 | BACK_LEFT, 1330 | BACK_RIGHT 1331 | ], 1332 | channels_3f2, _3F2 => [ 1333 | FRONT_LEFT, 1334 | FRONT_RIGHT, 1335 | FRONT_CENTER, 1336 | SIDE_LEFT, 1337 | SIDE_RIGHT 1338 | ], 1339 | channels_3f2_lfe, _3F2_LFE => [ 1340 | FRONT_LEFT, 1341 | FRONT_RIGHT, 1342 | FRONT_CENTER, 1343 | LOW_FREQUENCY, 1344 | SIDE_LEFT, 1345 | SIDE_RIGHT 1346 | ], 1347 | channels_3f2_back, _3F2_BACK => [ 1348 | FRONT_LEFT, 1349 | FRONT_RIGHT, 1350 | FRONT_CENTER, 1351 | BACK_LEFT, 1352 | BACK_RIGHT 1353 | ], 1354 | channels_3f2_lfe_back, _3F2_LFE_BACK => [ 1355 | FRONT_LEFT, 1356 | FRONT_RIGHT, 1357 | FRONT_CENTER, 1358 | LOW_FREQUENCY, 1359 | BACK_LEFT, 1360 | BACK_RIGHT 1361 | ], 1362 | channels_3f3r_lfe, _3F3R_LFE => [ 1363 | FRONT_LEFT, 1364 | FRONT_RIGHT, 1365 | FRONT_CENTER, 1366 | LOW_FREQUENCY, 1367 | BACK_CENTER, 1368 | SIDE_LEFT, 1369 | SIDE_RIGHT 1370 | ], 1371 | channels_3f4_lfe, _3F4_LFE => [ 1372 | FRONT_LEFT, 1373 | FRONT_RIGHT, 1374 | FRONT_CENTER, 1375 | LOW_FREQUENCY, 1376 | BACK_LEFT, 1377 | BACK_RIGHT, 1378 | SIDE_LEFT, 1379 | SIDE_RIGHT 1380 | ] 1381 | } 1382 | 1383 | #[test] 1384 | fn mono_channels_enumerate() { 1385 | let layout = ChannelLayout::MONO; 1386 | let mut iter = super::channel_layout_iter(layout).enumerate(); 1387 | assert_eq!(Some((0, ChannelLayout::FRONT_CENTER)), iter.next()); 1388 | assert_eq!(None, iter.next()); 1389 | } 1390 | 1391 | #[test] 1392 | fn stereo_channels_enumerate() { 1393 | let layout = ChannelLayout::STEREO; 1394 | let mut iter = super::channel_layout_iter(layout).enumerate(); 1395 | assert_eq!(Some((0, ChannelLayout::FRONT_LEFT)), iter.next()); 1396 | assert_eq!(Some((1, ChannelLayout::FRONT_RIGHT)), iter.next()); 1397 | assert_eq!(None, iter.next()); 1398 | } 1399 | 1400 | #[test] 1401 | fn quad_channels_enumerate() { 1402 | let layout = ChannelLayout::QUAD; 1403 | let mut iter = super::channel_layout_iter(layout).enumerate(); 1404 | assert_eq!(Some((0, ChannelLayout::FRONT_LEFT)), iter.next()); 1405 | assert_eq!(Some((1, ChannelLayout::FRONT_RIGHT)), iter.next()); 1406 | assert_eq!(Some((2, ChannelLayout::BACK_LEFT)), iter.next()); 1407 | assert_eq!(Some((3, ChannelLayout::BACK_RIGHT)), iter.next()); 1408 | assert_eq!(None, iter.next()); 1409 | } 1410 | 1411 | macro_rules! map_channel_tests { 1412 | {$($name: ident, $layout: ident => [ $($channels: ident),* ]),+} => { 1413 | $( 1414 | #[test] 1415 | fn $name() { 1416 | let map = layout_to_channel_map(ChannelLayout::$layout); 1417 | assert_eq!(map.channels, map_channel_tests!(__COUNT__ $($channels)*)); 1418 | map_channel_tests!(__EACH__ map, 0, $($channels)*); 1419 | } 1420 | 1421 | )* 1422 | }; 1423 | (__COUNT__) => (0u8); 1424 | (__COUNT__ $x:ident $($xs: ident)*) => (1u8 + map_channel_tests!(__COUNT__ $($xs)*)); 1425 | (__EACH__ $map:expr, $i:expr, ) => {}; 1426 | (__EACH__ $map:expr, $i:expr, $x:ident $($xs: ident)*) => { 1427 | assert_eq!($map.map[$i], $x); 1428 | map_channel_tests!(__EACH__ $map, $i+1, $($xs)* ); 1429 | }; 1430 | } 1431 | 1432 | map_channel_tests! { 1433 | map_channel_mono, MONO => [ 1434 | PA_CHANNEL_POSITION_MONO 1435 | ], 1436 | map_channel_mono_lfe, MONO_LFE => [ 1437 | PA_CHANNEL_POSITION_FRONT_CENTER, 1438 | PA_CHANNEL_POSITION_LFE 1439 | ], 1440 | map_channel_stereo, STEREO => [ 1441 | PA_CHANNEL_POSITION_FRONT_LEFT, 1442 | PA_CHANNEL_POSITION_FRONT_RIGHT 1443 | ], 1444 | map_channel_stereo_lfe, STEREO_LFE => [ 1445 | PA_CHANNEL_POSITION_FRONT_LEFT, 1446 | PA_CHANNEL_POSITION_FRONT_RIGHT, 1447 | PA_CHANNEL_POSITION_LFE 1448 | ], 1449 | map_channel_3f, _3F => [ 1450 | PA_CHANNEL_POSITION_FRONT_LEFT, 1451 | PA_CHANNEL_POSITION_FRONT_RIGHT, 1452 | PA_CHANNEL_POSITION_FRONT_CENTER 1453 | ], 1454 | map_channel_3f_lfe, _3F_LFE => [ 1455 | PA_CHANNEL_POSITION_FRONT_LEFT, 1456 | PA_CHANNEL_POSITION_FRONT_RIGHT, 1457 | PA_CHANNEL_POSITION_FRONT_CENTER, 1458 | PA_CHANNEL_POSITION_LFE 1459 | ], 1460 | map_channel_2f1, _2F1 => [ 1461 | PA_CHANNEL_POSITION_FRONT_LEFT, 1462 | PA_CHANNEL_POSITION_FRONT_RIGHT, 1463 | PA_CHANNEL_POSITION_REAR_CENTER 1464 | ], 1465 | map_channel_2f1_lfe, _2F1_LFE => [ 1466 | PA_CHANNEL_POSITION_FRONT_LEFT, 1467 | PA_CHANNEL_POSITION_FRONT_RIGHT, 1468 | PA_CHANNEL_POSITION_LFE, 1469 | PA_CHANNEL_POSITION_REAR_CENTER 1470 | ], 1471 | map_channel_3f1, _3F1 => [ 1472 | PA_CHANNEL_POSITION_FRONT_LEFT, 1473 | PA_CHANNEL_POSITION_FRONT_RIGHT, 1474 | PA_CHANNEL_POSITION_FRONT_CENTER, 1475 | PA_CHANNEL_POSITION_REAR_CENTER 1476 | ], 1477 | map_channel_3f1_lfe, _3F1_LFE =>[ 1478 | PA_CHANNEL_POSITION_FRONT_LEFT, 1479 | PA_CHANNEL_POSITION_FRONT_RIGHT, 1480 | PA_CHANNEL_POSITION_FRONT_CENTER, 1481 | PA_CHANNEL_POSITION_LFE, 1482 | PA_CHANNEL_POSITION_REAR_CENTER 1483 | ], 1484 | map_channel_2f2, _2F2 => [ 1485 | PA_CHANNEL_POSITION_FRONT_LEFT, 1486 | PA_CHANNEL_POSITION_FRONT_RIGHT, 1487 | PA_CHANNEL_POSITION_SIDE_LEFT, 1488 | PA_CHANNEL_POSITION_SIDE_RIGHT 1489 | ], 1490 | map_channel_2f2_lfe, _2F2_LFE => [ 1491 | PA_CHANNEL_POSITION_FRONT_LEFT, 1492 | PA_CHANNEL_POSITION_FRONT_RIGHT, 1493 | PA_CHANNEL_POSITION_LFE, 1494 | PA_CHANNEL_POSITION_SIDE_LEFT, 1495 | PA_CHANNEL_POSITION_SIDE_RIGHT 1496 | ], 1497 | map_channel_quad, QUAD => [ 1498 | PA_CHANNEL_POSITION_FRONT_LEFT, 1499 | PA_CHANNEL_POSITION_FRONT_RIGHT, 1500 | PA_CHANNEL_POSITION_REAR_LEFT, 1501 | PA_CHANNEL_POSITION_REAR_RIGHT 1502 | ], 1503 | map_channel_quad_lfe, QUAD_LFE => [ 1504 | PA_CHANNEL_POSITION_FRONT_LEFT, 1505 | PA_CHANNEL_POSITION_FRONT_RIGHT, 1506 | PA_CHANNEL_POSITION_LFE, 1507 | PA_CHANNEL_POSITION_REAR_LEFT, 1508 | PA_CHANNEL_POSITION_REAR_RIGHT 1509 | ], 1510 | map_channel_3f2, _3F2 => [ 1511 | PA_CHANNEL_POSITION_FRONT_LEFT, 1512 | PA_CHANNEL_POSITION_FRONT_RIGHT, 1513 | PA_CHANNEL_POSITION_FRONT_CENTER, 1514 | PA_CHANNEL_POSITION_SIDE_LEFT, 1515 | PA_CHANNEL_POSITION_SIDE_RIGHT 1516 | ], 1517 | map_channel_3f2_lfe, _3F2_LFE => [ 1518 | PA_CHANNEL_POSITION_FRONT_LEFT, 1519 | PA_CHANNEL_POSITION_FRONT_RIGHT, 1520 | PA_CHANNEL_POSITION_FRONT_CENTER, 1521 | PA_CHANNEL_POSITION_LFE, 1522 | PA_CHANNEL_POSITION_SIDE_LEFT, 1523 | PA_CHANNEL_POSITION_SIDE_RIGHT 1524 | ], 1525 | map_channel_3f2_back, _3F2_BACK => [ 1526 | PA_CHANNEL_POSITION_FRONT_LEFT, 1527 | PA_CHANNEL_POSITION_FRONT_RIGHT, 1528 | PA_CHANNEL_POSITION_FRONT_CENTER, 1529 | PA_CHANNEL_POSITION_REAR_LEFT, 1530 | PA_CHANNEL_POSITION_REAR_RIGHT 1531 | ], 1532 | map_channel_3f2_lfe_back, _3F2_LFE_BACK => [ 1533 | PA_CHANNEL_POSITION_FRONT_LEFT, 1534 | PA_CHANNEL_POSITION_FRONT_RIGHT, 1535 | PA_CHANNEL_POSITION_FRONT_CENTER, 1536 | PA_CHANNEL_POSITION_LFE, 1537 | PA_CHANNEL_POSITION_REAR_LEFT, 1538 | PA_CHANNEL_POSITION_REAR_RIGHT 1539 | ], 1540 | map_channel_3f3r_lfe, _3F3R_LFE => [ 1541 | PA_CHANNEL_POSITION_FRONT_LEFT, 1542 | PA_CHANNEL_POSITION_FRONT_RIGHT, 1543 | PA_CHANNEL_POSITION_FRONT_CENTER, 1544 | PA_CHANNEL_POSITION_LFE, 1545 | PA_CHANNEL_POSITION_REAR_CENTER, 1546 | PA_CHANNEL_POSITION_SIDE_LEFT, 1547 | PA_CHANNEL_POSITION_SIDE_RIGHT 1548 | ], 1549 | map_channel_3f4_lfe, _3F4_LFE => [ 1550 | PA_CHANNEL_POSITION_FRONT_LEFT, 1551 | PA_CHANNEL_POSITION_FRONT_RIGHT, 1552 | PA_CHANNEL_POSITION_FRONT_CENTER, 1553 | PA_CHANNEL_POSITION_LFE, 1554 | PA_CHANNEL_POSITION_REAR_LEFT, 1555 | PA_CHANNEL_POSITION_REAR_RIGHT, 1556 | PA_CHANNEL_POSITION_SIDE_LEFT, 1557 | PA_CHANNEL_POSITION_SIDE_RIGHT 1558 | ] 1559 | } 1560 | } 1561 | -------------------------------------------------------------------------------- /src/capi.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use backend::PulseContext; 7 | use cubeb_backend::{capi, ffi}; 8 | use std::os::raw::{c_char, c_int}; 9 | 10 | /// # Safety 11 | /// 12 | /// Entry point from C code. This function is unsafe because it dereferences 13 | /// the given `c` and `context_name` pointers. The caller should ensure those 14 | /// pointers are valid. 15 | #[no_mangle] 16 | pub unsafe extern "C" fn pulse_rust_init( 17 | c: *mut *mut ffi::cubeb, 18 | context_name: *const c_char, 19 | ) -> c_int { 20 | capi::capi_init::(c, context_name) 21 | } 22 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Cubeb backend interface to Pulse Audio 2 | 3 | // Copyright © 2017-2018 Mozilla Foundation 4 | // 5 | // This program is made available under an ISC-style license. See the 6 | // accompanying file LICENSE for details. 7 | 8 | #[macro_use] 9 | extern crate cubeb_backend; 10 | extern crate pulse; 11 | extern crate pulse_ffi; 12 | extern crate ringbuf; 13 | extern crate semver; 14 | 15 | mod backend; 16 | mod capi; 17 | 18 | pub use capi::pulse_rust_init; 19 | --------------------------------------------------------------------------------