зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1440538
- P4: Update cubeb-pulse-rs to commit f58dc34. r=kinetik
MozReview-Commit-ID: 5eQV3nUceQe --HG-- extra : rebase_source : c182a15ceda6ef3bb9e25ca3a4465aba325f32bf
This commit is contained in:
Родитель
16617a8283
Коммит
b486a46d6a
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "cubeb-pulse"
|
||||
version = "0.0.2"
|
||||
version = "0.1.1"
|
||||
authors = ["Dan Glastonbury <dglastonbury@mozilla.com>"]
|
||||
description = "Cubeb backed for PulseAudio written in Rust"
|
||||
|
||||
|
@ -11,7 +11,7 @@ pulse-dlopen = ["pulse-ffi/dlopen"]
|
|||
crate-type = ["staticlib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
cubeb-ffi = { path = "cubeb-ffi" }
|
||||
cubeb-backend = "0.4"
|
||||
pulse-ffi = { path = "pulse-ffi" }
|
||||
pulse = { path = "pulse-rs" }
|
||||
semver = "^0.6"
|
||||
|
|
|
@ -5,4 +5,4 @@ Makefile.in build files for the Mozilla build system.
|
|||
|
||||
The cubeb-pulse-rs git repository is: https://github.com/djg/cubeb-pulse-rs.git
|
||||
|
||||
The git commit ID used was deadde7d14e0010628471e33a538a0a1e59765ff (2018-01-25 10:00:16 +1000)
|
||||
The git commit ID used was f58dc34c5af519352aed4b4cd79bb34060e59c65 (2018-02-23 11:16:40 +1000)
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
[package]
|
||||
name = "cubeb-ffi"
|
||||
version = "0.0.2"
|
||||
authors = ["Dan Glastonbury <dglastonbury@mozilla.com>"]
|
||||
description = "FFI bindings for implementing cubeb backends"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.0"
|
|
@ -1,508 +0,0 @@
|
|||
// Copyright © 2017 Mozilla Foundation
|
||||
//
|
||||
// This program is made available under an ISC-style license. See the
|
||||
// accompanying file LICENSE for details.
|
||||
|
||||
use std::default::Default;
|
||||
use std::os::raw::{c_char, c_int, c_long, c_uint, c_void};
|
||||
use std::ptr;
|
||||
|
||||
pub enum Context {}
|
||||
pub enum Stream {}
|
||||
|
||||
// These need to match cubeb_sample_format
|
||||
pub type SampleFormat = c_int;
|
||||
pub const SAMPLE_S16LE: SampleFormat = 0;
|
||||
pub const SAMPLE_S16BE: SampleFormat = 1;
|
||||
pub const SAMPLE_FLOAT32LE: SampleFormat = 2;
|
||||
pub const SAMPLE_FLOAT32BE: SampleFormat = 3;
|
||||
|
||||
#[cfg(target_endian = "little")]
|
||||
pub const SAMPLE_S16NE: SampleFormat = SAMPLE_S16LE;
|
||||
#[cfg(target_endian = "little")]
|
||||
pub const SAMPLE_FLOAT32NE: SampleFormat = SAMPLE_FLOAT32LE;
|
||||
#[cfg(target_endian = "big")]
|
||||
pub const SAMPLE_S16NE: SampleFormat = SAMPLE_S16BE;
|
||||
#[cfg(target_endian = "big")]
|
||||
pub const SAMPLE_FLOAT32NE: SampleFormat = SAMPLE_FLOAT32BE;
|
||||
|
||||
pub type DeviceId = *const c_void;
|
||||
|
||||
// These need to match cubeb_channel_layout
|
||||
pub type ChannelLayout = c_int;
|
||||
pub const LAYOUT_UNDEFINED: ChannelLayout = 0;
|
||||
pub const LAYOUT_DUAL_MONO: ChannelLayout = 1;
|
||||
pub const LAYOUT_DUAL_MONO_LFE: ChannelLayout = 2;
|
||||
pub const LAYOUT_MONO: ChannelLayout = 3;
|
||||
pub const LAYOUT_MONO_LFE: ChannelLayout = 4;
|
||||
pub const LAYOUT_STEREO: ChannelLayout = 5;
|
||||
pub const LAYOUT_STEREO_LFE: ChannelLayout = 6;
|
||||
pub const LAYOUT_3F: ChannelLayout = 7;
|
||||
pub const LAYOUT_3F_LFE: ChannelLayout = 8;
|
||||
pub const LAYOUT_2F1: ChannelLayout = 9;
|
||||
pub const LAYOUT_2F1_LFE: ChannelLayout = 10;
|
||||
pub const LAYOUT_3F1: ChannelLayout = 11;
|
||||
pub const LAYOUT_3F1_LFE: ChannelLayout = 12;
|
||||
pub const LAYOUT_2F2: ChannelLayout = 13;
|
||||
pub const LAYOUT_2F2_LFE: ChannelLayout = 14;
|
||||
pub const LAYOUT_3F2: ChannelLayout = 15;
|
||||
pub const LAYOUT_3F2_LFE: ChannelLayout = 16;
|
||||
pub const LAYOUT_3F3R_LFE: ChannelLayout = 17;
|
||||
pub const LAYOUT_3F4_LFE: ChannelLayout = 18;
|
||||
pub const LAYOUT_MAX: ChannelLayout = 256;
|
||||
|
||||
|
||||
// These need to match cubeb_device_type
|
||||
bitflags! {
|
||||
#[repr(C)]
|
||||
pub struct StreamPrefs : u32 {
|
||||
const STREAM_PREF_NONE = 0x00;
|
||||
const STREAM_PREF_LOOPBACK = 0x01;
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct StreamParams {
|
||||
pub format: SampleFormat,
|
||||
pub rate: u32,
|
||||
pub channels: u32,
|
||||
pub layout: ChannelLayout,
|
||||
pub prefs: StreamPrefs,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Device {
|
||||
pub output_name: *mut c_char,
|
||||
pub input_name: *mut c_char,
|
||||
}
|
||||
|
||||
impl Default for Device {
|
||||
fn default() -> Self {
|
||||
Device {
|
||||
output_name: ptr::null_mut(),
|
||||
input_name: ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// These need to match cubeb_state
|
||||
pub type State = c_int;
|
||||
pub const STATE_STARTED: State = 0;
|
||||
pub const STATE_STOPPED: State = 1;
|
||||
pub const STATE_DRAINED: State = 2;
|
||||
pub const STATE_ERROR: State = 3;
|
||||
|
||||
pub const OK: i32 = 0;
|
||||
pub const ERROR: i32 = -1;
|
||||
pub const ERROR_INVALID_FORMAT: i32 = -2;
|
||||
pub const ERROR_INVALID_PARAMETER: i32 = -3;
|
||||
pub const ERROR_NOT_SUPPORTED: i32 = -4;
|
||||
pub const ERROR_DEVICE_UNAVAILABLE: i32 = -5;
|
||||
|
||||
// These need to match cubeb_device_type
|
||||
bitflags! {
|
||||
#[repr(C)]
|
||||
pub struct DeviceType : u32 {
|
||||
const UNKNOWN = 0b00;
|
||||
const INPUT = 0b01;
|
||||
const OUTPUT = 0b10;
|
||||
const ALL = 0b11;
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum DeviceState {
|
||||
Disabled = 0,
|
||||
Unplugged = 1,
|
||||
Enabled = 2,
|
||||
}
|
||||
|
||||
// These need to match cubeb_device_fmt
|
||||
bitflags! {
|
||||
#[repr(C)]
|
||||
pub struct DeviceFmt: u32 {
|
||||
const S16LE = 0x0010;
|
||||
const S16BE = 0x0020;
|
||||
const S16NE = {
|
||||
#[cfg(target_endian = "little")] { DeviceFmt::S16LE }
|
||||
#[cfg(target_endian = "big")] { DeviceFmt::S16BE }
|
||||
}.bits;
|
||||
const F32LE = 0x1000;
|
||||
const F32BE = 0x2000;
|
||||
const F32NE = {
|
||||
#[cfg(target_endian = "little")] { DeviceFmt::F32LE }
|
||||
#[cfg(target_endian = "big")] { DeviceFmt::F32BE }
|
||||
}.bits;
|
||||
const S16_MASK = DeviceFmt::S16LE.bits | DeviceFmt::S16BE.bits;
|
||||
const F32_MASK = DeviceFmt::F32LE.bits | DeviceFmt::F32BE.bits;
|
||||
const ALL = DeviceFmt::S16_MASK.bits | DeviceFmt::F32_MASK.bits;
|
||||
}
|
||||
}
|
||||
|
||||
// These need to match cubeb_device_pref
|
||||
bitflags! {
|
||||
#[repr(C)]
|
||||
pub struct DevicePref : u32 {
|
||||
const MULTIMEDIA = 0x1;
|
||||
const VOICE = 0x2;
|
||||
const NOTIFICATION = 0x4;
|
||||
const ALL = 0xF;
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct DeviceInfo {
|
||||
pub devid: DeviceId,
|
||||
pub device_id: *const c_char,
|
||||
pub friendly_name: *const c_char,
|
||||
pub group_id: *const c_char,
|
||||
pub vendor_name: *const c_char,
|
||||
pub devtype: DeviceType,
|
||||
pub state: DeviceState,
|
||||
pub preferred: DevicePref,
|
||||
pub format: DeviceFmt,
|
||||
pub default_format: DeviceFmt,
|
||||
pub max_channels: u32,
|
||||
pub default_rate: u32,
|
||||
pub max_rate: u32,
|
||||
pub min_rate: u32,
|
||||
pub latency_lo: u32,
|
||||
pub latency_hi: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct DeviceCollection {
|
||||
/// Array of device info.
|
||||
pub device: *const DeviceInfo,
|
||||
/// Device count in collection.
|
||||
pub count: usize,
|
||||
}
|
||||
|
||||
pub type DataCallback = Option<unsafe extern "C" fn(stream: *mut Stream,
|
||||
user_ptr: *mut c_void,
|
||||
input_buffer: *const c_void,
|
||||
output_buffer: *mut c_void,
|
||||
nframes: c_long)
|
||||
-> c_long>;
|
||||
pub type StateCallback = Option<unsafe extern "C" fn(stream: *mut Stream, user_ptr: *mut c_void, state: State)>;
|
||||
pub type DeviceChangedCallback = Option<unsafe extern "C" fn(user_ptr: *mut c_void)>;
|
||||
pub type DeviceCollectionChangedCallback = Option<unsafe extern "C" fn(context: *mut Context, user_ptr: *mut c_void)>;
|
||||
|
||||
pub type StreamInitFn = Option<unsafe extern "C" fn(context: *mut Context,
|
||||
stream: *mut *mut Stream,
|
||||
stream_name: *const c_char,
|
||||
input_device: DeviceId,
|
||||
input_stream_params: *mut StreamParams,
|
||||
output_device: DeviceId,
|
||||
output_stream_params: *mut StreamParams,
|
||||
latency: u32,
|
||||
data_callback: DataCallback,
|
||||
state_callback: StateCallback,
|
||||
user_ptr: *mut c_void)
|
||||
-> i32>;
|
||||
|
||||
pub type RegisterDeviceCollectionChangedFn = Option<unsafe extern "C" fn(context: *mut Context,
|
||||
devtype: DeviceType,
|
||||
callback: DeviceCollectionChangedCallback,
|
||||
user_ptr: *mut c_void)
|
||||
-> i32>;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Ops {
|
||||
pub init: Option<unsafe extern "C" fn(context: *mut *mut Context, context_name: *const c_char) -> i32>,
|
||||
pub get_backend_id: Option<unsafe extern "C" fn(context: *mut Context) -> *const c_char>,
|
||||
pub get_max_channel_count: Option<unsafe extern "C" fn(context: *mut Context, max_channels: *mut u32) -> i32>,
|
||||
pub get_min_latency: Option<unsafe extern "C" fn(context: *mut Context,
|
||||
params: StreamParams,
|
||||
latency_ms: *mut u32)
|
||||
-> i32>,
|
||||
pub get_preferred_sample_rate: Option<unsafe extern "C" fn(context: *mut Context, rate: *mut u32) -> i32>,
|
||||
pub get_preferred_channel_layout:
|
||||
Option<unsafe extern "C" fn(context: *mut Context, layout: *mut ChannelLayout) -> i32>,
|
||||
pub enumerate_devices: Option<unsafe extern "C" fn(context: *mut Context,
|
||||
devtype: DeviceType,
|
||||
collection: *mut DeviceCollection)
|
||||
-> i32>,
|
||||
pub device_collection_destroy:
|
||||
Option<unsafe extern "C" fn(context: *mut Context, collection: *mut DeviceCollection) -> i32>,
|
||||
pub destroy: Option<unsafe extern "C" fn(context: *mut Context)>,
|
||||
pub stream_init: StreamInitFn,
|
||||
pub stream_destroy: Option<unsafe extern "C" fn(stream: *mut Stream)>,
|
||||
pub stream_start: Option<unsafe extern "C" fn(stream: *mut Stream) -> i32>,
|
||||
pub stream_stop: Option<unsafe extern "C" fn(stream: *mut Stream) -> i32>,
|
||||
pub stream_reset_default_device: Option<unsafe extern "C" fn(stream: *mut Stream) -> i32>,
|
||||
pub stream_get_position: Option<unsafe extern "C" fn(stream: *mut Stream, position: *mut u64) -> i32>,
|
||||
pub stream_get_latency: Option<unsafe extern "C" fn(stream: *mut Stream, latency: *mut u32) -> i32>,
|
||||
pub stream_set_volume: Option<unsafe extern "C" fn(stream: *mut Stream, volumes: f32) -> i32>,
|
||||
pub stream_set_panning: Option<unsafe extern "C" fn(stream: *mut Stream, panning: f32) -> i32>,
|
||||
pub stream_get_current_device: Option<unsafe extern "C" fn(stream: *mut Stream, device: *mut *const Device) -> i32>,
|
||||
pub stream_device_destroy: Option<unsafe extern "C" fn(stream: *mut Stream, device: *mut Device) -> i32>,
|
||||
pub stream_register_device_changed_callback:
|
||||
Option<unsafe extern "C" fn(stream: *mut Stream,
|
||||
device_changed_callback: DeviceChangedCallback)
|
||||
-> i32>,
|
||||
pub register_device_collection_changed: RegisterDeviceCollectionChangedFn,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct LayoutMap {
|
||||
pub name: *const c_char,
|
||||
pub channels: u32,
|
||||
pub layout: ChannelLayout,
|
||||
}
|
||||
|
||||
// cubeb_mixer.h
|
||||
pub type Channel = c_int;
|
||||
|
||||
// These need to match cubeb_channel
|
||||
pub const CHANNEL_INVALID: Channel = -1;
|
||||
pub const CHANNEL_MONO: Channel = 0;
|
||||
pub const CHANNEL_LEFT: Channel = 1;
|
||||
pub const CHANNEL_RIGHT: Channel = 2;
|
||||
pub const CHANNEL_CENTER: Channel = 3;
|
||||
pub const CHANNEL_LS: Channel = 4;
|
||||
pub const CHANNEL_RS: Channel = 5;
|
||||
pub const CHANNEL_RLS: Channel = 6;
|
||||
pub const CHANNEL_RCENTER: Channel = 7;
|
||||
pub const CHANNEL_RRS: Channel = 8;
|
||||
pub const CHANNEL_LFE: Channel = 9;
|
||||
pub const CHANNEL_MAX: Channel = 10;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ChannelMap {
|
||||
pub channels: c_uint,
|
||||
pub map: [Channel; CHANNEL_MAX as usize],
|
||||
}
|
||||
impl ::std::default::Default for ChannelMap {
|
||||
fn default() -> Self {
|
||||
ChannelMap {
|
||||
channels: 0,
|
||||
map: [CHANNEL_INVALID,
|
||||
CHANNEL_INVALID,
|
||||
CHANNEL_INVALID,
|
||||
CHANNEL_INVALID,
|
||||
CHANNEL_INVALID,
|
||||
CHANNEL_INVALID,
|
||||
CHANNEL_INVALID,
|
||||
CHANNEL_INVALID,
|
||||
CHANNEL_INVALID,
|
||||
CHANNEL_INVALID],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
pub fn cubeb_channel_map_to_layout(channel_map: *const ChannelMap) -> ChannelLayout;
|
||||
pub fn cubeb_should_upmix(stream: *const StreamParams, mixer: *const StreamParams) -> bool;
|
||||
pub fn cubeb_should_downmix(stream: *const StreamParams, mixer: *const StreamParams) -> bool;
|
||||
pub fn cubeb_downmix_float(input: *const f32,
|
||||
inframes: c_long,
|
||||
output: *mut f32,
|
||||
in_channels: u32,
|
||||
out_channels: u32,
|
||||
in_layout: ChannelLayout,
|
||||
out_layout: ChannelLayout);
|
||||
pub fn cubeb_upmix_float(input: *const f32,
|
||||
inframes: c_long,
|
||||
output: *mut f32,
|
||||
in_channels: u32,
|
||||
out_channels: u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_stream_params() {
|
||||
assert_eq!(::std::mem::size_of::<StreamParams>(),
|
||||
20usize,
|
||||
concat!("Size of: ", stringify!(StreamParams)));
|
||||
assert_eq!(::std::mem::align_of::<StreamParams>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(StreamParams)));
|
||||
assert_eq!(unsafe { &(*(0 as *const StreamParams)).format as *const _ as usize },
|
||||
0usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(StreamParams),
|
||||
"::",
|
||||
stringify!(format)));
|
||||
assert_eq!(unsafe { &(*(0 as *const StreamParams)).rate as *const _ as usize },
|
||||
4usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(StreamParams),
|
||||
"::",
|
||||
stringify!(rate)));
|
||||
assert_eq!(unsafe { &(*(0 as *const StreamParams)).channels as *const _ as usize },
|
||||
8usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(StreamParams),
|
||||
"::",
|
||||
stringify!(channels)));
|
||||
assert_eq!(unsafe { &(*(0 as *const StreamParams)).layout as *const _ as usize },
|
||||
12usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(StreamParams),
|
||||
"::",
|
||||
stringify!(layout)));
|
||||
assert_eq!(unsafe { &(*(0 as *const StreamParams)).prefs as *const _ as usize },
|
||||
16usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(StreamParams),
|
||||
"::",
|
||||
stringify!(layout)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_cubeb_device() {
|
||||
assert_eq!(::std::mem::size_of::<Device>(),
|
||||
16usize,
|
||||
concat!("Size of: ", stringify!(Device)));
|
||||
assert_eq!(::std::mem::align_of::<Device>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(Device)));
|
||||
assert_eq!(unsafe { &(*(0 as *const Device)).output_name as *const _ as usize },
|
||||
0usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(Device),
|
||||
"::",
|
||||
stringify!(output_name)));
|
||||
assert_eq!(unsafe { &(*(0 as *const Device)).input_name as *const _ as usize },
|
||||
8usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(Device),
|
||||
"::",
|
||||
stringify!(input_name)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_cubeb_device_info() {
|
||||
assert_eq!(::std::mem::size_of::<DeviceInfo>(),
|
||||
88usize,
|
||||
concat!("Size of: ", stringify!(DeviceInfo)));
|
||||
assert_eq!(::std::mem::align_of::<DeviceInfo>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(DeviceInfo)));
|
||||
assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).devid as *const _ as usize },
|
||||
0usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(DeviceInfo),
|
||||
"::",
|
||||
stringify!(devid)));
|
||||
assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).device_id as *const _ as usize },
|
||||
8usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(DeviceInfo),
|
||||
"::",
|
||||
stringify!(device_id)));
|
||||
assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).friendly_name as *const _ as usize },
|
||||
16usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(DeviceInfo),
|
||||
"::",
|
||||
stringify!(friendly_name)));
|
||||
assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).group_id as *const _ as usize },
|
||||
24usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(DeviceInfo),
|
||||
"::",
|
||||
stringify!(group_id)));
|
||||
assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).vendor_name as *const _ as usize },
|
||||
32usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(DeviceInfo),
|
||||
"::",
|
||||
stringify!(vendor_name)));
|
||||
assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).devtype as *const _ as usize },
|
||||
40usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(DeviceInfo),
|
||||
"::",
|
||||
stringify!(type_)));
|
||||
assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).state as *const _ as usize },
|
||||
44usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(DeviceInfo),
|
||||
"::",
|
||||
stringify!(state)));
|
||||
assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).preferred as *const _ as usize },
|
||||
48usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(DeviceInfo),
|
||||
"::",
|
||||
stringify!(preferred)));
|
||||
assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).format as *const _ as usize },
|
||||
52usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(DeviceInfo),
|
||||
"::",
|
||||
stringify!(format)));
|
||||
assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).default_format as *const _ as usize },
|
||||
56usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(DeviceInfo),
|
||||
"::",
|
||||
stringify!(default_format)));
|
||||
assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).max_channels as *const _ as usize },
|
||||
60usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(DeviceInfo),
|
||||
"::",
|
||||
stringify!(max_channels)));
|
||||
assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).default_rate as *const _ as usize },
|
||||
64usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(DeviceInfo),
|
||||
"::",
|
||||
stringify!(default_rate)));
|
||||
assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).max_rate as *const _ as usize },
|
||||
68usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(DeviceInfo),
|
||||
"::",
|
||||
stringify!(max_rate)));
|
||||
assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).min_rate as *const _ as usize },
|
||||
72usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(DeviceInfo),
|
||||
"::",
|
||||
stringify!(min_rate)));
|
||||
assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).latency_lo as *const _ as usize },
|
||||
76usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(DeviceInfo),
|
||||
"::",
|
||||
stringify!(latency_lo)));
|
||||
assert_eq!(unsafe { &(*(0 as *const DeviceInfo)).latency_hi as *const _ as usize },
|
||||
80usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(DeviceInfo),
|
||||
"::",
|
||||
stringify!(latency_hi)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_cubeb_device_collection() {
|
||||
assert_eq!(::std::mem::size_of::<DeviceCollection>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(DeviceCollection)));
|
||||
assert_eq!(::std::mem::align_of::<DeviceCollection>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(DeviceCollection)));
|
||||
assert_eq!(unsafe { &(*(0 as *const DeviceCollection)).count as *const _ as usize },
|
||||
0usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(DeviceCollection),
|
||||
"::",
|
||||
stringify!(count)));
|
||||
assert_eq!(unsafe { &(*(0 as *const DeviceCollection)).device as *const _ as usize },
|
||||
8usize,
|
||||
concat!("Alignment of field: ",
|
||||
stringify!(DeviceCollection),
|
||||
"::",
|
||||
stringify!(device)));
|
||||
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
// Copyright © 2017 Mozilla Foundation
|
||||
//
|
||||
// This program is made available under an ISC-style license. See the
|
||||
// accompanying file LICENSE for details.
|
||||
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
|
||||
mod ffi;
|
||||
mod log;
|
||||
pub mod mixer;
|
||||
|
||||
pub use ffi::*;
|
||||
pub use log::*;
|
|
@ -1,75 +0,0 @@
|
|||
use std::os::raw::c_char;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log_internal {
|
||||
($level: expr, $msg: expr) => {
|
||||
#[allow(unused_unsafe)]
|
||||
unsafe {
|
||||
if $level <= $crate::g_cubeb_log_level {
|
||||
if let Some(log_callback) = $crate::g_cubeb_log_callback {
|
||||
let cstr = ::std::ffi::CString::new(concat!("%s:%d: ", $msg, "\n")).unwrap();
|
||||
log_callback(cstr.as_ptr(), file!(), line!());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
($level: expr, $fmt: expr, $($arg:tt)+) => {
|
||||
#[allow(unused_unsafe)]
|
||||
unsafe {
|
||||
if $level <= $crate::g_cubeb_log_level {
|
||||
if let Some(log_callback) = $crate::g_cubeb_log_callback {
|
||||
let cstr = ::std::ffi::CString::new(format!(concat!("%s:%d: ", $fmt, "\n"), $($arg)+)).unwrap();
|
||||
log_callback(cstr.as_ptr(), file!(), line!());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! logv {
|
||||
($msg: expr) => (log_internal!($crate::LogLevel::Verbose, $msg));
|
||||
($fmt: expr, $($arg: tt)+) => (log_internal!($crate::LogLevel::Verbose, $fmt, $($arg)*));
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log {
|
||||
($msg: expr) => (log_internal!($crate::LogLevel::Normal, $msg));
|
||||
($fmt: expr, $($arg: tt)+) => (log_internal!($crate::LogLevel::Normal, $fmt, $($arg)*));
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum LogLevel {
|
||||
Disabled = 0,
|
||||
Normal = 1,
|
||||
Verbose = 2,
|
||||
}
|
||||
|
||||
pub type LogCallback = Option<unsafe extern "C" fn(fmt: *const c_char, ...)>;
|
||||
|
||||
extern "C" {
|
||||
pub static g_cubeb_log_level: LogLevel;
|
||||
pub static g_cubeb_log_callback: LogCallback;
|
||||
}
|
||||
|
||||
pub fn log_enabled() -> bool {
|
||||
unsafe { g_cubeb_log_level != LogLevel::Disabled }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_logging() {
|
||||
log!("This is log at normal level");
|
||||
log!("Formatted log %d", 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verbose_logging() {
|
||||
logv!("This is a log at verbose level");
|
||||
logv!("Formatted log %d", 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_logging_disabled_by_default() {
|
||||
assert!(!log_enabled());
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
// Copyright © 2017 Mozilla Foundation
|
||||
//
|
||||
// This program is made available under an ISC-style license. See the
|
||||
// accompanying file LICENSE for details.
|
||||
|
||||
use ::*;
|
||||
|
||||
static CHANNEL_LAYOUT_UNDEFINED: &'static [Channel] = &[CHANNEL_INVALID];
|
||||
static CHANNEL_LAYOUT_DUAL_MONO: &'static [Channel] = &[CHANNEL_LEFT, CHANNEL_RIGHT];
|
||||
static CHANNEL_LAYOUT_DUAL_MONO_LFE: &'static [Channel] = &[CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE];
|
||||
static CHANNEL_LAYOUT_MONO: &'static [Channel] = &[CHANNEL_MONO];
|
||||
static CHANNEL_LAYOUT_MONO_LFE: &'static [Channel] = &[CHANNEL_MONO, CHANNEL_LFE];
|
||||
static CHANNEL_LAYOUT_STEREO: &'static [Channel] = &[CHANNEL_LEFT, CHANNEL_RIGHT];
|
||||
static CHANNEL_LAYOUT_STEREO_LFE: &'static [Channel] = &[CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE];
|
||||
static CHANNEL_LAYOUT_3F: &'static [Channel] = &[CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER];
|
||||
static CHANNEL_LAYOUT_3FLFE: &'static [Channel] = &[CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE];
|
||||
static CHANNEL_LAYOUT_2F1: &'static [Channel] = &[CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_RCENTER];
|
||||
static CHANNEL_LAYOUT_2F1LFE: &'static [Channel] = &[CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE, CHANNEL_RCENTER];
|
||||
static CHANNEL_LAYOUT_3F1: &'static [Channel] = &[CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_RCENTER];
|
||||
static CHANNEL_LAYOUT_3F1LFE: &'static [Channel] = &[CHANNEL_LEFT,
|
||||
CHANNEL_RIGHT,
|
||||
CHANNEL_CENTER,
|
||||
CHANNEL_LFE,
|
||||
CHANNEL_RCENTER];
|
||||
static CHANNEL_LAYOUT_2F2: &'static [Channel] = &[CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LS, CHANNEL_RS];
|
||||
static CHANNEL_LAYOUT_2F2LFE: &'static [Channel] = &[CHANNEL_LEFT,
|
||||
CHANNEL_RIGHT,
|
||||
CHANNEL_LFE,
|
||||
CHANNEL_LS,
|
||||
CHANNEL_RS];
|
||||
static CHANNEL_LAYOUT_3F2: &'static [Channel] = &[CHANNEL_LEFT,
|
||||
CHANNEL_RIGHT,
|
||||
CHANNEL_CENTER,
|
||||
CHANNEL_LS,
|
||||
CHANNEL_RS];
|
||||
static CHANNEL_LAYOUT_3F2LFE: &'static [Channel] = &[CHANNEL_LEFT,
|
||||
CHANNEL_RIGHT,
|
||||
CHANNEL_CENTER,
|
||||
CHANNEL_LFE,
|
||||
CHANNEL_LS,
|
||||
CHANNEL_RS];
|
||||
static CHANNEL_LAYOUT_3F3RLFE: &'static [Channel] = &[CHANNEL_LEFT,
|
||||
CHANNEL_RIGHT,
|
||||
CHANNEL_CENTER,
|
||||
CHANNEL_LFE,
|
||||
CHANNEL_RCENTER,
|
||||
CHANNEL_LS,
|
||||
CHANNEL_RS];
|
||||
static CHANNEL_LAYOUT_3F4LFE: &'static [Channel] = &[CHANNEL_LEFT,
|
||||
CHANNEL_RIGHT,
|
||||
CHANNEL_CENTER,
|
||||
CHANNEL_LFE,
|
||||
CHANNEL_RLS,
|
||||
CHANNEL_RRS,
|
||||
CHANNEL_LS,
|
||||
CHANNEL_RS];
|
||||
|
||||
pub fn channel_index_to_order(layout: ChannelLayout) -> &'static [Channel] {
|
||||
match layout {
|
||||
LAYOUT_DUAL_MONO => CHANNEL_LAYOUT_DUAL_MONO,
|
||||
LAYOUT_DUAL_MONO_LFE => CHANNEL_LAYOUT_DUAL_MONO_LFE,
|
||||
LAYOUT_MONO => CHANNEL_LAYOUT_MONO,
|
||||
LAYOUT_MONO_LFE => CHANNEL_LAYOUT_MONO_LFE,
|
||||
LAYOUT_STEREO => CHANNEL_LAYOUT_STEREO,
|
||||
LAYOUT_STEREO_LFE => CHANNEL_LAYOUT_STEREO_LFE,
|
||||
LAYOUT_3F => CHANNEL_LAYOUT_3F,
|
||||
LAYOUT_3F_LFE => CHANNEL_LAYOUT_3FLFE,
|
||||
LAYOUT_2F1 => CHANNEL_LAYOUT_2F1,
|
||||
LAYOUT_2F1_LFE => CHANNEL_LAYOUT_2F1LFE,
|
||||
LAYOUT_3F1 => CHANNEL_LAYOUT_3F1,
|
||||
LAYOUT_3F1_LFE => CHANNEL_LAYOUT_3F1LFE,
|
||||
LAYOUT_2F2 => CHANNEL_LAYOUT_2F2,
|
||||
LAYOUT_2F2_LFE => CHANNEL_LAYOUT_2F2LFE,
|
||||
LAYOUT_3F2 => CHANNEL_LAYOUT_3F2,
|
||||
LAYOUT_3F2_LFE => CHANNEL_LAYOUT_3F2LFE,
|
||||
LAYOUT_3F3R_LFE => CHANNEL_LAYOUT_3F3RLFE,
|
||||
LAYOUT_3F4_LFE => CHANNEL_LAYOUT_3F4LFE,
|
||||
_ => CHANNEL_LAYOUT_UNDEFINED,
|
||||
}
|
||||
}
|
|
@ -1,48 +1,52 @@
|
|||
// Copyright © 2017 Mozilla Foundation
|
||||
// Copyright © 2017-2018 Mozilla Foundation
|
||||
//
|
||||
// This program is made available under an ISC-style license. See the
|
||||
// accompanying file LICENSE for details.
|
||||
|
||||
use backend::*;
|
||||
use capi::PULSE_OPS;
|
||||
use cubeb;
|
||||
use cubeb_backend::{ffi, log_enabled, ChannelLayout, Context, ContextOps, DeviceCollectionRef,
|
||||
DeviceId, DeviceType, Error, Ops, Result, Stream, StreamParams,
|
||||
StreamParamsRef};
|
||||
use pulse::{self, ProplistExt};
|
||||
use pulse_ffi::*;
|
||||
use semver;
|
||||
use std::cell::RefCell;
|
||||
use std::default::Default;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::mem;
|
||||
use std::os::raw::{c_char, c_void};
|
||||
use std::os::raw::c_void;
|
||||
use std::ptr;
|
||||
use std::cell::RefCell;
|
||||
|
||||
fn pa_channel_to_cubeb_channel(channel: pulse::ChannelPosition) -> cubeb::Channel {
|
||||
fn pa_channel_to_cubeb_channel(channel: pulse::ChannelPosition) -> ffi::cubeb_channel {
|
||||
use cubeb_backend::ffi::*;
|
||||
use pulse::ChannelPosition;
|
||||
assert_ne!(channel, ChannelPosition::Invalid);
|
||||
match channel {
|
||||
ChannelPosition::Mono => cubeb::CHANNEL_MONO,
|
||||
ChannelPosition::FrontLeft => cubeb::CHANNEL_LEFT,
|
||||
ChannelPosition::FrontRight => cubeb::CHANNEL_RIGHT,
|
||||
ChannelPosition::FrontCenter => cubeb::CHANNEL_CENTER,
|
||||
ChannelPosition::SideLeft => cubeb::CHANNEL_LS,
|
||||
ChannelPosition::SideRight => cubeb::CHANNEL_RS,
|
||||
ChannelPosition::RearLeft => cubeb::CHANNEL_RLS,
|
||||
ChannelPosition::RearCenter => cubeb::CHANNEL_RCENTER,
|
||||
ChannelPosition::RearRight => cubeb::CHANNEL_RRS,
|
||||
ChannelPosition::LowFreqEffects => cubeb::CHANNEL_LFE,
|
||||
_ => cubeb::CHANNEL_INVALID,
|
||||
ChannelPosition::Mono => CHANNEL_MONO,
|
||||
ChannelPosition::FrontLeft => CHANNEL_LEFT,
|
||||
ChannelPosition::FrontRight => CHANNEL_RIGHT,
|
||||
ChannelPosition::FrontCenter => CHANNEL_CENTER,
|
||||
ChannelPosition::SideLeft => CHANNEL_LS,
|
||||
ChannelPosition::SideRight => CHANNEL_RS,
|
||||
ChannelPosition::RearLeft => CHANNEL_RLS,
|
||||
ChannelPosition::RearCenter => CHANNEL_RCENTER,
|
||||
ChannelPosition::RearRight => CHANNEL_RRS,
|
||||
ChannelPosition::LowFreqEffects => CHANNEL_LFE,
|
||||
_ => CHANNEL_INVALID,
|
||||
}
|
||||
}
|
||||
|
||||
fn channel_map_to_layout(cm: &pulse::ChannelMap) -> cubeb::ChannelLayout {
|
||||
fn channel_map_to_layout(cm: &pulse::ChannelMap) -> ChannelLayout {
|
||||
use cubeb_backend::ffi::{cubeb_channel_map, cubeb_channel_map_to_layout};
|
||||
use pulse::ChannelPosition;
|
||||
let mut cubeb_map: cubeb::ChannelMap = Default::default();
|
||||
let mut cubeb_map: cubeb_channel_map = unsafe { mem::zeroed() };
|
||||
cubeb_map.channels = u32::from(cm.channels);
|
||||
for i in 0usize..cm.channels as usize {
|
||||
cubeb_map.map[i] = pa_channel_to_cubeb_channel(ChannelPosition::try_from(cm.map[i])
|
||||
.unwrap_or(ChannelPosition::Invalid));
|
||||
cubeb_map.map[i] = pa_channel_to_cubeb_channel(
|
||||
ChannelPosition::try_from(cm.map[i]).unwrap_or(ChannelPosition::Invalid),
|
||||
);
|
||||
}
|
||||
unsafe { cubeb::cubeb_channel_map_to_layout(&cubeb_map) }
|
||||
ChannelLayout::from(unsafe { cubeb_channel_map_to_layout(&cubeb_map) })
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -52,104 +56,106 @@ pub struct DefaultInfo {
|
|||
pub flags: pulse::SinkFlags,
|
||||
}
|
||||
|
||||
pub const PULSE_OPS: Ops = capi_new!(PulseContext, PulseStream);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Context {
|
||||
pub ops: *const cubeb::Ops,
|
||||
pub struct PulseContext {
|
||||
_ops: *const Ops,
|
||||
pub mainloop: pulse::ThreadedMainloop,
|
||||
pub context: Option<pulse::Context>,
|
||||
pub default_sink_info: Option<DefaultInfo>,
|
||||
pub context_name: Option<CString>,
|
||||
pub collection_changed_callback: cubeb::DeviceCollectionChangedCallback,
|
||||
pub collection_changed_callback: ffi::cubeb_device_collection_changed_callback,
|
||||
pub collection_changed_user_ptr: *mut c_void,
|
||||
pub error: bool,
|
||||
pub version_2_0_0: bool,
|
||||
pub version_0_9_8: bool,
|
||||
#[cfg(feature = "pulse-dlopen")]
|
||||
pub libpulse: LibLoader,
|
||||
#[cfg(feature = "pulse-dlopen")] pub libpulse: LibLoader,
|
||||
devids: RefCell<Intern>,
|
||||
}
|
||||
|
||||
impl Drop for Context {
|
||||
fn drop(&mut self) {
|
||||
self.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
impl PulseContext {
|
||||
#[cfg(feature = "pulse-dlopen")]
|
||||
fn _new(name: Option<CString>) -> Result<Box<Self>> {
|
||||
let libpulse = unsafe { open() };
|
||||
if libpulse.is_none() {
|
||||
return Err(cubeb::ERROR);
|
||||
return Err(Error::error());
|
||||
}
|
||||
|
||||
let ctx = Box::new(Context {
|
||||
ops: &PULSE_OPS,
|
||||
libpulse: libpulse.unwrap(),
|
||||
mainloop: pulse::ThreadedMainloop::new(),
|
||||
context: None,
|
||||
default_sink_info: None,
|
||||
context_name: name,
|
||||
collection_changed_callback: None,
|
||||
collection_changed_user_ptr: ptr::null_mut(),
|
||||
error: true,
|
||||
version_0_9_8: false,
|
||||
version_2_0_0: false,
|
||||
devids: RefCell::new(Intern::new()),
|
||||
});
|
||||
let ctx = Box::new(PulseContext {
|
||||
_ops: &PULSE_OPS,
|
||||
libpulse: libpulse.unwrap(),
|
||||
mainloop: pulse::ThreadedMainloop::new(),
|
||||
context: None,
|
||||
default_sink_info: None,
|
||||
context_name: name,
|
||||
collection_changed_callback: None,
|
||||
collection_changed_user_ptr: ptr::null_mut(),
|
||||
error: true,
|
||||
version_0_9_8: false,
|
||||
version_2_0_0: false,
|
||||
devids: RefCell::new(Intern::new()),
|
||||
});
|
||||
|
||||
Ok(ctx)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "pulse-dlopen"))]
|
||||
fn _new(name: Option<CString>) -> Result<Box<Self>> {
|
||||
Ok(Box::new(Context {
|
||||
ops: &PULSE_OPS,
|
||||
mainloop: pulse::ThreadedMainloop::new(),
|
||||
context: None,
|
||||
default_sink_info: None,
|
||||
context_name: name,
|
||||
collection_changed_callback: None,
|
||||
collection_changed_user_ptr: ptr::null_mut(),
|
||||
error: true,
|
||||
version_0_9_8: false,
|
||||
version_2_0_0: false,
|
||||
devids: RefCell::new(Intern::new()),
|
||||
}))
|
||||
Ok(Box::new(PulseContext {
|
||||
_ops: &PULSE_OPS,
|
||||
mainloop: pulse::ThreadedMainloop::new(),
|
||||
context: None,
|
||||
default_sink_info: None,
|
||||
context_name: name,
|
||||
collection_changed_callback: None,
|
||||
collection_changed_user_ptr: ptr::null_mut(),
|
||||
error: true,
|
||||
version_0_9_8: false,
|
||||
version_2_0_0: false,
|
||||
devids: RefCell::new(Intern::new()),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn new(name: *const c_char) -> Result<Box<Self>> {
|
||||
fn new(name: Option<&CStr>) -> Result<Box<Self>> {
|
||||
fn server_info_cb(context: &pulse::Context, info: &pulse::ServerInfo, u: *mut c_void) {
|
||||
fn sink_info_cb(_: &pulse::Context, i: *const pulse::SinkInfo, eol: i32, u: *mut c_void) {
|
||||
let ctx = unsafe { &mut *(u as *mut Context) };
|
||||
fn sink_info_cb(
|
||||
_: &pulse::Context,
|
||||
i: *const pulse::SinkInfo,
|
||||
eol: i32,
|
||||
u: *mut c_void,
|
||||
) {
|
||||
let ctx = unsafe { &mut *(u as *mut PulseContext) };
|
||||
if eol == 0 {
|
||||
let info = unsafe { &*i };
|
||||
let flags = pulse::SinkFlags::from_bits_truncate(info.flags);
|
||||
ctx.default_sink_info = Some(DefaultInfo {
|
||||
sample_spec: info.sample_spec,
|
||||
channel_map: info.channel_map,
|
||||
flags: flags,
|
||||
});
|
||||
sample_spec: info.sample_spec,
|
||||
channel_map: info.channel_map,
|
||||
flags: flags,
|
||||
});
|
||||
}
|
||||
ctx.mainloop.signal();
|
||||
}
|
||||
|
||||
let _ = context.get_sink_info_by_name(try_cstr_from(info.default_sink_name),
|
||||
sink_info_cb,
|
||||
u);
|
||||
let _ = context.get_sink_info_by_name(
|
||||
try_cstr_from(info.default_sink_name),
|
||||
sink_info_cb,
|
||||
u,
|
||||
);
|
||||
}
|
||||
|
||||
let name = super::try_cstr_from(name).map(|s| s.to_owned());
|
||||
let mut ctx = try!(Context::_new(name));
|
||||
let name = name.map(|s| s.to_owned());
|
||||
let mut ctx = try!(PulseContext::_new(name));
|
||||
|
||||
if ctx.mainloop.start().is_err() {
|
||||
ctx.destroy();
|
||||
return Err(cubeb::ERROR);
|
||||
return Err(Error::error());
|
||||
}
|
||||
|
||||
if ctx.context_init() != cubeb::OK {
|
||||
if ctx.context_init().is_err() {
|
||||
ctx.destroy();
|
||||
return Err(cubeb::ERROR);
|
||||
return Err(Error::error());
|
||||
}
|
||||
|
||||
ctx.mainloop.lock();
|
||||
|
@ -175,63 +181,55 @@ impl Context {
|
|||
self.mainloop.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))]
|
||||
pub fn new_stream(&mut self,
|
||||
stream_name: &CStr,
|
||||
input_device: cubeb::DeviceId,
|
||||
input_stream_params: Option<cubeb::StreamParams>,
|
||||
output_device: cubeb::DeviceId,
|
||||
output_stream_params: Option<cubeb::StreamParams>,
|
||||
latency_frames: u32,
|
||||
data_callback: cubeb::DataCallback,
|
||||
state_callback: cubeb::StateCallback,
|
||||
user_ptr: *mut c_void)
|
||||
-> Result<Box<Stream>> {
|
||||
if self.error && self.context_init() != 0 {
|
||||
return Err(cubeb::ERROR);
|
||||
}
|
||||
|
||||
Stream::new(self,
|
||||
stream_name,
|
||||
input_device,
|
||||
input_stream_params,
|
||||
output_device,
|
||||
output_stream_params,
|
||||
latency_frames,
|
||||
data_callback,
|
||||
state_callback,
|
||||
user_ptr)
|
||||
impl ContextOps for PulseContext {
|
||||
fn init(context_name: Option<&CStr>) -> Result<Context> {
|
||||
let ctx = try!(PulseContext::new(context_name));
|
||||
Ok(unsafe { Context::from_ptr(Box::into_raw(ctx) as *mut _) })
|
||||
}
|
||||
|
||||
pub fn max_channel_count(&self) -> Result<u32> {
|
||||
fn backend_id(&mut self) -> &'static CStr {
|
||||
unsafe { CStr::from_ptr(b"pulse-rust\0".as_ptr() as *const _) }
|
||||
}
|
||||
|
||||
fn max_channel_count(&mut self) -> Result<u32> {
|
||||
match self.default_sink_info {
|
||||
Some(ref info) => Ok(u32::from(info.channel_map.channels)),
|
||||
None => Err(cubeb::ERROR),
|
||||
None => Err(Error::error()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn preferred_sample_rate(&self) -> Result<u32> {
|
||||
fn min_latency(&mut self, params: StreamParams) -> Result<u32> {
|
||||
// According to PulseAudio developers, this is a safe minimum.
|
||||
Ok(25 * params.rate() / 1000)
|
||||
}
|
||||
|
||||
fn preferred_sample_rate(&mut self) -> Result<u32> {
|
||||
match self.default_sink_info {
|
||||
Some(ref info) => Ok(info.sample_spec.rate),
|
||||
None => Err(cubeb::ERROR),
|
||||
None => Err(Error::error()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn min_latency(&self, params: &cubeb::StreamParams) -> Result<u32> {
|
||||
// According to PulseAudio developers, this is a safe minimum.
|
||||
Ok(25 * params.rate / 1000)
|
||||
}
|
||||
|
||||
pub fn preferred_channel_layout(&self) -> Result<cubeb::ChannelLayout> {
|
||||
fn preferred_channel_layout(&mut self) -> Result<ChannelLayout> {
|
||||
match self.default_sink_info {
|
||||
Some(ref info) => Ok(channel_map_to_layout(&info.channel_map)),
|
||||
None => Err(cubeb::ERROR),
|
||||
None => Err(Error::error()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enumerate_devices(&self, devtype: cubeb::DeviceType) -> Result<cubeb::DeviceCollection> {
|
||||
fn add_output_device(_: &pulse::Context, i: *const pulse::SinkInfo, eol: i32, user_data: *mut c_void) {
|
||||
fn enumerate_devices(
|
||||
&mut self,
|
||||
devtype: DeviceType,
|
||||
collection: &DeviceCollectionRef,
|
||||
) -> Result<()> {
|
||||
fn add_output_device(
|
||||
_: &pulse::Context,
|
||||
i: *const pulse::SinkInfo,
|
||||
eol: i32,
|
||||
user_data: *mut c_void,
|
||||
) {
|
||||
let list_data = unsafe { &mut *(user_data as *mut PulseDevListData) };
|
||||
let ctx = &(*list_data.context);
|
||||
|
||||
|
@ -259,23 +257,23 @@ impl Context {
|
|||
let info_description = unsafe { CStr::from_ptr(info.description) }.to_owned();
|
||||
|
||||
let preferred = if *info_name == *list_data.default_sink_name {
|
||||
cubeb::DevicePref::ALL
|
||||
ffi::CUBEB_DEVICE_PREF_ALL
|
||||
} else {
|
||||
cubeb::DevicePref::empty()
|
||||
ffi::CUBEB_DEVICE_PREF_NONE
|
||||
};
|
||||
|
||||
let device_id = ctx.devids.borrow_mut().add(info_name);
|
||||
let friendly_name = info_description.into_raw();
|
||||
let devinfo = cubeb::DeviceInfo {
|
||||
let devinfo = ffi::cubeb_device_info {
|
||||
device_id: device_id,
|
||||
devid: device_id as cubeb::DeviceId,
|
||||
devid: device_id as ffi::cubeb_devid,
|
||||
friendly_name: friendly_name,
|
||||
group_id: group_id,
|
||||
vendor_name: vendor_name,
|
||||
devtype: cubeb::DeviceType::OUTPUT,
|
||||
device_type: ffi::CUBEB_DEVICE_TYPE_OUTPUT,
|
||||
state: ctx.state_from_port(info.active_port),
|
||||
preferred: preferred,
|
||||
format: cubeb::DeviceFmt::all(),
|
||||
format: ffi::CUBEB_DEVICE_FMT_ALL,
|
||||
default_format: pulse_format_to_cubeb_format(info.sample_spec.format),
|
||||
max_channels: u32::from(info.channel_map.channels),
|
||||
min_rate: 1,
|
||||
|
@ -287,7 +285,12 @@ impl Context {
|
|||
list_data.devinfo.push(devinfo);
|
||||
}
|
||||
|
||||
fn add_input_device(_: &pulse::Context, i: *const pulse::SourceInfo, eol: i32, user_data: *mut c_void) {
|
||||
fn add_input_device(
|
||||
_: &pulse::Context,
|
||||
i: *const pulse::SourceInfo,
|
||||
eol: i32,
|
||||
user_data: *mut c_void,
|
||||
) {
|
||||
let list_data = unsafe { &mut *(user_data as *mut PulseDevListData) };
|
||||
let ctx = &(*list_data.context);
|
||||
|
||||
|
@ -315,23 +318,23 @@ impl Context {
|
|||
let info_description = unsafe { CStr::from_ptr(info.description) }.to_owned();
|
||||
|
||||
let preferred = if *info_name == *list_data.default_source_name {
|
||||
cubeb::DevicePref::ALL
|
||||
ffi::CUBEB_DEVICE_PREF_ALL
|
||||
} else {
|
||||
cubeb::DevicePref::empty()
|
||||
ffi::CUBEB_DEVICE_PREF_NONE
|
||||
};
|
||||
|
||||
let device_id = ctx.devids.borrow_mut().add(info_name);
|
||||
let friendly_name = info_description.into_raw();
|
||||
let devinfo = cubeb::DeviceInfo {
|
||||
let devinfo = ffi::cubeb_device_info {
|
||||
device_id: device_id,
|
||||
devid: device_id as cubeb::DeviceId,
|
||||
devid: device_id as ffi::cubeb_devid,
|
||||
friendly_name: friendly_name,
|
||||
group_id: group_id,
|
||||
vendor_name: vendor_name,
|
||||
devtype: cubeb::DeviceType::INPUT,
|
||||
device_type: ffi::CUBEB_DEVICE_TYPE_INPUT,
|
||||
state: ctx.state_from_port(info.active_port),
|
||||
preferred: preferred,
|
||||
format: cubeb::DeviceFmt::all(),
|
||||
format: ffi::CUBEB_DEVICE_FMT_ALL,
|
||||
default_format: pulse_format_to_cubeb_format(info.sample_spec.format),
|
||||
max_channels: u32::from(info.channel_map.channels),
|
||||
min_rate: 1,
|
||||
|
@ -344,7 +347,11 @@ impl Context {
|
|||
list_data.devinfo.push(devinfo);
|
||||
}
|
||||
|
||||
fn default_device_names(_: &pulse::Context, info: &pulse::ServerInfo, user_data: *mut c_void) {
|
||||
fn default_device_names(
|
||||
_: &pulse::Context,
|
||||
info: &pulse::ServerInfo,
|
||||
user_data: *mut c_void,
|
||||
) {
|
||||
let list_data = unsafe { &mut *(user_data as *mut PulseDevListData) };
|
||||
|
||||
list_data.default_sink_name = super::try_cstr_from(info.default_sink_name)
|
||||
|
@ -362,18 +369,24 @@ impl Context {
|
|||
if let Some(ref context) = self.context {
|
||||
self.mainloop.lock();
|
||||
|
||||
if let Ok(o) = context.get_server_info(default_device_names, &mut user_data as *mut _ as *mut _) {
|
||||
if let Ok(o) =
|
||||
context.get_server_info(default_device_names, &mut user_data as *mut _ as *mut _)
|
||||
{
|
||||
self.operation_wait(None, &o);
|
||||
}
|
||||
|
||||
if devtype.contains(cubeb::DeviceType::OUTPUT) {
|
||||
if let Ok(o) = context.get_sink_info_list(add_output_device, &mut user_data as *mut _ as *mut _) {
|
||||
if devtype.contains(DeviceType::OUTPUT) {
|
||||
if let Ok(o) = context
|
||||
.get_sink_info_list(add_output_device, &mut user_data as *mut _ as *mut _)
|
||||
{
|
||||
self.operation_wait(None, &o);
|
||||
}
|
||||
}
|
||||
|
||||
if devtype.contains(cubeb::DeviceType::INPUT) {
|
||||
if let Ok(o) = context.get_source_info_list(add_input_device, &mut user_data as *mut _ as *mut _) {
|
||||
if devtype.contains(DeviceType::INPUT) {
|
||||
if let Ok(o) = context
|
||||
.get_source_info_list(add_input_device, &mut user_data as *mut _ as *mut _)
|
||||
{
|
||||
self.operation_wait(None, &o);
|
||||
}
|
||||
}
|
||||
|
@ -385,24 +398,25 @@ impl Context {
|
|||
// PulseDevListData and convert it into C representation.
|
||||
let mut tmp = Vec::new();
|
||||
mem::swap(&mut user_data.devinfo, &mut tmp);
|
||||
let devices = tmp.into_boxed_slice();
|
||||
let coll = cubeb::DeviceCollection {
|
||||
device: devices.as_ptr(),
|
||||
count: devices.len(),
|
||||
};
|
||||
let mut devices = tmp.into_boxed_slice();
|
||||
let coll = unsafe { &mut *collection.as_ptr() };
|
||||
coll.device = devices.as_mut_ptr();
|
||||
coll.count = devices.len();
|
||||
|
||||
// Giving away the memory owned by devices. Don't free it!
|
||||
mem::forget(devices);
|
||||
Ok(coll)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn device_collection_destroy(&self, collection: *mut cubeb::DeviceCollection) {
|
||||
debug_assert!(!collection.is_null());
|
||||
fn device_collection_destroy(&mut self, collection: &mut DeviceCollectionRef) -> Result<()> {
|
||||
debug_assert!(!collection.as_ptr().is_null());
|
||||
unsafe {
|
||||
let coll = *collection;
|
||||
let mut devices = Vec::from_raw_parts(coll.device as *mut cubeb::DeviceInfo,
|
||||
coll.count,
|
||||
coll.count);
|
||||
let coll = &mut *collection.as_ptr();
|
||||
let mut devices = Vec::from_raw_parts(
|
||||
coll.device as *mut ffi::cubeb_device_info,
|
||||
coll.count,
|
||||
coll.count,
|
||||
);
|
||||
for dev in &mut devices {
|
||||
if !dev.group_id.is_null() {
|
||||
let _ = CString::from_raw(dev.group_id as *mut _);
|
||||
|
@ -414,52 +428,90 @@ impl Context {
|
|||
let _ = CString::from_raw(dev.friendly_name as *mut _);
|
||||
}
|
||||
}
|
||||
coll.device = ptr::null_mut();
|
||||
coll.count = 0;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn register_device_collection_changed(&mut self,
|
||||
devtype: cubeb::DeviceType,
|
||||
cb: cubeb::DeviceCollectionChangedCallback,
|
||||
user_ptr: *mut c_void)
|
||||
-> i32 {
|
||||
fn update_collection(_: &pulse::Context, event: pulse::SubscriptionEvent, index: u32, user_data: *mut c_void) {
|
||||
let ctx = unsafe { &mut *(user_data as *mut Context) };
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))]
|
||||
fn stream_init(
|
||||
&mut self,
|
||||
stream_name: Option<&CStr>,
|
||||
input_device: DeviceId,
|
||||
input_stream_params: Option<&StreamParamsRef>,
|
||||
output_device: DeviceId,
|
||||
output_stream_params: Option<&StreamParamsRef>,
|
||||
latency_frames: u32,
|
||||
data_callback: ffi::cubeb_data_callback,
|
||||
state_callback: ffi::cubeb_state_callback,
|
||||
user_ptr: *mut c_void,
|
||||
) -> Result<Stream> {
|
||||
if self.error {
|
||||
let _ = try!(self.context_init());
|
||||
}
|
||||
|
||||
let stm = try!(PulseStream::new(
|
||||
self,
|
||||
stream_name,
|
||||
input_device,
|
||||
input_stream_params,
|
||||
output_device,
|
||||
output_stream_params,
|
||||
latency_frames,
|
||||
data_callback,
|
||||
state_callback,
|
||||
user_ptr,
|
||||
));
|
||||
Ok(unsafe { Stream::from_ptr(Box::into_raw(stm) as *mut _) })
|
||||
}
|
||||
|
||||
fn register_device_collection_changed(
|
||||
&mut self,
|
||||
devtype: DeviceType,
|
||||
cb: ffi::cubeb_device_collection_changed_callback,
|
||||
user_ptr: *mut c_void,
|
||||
) -> Result<()> {
|
||||
fn update_collection(
|
||||
_: &pulse::Context,
|
||||
event: pulse::SubscriptionEvent,
|
||||
index: u32,
|
||||
user_data: *mut c_void,
|
||||
) {
|
||||
let ctx = unsafe { &mut *(user_data as *mut PulseContext) };
|
||||
|
||||
let (f, t) = (event.event_facility(), event.event_type());
|
||||
match f {
|
||||
pulse::SubscriptionEventFacility::Source |
|
||||
pulse::SubscriptionEventFacility::Sink => {
|
||||
match t {
|
||||
pulse::SubscriptionEventType::Remove |
|
||||
pulse::SubscriptionEventType::New => {
|
||||
if cubeb::log_enabled() {
|
||||
let op = if t == pulse::SubscriptionEventType::New {
|
||||
"Adding"
|
||||
} else {
|
||||
"Removing"
|
||||
};
|
||||
let dev = if f == pulse::SubscriptionEventFacility::Sink {
|
||||
"sink"
|
||||
} else {
|
||||
"source "
|
||||
};
|
||||
log!("{} {} index {}", op, dev, index);
|
||||
|
||||
unsafe {
|
||||
ctx.collection_changed_callback.unwrap()(ctx as *mut _ as *mut _,
|
||||
ctx.collection_changed_user_ptr);
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
if (f == pulse::SubscriptionEventFacility::Source)
|
||||
| (f == pulse::SubscriptionEventFacility::Sink)
|
||||
{
|
||||
if (t == pulse::SubscriptionEventType::Remove)
|
||||
| (t == pulse::SubscriptionEventType::New)
|
||||
{
|
||||
if log_enabled() {
|
||||
let op = if t == pulse::SubscriptionEventType::New {
|
||||
"Adding"
|
||||
} else {
|
||||
"Removing"
|
||||
};
|
||||
let dev = if f == pulse::SubscriptionEventFacility::Sink {
|
||||
"sink"
|
||||
} else {
|
||||
"source "
|
||||
};
|
||||
cubeb_log!("{} {} index {}", op, dev, index);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
unsafe {
|
||||
ctx.collection_changed_callback.unwrap()(
|
||||
ctx as *mut _ as *mut _,
|
||||
ctx.collection_changed_user_ptr,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn success(_: &pulse::Context, success: i32, user_data: *mut c_void) {
|
||||
let ctx = unsafe { &*(user_data as *mut Context) };
|
||||
let ctx = unsafe { &*(user_data as *mut PulseContext) };
|
||||
debug_assert_ne!(success, 0);
|
||||
ctx.mainloop.signal();
|
||||
}
|
||||
|
@ -467,7 +519,7 @@ impl Context {
|
|||
self.collection_changed_callback = cb;
|
||||
self.collection_changed_user_ptr = user_ptr;
|
||||
|
||||
let user_data: *mut c_void = self as *mut _ as *mut _;
|
||||
let user_data: *mut c_void = self as *const _ as *mut _;
|
||||
if let Some(ref context) = self.context {
|
||||
self.mainloop.lock();
|
||||
|
||||
|
@ -477,10 +529,10 @@ impl Context {
|
|||
context.clear_subscribe_callback();
|
||||
} else {
|
||||
context.set_subscribe_callback(update_collection, user_data);
|
||||
if devtype.contains(cubeb::DeviceType::INPUT) {
|
||||
if devtype.contains(DeviceType::INPUT) {
|
||||
mask |= pulse::SubscriptionMask::SOURCE
|
||||
};
|
||||
if devtype.contains(cubeb::DeviceType::OUTPUT) {
|
||||
if devtype.contains(DeviceType::OUTPUT) {
|
||||
mask = pulse::SubscriptionMask::SINK
|
||||
};
|
||||
}
|
||||
|
@ -489,19 +541,28 @@ impl Context {
|
|||
self.operation_wait(None, &o);
|
||||
} else {
|
||||
self.mainloop.unlock();
|
||||
log!("Context subscribe failed");
|
||||
return cubeb::ERROR;
|
||||
cubeb_log!("Context subscribe failed");
|
||||
return Err(Error::error());
|
||||
}
|
||||
|
||||
self.mainloop.unlock();
|
||||
}
|
||||
|
||||
cubeb::OK
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn context_init(&mut self) -> i32 {
|
||||
impl Drop for PulseContext {
|
||||
fn drop(&mut self) {
|
||||
self.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
impl PulseContext {
|
||||
/* Initialize PulseAudio Context */
|
||||
fn context_init(&mut self) -> Result<()> {
|
||||
fn error_state(c: &pulse::Context, u: *mut c_void) {
|
||||
let ctx = unsafe { &mut *(u as *mut Context) };
|
||||
let ctx = unsafe { &mut *(u as *mut PulseContext) };
|
||||
if !c.get_state().is_good() {
|
||||
ctx.error = true;
|
||||
}
|
||||
|
@ -523,7 +584,7 @@ impl Context {
|
|||
|
||||
let context_ptr: *mut c_void = self as *mut _ as *mut _;
|
||||
if self.context.is_none() {
|
||||
return cubeb::ERROR;
|
||||
return Err(Error::error());
|
||||
}
|
||||
|
||||
self.mainloop.lock();
|
||||
|
@ -535,25 +596,27 @@ impl Context {
|
|||
if !self.wait_until_context_ready() {
|
||||
self.mainloop.unlock();
|
||||
self.context_destroy();
|
||||
return cubeb::ERROR;
|
||||
return Err(Error::error());
|
||||
}
|
||||
|
||||
self.mainloop.unlock();
|
||||
|
||||
let version_str = unsafe { CStr::from_ptr(pulse::library_version()) };
|
||||
if let Ok(version) = semver::Version::parse(&version_str.to_string_lossy()) {
|
||||
self.version_0_9_8 = version >= semver::Version::parse("0.9.8").expect("Failed to parse version");
|
||||
self.version_2_0_0 = version >= semver::Version::parse("2.0.0").expect("Failed to parse version");
|
||||
self.version_0_9_8 =
|
||||
version >= semver::Version::parse("0.9.8").expect("Failed to parse version");
|
||||
self.version_2_0_0 =
|
||||
version >= semver::Version::parse("2.0.0").expect("Failed to parse version");
|
||||
}
|
||||
|
||||
self.error = false;
|
||||
|
||||
cubeb::OK
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn context_destroy(&mut self) {
|
||||
fn drain_complete(_: &pulse::Context, u: *mut c_void) {
|
||||
let ctx = unsafe { &*(u as *mut Context) };
|
||||
let ctx = unsafe { &*(u as *mut PulseContext) };
|
||||
ctx.mainloop.signal();
|
||||
}
|
||||
|
||||
|
@ -571,7 +634,8 @@ impl Context {
|
|||
}
|
||||
|
||||
pub fn operation_wait<'a, S>(&self, s: S, o: &pulse::Operation) -> bool
|
||||
where S: Into<Option<&'a pulse::Stream>>
|
||||
where
|
||||
S: Into<Option<&'a pulse::Stream>>,
|
||||
{
|
||||
let stream = s.into();
|
||||
while o.get_state() == PA_OPERATION_RUNNING {
|
||||
|
@ -609,16 +673,16 @@ impl Context {
|
|||
true
|
||||
}
|
||||
|
||||
fn state_from_port(&self, i: *const pa_port_info) -> cubeb::DeviceState {
|
||||
fn state_from_port(&self, i: *const pa_port_info) -> ffi::cubeb_device_state {
|
||||
if !i.is_null() {
|
||||
let info = unsafe { *i };
|
||||
if self.version_2_0_0 && info.available == PA_PORT_AVAILABLE_NO {
|
||||
cubeb::DeviceState::Unplugged
|
||||
ffi::CUBEB_DEVICE_STATE_UNPLUGGED
|
||||
} else {
|
||||
cubeb::DeviceState::Enabled
|
||||
ffi::CUBEB_DEVICE_STATE_ENABLED
|
||||
}
|
||||
} else {
|
||||
cubeb::DeviceState::Enabled
|
||||
ffi::CUBEB_DEVICE_STATE_ENABLED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -626,13 +690,14 @@ impl Context {
|
|||
struct PulseDevListData<'a> {
|
||||
default_sink_name: CString,
|
||||
default_source_name: CString,
|
||||
devinfo: Vec<cubeb::DeviceInfo>,
|
||||
context: &'a Context,
|
||||
devinfo: Vec<ffi::cubeb_device_info>,
|
||||
context: &'a PulseContext,
|
||||
}
|
||||
|
||||
impl<'a> PulseDevListData<'a> {
|
||||
pub fn new<'b>(context: &'b Context) -> Self
|
||||
where 'b: 'a
|
||||
pub fn new<'b>(context: &'b PulseContext) -> Self
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
PulseDevListData {
|
||||
default_sink_name: CString::default(),
|
||||
|
@ -651,13 +716,13 @@ impl<'a> Drop for PulseDevListData<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn pulse_format_to_cubeb_format(format: pa_sample_format_t) -> cubeb::DeviceFmt {
|
||||
fn pulse_format_to_cubeb_format(format: pa_sample_format_t) -> ffi::cubeb_device_fmt {
|
||||
match format {
|
||||
PA_SAMPLE_S16LE => cubeb::DeviceFmt::S16LE,
|
||||
PA_SAMPLE_S16BE => cubeb::DeviceFmt::S16BE,
|
||||
PA_SAMPLE_FLOAT32LE => cubeb::DeviceFmt::F32LE,
|
||||
PA_SAMPLE_FLOAT32BE => cubeb::DeviceFmt::F32BE,
|
||||
PA_SAMPLE_S16LE => ffi::CUBEB_DEVICE_FMT_S16LE,
|
||||
PA_SAMPLE_S16BE => ffi::CUBEB_DEVICE_FMT_S16BE,
|
||||
PA_SAMPLE_FLOAT32LE => ffi::CUBEB_DEVICE_FMT_F32LE,
|
||||
PA_SAMPLE_FLOAT32BE => ffi::CUBEB_DEVICE_FMT_F32BE,
|
||||
// Unsupported format, return F32NE
|
||||
_ => cubeb::DeviceFmt::F32NE,
|
||||
_ => ffi::CUBEB_DEVICE_FMT_F32NE,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright © 2017 Mozilla Foundation
|
||||
// Copyright © 2017-2018 Mozilla Foundation
|
||||
//
|
||||
// This program is made available under an ISC-style license. See the
|
||||
// accompanying file LICENSE for details.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright © 2017 Mozilla Foundation
|
||||
// Copyright © 2017-2018 Mozilla Foundation
|
||||
//
|
||||
// This program is made available under an ISC-style license. See the
|
||||
// accompanying file LICENSE for details.
|
||||
|
@ -8,18 +8,18 @@ use std::os::raw::c_char;
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct Intern {
|
||||
vec: Vec<Box<CString>>
|
||||
vec: Vec<Box<CString>>,
|
||||
}
|
||||
|
||||
impl Intern {
|
||||
pub fn new() -> Intern {
|
||||
Intern {
|
||||
vec: Vec::new()
|
||||
}
|
||||
Intern { vec: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn add(&mut self, string: &CStr) -> *const c_char {
|
||||
fn eq(s1: &CStr, s2: &CStr) -> bool { s1 == s2 }
|
||||
fn eq(s1: &CStr, s2: &CStr) -> bool {
|
||||
s1 == s2
|
||||
}
|
||||
for s in &self.vec {
|
||||
if eq(s, string) {
|
||||
return s.as_ptr();
|
||||
|
@ -33,8 +33,8 @@ impl Intern {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ffi::CStr;
|
||||
use super::Intern;
|
||||
use std::ffi::CStr;
|
||||
|
||||
#[test]
|
||||
fn intern() {
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
// Copyright © 2017-2018 Mozilla Foundation
|
||||
//
|
||||
// This program is made available under an ISC-style license. See the
|
||||
// accompanying file LICENSE for details.
|
||||
|
||||
use cubeb_backend::ffi::*;
|
||||
|
||||
static CHANNEL_LAYOUT_UNDEFINED: &'static [cubeb_channel] = &[CHANNEL_INVALID];
|
||||
static CHANNEL_LAYOUT_DUAL_MONO: &'static [cubeb_channel] = &[CHANNEL_LEFT, CHANNEL_RIGHT];
|
||||
static CHANNEL_LAYOUT_DUAL_MONO_LFE: &'static [cubeb_channel] =
|
||||
&[CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE];
|
||||
static CHANNEL_LAYOUT_MONO: &'static [cubeb_channel] = &[CHANNEL_MONO];
|
||||
static CHANNEL_LAYOUT_MONO_LFE: &'static [cubeb_channel] = &[CHANNEL_MONO, CHANNEL_LFE];
|
||||
static CHANNEL_LAYOUT_STEREO: &'static [cubeb_channel] = &[CHANNEL_LEFT, CHANNEL_RIGHT];
|
||||
static CHANNEL_LAYOUT_STEREO_LFE: &'static [cubeb_channel] =
|
||||
&[CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE];
|
||||
static CHANNEL_LAYOUT_3F: &'static [cubeb_channel] = &[CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER];
|
||||
static CHANNEL_LAYOUT_3FLFE: &'static [cubeb_channel] =
|
||||
&[CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE];
|
||||
static CHANNEL_LAYOUT_2F1: &'static [cubeb_channel] =
|
||||
&[CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_RCENTER];
|
||||
static CHANNEL_LAYOUT_2F1LFE: &'static [cubeb_channel] =
|
||||
&[CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE, CHANNEL_RCENTER];
|
||||
static CHANNEL_LAYOUT_3F1: &'static [cubeb_channel] =
|
||||
&[CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_RCENTER];
|
||||
static CHANNEL_LAYOUT_3F1LFE: &'static [cubeb_channel] = &[
|
||||
CHANNEL_LEFT,
|
||||
CHANNEL_RIGHT,
|
||||
CHANNEL_CENTER,
|
||||
CHANNEL_LFE,
|
||||
CHANNEL_RCENTER,
|
||||
];
|
||||
static CHANNEL_LAYOUT_2F2: &'static [cubeb_channel] =
|
||||
&[CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LS, CHANNEL_RS];
|
||||
static CHANNEL_LAYOUT_2F2LFE: &'static [cubeb_channel] = &[
|
||||
CHANNEL_LEFT,
|
||||
CHANNEL_RIGHT,
|
||||
CHANNEL_LFE,
|
||||
CHANNEL_LS,
|
||||
CHANNEL_RS,
|
||||
];
|
||||
static CHANNEL_LAYOUT_3F2: &'static [cubeb_channel] = &[
|
||||
CHANNEL_LEFT,
|
||||
CHANNEL_RIGHT,
|
||||
CHANNEL_CENTER,
|
||||
CHANNEL_LS,
|
||||
CHANNEL_RS,
|
||||
];
|
||||
static CHANNEL_LAYOUT_3F2LFE: &'static [cubeb_channel] = &[
|
||||
CHANNEL_LEFT,
|
||||
CHANNEL_RIGHT,
|
||||
CHANNEL_CENTER,
|
||||
CHANNEL_LFE,
|
||||
CHANNEL_LS,
|
||||
CHANNEL_RS,
|
||||
];
|
||||
static CHANNEL_LAYOUT_3F3RLFE: &'static [cubeb_channel] = &[
|
||||
CHANNEL_LEFT,
|
||||
CHANNEL_RIGHT,
|
||||
CHANNEL_CENTER,
|
||||
CHANNEL_LFE,
|
||||
CHANNEL_RCENTER,
|
||||
CHANNEL_LS,
|
||||
CHANNEL_RS,
|
||||
];
|
||||
static CHANNEL_LAYOUT_3F4LFE: &'static [cubeb_channel] = &[
|
||||
CHANNEL_LEFT,
|
||||
CHANNEL_RIGHT,
|
||||
CHANNEL_CENTER,
|
||||
CHANNEL_LFE,
|
||||
CHANNEL_RLS,
|
||||
CHANNEL_RRS,
|
||||
CHANNEL_LS,
|
||||
CHANNEL_RS,
|
||||
];
|
||||
|
||||
pub fn channel_index_to_order(layout: cubeb_channel_layout) -> &'static [cubeb_channel] {
|
||||
match layout {
|
||||
CUBEB_LAYOUT_DUAL_MONO => CHANNEL_LAYOUT_DUAL_MONO,
|
||||
CUBEB_LAYOUT_DUAL_MONO_LFE => CHANNEL_LAYOUT_DUAL_MONO_LFE,
|
||||
CUBEB_LAYOUT_MONO => CHANNEL_LAYOUT_MONO,
|
||||
CUBEB_LAYOUT_MONO_LFE => CHANNEL_LAYOUT_MONO_LFE,
|
||||
CUBEB_LAYOUT_STEREO => CHANNEL_LAYOUT_STEREO,
|
||||
CUBEB_LAYOUT_STEREO_LFE => CHANNEL_LAYOUT_STEREO_LFE,
|
||||
CUBEB_LAYOUT_3F => CHANNEL_LAYOUT_3F,
|
||||
CUBEB_LAYOUT_3F_LFE => CHANNEL_LAYOUT_3FLFE,
|
||||
CUBEB_LAYOUT_2F1 => CHANNEL_LAYOUT_2F1,
|
||||
CUBEB_LAYOUT_2F1_LFE => CHANNEL_LAYOUT_2F1LFE,
|
||||
CUBEB_LAYOUT_3F1 => CHANNEL_LAYOUT_3F1,
|
||||
CUBEB_LAYOUT_3F1_LFE => CHANNEL_LAYOUT_3F1LFE,
|
||||
CUBEB_LAYOUT_2F2 => CHANNEL_LAYOUT_2F2,
|
||||
CUBEB_LAYOUT_2F2_LFE => CHANNEL_LAYOUT_2F2LFE,
|
||||
CUBEB_LAYOUT_3F2 => CHANNEL_LAYOUT_3F2,
|
||||
CUBEB_LAYOUT_3F2_LFE => CHANNEL_LAYOUT_3F2LFE,
|
||||
CUBEB_LAYOUT_3F3R_LFE => CHANNEL_LAYOUT_3F3RLFE,
|
||||
CUBEB_LAYOUT_3F4_LFE => CHANNEL_LAYOUT_3F4LFE,
|
||||
_ => CHANNEL_LAYOUT_UNDEFINED,
|
||||
}
|
||||
}
|
|
@ -1,24 +1,26 @@
|
|||
// Copyright © 2017 Mozilla Foundation
|
||||
// Copyright © 2017-2018 Mozilla Foundation
|
||||
//
|
||||
// This program is made available under an ISC-style license. See the
|
||||
// accompanying file LICENSE for details.
|
||||
|
||||
mod context;
|
||||
mod cork_state;
|
||||
mod mixer;
|
||||
mod stream;
|
||||
mod intern;
|
||||
|
||||
use std::os::raw::c_char;
|
||||
use std::ffi::CStr;
|
||||
|
||||
pub type Result<T> = ::std::result::Result<T, i32>;
|
||||
|
||||
pub use self::context::Context;
|
||||
pub use self::stream::Device;
|
||||
pub use self::stream::Stream;
|
||||
pub use self::context::PulseContext;
|
||||
use self::intern::Intern;
|
||||
pub use self::stream::Device;
|
||||
pub use self::stream::PulseStream;
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
// helper to convert *const c_char to Option<CStr>
|
||||
fn try_cstr_from<'str>(s: *const c_char) -> Option<&'str CStr> {
|
||||
if s.is_null() { None } else { Some(unsafe { CStr::from_ptr(s) }) }
|
||||
if s.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { CStr::from_ptr(s) })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,47 +1,43 @@
|
|||
// Copyright © 2017 Mozilla Foundation
|
||||
// Copyright © 2017-2018 Mozilla Foundation
|
||||
//
|
||||
// This program is made available under an ISC-style license. See the
|
||||
// accompanying file LICENSE for details.
|
||||
|
||||
use backend::*;
|
||||
use backend::cork_state::CorkState;
|
||||
use cubeb;
|
||||
use cubeb_backend::{ffi, log_enabled, ChannelLayout, DeviceId, DeviceRef, Error, Result,
|
||||
SampleFormat, StreamOps, StreamParamsRef, StreamPrefs};
|
||||
use pulse::{self, CVolumeExt, ChannelMapExt, SampleSpecExt, StreamLatency, USecExt};
|
||||
use pulse_ffi::*;
|
||||
use std::{mem, ptr};
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::os::raw::{c_long, c_void};
|
||||
use std::ptr;
|
||||
|
||||
const PULSE_NO_GAIN: f32 = -1.0;
|
||||
|
||||
fn cubeb_channel_to_pa_channel(channel: cubeb::Channel) -> pa_channel_position_t {
|
||||
assert_ne!(channel, cubeb::CHANNEL_INVALID);
|
||||
fn cubeb_channel_to_pa_channel(channel: ffi::cubeb_channel) -> pa_channel_position_t {
|
||||
use cubeb_backend::ffi::*;
|
||||
assert_ne!(channel, CHANNEL_INVALID);
|
||||
|
||||
// This variable may be used for multiple times, so we should avoid to
|
||||
// allocate it in stack, or it will be created and removed repeatedly.
|
||||
// Use static to allocate this local variable in data space instead of stack.
|
||||
static MAP: [pa_channel_position_t; 10] = [
|
||||
// PA_CHANNEL_POSITION_INVALID, // CHANNEL_INVALID
|
||||
PA_CHANNEL_POSITION_MONO, // CHANNEL_MONO
|
||||
PA_CHANNEL_POSITION_FRONT_LEFT, // CHANNEL_LEFT
|
||||
PA_CHANNEL_POSITION_FRONT_RIGHT, // CHANNEL_RIGHT
|
||||
PA_CHANNEL_POSITION_FRONT_CENTER, // CHANNEL_CENTER
|
||||
PA_CHANNEL_POSITION_SIDE_LEFT, // CHANNEL_LS
|
||||
PA_CHANNEL_POSITION_SIDE_RIGHT, // CHANNEL_RS
|
||||
PA_CHANNEL_POSITION_REAR_LEFT, // CHANNEL_RLS
|
||||
PA_CHANNEL_POSITION_REAR_CENTER, // CHANNEL_RCENTER
|
||||
PA_CHANNEL_POSITION_REAR_RIGHT, // CHANNEL_RRS
|
||||
PA_CHANNEL_POSITION_LFE // CHANNEL_LFE
|
||||
];
|
||||
|
||||
let idx: i32 = channel;
|
||||
MAP[idx as usize]
|
||||
match channel {
|
||||
CHANNEL_LEFT => PA_CHANNEL_POSITION_FRONT_LEFT,
|
||||
CHANNEL_RIGHT => PA_CHANNEL_POSITION_FRONT_RIGHT,
|
||||
CHANNEL_CENTER => PA_CHANNEL_POSITION_FRONT_CENTER,
|
||||
CHANNEL_LS => PA_CHANNEL_POSITION_SIDE_LEFT,
|
||||
CHANNEL_RS => PA_CHANNEL_POSITION_SIDE_RIGHT,
|
||||
CHANNEL_RLS => PA_CHANNEL_POSITION_REAR_LEFT,
|
||||
CHANNEL_RCENTER => PA_CHANNEL_POSITION_REAR_CENTER,
|
||||
CHANNEL_RRS => PA_CHANNEL_POSITION_REAR_RIGHT,
|
||||
CHANNEL_LFE => PA_CHANNEL_POSITION_LFE,
|
||||
// Also handles CHANNEL_MONO case
|
||||
_ => PA_CHANNEL_POSITION_MONO,
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_to_channel_map(layout: cubeb::ChannelLayout) -> pulse::ChannelMap {
|
||||
assert_ne!(layout, cubeb::LAYOUT_UNDEFINED);
|
||||
fn layout_to_channel_map(layout: ChannelLayout) -> pulse::ChannelMap {
|
||||
assert_ne!(layout, ChannelLayout::Undefined);
|
||||
|
||||
let order = cubeb::mixer::channel_index_to_order(layout);
|
||||
let order = mixer::channel_index_to_order(layout.into());
|
||||
|
||||
let mut cm = pulse::ChannelMap::init();
|
||||
cm.channels = order.len() as u8;
|
||||
|
@ -51,78 +47,74 @@ fn layout_to_channel_map(layout: cubeb::ChannelLayout) -> pulse::ChannelMap {
|
|||
cm
|
||||
}
|
||||
|
||||
pub struct Device(cubeb::Device);
|
||||
pub struct Device(ffi::cubeb_device);
|
||||
|
||||
impl Drop for Device {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if !self.0.input_name.is_null() {
|
||||
let _ = CString::from_raw(self.0.input_name);
|
||||
let _ = CString::from_raw(self.0.input_name as *mut _);
|
||||
}
|
||||
if !self.0.output_name.is_null() {
|
||||
let _ = CString::from_raw(self.0.output_name);
|
||||
let _ = CString::from_raw(self.0.output_name as *mut _);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Stream<'ctx> {
|
||||
context: &'ctx Context,
|
||||
pub struct PulseStream<'ctx> {
|
||||
context: &'ctx PulseContext,
|
||||
user_ptr: *mut c_void,
|
||||
output_stream: Option<pulse::Stream>,
|
||||
input_stream: Option<pulse::Stream>,
|
||||
data_callback: cubeb::DataCallback,
|
||||
state_callback: cubeb::StateCallback,
|
||||
user_ptr: *mut c_void,
|
||||
data_callback: ffi::cubeb_data_callback,
|
||||
state_callback: ffi::cubeb_state_callback,
|
||||
drain_timer: *mut pa_time_event,
|
||||
output_sample_spec: pulse::SampleSpec,
|
||||
input_sample_spec: pulse::SampleSpec,
|
||||
shutdown: bool,
|
||||
volume: f32,
|
||||
state: cubeb::State,
|
||||
state: ffi::cubeb_state,
|
||||
}
|
||||
|
||||
impl<'ctx> Drop for Stream<'ctx> {
|
||||
fn drop(&mut self) {
|
||||
self.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> Stream<'ctx> {
|
||||
impl<'ctx> PulseStream<'ctx> {
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))]
|
||||
pub fn new(context: &'ctx Context,
|
||||
stream_name: &CStr,
|
||||
input_device: cubeb::DeviceId,
|
||||
input_stream_params: Option<cubeb::StreamParams>,
|
||||
output_device: cubeb::DeviceId,
|
||||
output_stream_params: Option<cubeb::StreamParams>,
|
||||
latency_frames: u32,
|
||||
data_callback: cubeb::DataCallback,
|
||||
state_callback: cubeb::StateCallback,
|
||||
user_ptr: *mut c_void)
|
||||
-> Result<Box<Stream<'ctx>>> {
|
||||
|
||||
pub fn new(
|
||||
context: &'ctx PulseContext,
|
||||
stream_name: Option<&CStr>,
|
||||
input_device: DeviceId,
|
||||
input_stream_params: Option<&StreamParamsRef>,
|
||||
output_device: DeviceId,
|
||||
output_stream_params: Option<&StreamParamsRef>,
|
||||
latency_frames: u32,
|
||||
data_callback: ffi::cubeb_data_callback,
|
||||
state_callback: ffi::cubeb_state_callback,
|
||||
user_ptr: *mut c_void,
|
||||
) -> Result<Box<Self>> {
|
||||
fn check_error(s: &pulse::Stream, u: *mut c_void) {
|
||||
let stm = unsafe { &mut *(u as *mut Stream) };
|
||||
let stm = unsafe { &mut *(u as *mut PulseStream) };
|
||||
if !s.get_state().is_good() {
|
||||
stm.state_change_callback(cubeb::STATE_ERROR);
|
||||
stm.state_change_callback(ffi::CUBEB_STATE_ERROR);
|
||||
}
|
||||
stm.context.mainloop.signal();
|
||||
}
|
||||
|
||||
fn read_data(s: &pulse::Stream, nbytes: usize, u: *mut c_void) {
|
||||
fn read_from_input(s: &pulse::Stream, buffer: *mut *const c_void, size: *mut usize) -> i32 {
|
||||
let readable_size: i32 = s.readable_size()
|
||||
.and_then(|s| Ok(s as i32))
|
||||
.unwrap_or(-1);
|
||||
fn read_from_input(
|
||||
s: &pulse::Stream,
|
||||
buffer: *mut *const c_void,
|
||||
size: *mut usize,
|
||||
) -> i32 {
|
||||
let readable_size: i32 = s.readable_size().and_then(|s| Ok(s as i32)).unwrap_or(-1);
|
||||
if readable_size > 0 && unsafe { s.peek(buffer, size).is_err() } {
|
||||
return -1;
|
||||
}
|
||||
readable_size
|
||||
}
|
||||
|
||||
logv!("Input callback buffer size {}", nbytes);
|
||||
let stm = unsafe { &mut *(u as *mut Stream) };
|
||||
cubeb_logv!("Input callback buffer size {}", nbytes);
|
||||
let stm = unsafe { &mut *(u as *mut PulseStream) };
|
||||
if stm.shutdown {
|
||||
return;
|
||||
}
|
||||
|
@ -144,11 +136,13 @@ impl<'ctx> Stream<'ctx> {
|
|||
} else {
|
||||
// input/capture only operation. Call callback directly
|
||||
let got = unsafe {
|
||||
stm.data_callback.unwrap()(stm as *mut _ as *mut _,
|
||||
stm.user_ptr,
|
||||
read_data,
|
||||
ptr::null_mut(),
|
||||
read_frames as c_long)
|
||||
stm.data_callback.unwrap()(
|
||||
stm as *mut _ as *mut _,
|
||||
stm.user_ptr,
|
||||
read_data,
|
||||
ptr::null_mut(),
|
||||
read_frames as c_long,
|
||||
)
|
||||
};
|
||||
|
||||
if got < 0 || got as usize != read_frames {
|
||||
|
@ -170,9 +164,9 @@ impl<'ctx> Stream<'ctx> {
|
|||
}
|
||||
|
||||
fn write_data(_: &pulse::Stream, nbytes: usize, u: *mut c_void) {
|
||||
logv!("Output callback to be written buffer size {}", nbytes);
|
||||
let stm = unsafe { &mut *(u as *mut Stream) };
|
||||
if stm.shutdown || stm.state != cubeb::STATE_STARTED {
|
||||
cubeb_logv!("Output callback to be written buffer size {}", nbytes);
|
||||
let stm = unsafe { &mut *(u as *mut PulseStream) };
|
||||
if stm.shutdown || stm.state != ffi::CUBEB_STATE_STARTED {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -184,56 +178,60 @@ impl<'ctx> Stream<'ctx> {
|
|||
}
|
||||
}
|
||||
|
||||
let mut stm = Box::new(Stream {
|
||||
context: context,
|
||||
output_stream: None,
|
||||
input_stream: None,
|
||||
data_callback: data_callback,
|
||||
state_callback: state_callback,
|
||||
user_ptr: user_ptr,
|
||||
drain_timer: ptr::null_mut(),
|
||||
output_sample_spec: pulse::SampleSpec::default(),
|
||||
input_sample_spec: pulse::SampleSpec::default(),
|
||||
shutdown: false,
|
||||
volume: PULSE_NO_GAIN,
|
||||
state: cubeb::STATE_ERROR,
|
||||
});
|
||||
let mut stm = Box::new(PulseStream {
|
||||
context: context,
|
||||
output_stream: None,
|
||||
input_stream: None,
|
||||
data_callback: data_callback,
|
||||
state_callback: state_callback,
|
||||
user_ptr: user_ptr,
|
||||
drain_timer: ptr::null_mut(),
|
||||
output_sample_spec: pulse::SampleSpec::default(),
|
||||
input_sample_spec: pulse::SampleSpec::default(),
|
||||
shutdown: false,
|
||||
volume: PULSE_NO_GAIN,
|
||||
state: ffi::CUBEB_STATE_ERROR,
|
||||
});
|
||||
|
||||
if let Some(ref context) = stm.context.context {
|
||||
stm.context.mainloop.lock();
|
||||
|
||||
// Setup output stream
|
||||
if let Some(ref stream_params) = output_stream_params {
|
||||
match Stream::stream_init(context, stream_params, stream_name) {
|
||||
if let Some(stream_params) = output_stream_params {
|
||||
match PulseStream::stream_init(context, stream_params, stream_name) {
|
||||
Ok(s) => {
|
||||
stm.output_sample_spec = *s.get_sample_spec();
|
||||
|
||||
s.set_state_callback(check_error, stm.as_mut() as *mut _ as *mut _);
|
||||
s.set_write_callback(write_data, stm.as_mut() as *mut _ as *mut _);
|
||||
|
||||
let battr = set_buffering_attribute(latency_frames, &stm.output_sample_spec);
|
||||
let battr =
|
||||
set_buffering_attribute(latency_frames, &stm.output_sample_spec);
|
||||
let device_name = super::try_cstr_from(output_device as *const _);
|
||||
let _ = s.connect_playback(device_name,
|
||||
&battr,
|
||||
pulse::StreamFlags::AUTO_TIMING_UPDATE | pulse::StreamFlags::INTERPOLATE_TIMING |
|
||||
pulse::StreamFlags::START_CORKED |
|
||||
pulse::StreamFlags::ADJUST_LATENCY,
|
||||
None,
|
||||
None);
|
||||
let _ = s.connect_playback(
|
||||
device_name,
|
||||
&battr,
|
||||
pulse::StreamFlags::AUTO_TIMING_UPDATE
|
||||
| pulse::StreamFlags::INTERPOLATE_TIMING
|
||||
| pulse::StreamFlags::START_CORKED
|
||||
| pulse::StreamFlags::ADJUST_LATENCY,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
stm.output_stream = Some(s);
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
stm.context.mainloop.unlock();
|
||||
stm.destroy();
|
||||
return Err(e);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set up input stream
|
||||
if let Some(ref stream_params) = input_stream_params {
|
||||
match Stream::stream_init(context, stream_params, stream_name) {
|
||||
match PulseStream::stream_init(context, stream_params, stream_name) {
|
||||
Ok(s) => {
|
||||
stm.input_sample_spec = *s.get_sample_spec();
|
||||
|
||||
|
@ -242,19 +240,22 @@ impl<'ctx> Stream<'ctx> {
|
|||
|
||||
let battr = set_buffering_attribute(latency_frames, &stm.input_sample_spec);
|
||||
let device_name = super::try_cstr_from(input_device as *const _);
|
||||
let _ = s.connect_record(device_name,
|
||||
&battr,
|
||||
pulse::StreamFlags::AUTO_TIMING_UPDATE | pulse::StreamFlags::INTERPOLATE_TIMING |
|
||||
pulse::StreamFlags::START_CORKED |
|
||||
pulse::StreamFlags::ADJUST_LATENCY);
|
||||
let _ = s.connect_record(
|
||||
device_name,
|
||||
&battr,
|
||||
pulse::StreamFlags::AUTO_TIMING_UPDATE
|
||||
| pulse::StreamFlags::INTERPOLATE_TIMING
|
||||
| pulse::StreamFlags::START_CORKED
|
||||
| pulse::StreamFlags::ADJUST_LATENCY,
|
||||
);
|
||||
|
||||
stm.input_stream = Some(s);
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
stm.context.mainloop.unlock();
|
||||
stm.destroy();
|
||||
return Err(e);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -270,30 +271,35 @@ impl<'ctx> Stream<'ctx> {
|
|||
|
||||
if !r {
|
||||
stm.destroy();
|
||||
return Err(cubeb::ERROR);
|
||||
return Err(Error::error());
|
||||
}
|
||||
|
||||
if cubeb::log_enabled() {
|
||||
// TODO:
|
||||
if log_enabled() {
|
||||
if let Some(ref output_stream) = stm.output_stream {
|
||||
let output_att = output_stream.get_buffer_attr();
|
||||
log!("Output buffer attributes maxlength {}, tlength {}, \
|
||||
cubeb_log!(
|
||||
"Output buffer attributes maxlength {}, tlength {}, \
|
||||
prebuf {}, minreq {}, fragsize {}",
|
||||
output_att.maxlength,
|
||||
output_att.tlength,
|
||||
output_att.prebuf,
|
||||
output_att.minreq,
|
||||
output_att.fragsize);
|
||||
output_att.maxlength,
|
||||
output_att.tlength,
|
||||
output_att.prebuf,
|
||||
output_att.minreq,
|
||||
output_att.fragsize
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(ref input_stream) = stm.input_stream {
|
||||
let input_att = input_stream.get_buffer_attr();
|
||||
log!("Input buffer attributes maxlength {}, tlength {}, \
|
||||
prebuf {}, minreq {}, fragsize {}",
|
||||
input_att.maxlength,
|
||||
input_att.tlength,
|
||||
input_att.prebuf,
|
||||
input_att.minreq,
|
||||
input_att.fragsize);
|
||||
cubeb_log!(
|
||||
"Input buffer attributes maxlength {}, tlength {}, \
|
||||
prebuf {}, minreq {}, fragsize {}",
|
||||
input_att.maxlength,
|
||||
input_att.tlength,
|
||||
input_att.prebuf,
|
||||
input_att.minreq,
|
||||
input_att.fragsize
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -309,10 +315,7 @@ impl<'ctx> Stream<'ctx> {
|
|||
if let Some(stm) = self.output_stream.take() {
|
||||
if !self.drain_timer.is_null() {
|
||||
/* there's no pa_rttime_free, so use this instead. */
|
||||
self.context
|
||||
.mainloop
|
||||
.get_api()
|
||||
.time_free(self.drain_timer);
|
||||
self.context.mainloop.get_api().time_free(self.drain_timer);
|
||||
}
|
||||
stm.clear_state_callback();
|
||||
stm.clear_write_callback();
|
||||
|
@ -329,10 +332,18 @@ impl<'ctx> Stream<'ctx> {
|
|||
}
|
||||
self.context.mainloop.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self) -> i32 {
|
||||
impl<'ctx> Drop for PulseStream<'ctx> {
|
||||
fn drop(&mut self) {
|
||||
self.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> StreamOps for PulseStream<'ctx> {
|
||||
fn start(&mut self) -> Result<()> {
|
||||
fn output_preroll(_: &pulse::MainloopApi, u: *mut c_void) {
|
||||
let stm = unsafe { &mut *(u as *mut Stream) };
|
||||
let stm = unsafe { &mut *(u as *mut PulseStream) };
|
||||
if !stm.shutdown {
|
||||
let size = stm.output_stream
|
||||
.as_ref()
|
||||
|
@ -352,14 +363,14 @@ impl<'ctx> Stream<'ctx> {
|
|||
self.context
|
||||
.mainloop
|
||||
.get_api()
|
||||
.once(output_preroll, self as *mut _ as *mut _);
|
||||
.once(output_preroll, self as *const _ as *mut _);
|
||||
self.context.mainloop.unlock();
|
||||
}
|
||||
|
||||
cubeb::OK
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) -> i32 {
|
||||
fn stop(&mut self) -> Result<()> {
|
||||
{
|
||||
self.context.mainloop.lock();
|
||||
self.shutdown = true;
|
||||
|
@ -371,27 +382,31 @@ impl<'ctx> Stream<'ctx> {
|
|||
}
|
||||
self.cork(CorkState::cork() | CorkState::notify());
|
||||
|
||||
cubeb::OK
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn position(&self) -> Result<u64> {
|
||||
fn reset_default_device(&mut self) -> Result<()> {
|
||||
Err(not_supported())
|
||||
}
|
||||
|
||||
fn position(&mut self) -> Result<u64> {
|
||||
let in_thread = self.context.mainloop.in_thread();
|
||||
|
||||
if !in_thread {
|
||||
self.context.mainloop.lock();
|
||||
}
|
||||
|
||||
let r = match self.output_stream {
|
||||
None => Err(cubeb::ERROR),
|
||||
Some(ref stm) => {
|
||||
match stm.get_time() {
|
||||
Ok(r_usec) => {
|
||||
let bytes = r_usec.to_bytes(&self.output_sample_spec);
|
||||
Ok((bytes / self.output_sample_spec.frame_size()) as u64)
|
||||
},
|
||||
Err(_) => Err(cubeb::ERROR),
|
||||
}
|
||||
},
|
||||
if self.output_stream.is_none() {
|
||||
return Err(Error::error());
|
||||
}
|
||||
|
||||
let stm = self.output_stream.as_ref().unwrap();
|
||||
let r = match stm.get_time() {
|
||||
Ok(r_usec) => {
|
||||
let bytes = r_usec.to_bytes(&self.output_sample_spec);
|
||||
Ok((bytes / self.output_sample_spec.frame_size()) as u64)
|
||||
}
|
||||
Err(_) => Err(Error::error()),
|
||||
};
|
||||
|
||||
if !in_thread {
|
||||
|
@ -401,27 +416,26 @@ impl<'ctx> Stream<'ctx> {
|
|||
r
|
||||
}
|
||||
|
||||
pub fn latency(&self) -> Result<u32> {
|
||||
fn latency(&mut self) -> Result<u32> {
|
||||
match self.output_stream {
|
||||
None => Err(cubeb::ERROR),
|
||||
Some(ref stm) => {
|
||||
match stm.get_latency() {
|
||||
Ok(StreamLatency::Positive(r_usec)) => {
|
||||
let latency = (r_usec * pa_usec_t::from(self.output_sample_spec.rate) / PA_USEC_PER_SEC) as u32;
|
||||
Ok(latency)
|
||||
},
|
||||
Ok(_) => {
|
||||
panic!("Can not handle negative latency values.");
|
||||
},
|
||||
Err(_) => Err(cubeb::ERROR),
|
||||
None => Err(Error::error()),
|
||||
Some(ref stm) => match stm.get_latency() {
|
||||
Ok(StreamLatency::Positive(r_usec)) => {
|
||||
let latency = (r_usec * pa_usec_t::from(self.output_sample_spec.rate)
|
||||
/ PA_USEC_PER_SEC) as u32;
|
||||
Ok(latency)
|
||||
}
|
||||
Ok(_) => {
|
||||
panic!("Can not handle negative latency values.");
|
||||
}
|
||||
Err(_) => Err(Error::error()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_volume(&mut self, volume: f32) -> i32 {
|
||||
fn set_volume(&mut self, volume: f32) -> Result<()> {
|
||||
match self.output_stream {
|
||||
None => cubeb::ERROR,
|
||||
None => Err(Error::error()),
|
||||
Some(ref stm) => {
|
||||
if let Some(ref context) = self.context.context {
|
||||
self.context.mainloop.lock();
|
||||
|
@ -448,28 +462,38 @@ impl<'ctx> Stream<'ctx> {
|
|||
let index = stm.get_index();
|
||||
|
||||
let context_ptr = self.context as *const _ as *mut _;
|
||||
if let Ok(o) = context.set_sink_input_volume(index, &cvol, context_success, context_ptr) {
|
||||
if let Ok(o) = context.set_sink_input_volume(
|
||||
index,
|
||||
&cvol,
|
||||
context_success,
|
||||
context_ptr,
|
||||
) {
|
||||
self.context.operation_wait(stm, &o);
|
||||
}
|
||||
}
|
||||
|
||||
self.context.mainloop.unlock();
|
||||
cubeb::OK
|
||||
Ok(())
|
||||
} else {
|
||||
cubeb::ERROR
|
||||
Err(Error::error())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_panning(&mut self, panning: f32) -> i32 {
|
||||
fn set_panning(&mut self, panning: f32) -> Result<()> {
|
||||
#[repr(C)]
|
||||
struct SinkInputInfoResult<'a> {
|
||||
pub cvol: pulse::CVolume,
|
||||
pub mainloop: &'a pulse::ThreadedMainloop,
|
||||
}
|
||||
|
||||
fn get_input_volume(_: &pulse::Context, info: *const pulse::SinkInputInfo, eol: i32, u: *mut c_void) {
|
||||
fn get_input_volume(
|
||||
_: &pulse::Context,
|
||||
info: *const pulse::SinkInputInfo,
|
||||
eol: i32,
|
||||
u: *mut c_void,
|
||||
) {
|
||||
let r = unsafe { &mut *(u as *mut SinkInputInfoResult) };
|
||||
if eol == 0 {
|
||||
let info = unsafe { *info };
|
||||
|
@ -478,126 +502,149 @@ impl<'ctx> Stream<'ctx> {
|
|||
r.mainloop.signal();
|
||||
}
|
||||
|
||||
match self.output_stream {
|
||||
None => cubeb::ERROR,
|
||||
Some(ref stm) => {
|
||||
if let Some(ref context) = self.context.context {
|
||||
self.context.mainloop.lock();
|
||||
|
||||
let map = stm.get_channel_map();
|
||||
if !map.can_balance() {
|
||||
self.context.mainloop.unlock();
|
||||
return cubeb::ERROR;
|
||||
}
|
||||
|
||||
let index = stm.get_index();
|
||||
|
||||
let mut r = SinkInputInfoResult {
|
||||
cvol: pulse::CVolume::default(),
|
||||
mainloop: &self.context.mainloop,
|
||||
};
|
||||
|
||||
if let Ok(o) = context.get_sink_input_info(index, get_input_volume, &mut r as *mut _ as *mut _) {
|
||||
self.context.operation_wait(stm, &o);
|
||||
}
|
||||
|
||||
r.cvol.set_balance(map, panning);
|
||||
|
||||
let context_ptr = self.context as *const _ as *mut _;
|
||||
if let Ok(o) = context.set_sink_input_volume(index, &r.cvol, context_success, context_ptr) {
|
||||
self.context.operation_wait(stm, &o);
|
||||
}
|
||||
|
||||
self.context.mainloop.unlock();
|
||||
|
||||
cubeb::OK
|
||||
} else {
|
||||
cubeb::ERROR
|
||||
}
|
||||
},
|
||||
if self.output_stream.is_none() {
|
||||
return Err(Error::error());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_device(&self) -> Result<Box<cubeb::Device>> {
|
||||
if self.context.version_0_9_8 {
|
||||
let mut dev = Box::new(cubeb::Device::default());
|
||||
let stm = self.output_stream.as_ref().unwrap();
|
||||
|
||||
if self.input_stream.is_some() {
|
||||
if let Some(ref stm) = self.input_stream {
|
||||
dev.input_name = match stm.get_device_name() {
|
||||
Ok(name) => name.to_owned().into_raw(),
|
||||
Err(_) => {
|
||||
return Err(cubeb::ERROR);
|
||||
},
|
||||
}
|
||||
}
|
||||
if let Some(ref context) = self.context.context {
|
||||
self.context.mainloop.lock();
|
||||
|
||||
let map = stm.get_channel_map();
|
||||
if !map.can_balance() {
|
||||
self.context.mainloop.unlock();
|
||||
return Err(Error::error());
|
||||
}
|
||||
|
||||
if !self.output_stream.is_some() {
|
||||
if let Some(ref stm) = self.output_stream {
|
||||
dev.output_name = match stm.get_device_name() {
|
||||
Ok(name) => name.to_owned().into_raw(),
|
||||
Err(_) => {
|
||||
return Err(cubeb::ERROR);
|
||||
},
|
||||
}
|
||||
}
|
||||
let index = stm.get_index();
|
||||
|
||||
let mut r = SinkInputInfoResult {
|
||||
cvol: pulse::CVolume::default(),
|
||||
mainloop: &self.context.mainloop,
|
||||
};
|
||||
|
||||
if let Ok(o) =
|
||||
context.get_sink_input_info(index, get_input_volume, &mut r as *mut _ as *mut _)
|
||||
{
|
||||
self.context.operation_wait(stm, &o);
|
||||
}
|
||||
|
||||
Ok(dev)
|
||||
r.cvol.set_balance(map, panning);
|
||||
|
||||
let context_ptr = self.context as *const _ as *mut _;
|
||||
if let Ok(o) =
|
||||
context.set_sink_input_volume(index, &r.cvol, context_success, context_ptr)
|
||||
{
|
||||
self.context.operation_wait(stm, &o);
|
||||
}
|
||||
|
||||
self.context.mainloop.unlock();
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(cubeb::ERROR_NOT_SUPPORTED)
|
||||
Err(Error::error())
|
||||
}
|
||||
}
|
||||
|
||||
fn stream_init(context: &pulse::Context,
|
||||
stream_params: &cubeb::StreamParams,
|
||||
stream_name: &CStr)
|
||||
-> Result<pulse::Stream> {
|
||||
fn current_device(&mut self) -> Result<&DeviceRef> {
|
||||
if self.context.version_0_9_8 {
|
||||
let mut dev: Box<ffi::cubeb_device> = Box::new(unsafe { mem::zeroed() });
|
||||
|
||||
if stream_params.prefs == cubeb::StreamPrefs::STREAM_PREF_LOOPBACK {
|
||||
return Err(cubeb::ERROR_NOT_SUPPORTED);
|
||||
if let Some(ref stm) = self.input_stream {
|
||||
dev.input_name = match stm.get_device_name() {
|
||||
Ok(name) => name.to_owned().into_raw(),
|
||||
Err(_) => {
|
||||
return Err(Error::error());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref stm) = self.output_stream {
|
||||
dev.output_name = match stm.get_device_name() {
|
||||
Ok(name) => name.to_owned().into_raw(),
|
||||
Err(_) => {
|
||||
return Err(Error::error());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(unsafe { DeviceRef::from_ptr(Box::into_raw(dev) as *mut _) })
|
||||
} else {
|
||||
Err(not_supported())
|
||||
}
|
||||
}
|
||||
|
||||
fn device_destroy(&mut self, device: &DeviceRef) -> Result<()> {
|
||||
if device.as_ptr().is_null() {
|
||||
Err(Error::error())
|
||||
} else {
|
||||
unsafe {
|
||||
let _: Box<Device> = Box::from_raw(device.as_ptr() as *mut _);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn register_device_changed_callback(
|
||||
&mut self,
|
||||
_: ffi::cubeb_device_changed_callback,
|
||||
) -> Result<()> {
|
||||
Err(Error::error())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> PulseStream<'ctx> {
|
||||
fn stream_init(
|
||||
context: &pulse::Context,
|
||||
stream_params: &StreamParamsRef,
|
||||
stream_name: Option<&CStr>,
|
||||
) -> Result<pulse::Stream> {
|
||||
if stream_params.prefs() == StreamPrefs::LOOPBACK {
|
||||
return Err(not_supported());
|
||||
}
|
||||
|
||||
fn to_pulse_format(format: cubeb::SampleFormat) -> pulse::SampleFormat {
|
||||
fn to_pulse_format(format: SampleFormat) -> pulse::SampleFormat {
|
||||
match format {
|
||||
cubeb::SAMPLE_S16LE => pulse::SampleFormat::Signed16LE,
|
||||
cubeb::SAMPLE_S16BE => pulse::SampleFormat::Signed16BE,
|
||||
cubeb::SAMPLE_FLOAT32LE => pulse::SampleFormat::Float32LE,
|
||||
cubeb::SAMPLE_FLOAT32BE => pulse::SampleFormat::Float32BE,
|
||||
SampleFormat::S16LE => pulse::SampleFormat::Signed16LE,
|
||||
SampleFormat::S16BE => pulse::SampleFormat::Signed16BE,
|
||||
SampleFormat::Float32LE => pulse::SampleFormat::Float32LE,
|
||||
SampleFormat::Float32BE => pulse::SampleFormat::Float32BE,
|
||||
_ => pulse::SampleFormat::Invalid,
|
||||
}
|
||||
}
|
||||
|
||||
let fmt = to_pulse_format(stream_params.format);
|
||||
let fmt = to_pulse_format(stream_params.format());
|
||||
if fmt == pulse::SampleFormat::Invalid {
|
||||
return Err(cubeb::ERROR_INVALID_FORMAT);
|
||||
return Err(invalid_format());
|
||||
}
|
||||
|
||||
let ss = pulse::SampleSpec {
|
||||
channels: stream_params.channels as u8,
|
||||
channels: stream_params.channels() as u8,
|
||||
format: fmt.into(),
|
||||
rate: stream_params.rate,
|
||||
rate: stream_params.rate(),
|
||||
};
|
||||
|
||||
let cm: Option<pa_channel_map> = match stream_params.layout {
|
||||
cubeb::LAYOUT_UNDEFINED => None,
|
||||
_ => Some(layout_to_channel_map(stream_params.layout)),
|
||||
let cm: Option<pa_channel_map> = match stream_params.layout() {
|
||||
ChannelLayout::Undefined => None,
|
||||
_ => Some(layout_to_channel_map(stream_params.layout())),
|
||||
};
|
||||
|
||||
let stream = pulse::Stream::new(context, stream_name, &ss, cm.as_ref());
|
||||
let stream = pulse::Stream::new(context, stream_name.unwrap(), &ss, cm.as_ref());
|
||||
|
||||
match stream {
|
||||
None => Err(cubeb::ERROR),
|
||||
None => Err(Error::error()),
|
||||
Some(stm) => Ok(stm),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cork_stream(&self, stream: Option<&pulse::Stream>, state: CorkState) {
|
||||
if let Some(stm) = stream {
|
||||
if let Ok(o) = stm.cork(state.is_cork() as i32,
|
||||
stream_success,
|
||||
self as *const _ as *mut _) {
|
||||
if let Ok(o) = stm.cork(
|
||||
state.is_cork() as i32,
|
||||
stream_success,
|
||||
self as *const _ as *mut _,
|
||||
) {
|
||||
self.context.operation_wait(stream, &o);
|
||||
}
|
||||
}
|
||||
|
@ -613,10 +660,10 @@ impl<'ctx> Stream<'ctx> {
|
|||
|
||||
if state.is_notify() {
|
||||
self.state_change_callback(if state.is_cork() {
|
||||
cubeb::STATE_STOPPED
|
||||
} else {
|
||||
cubeb::STATE_STARTED
|
||||
});
|
||||
ffi::CUBEB_STATE_STOPPED
|
||||
} else {
|
||||
ffi::CUBEB_STATE_STARTED
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -642,15 +689,22 @@ impl<'ctx> Stream<'ctx> {
|
|||
r
|
||||
}
|
||||
|
||||
pub fn state_change_callback(&mut self, s: cubeb::State) {
|
||||
pub fn state_change_callback(&mut self, s: ffi::cubeb_state) {
|
||||
self.state = s;
|
||||
unsafe {
|
||||
(self.state_callback.unwrap())(self as *mut Stream as *mut cubeb::Stream, self.user_ptr, s);
|
||||
}
|
||||
(self.state_callback.unwrap())(
|
||||
self as *mut PulseStream as *mut ffi::cubeb_stream,
|
||||
self.user_ptr,
|
||||
s,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
fn wait_until_ready(&self) -> bool {
|
||||
fn wait_until_io_stream_ready(stm: &pulse::Stream, mainloop: &pulse::ThreadedMainloop) -> bool {
|
||||
fn wait_until_io_stream_ready(
|
||||
stm: &pulse::Stream,
|
||||
mainloop: &pulse::ThreadedMainloop,
|
||||
) -> bool {
|
||||
if mainloop.is_null() {
|
||||
return false;
|
||||
}
|
||||
|
@ -686,10 +740,15 @@ impl<'ctx> Stream<'ctx> {
|
|||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
|
||||
fn trigger_user_callback(&mut self, input_data: *const c_void, nbytes: usize) {
|
||||
fn drained_cb(a: &pulse::MainloopApi, e: *mut pa_time_event, _tv: &pulse::TimeVal, u: *mut c_void) {
|
||||
let stm = unsafe { &mut *(u as *mut Stream) };
|
||||
fn drained_cb(
|
||||
a: &pulse::MainloopApi,
|
||||
e: *mut pa_time_event,
|
||||
_tv: &pulse::TimeVal,
|
||||
u: *mut c_void,
|
||||
) {
|
||||
let stm = unsafe { &mut *(u as *mut PulseStream) };
|
||||
debug_assert_eq!(stm.drain_timer, e);
|
||||
stm.state_change_callback(cubeb::STATE_DRAINED);
|
||||
stm.state_change_callback(ffi::CUBEB_STATE_DRAINED);
|
||||
/* there's no pa_rttime_free, so use this instead. */
|
||||
a.time_free(stm.drain_timer);
|
||||
stm.drain_timer = ptr::null_mut();
|
||||
|
@ -697,7 +756,6 @@ impl<'ctx> Stream<'ctx> {
|
|||
}
|
||||
|
||||
if let Some(ref stm) = self.output_stream {
|
||||
|
||||
let frame_size = self.output_sample_spec.frame_size();
|
||||
debug_assert_eq!(nbytes % frame_size, 0);
|
||||
|
||||
|
@ -707,21 +765,26 @@ impl<'ctx> Stream<'ctx> {
|
|||
match stm.begin_write(towrite) {
|
||||
Err(e) => {
|
||||
panic!("Failed to write data: {}", e);
|
||||
},
|
||||
}
|
||||
Ok((buffer, size)) => {
|
||||
debug_assert!(size > 0);
|
||||
debug_assert_eq!(size % frame_size, 0);
|
||||
|
||||
logv!("Trigger user callback with output buffer size={}, read_offset={}",
|
||||
size,
|
||||
read_offset);
|
||||
let read_ptr = unsafe { (input_data as *const u8).offset(read_offset as isize) };
|
||||
cubeb_logv!(
|
||||
"Trigger user callback with output buffer size={}, read_offset={}",
|
||||
size,
|
||||
read_offset
|
||||
);
|
||||
let read_ptr =
|
||||
unsafe { (input_data as *const u8).offset(read_offset as isize) };
|
||||
let got = unsafe {
|
||||
self.data_callback.unwrap()(self as *const _ as *mut _,
|
||||
self.user_ptr,
|
||||
read_ptr as *const _ as *mut _,
|
||||
buffer,
|
||||
(size / frame_size) as c_long)
|
||||
self.data_callback.unwrap()(
|
||||
self as *const _ as *mut _,
|
||||
self.user_ptr,
|
||||
read_ptr as *const _ as *mut _,
|
||||
buffer,
|
||||
(size / frame_size) as c_long,
|
||||
)
|
||||
};
|
||||
if got < 0 {
|
||||
let _ = stm.cancel_write();
|
||||
|
@ -736,10 +799,12 @@ impl<'ctx> Stream<'ctx> {
|
|||
}
|
||||
|
||||
if self.volume != PULSE_NO_GAIN {
|
||||
let samples = (self.output_sample_spec.channels as usize * size / frame_size) as isize;
|
||||
let samples = (self.output_sample_spec.channels as usize * size
|
||||
/ frame_size) as isize;
|
||||
|
||||
if self.output_sample_spec.format == PA_SAMPLE_S16BE ||
|
||||
self.output_sample_spec.format == PA_SAMPLE_S16LE {
|
||||
if self.output_sample_spec.format == PA_SAMPLE_S16BE
|
||||
|| self.output_sample_spec.format == PA_SAMPLE_S16LE
|
||||
{
|
||||
let b = buffer as *mut i16;
|
||||
for i in 0..samples {
|
||||
unsafe { *b.offset(i) *= self.volume as i16 };
|
||||
|
@ -752,10 +817,12 @@ impl<'ctx> Stream<'ctx> {
|
|||
}
|
||||
}
|
||||
|
||||
let r = stm.write(buffer,
|
||||
got as usize * frame_size,
|
||||
0,
|
||||
pulse::SeekMode::Relative);
|
||||
let r = stm.write(
|
||||
buffer,
|
||||
got as usize * frame_size,
|
||||
0,
|
||||
pulse::SeekMode::Relative,
|
||||
);
|
||||
debug_assert!(r.is_ok());
|
||||
|
||||
if (got as usize) < size / frame_size {
|
||||
|
@ -763,12 +830,15 @@ impl<'ctx> Stream<'ctx> {
|
|||
Ok(StreamLatency::Positive(l)) => l,
|
||||
Ok(_) => {
|
||||
panic!("Can not handle negative latency values.");
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
debug_assert_eq!(e, pulse::ErrorCode::from_error_code(PA_ERR_NODATA));
|
||||
debug_assert_eq!(
|
||||
e,
|
||||
pulse::ErrorCode::from_error_code(PA_ERR_NODATA)
|
||||
);
|
||||
/* this needs a better guess. */
|
||||
100 * PA_USEC_PER_MSEC
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/* pa_stream_drain is useless, see PA bug# 866. this is a workaround. */
|
||||
|
@ -776,15 +846,18 @@ impl<'ctx> Stream<'ctx> {
|
|||
debug_assert!(self.drain_timer.is_null());
|
||||
let stream_ptr = self as *const _ as *mut _;
|
||||
if let Some(ref context) = self.context.context {
|
||||
self.drain_timer =
|
||||
context.rttime_new(pulse::rtclock_now() + 2 * latency, drained_cb, stream_ptr);
|
||||
self.drain_timer = context.rttime_new(
|
||||
pulse::rtclock_now() + 2 * latency,
|
||||
drained_cb,
|
||||
stream_ptr,
|
||||
);
|
||||
}
|
||||
self.shutdown = true;
|
||||
return;
|
||||
}
|
||||
|
||||
towrite -= size;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
debug_assert_eq!(towrite, 0);
|
||||
|
@ -793,13 +866,13 @@ impl<'ctx> Stream<'ctx> {
|
|||
}
|
||||
|
||||
fn stream_success(_: &pulse::Stream, success: i32, u: *mut c_void) {
|
||||
let stm = unsafe { &*(u as *mut Stream) };
|
||||
let stm = unsafe { &*(u as *mut PulseStream) };
|
||||
debug_assert_ne!(success, 0);
|
||||
stm.context.mainloop.signal();
|
||||
}
|
||||
|
||||
fn context_success(_: &pulse::Context, success: i32, u: *mut c_void) {
|
||||
let ctx = unsafe { &*(u as *mut Context) };
|
||||
let ctx = unsafe { &*(u as *mut PulseContext) };
|
||||
debug_assert_ne!(success, 0);
|
||||
ctx.mainloop.signal();
|
||||
}
|
||||
|
@ -815,12 +888,22 @@ fn set_buffering_attribute(latency_frames: u32, sample_spec: &pa_sample_spec) ->
|
|||
fragsize: minreq,
|
||||
};
|
||||
|
||||
log!("Requested buffer attributes maxlength {}, tlength {}, prebuf {}, minreq {}, fragsize {}",
|
||||
battr.maxlength,
|
||||
battr.tlength,
|
||||
battr.prebuf,
|
||||
battr.minreq,
|
||||
battr.fragsize);
|
||||
cubeb_log!(
|
||||
"Requested buffer attributes maxlength {}, tlength {}, prebuf {}, minreq {}, fragsize {}",
|
||||
battr.maxlength,
|
||||
battr.tlength,
|
||||
battr.prebuf,
|
||||
battr.minreq,
|
||||
battr.fragsize
|
||||
);
|
||||
|
||||
battr
|
||||
}
|
||||
|
||||
fn invalid_format() -> Error {
|
||||
unsafe { Error::from_raw(ffi::CUBEB_ERROR_INVALID_FORMAT) }
|
||||
}
|
||||
|
||||
fn not_supported() -> Error {
|
||||
unsafe { Error::from_raw(ffi::CUBEB_ERROR_NOT_SUPPORTED) }
|
||||
}
|
||||
|
|
|
@ -1,249 +1,17 @@
|
|||
// Copyright © 2017 Mozilla Foundation
|
||||
// Copyright © 2017-2018 Mozilla Foundation
|
||||
//
|
||||
// This program is made available under an ISC-style license. See the
|
||||
// accompanying file LICENSE for details.
|
||||
|
||||
use backend;
|
||||
use cubeb;
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::{c_char, c_void};
|
||||
|
||||
unsafe extern "C" fn capi_init(c: *mut *mut cubeb::Context, context_name: *const c_char) -> i32 {
|
||||
match backend::Context::new(context_name) {
|
||||
Ok(ctx) => {
|
||||
*c = Box::into_raw(ctx) as *mut _;
|
||||
cubeb::OK
|
||||
},
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn capi_get_backend_id(_: *mut cubeb::Context) -> *const c_char {
|
||||
"pulse-rust\0".as_ptr() as *const c_char
|
||||
}
|
||||
|
||||
unsafe extern "C" fn capi_get_max_channel_count(c: *mut cubeb::Context, max_channels: *mut u32) -> i32 {
|
||||
let ctx = &*(c as *mut backend::Context);
|
||||
|
||||
match ctx.max_channel_count() {
|
||||
Ok(mc) => {
|
||||
*max_channels = mc;
|
||||
cubeb::OK
|
||||
},
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn capi_get_min_latency(c: *mut cubeb::Context,
|
||||
param: cubeb::StreamParams,
|
||||
latency_frames: *mut u32)
|
||||
-> i32 {
|
||||
let ctx = &*(c as *mut backend::Context);
|
||||
|
||||
match ctx.min_latency(¶m) {
|
||||
Ok(l) => {
|
||||
*latency_frames = l;
|
||||
cubeb::OK
|
||||
},
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn capi_get_preferred_sample_rate(c: *mut cubeb::Context, rate: *mut u32) -> i32 {
|
||||
let ctx = &*(c as *mut backend::Context);
|
||||
|
||||
match ctx.preferred_sample_rate() {
|
||||
Ok(r) => {
|
||||
*rate = r;
|
||||
cubeb::OK
|
||||
},
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn capi_get_preferred_channel_layout(c: *mut cubeb::Context,
|
||||
layout: *mut cubeb::ChannelLayout)
|
||||
-> i32 {
|
||||
let ctx = &*(c as *mut backend::Context);
|
||||
|
||||
match ctx.preferred_channel_layout() {
|
||||
Ok(l) => {
|
||||
*layout = l;
|
||||
cubeb::OK
|
||||
},
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn capi_enumerate_devices(c: *mut cubeb::Context,
|
||||
devtype: cubeb::DeviceType,
|
||||
collection: *mut cubeb::DeviceCollection)
|
||||
-> i32 {
|
||||
let ctx = &*(c as *mut backend::Context);
|
||||
|
||||
match ctx.enumerate_devices(devtype) {
|
||||
Ok(dc) => {
|
||||
*collection = dc;
|
||||
cubeb::OK
|
||||
},
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn capi_device_collection_destroy(c: *mut cubeb::Context,
|
||||
collection: *mut cubeb::DeviceCollection)
|
||||
-> i32 {
|
||||
let ctx = &*(c as *mut backend::Context);
|
||||
|
||||
ctx.device_collection_destroy(collection);
|
||||
cubeb::OK
|
||||
}
|
||||
|
||||
unsafe extern "C" fn capi_destroy(c: *mut cubeb::Context) {
|
||||
let _: Box<backend::Context> = Box::from_raw(c as *mut _);
|
||||
}
|
||||
|
||||
unsafe extern "C" fn capi_stream_init(c: *mut cubeb::Context,
|
||||
s: *mut *mut cubeb::Stream,
|
||||
stream_name: *const c_char,
|
||||
input_device: cubeb::DeviceId,
|
||||
input_stream_params: *mut cubeb::StreamParams,
|
||||
output_device: cubeb::DeviceId,
|
||||
output_stream_params: *mut cubeb::StreamParams,
|
||||
latency_frames: u32,
|
||||
data_callback: cubeb::DataCallback,
|
||||
state_callback: cubeb::StateCallback,
|
||||
user_ptr: *mut c_void)
|
||||
-> i32 {
|
||||
fn try_stream_params_from(sp: *mut cubeb::StreamParams) -> Option<cubeb::StreamParams> {
|
||||
if sp.is_null() { None } else { Some(unsafe { *sp }) }
|
||||
}
|
||||
|
||||
let ctx = &mut *(c as *mut backend::Context);
|
||||
let stream_name = CStr::from_ptr(stream_name);
|
||||
|
||||
match ctx.new_stream(stream_name,
|
||||
input_device,
|
||||
try_stream_params_from(input_stream_params),
|
||||
output_device,
|
||||
try_stream_params_from(output_stream_params),
|
||||
latency_frames,
|
||||
data_callback,
|
||||
state_callback,
|
||||
user_ptr as _) {
|
||||
Ok(stm) => {
|
||||
*s = Box::into_raw(stm) as *mut _;
|
||||
cubeb::OK
|
||||
},
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn capi_stream_destroy(s: *mut cubeb::Stream) {
|
||||
let _ = Box::from_raw(s as *mut backend::Stream);
|
||||
}
|
||||
|
||||
unsafe extern "C" fn capi_stream_start(s: *mut cubeb::Stream) -> i32 {
|
||||
let stm = &mut *(s as *mut backend::Stream);
|
||||
|
||||
stm.start()
|
||||
}
|
||||
|
||||
unsafe extern "C" fn capi_stream_stop(s: *mut cubeb::Stream) -> i32 {
|
||||
let stm = &mut *(s as *mut backend::Stream);
|
||||
|
||||
stm.stop()
|
||||
}
|
||||
|
||||
unsafe extern "C" fn capi_stream_get_position(s: *mut cubeb::Stream, position: *mut u64) -> i32 {
|
||||
let stm = &*(s as *mut backend::Stream);
|
||||
|
||||
match stm.position() {
|
||||
Ok(pos) => {
|
||||
*position = pos;
|
||||
cubeb::OK
|
||||
},
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn capi_stream_get_latency(s: *mut cubeb::Stream, latency: *mut u32) -> i32 {
|
||||
let stm = &*(s as *mut backend::Stream);
|
||||
|
||||
match stm.latency() {
|
||||
Ok(lat) => {
|
||||
*latency = lat;
|
||||
cubeb::OK
|
||||
},
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn capi_stream_set_volume(s: *mut cubeb::Stream, volume: f32) -> i32 {
|
||||
let stm = &mut *(s as *mut backend::Stream);
|
||||
|
||||
stm.set_volume(volume)
|
||||
}
|
||||
|
||||
unsafe extern "C" fn capi_stream_set_panning(s: *mut cubeb::Stream, panning: f32) -> i32 {
|
||||
let stm = &mut *(s as *mut backend::Stream);
|
||||
|
||||
stm.set_panning(panning)
|
||||
}
|
||||
|
||||
unsafe extern "C" fn capi_stream_get_current_device(s: *mut cubeb::Stream, device: *mut *const cubeb::Device) -> i32 {
|
||||
let stm = &*(s as *mut backend::Stream);
|
||||
|
||||
match stm.current_device() {
|
||||
Ok(d) => {
|
||||
*device = Box::into_raw(d) as *mut _;
|
||||
cubeb::OK
|
||||
},
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn capi_stream_device_destroy(_: *mut cubeb::Stream, device: *mut cubeb::Device) -> i32 {
|
||||
let _: Box<backend::Device> = Box::from_raw(device as *mut backend::Device);
|
||||
cubeb::OK
|
||||
}
|
||||
|
||||
unsafe extern "C" fn capi_register_device_collection_changed(c: *mut cubeb::Context,
|
||||
devtype: cubeb::DeviceType,
|
||||
collection_changed_callback:
|
||||
cubeb::DeviceCollectionChangedCallback,
|
||||
user_ptr: *mut c_void) -> i32
|
||||
{
|
||||
let ctx = &mut *(c as *mut backend::Context);
|
||||
ctx.register_device_collection_changed(devtype, collection_changed_callback, user_ptr as _)
|
||||
}
|
||||
|
||||
pub const PULSE_OPS: cubeb::Ops = cubeb::Ops {
|
||||
init: Some(capi_init),
|
||||
get_backend_id: Some(capi_get_backend_id),
|
||||
get_max_channel_count: Some(capi_get_max_channel_count),
|
||||
get_min_latency: Some(capi_get_min_latency),
|
||||
get_preferred_sample_rate: Some(capi_get_preferred_sample_rate),
|
||||
get_preferred_channel_layout: Some(capi_get_preferred_channel_layout),
|
||||
enumerate_devices: Some(capi_enumerate_devices),
|
||||
device_collection_destroy: Some(capi_device_collection_destroy),
|
||||
destroy: Some(capi_destroy),
|
||||
stream_init: Some(capi_stream_init),
|
||||
stream_destroy: Some(capi_stream_destroy),
|
||||
stream_start: Some(capi_stream_start),
|
||||
stream_stop: Some(capi_stream_stop),
|
||||
stream_reset_default_device: None,
|
||||
stream_get_position: Some(capi_stream_get_position),
|
||||
stream_get_latency: Some(capi_stream_get_latency),
|
||||
stream_set_volume: Some(capi_stream_set_volume),
|
||||
stream_set_panning: Some(capi_stream_set_panning),
|
||||
stream_get_current_device: Some(capi_stream_get_current_device),
|
||||
stream_device_destroy: Some(capi_stream_device_destroy),
|
||||
stream_register_device_changed_callback: None,
|
||||
register_device_collection_changed: Some(capi_register_device_collection_changed),
|
||||
};
|
||||
use backend::PulseContext;
|
||||
use cubeb_backend::{capi, ffi};
|
||||
use std::os::raw::{c_char, c_int};
|
||||
|
||||
/// Entry point from C code.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn pulse_rust_init(c: *mut *mut cubeb::Context, context_name: *const c_char) -> i32 {
|
||||
capi_init(c, context_name)
|
||||
pub unsafe extern "C" fn pulse_rust_init(
|
||||
c: *mut *mut ffi::cubeb,
|
||||
context_name: *const c_char,
|
||||
) -> c_int {
|
||||
capi::capi_init::<PulseContext>(c, context_name)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
//! Cubeb backend interface to Pulse Audio
|
||||
|
||||
// Copyright © 2017 Mozilla Foundation
|
||||
// Copyright © 2017-2018 Mozilla Foundation
|
||||
//
|
||||
// This program is made available under an ISC-style license. See the
|
||||
// accompanying file LICENSE for details.
|
||||
|
||||
#[macro_use]
|
||||
extern crate cubeb_ffi as cubeb;
|
||||
extern crate pulse_ffi;
|
||||
extern crate cubeb_backend;
|
||||
extern crate pulse;
|
||||
extern crate pulse_ffi;
|
||||
extern crate semver;
|
||||
|
||||
mod capi;
|
||||
|
|
Загрузка…
Ссылка в новой задаче