blob: 2b3d21e0b69e224b43975495d853686bb8ac0722 [file] [log] [blame]
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
extern crate audio_streams;
extern crate data_model;
use std::cmp::min;
use std::convert::{TryFrom, TryInto};
use std::error;
use std::fmt;
use std::iter::FromIterator;
use std::os::raw::c_char;
use std::str::FromStr;
use std::time::Duration;
#[allow(dead_code)]
#[allow(non_upper_case_globals)]
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
pub mod gen;
use gen::{
_snd_pcm_format, audio_dev_debug_info, audio_message, audio_stream_debug_info,
cras_audio_format_packed, cras_iodev_info, cras_ionode_info, cras_ionode_info__bindgen_ty_1,
cras_timespec, snd_pcm_format_t, CRAS_AUDIO_MESSAGE_ID, CRAS_CHANNEL, CRAS_CLIENT_TYPE,
CRAS_NODE_TYPE, CRAS_STREAM_DIRECTION, CRAS_STREAM_EFFECT, CRAS_STREAM_TYPE,
};
use audio_streams::{SampleFormat, StreamDirection, StreamEffect};
unsafe impl data_model::DataInit for gen::audio_message {}
unsafe impl data_model::DataInit for gen::audio_debug_info {}
unsafe impl data_model::DataInit for gen::audio_dev_debug_info {}
unsafe impl data_model::DataInit for gen::audio_stream_debug_info {}
unsafe impl data_model::DataInit for gen::cras_client_connected {}
unsafe impl data_model::DataInit for gen::cras_client_stream_connected {}
unsafe impl data_model::DataInit for gen::cras_connect_message {}
unsafe impl data_model::DataInit for gen::cras_disconnect_stream_message {}
unsafe impl data_model::DataInit for gen::cras_dump_audio_thread {}
unsafe impl data_model::DataInit for gen::cras_iodev_info {}
unsafe impl data_model::DataInit for gen::cras_ionode_info {}
unsafe impl data_model::DataInit for gen::cras_server_state {}
unsafe impl data_model::DataInit for gen::cras_set_system_mute {}
unsafe impl data_model::DataInit for gen::cras_set_system_volume {}
/// An enumeration of errors that can occur when converting the packed C
/// structs into Rust-style structs.
#[derive(Debug)]
pub enum Error {
InvalidChannel(i8),
InvalidClientType(u32),
InvalidClientTypeStr,
InvalidStreamType(u32),
}
impl error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Error::*;
match self {
InvalidChannel(c) => write!(
f,
"Channel value {} is not within valid range [0, {})",
c,
CRAS_CHANNEL::CRAS_CH_MAX as u32
),
InvalidClientType(t) => write!(
f,
"Client type {} is not within valid range [0, {})",
t,
CRAS_CLIENT_TYPE::CRAS_CLIENT_TYPE_SERVER_STREAM as u32 + 1
),
InvalidClientTypeStr => write!(f, "Invalid client type string"),
InvalidStreamType(t) => write!(
f,
"Stream type {} is not within valid range [0, {})",
t,
CRAS_STREAM_TYPE::CRAS_STREAM_NUM_TYPES as u32
),
}
}
}
impl cras_audio_format_packed {
/// Initializes `cras_audio_format_packed` from input parameters.
/// Field `channel_layout` will be assigned with default channel layout defined in
/// `Self::default_channel_layout`.
///
/// # Arguments
/// * `format` - Format in used.
/// * `rate` - Rate in used.
/// * `num_channels` - Number of channels in used.
/// * `direction` - Stream direction enumeration.
///
/// # Returns
/// Structure `cras_audio_format_packed`
pub fn new(
format: _snd_pcm_format,
rate: u32,
num_channels: usize,
direction: CRAS_STREAM_DIRECTION,
) -> Self {
Self {
format: format as i32,
frame_rate: rate,
num_channels: num_channels as u32,
channel_layout: Self::default_channel_layout(num_channels, direction),
}
}
/// Generates default channel layout by given number of channels and stream direction.
/// ```
/// use cras_sys::gen::{
/// _snd_pcm_format,
/// cras_audio_format_packed,
/// CRAS_STREAM_DIRECTION::*
/// };
/// let test_one = | num_channels, direction, expected_results | {
/// let default_channel_fmt = cras_audio_format_packed::new(
/// _snd_pcm_format::SND_PCM_FORMAT_S16,
/// 48000,
/// num_channels,
/// direction
/// );
/// assert_eq!(default_channel_fmt.channel_layout, expected_results);
/// };
/// test_one(2, CRAS_STREAM_OUTPUT, [0, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1]);
/// test_one(4, CRAS_STREAM_OUTPUT, [0, 1, 2, 3, -1, -1, -1, -1, -1, -1, -1]);
/// test_one(6, CRAS_STREAM_OUTPUT, [0, 1, 4, 5, 2, 3, -1, -1, -1, -1, -1]);
/// test_one(2, CRAS_STREAM_INPUT, [0, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1]);
/// test_one(4, CRAS_STREAM_INPUT, [0, 1, 2, 3, -1, -1, -1, -1, -1, -1, -1]);
/// test_one(6, CRAS_STREAM_INPUT, [0, 1, 2, 3, 4, 5, -1, -1, -1, -1, -1]);
/// ```
fn default_channel_layout(
num_channels: usize,
direction: CRAS_STREAM_DIRECTION,
) -> [i8; CRAS_CHANNEL::CRAS_CH_MAX as usize] {
use {CRAS_CHANNEL::*, CRAS_STREAM_DIRECTION::*};
let mut channel_layout = [-1; CRAS_CH_MAX as usize];
match (num_channels, direction) {
(6, CRAS_STREAM_OUTPUT) => {
[
CRAS_CH_FL,
CRAS_CH_FR,
CRAS_CH_FC,
CRAS_CH_LFE,
CRAS_CH_RL,
CRAS_CH_RR,
]
.iter()
.enumerate()
.for_each(|(idx, &channel)| channel_layout[channel as usize] = idx as i8);
}
_ => {
for (i, channel) in channel_layout
.iter_mut()
.enumerate()
.take(min(num_channels, CRAS_CH_MAX as usize))
{
*channel = i as i8;
}
}
}
channel_layout
}
}
impl Default for audio_message {
fn default() -> Self {
Self {
error: 0,
frames: 0,
id: CRAS_AUDIO_MESSAGE_ID::NUM_AUDIO_MESSAGES,
}
}
}
impl Default for cras_iodev_info {
fn default() -> Self {
Self {
idx: 0,
name: [0; 64usize],
stable_id: 0,
max_supported_channels: 0,
}
}
}
#[derive(Debug)]
pub struct CrasIodevInfo {
pub index: u32,
pub name: String,
}
fn cstring_to_string(cstring: &[c_char]) -> String {
let null_idx = match cstring.iter().enumerate().find(|(_, &c)| c == 0) {
Some((i, _)) => i,
None => return "".to_owned(),
};
let ptr = cstring.as_ptr() as *const u8;
let slice = unsafe { core::slice::from_raw_parts(ptr, null_idx) };
String::from_utf8_lossy(slice).to_string()
}
impl From<cras_iodev_info> for CrasIodevInfo {
fn from(info: cras_iodev_info) -> Self {
Self {
index: info.idx,
name: cstring_to_string(&info.name),
}
}
}
impl Default for cras_ionode_info {
fn default() -> Self {
Self {
iodev_idx: 0,
ionode_idx: 0,
plugged: 0,
active: 0,
plugged_time: cras_ionode_info__bindgen_ty_1 {
tv_sec: 0,
tv_usec: 0,
},
volume: 0,
ui_gain_scaler: 0.0,
capture_gain: 0,
left_right_swapped: 0,
type_enum: 0,
stable_id: 0,
type_: [0; 32usize],
name: [0; 64usize],
active_hotword_model: [0; 16usize],
}
}
}
impl From<u32> for CRAS_NODE_TYPE {
fn from(node_type: u32) -> CRAS_NODE_TYPE {
use CRAS_NODE_TYPE::*;
match node_type {
0 => CRAS_NODE_TYPE_INTERNAL_SPEAKER,
1 => CRAS_NODE_TYPE_HEADPHONE,
2 => CRAS_NODE_TYPE_HDMI,
3 => CRAS_NODE_TYPE_HAPTIC,
4 => CRAS_NODE_TYPE_LINEOUT,
5 => CRAS_NODE_TYPE_MIC,
6 => CRAS_NODE_TYPE_HOTWORD,
7 => CRAS_NODE_TYPE_POST_MIX_PRE_DSP,
8 => CRAS_NODE_TYPE_POST_DSP,
9 => CRAS_NODE_TYPE_USB,
10 => CRAS_NODE_TYPE_BLUETOOTH,
_ => CRAS_NODE_TYPE_UNKNOWN,
}
}
}
#[derive(Debug)]
pub struct CrasIonodeInfo {
pub name: String,
pub iodev_index: u32,
pub ionode_index: u32,
pub stable_id: u32,
pub plugged: bool,
pub active: bool,
pub node_type: CRAS_NODE_TYPE,
pub type_name: String,
pub volume: u32,
pub capture_gain: i32,
pub plugged_time: cras_timespec,
}
impl From<cras_ionode_info> for CrasIonodeInfo {
fn from(info: cras_ionode_info) -> Self {
Self {
name: cstring_to_string(&info.name),
iodev_index: info.iodev_idx,
ionode_index: info.ionode_idx,
stable_id: info.stable_id,
plugged: info.plugged != 0,
active: info.active != 0,
node_type: CRAS_NODE_TYPE::from(info.type_enum),
type_name: cstring_to_string(&info.type_),
volume: info.volume,
capture_gain: info.capture_gain,
plugged_time: cras_timespec {
tv_sec: info.plugged_time.tv_sec,
tv_nsec: info.plugged_time.tv_usec * 1000,
},
}
}
}
impl From<u32> for CRAS_STREAM_DIRECTION {
fn from(node_type: u32) -> CRAS_STREAM_DIRECTION {
use CRAS_STREAM_DIRECTION::*;
match node_type {
0 => CRAS_STREAM_OUTPUT,
1 => CRAS_STREAM_INPUT,
2 => CRAS_STREAM_UNDEFINED,
3 => CRAS_STREAM_POST_MIX_PRE_DSP,
_ => CRAS_STREAM_UNDEFINED,
}
}
}
impl Default for audio_dev_debug_info {
fn default() -> Self {
Self {
dev_name: [0; 64],
buffer_size: 0,
min_buffer_level: 0,
min_cb_level: 0,
max_cb_level: 0,
frame_rate: 0,
num_channels: 0,
est_rate_ratio: 0.0,
direction: 0,
num_underruns: 0,
num_severe_underruns: 0,
highest_hw_level: 0,
runtime_sec: 0,
runtime_nsec: 0,
longest_wake_sec: 0,
longest_wake_nsec: 0,
software_gain_scaler: 0.0,
}
}
}
/// A rust-style representation of the server's packed audio_dev_debug_info
/// struct.
#[derive(Debug)]
pub struct AudioDevDebugInfo {
pub dev_name: String,
pub buffer_size: u32,
pub min_buffer_level: u32,
pub min_cb_level: u32,
pub max_cb_level: u32,
pub frame_rate: u32,
pub num_channels: u32,
pub est_rate_ratio: f64,
pub direction: CRAS_STREAM_DIRECTION,
pub num_underruns: u32,
pub num_severe_underruns: u32,
pub highest_hw_level: u32,
pub runtime: Duration,
pub longest_wake: Duration,
pub software_gain_scaler: f64,
}
impl From<audio_dev_debug_info> for AudioDevDebugInfo {
fn from(info: audio_dev_debug_info) -> Self {
Self {
dev_name: cstring_to_string(&info.dev_name),
buffer_size: info.buffer_size,
min_buffer_level: info.min_buffer_level,
min_cb_level: info.min_cb_level,
max_cb_level: info.max_cb_level,
frame_rate: info.frame_rate,
num_channels: info.num_channels,
est_rate_ratio: info.est_rate_ratio,
direction: CRAS_STREAM_DIRECTION::from(u32::from(info.direction)),
num_underruns: info.num_underruns,
num_severe_underruns: info.num_severe_underruns,
highest_hw_level: info.highest_hw_level,
runtime: Duration::new(info.runtime_sec.into(), info.runtime_nsec),
longest_wake: Duration::new(info.longest_wake_sec.into(), info.longest_wake_nsec),
software_gain_scaler: info.software_gain_scaler,
}
}
}
impl fmt::Display for AudioDevDebugInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "Device: {}", self.dev_name)?;
writeln!(f, " Direction: {:?}", self.direction)?;
writeln!(f, " Buffer size: {}", self.buffer_size)?;
writeln!(f, " Minimum buffer level: {}", self.min_buffer_level)?;
writeln!(f, " Minimum callback level: {}", self.min_cb_level)?;
writeln!(f, " Max callback level: {}", self.max_cb_level)?;
writeln!(f, " Frame rate: {}", self.frame_rate)?;
writeln!(f, " Number of channels: {}", self.num_channels)?;
writeln!(f, " Estimated rate ratio: {:.2}", self.est_rate_ratio)?;
writeln!(f, " Underrun count: {}", self.num_underruns)?;
writeln!(f, " Severe underrun count: {}", self.num_severe_underruns)?;
writeln!(f, " Highest hardware level: {}", self.highest_hw_level)?;
writeln!(f, " Runtime: {:?}", self.runtime)?;
writeln!(f, " Longest wake: {:?}", self.longest_wake)?;
writeln!(f, " Software gain scaler: {}", self.software_gain_scaler)?;
Ok(())
}
}
impl TryFrom<u32> for CRAS_STREAM_TYPE {
type Error = Error;
fn try_from(stream_type: u32) -> Result<Self, Self::Error> {
use CRAS_STREAM_TYPE::*;
match stream_type {
0 => Ok(CRAS_STREAM_TYPE_DEFAULT),
1 => Ok(CRAS_STREAM_TYPE_MULTIMEDIA),
2 => Ok(CRAS_STREAM_TYPE_VOICE_COMMUNICATION),
3 => Ok(CRAS_STREAM_TYPE_SPEECH_RECOGNITION),
4 => Ok(CRAS_STREAM_TYPE_PRO_AUDIO),
5 => Ok(CRAS_STREAM_TYPE_ACCESSIBILITY),
_ => Err(Error::InvalidStreamType(stream_type)),
}
}
}
impl TryFrom<u32> for CRAS_CLIENT_TYPE {
type Error = Error;
fn try_from(client_type: u32) -> Result<Self, Self::Error> {
use CRAS_CLIENT_TYPE::*;
match client_type {
0 => Ok(CRAS_CLIENT_TYPE_UNKNOWN),
1 => Ok(CRAS_CLIENT_TYPE_LEGACY),
2 => Ok(CRAS_CLIENT_TYPE_TEST),
3 => Ok(CRAS_CLIENT_TYPE_PCM),
4 => Ok(CRAS_CLIENT_TYPE_CHROME),
5 => Ok(CRAS_CLIENT_TYPE_ARC),
6 => Ok(CRAS_CLIENT_TYPE_CROSVM),
7 => Ok(CRAS_CLIENT_TYPE_SERVER_STREAM),
8 => Ok(CRAS_CLIENT_TYPE_LACROS),
_ => Err(Error::InvalidClientType(client_type)),
}
}
}
impl FromStr for CRAS_CLIENT_TYPE {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
use CRAS_CLIENT_TYPE::*;
match s {
"crosvm" => Ok(CRAS_CLIENT_TYPE_CROSVM),
"arcvm" => Ok(CRAS_CLIENT_TYPE_ARCVM),
_ => Err(Error::InvalidClientTypeStr),
}
}
}
impl Default for audio_stream_debug_info {
fn default() -> Self {
Self {
stream_id: 0,
dev_idx: 0,
direction: 0,
stream_type: 0,
client_type: 0,
buffer_frames: 0,
cb_threshold: 0,
effects: 0,
flags: 0,
frame_rate: 0,
num_channels: 0,
longest_fetch_sec: 0,
longest_fetch_nsec: 0,
num_missed_cb: 0,
num_overruns: 0,
is_pinned: 0,
pinned_dev_idx: 0,
runtime_sec: 0,
runtime_nsec: 0,
stream_volume: 0.0,
channel_layout: [0; 11],
}
}
}
impl TryFrom<i8> for CRAS_CHANNEL {
type Error = Error;
fn try_from(channel: i8) -> Result<Self, Self::Error> {
use CRAS_CHANNEL::*;
match channel {
0 => Ok(CRAS_CH_FL),
1 => Ok(CRAS_CH_FR),
2 => Ok(CRAS_CH_RL),
3 => Ok(CRAS_CH_RR),
4 => Ok(CRAS_CH_FC),
5 => Ok(CRAS_CH_LFE),
6 => Ok(CRAS_CH_SL),
7 => Ok(CRAS_CH_SR),
8 => Ok(CRAS_CH_RC),
9 => Ok(CRAS_CH_FLC),
10 => Ok(CRAS_CH_FRC),
_ => Err(Error::InvalidChannel(channel)),
}
}
}
/// A rust-style representation of the server's packed audio_stream_debug_info
/// struct.
#[derive(Debug)]
pub struct AudioStreamDebugInfo {
pub stream_id: u64,
pub dev_idx: u32,
pub direction: CRAS_STREAM_DIRECTION,
pub stream_type: CRAS_STREAM_TYPE,
pub client_type: CRAS_CLIENT_TYPE,
pub buffer_frames: u32,
pub cb_threshold: u32,
pub effects: u64,
pub flags: u32,
pub frame_rate: u32,
pub num_channels: u32,
pub longest_fetch: Duration,
pub num_missed_cb: u32,
pub num_overruns: u32,
pub is_pinned: bool,
pub pinned_dev_idx: u32,
pub runtime: Duration,
pub stream_volume: f64,
pub channel_layout: Vec<CRAS_CHANNEL>,
}
impl TryFrom<audio_stream_debug_info> for AudioStreamDebugInfo {
type Error = Error;
fn try_from(info: audio_stream_debug_info) -> Result<Self, Self::Error> {
let channel_layout = info
.channel_layout
.iter()
.cloned()
.take_while(|&c| c != -1)
.map(TryInto::try_into)
.collect::<Result<Vec<_>, _>>()?;
Ok(Self {
stream_id: info.stream_id,
dev_idx: info.dev_idx,
direction: info.direction.into(),
stream_type: info.stream_type.try_into()?,
client_type: info.client_type.try_into()?,
buffer_frames: info.buffer_frames,
cb_threshold: info.cb_threshold,
effects: info.effects,
flags: info.flags,
frame_rate: info.frame_rate,
num_channels: info.num_channels,
longest_fetch: Duration::new(info.longest_fetch_sec.into(), info.longest_fetch_nsec),
num_missed_cb: info.num_missed_cb,
num_overruns: info.num_overruns,
is_pinned: info.is_pinned != 0,
pinned_dev_idx: info.pinned_dev_idx,
runtime: Duration::new(info.runtime_sec.into(), info.runtime_nsec),
stream_volume: info.stream_volume,
channel_layout,
})
}
}
impl fmt::Display for AudioStreamDebugInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(
f,
"Stream: {}, Device index: {}",
self.stream_id, self.dev_idx
)?;
writeln!(f, " Direction: {:?}", self.direction)?;
writeln!(f, " Stream type: {:?}", self.stream_type)?;
writeln!(f, " Client type: {:?}", self.client_type)?;
writeln!(f, " Buffer frames: {}", self.buffer_frames)?;
writeln!(f, " Callback threshold: {}", self.cb_threshold)?;
writeln!(f, " Effects: {:#x}", self.effects)?;
writeln!(f, " Frame rate: {}", self.frame_rate)?;
writeln!(f, " Number of channels: {}", self.num_channels)?;
writeln!(f, " Longest fetch: {:?}", self.longest_fetch)?;
writeln!(f, " Overrun count: {}", self.num_overruns)?;
writeln!(f, " Pinned: {}", self.is_pinned)?;
writeln!(f, " Pinned device index: {}", self.pinned_dev_idx)?;
writeln!(f, " Missed callbacks: {}", self.num_missed_cb)?;
match self.direction {
CRAS_STREAM_DIRECTION::CRAS_STREAM_OUTPUT => {
writeln!(f, " Volume: {:.2}", self.stream_volume)?
}
CRAS_STREAM_DIRECTION::CRAS_STREAM_INPUT => {
writeln!(f, " Gain: {:.2}", self.stream_volume)?
}
_ => (),
};
writeln!(f, " Runtime: {:?}", self.runtime)?;
write!(f, " Channel map:")?;
for channel in &self.channel_layout {
write!(f, " {:?}", channel)?;
}
writeln!(f)?;
Ok(())
}
}
/// A rust-style representation of the server's audio debug info.
pub struct AudioDebugInfo {
pub devices: Vec<AudioDevDebugInfo>,
pub streams: Vec<AudioStreamDebugInfo>,
}
impl AudioDebugInfo {
pub fn new(devices: Vec<AudioDevDebugInfo>, streams: Vec<AudioStreamDebugInfo>) -> Self {
Self { devices, streams }
}
}
impl Into<u64> for CRAS_STREAM_EFFECT {
fn into(self) -> u64 {
u64::from(self.0)
}
}
impl CRAS_STREAM_EFFECT {
pub fn empty() -> Self {
CRAS_STREAM_EFFECT(0)
}
}
impl From<StreamDirection> for CRAS_STREAM_DIRECTION {
/// Convert an audio_streams StreamDirection into the corresponding CRAS_STREAM_DIRECTION.
fn from(direction: StreamDirection) -> Self {
match direction {
StreamDirection::Playback => CRAS_STREAM_DIRECTION::CRAS_STREAM_OUTPUT,
StreamDirection::Capture => CRAS_STREAM_DIRECTION::CRAS_STREAM_INPUT,
}
}
}
impl From<StreamEffect> for CRAS_STREAM_EFFECT {
/// Convert an audio_streams StreamEffect into the corresponding CRAS_STREAM_EFFECT.
fn from(effect: StreamEffect) -> Self {
match effect {
StreamEffect::NoEffect => CRAS_STREAM_EFFECT::empty(),
StreamEffect::EchoCancellation => CRAS_STREAM_EFFECT::APM_ECHO_CANCELLATION,
}
}
}
impl<'a> FromIterator<&'a StreamEffect> for CRAS_STREAM_EFFECT {
fn from_iter<I>(iter: I) -> Self
where
I: IntoIterator<Item = &'a StreamEffect>,
{
iter.into_iter().fold(
CRAS_STREAM_EFFECT::empty(),
|cras_effect, &stream_effect| cras_effect | stream_effect.into(),
)
}
}
/// Convert an audio_streams SampleFormat into the corresponding pcm_format.
impl From<SampleFormat> for snd_pcm_format_t {
fn from(format: SampleFormat) -> Self {
match format {
SampleFormat::U8 => snd_pcm_format_t::SND_PCM_FORMAT_U8,
SampleFormat::S16LE => snd_pcm_format_t::SND_PCM_FORMAT_S16_LE,
SampleFormat::S24LE => snd_pcm_format_t::SND_PCM_FORMAT_S24_LE,
SampleFormat::S32LE => snd_pcm_format_t::SND_PCM_FORMAT_S32_LE,
}
}
}