Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  stream.rs   Sprache: unbekannt

 
Spracherkennung für: .rs vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

// Copyright © 2017-2018 Mozilla Foundation
//
// This program is made available under an ISC-style license.  See the
// accompanying file LICENSE for details.

use backend::cork_state::CorkState;
use backend::*;
use cubeb_backend::{
    ffi, log_enabled, ChannelLayout, DeviceId, DeviceRef, Error, InputProcessingParams, Result,
    SampleFormat, StreamOps, StreamParamsRef, StreamPrefs,
};
use pulse::{self, CVolumeExt, ChannelMapExt, SampleSpecExt, StreamLatency, USecExt};
use pulse_ffi::*;
use ringbuf::RingBuffer;
use std::ffi::{CStr, CString};
use std::os::raw::{c_long, c_void};
use std::slice;
use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
use std::{mem, ptr};

use self::LinearInputBuffer::*;
use self::RingBufferConsumer::*;
use self::RingBufferProducer::*;

const PULSE_NO_GAIN: f32 = -1.0;

/// Iterator interface to `ChannelLayout`.
///
/// Iterates each channel in the set represented by `ChannelLayout`.
struct ChannelLayoutIter {
    /// The layout set being iterated
    layout: ChannelLayout,
    /// The next flag to test
    index: u8,
}

fn channel_layout_iter(layout: ChannelLayout) -> ChannelLayoutIter {
    let index = 0;
    ChannelLayoutIter { layout, index }
}

impl Iterator for ChannelLayoutIter {
    type Item = ChannelLayout;

    fn next(&mut self) -> Option<Self::Item> {
        while !self.layout.is_empty() {
            let test = Self::Item::from_bits_truncate(1 << self.index);
            self.index += 1;
            if self.layout.contains(test) {
                self.layout.remove(test);
                return Some(test);
            }
        }
        None
    }
}

fn cubeb_channel_to_pa_channel(channel: ffi::cubeb_channel) -> pa_channel_position_t {
    match channel {
        ffi::CHANNEL_FRONT_LEFT => PA_CHANNEL_POSITION_FRONT_LEFT,
        ffi::CHANNEL_FRONT_RIGHT => PA_CHANNEL_POSITION_FRONT_RIGHT,
        ffi::CHANNEL_FRONT_CENTER => PA_CHANNEL_POSITION_FRONT_CENTER,
        ffi::CHANNEL_LOW_FREQUENCY => PA_CHANNEL_POSITION_LFE,
        ffi::CHANNEL_BACK_LEFT => PA_CHANNEL_POSITION_REAR_LEFT,
        ffi::CHANNEL_BACK_RIGHT => PA_CHANNEL_POSITION_REAR_RIGHT,
        ffi::CHANNEL_FRONT_LEFT_OF_CENTER => PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
        ffi::CHANNEL_FRONT_RIGHT_OF_CENTER => PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER,
        ffi::CHANNEL_BACK_CENTER => PA_CHANNEL_POSITION_REAR_CENTER,
        ffi::CHANNEL_SIDE_LEFT => PA_CHANNEL_POSITION_SIDE_LEFT,
        ffi::CHANNEL_SIDE_RIGHT => PA_CHANNEL_POSITION_SIDE_RIGHT,
        ffi::CHANNEL_TOP_CENTER => PA_CHANNEL_POSITION_TOP_CENTER,
        ffi::CHANNEL_TOP_FRONT_LEFT => PA_CHANNEL_POSITION_TOP_FRONT_LEFT,
        ffi::CHANNEL_TOP_FRONT_CENTER => PA_CHANNEL_POSITION_TOP_FRONT_CENTER,
        ffi::CHANNEL_TOP_FRONT_RIGHT => PA_CHANNEL_POSITION_TOP_FRONT_RIGHT,
        ffi::CHANNEL_TOP_BACK_LEFT => PA_CHANNEL_POSITION_TOP_REAR_LEFT,
        ffi::CHANNEL_TOP_BACK_CENTER => PA_CHANNEL_POSITION_TOP_REAR_CENTER,
        ffi::CHANNEL_TOP_BACK_RIGHT => PA_CHANNEL_POSITION_TOP_REAR_RIGHT,
        _ => PA_CHANNEL_POSITION_INVALID,
    }
}

fn layout_to_channel_map(layout: ChannelLayout) -> pulse::ChannelMap {
    assert_ne!(layout, ChannelLayout::UNDEFINED);

    let mut cm = pulse::ChannelMap::init();
    for (i, channel) in channel_layout_iter(layout).enumerate() {
        cm.map[i] = cubeb_channel_to_pa_channel(channel.into());
    }
    cm.channels = layout.num_channels() as _;

    // Special case single channel center mapping as mono.
    if cm.channels == 1 && cm.map[0] == PA_CHANNEL_POSITION_FRONT_CENTER {
        cm.map[0] = PA_CHANNEL_POSITION_MONO;
    }

    cm
}

fn default_layout_for_channels(ch: u32) -> ChannelLayout {
    match ch {
        1 => ChannelLayout::MONO,
        2 => ChannelLayout::STEREO,
        3 => ChannelLayout::_3F,
        4 => ChannelLayout::QUAD,
        5 => ChannelLayout::_3F2,
        6 => ChannelLayout::_3F_LFE | ChannelLayout::SIDE_LEFT | ChannelLayout::SIDE_RIGHT,
        7 => ChannelLayout::_3F3R_LFE,
        8 => ChannelLayout::_3F4_LFE,
        _ => panic!("channel must be between 1 to 8."),
    }
}

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 as *mut _);
            }
            if !self.0.output_name.is_null() {
                let _ = CString::from_raw(self.0.output_name as *mut _);
            }
        }
    }
}

enum RingBufferConsumer {
    IntegerRingBufferConsumer(ringbuf::Consumer<i16>),
    FloatRingBufferConsumer(ringbuf::Consumer<f32>),
}

enum RingBufferProducer {
    IntegerRingBufferProducer(ringbuf::Producer<i16>),
    FloatRingBufferProducer(ringbuf::Producer<f32>),
}

enum LinearInputBuffer {
    IntegerLinearInputBuffer(Vec<i16>),
    FloatLinearInputBuffer(Vec<f32>),
}

struct BufferManager {
    consumer: RingBufferConsumer,
    producer: RingBufferProducer,
    linear_input_buffer: LinearInputBuffer,
}

impl BufferManager {
    // When opening a duplex stream, the sample-spec are guaranteed to match. It's ok to have
    // either the input or output sample-spec here.
    fn new(input_buffer_size: usize, sample_spec: &pulse::SampleSpec) -> BufferManager {
        if sample_spec.format == PA_SAMPLE_S16BE || sample_spec.format == PA_SAMPLE_S16LE {
            let ring = RingBuffer::<i16>::new(input_buffer_size);
            let (prod, cons) = ring.split();
            BufferManager {
                producer: IntegerRingBufferProducer(prod),
                consumer: IntegerRingBufferConsumer(cons),
                linear_input_buffer: IntegerLinearInputBuffer(Vec::<i16>::with_capacity(
                    input_buffer_size,
                )),
            }
        } else {
            let ring = RingBuffer::<f32>::new(input_buffer_size);
            let (prod, cons) = ring.split();
            BufferManager {
                producer: FloatRingBufferProducer(prod),
                consumer: FloatRingBufferConsumer(cons),
                linear_input_buffer: FloatLinearInputBuffer(Vec::<f32>::with_capacity(
                    input_buffer_size,
                )),
            }
        }
    }

    fn push_input_data(&mut self, input_data: *const c_void, read_samples: usize) {
        match &mut self.producer {
            RingBufferProducer::FloatRingBufferProducer(p) => {
                let input_data =
                    unsafe { slice::from_raw_parts::<f32>(input_data as *const f32, read_samples) };
                // we don't do anything in particular if we can't push everything
                p.push_slice(input_data);
            }
            RingBufferProducer::IntegerRingBufferProducer(p) => {
                let input_data =
                    unsafe { slice::from_raw_parts::<i16>(input_data as *const i16, read_samples) };
                p.push_slice(input_data);
            }
        }
    }

    fn pull_input_data(&mut self, input_data: *mut c_void, needed_samples: usize) {
        match &mut self.consumer {
            IntegerRingBufferConsumer(p) => {
                let input: &mut [i16] = unsafe {
                    slice::from_raw_parts_mut::<i16>(input_data as *mut i16, needed_samples)
                };
                let read = p.pop_slice(input);
                if read < needed_samples {
                    for i in 0..(needed_samples - read) {
                        input[read + i] = 0;
                    }
                }
            }
            FloatRingBufferConsumer(p) => {
                let input: &mut [f32] = unsafe {
                    slice::from_raw_parts_mut::<f32>(input_data as *mut f32, needed_samples)
                };
                let read = p.pop_slice(input);
                if read < needed_samples {
                    for i in 0..(needed_samples - read) {
                        input[read + i] = 0.;
                    }
                }
            }
        }
    }

    fn get_linear_input_data(&mut self, nsamples: usize) -> *const c_void {
        let p = match &mut self.linear_input_buffer {
            LinearInputBuffer::IntegerLinearInputBuffer(b) => {
                b.resize(nsamples, 0);
                b.as_mut_ptr() as *mut c_void
            }
            LinearInputBuffer::FloatLinearInputBuffer(b) => {
                b.resize(nsamples, 0.);
                b.as_mut_ptr() as *mut c_void
            }
        };
        self.pull_input_data(p, nsamples);

        p
    }

    pub fn trim(&mut self, final_size: usize) {
        match &self.linear_input_buffer {
            LinearInputBuffer::IntegerLinearInputBuffer(b) => {
                let length = b.len();
                assert!(final_size <= length);
                let nframes_to_pop = length - final_size;
                self.get_linear_input_data(nframes_to_pop);
            }
            LinearInputBuffer::FloatLinearInputBuffer(b) => {
                let length = b.len();
                assert!(final_size <= length);
                let nframes_to_pop = length - final_size;
                self.get_linear_input_data(nframes_to_pop);
            }
        }
    }
    pub fn available_samples(&mut self) -> usize {
        match &self.linear_input_buffer {
            LinearInputBuffer::IntegerLinearInputBuffer(b) => b.len(),
            LinearInputBuffer::FloatLinearInputBuffer(b) => b.len(),
        }
    }
}

impl std::fmt::Debug for BufferManager {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "")
    }
}

#[repr(C)]
#[derive(Debug)]
pub struct PulseStream<'ctx> {
    context: &'ctx PulseContext,
    user_ptr: *mut c_void,
    output_stream: Option<pulse::Stream>,
    input_stream: Option<pulse::Stream>,
    data_callback: ffi::cubeb_data_callback,
    state_callback: ffi::cubeb_state_callback,
    drain_timer: AtomicPtr<pa_time_event>,
    output_sample_spec: pulse::SampleSpec,
    input_sample_spec: pulse::SampleSpec,
    // output frames count excluding pre-buffering
    output_frame_count: AtomicUsize,
    shutdown: bool,
    volume: f32,
    state: ffi::cubeb_state,
    input_buffer_manager: Option<BufferManager>,
}

impl<'ctx> PulseStream<'ctx> {
    #[allow(clippy::too_many_arguments)]
    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 PulseStream) };
            if !s.get_state().is_good() {
                cubeb_alog!("Calling error callback");
                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 = s.readable_size().map(|s| s as i32).unwrap_or(-1);
                if readable_size > 0 && unsafe { s.peek(buffer, size).is_err() } {
                    cubeb_logv!("Error while peeking the input stream");
                    return -1;
                }
                readable_size
            }

            cubeb_alogv!("Input callback buffer size {}", nbytes);
            let stm = unsafe { &mut *(u as *mut PulseStream) };
            if stm.shutdown {
                return;
            }

            let mut read_data: *const c_void = ptr::null();
            let mut read_size: usize = 0;
            while read_from_input(s, &mut read_data, &mut read_size) > 0 {
                /* read_data can be NULL in case of a hole. */
                if !read_data.is_null() {
                    let in_frame_size = stm.input_sample_spec.frame_size();
                    let read_frames = read_size / in_frame_size;
                    let read_samples = read_size / stm.input_sample_spec.sample_size();

                    if stm.output_stream.is_some() {
                        // duplex stream: push the input data to the ring buffer.
                        stm.input_buffer_manager
                            .as_mut()
                            .unwrap()
                            .push_input_data(read_data, read_samples);
                    } 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,
                            )
                        };

                        if got < 0 || got as usize != read_frames {
                            let _ = s.cancel_write();
                            stm.shutdown = true;
                            if got < 0 {
                                unsafe {
                                    stm.state_callback.unwrap()(
                                        stm as *mut _ as *mut _,
                                        stm.user_ptr,
                                        ffi::CUBEB_STATE_ERROR,
                                    );
                                }
                            }
                            break;
                        }
                    }
                }

                if read_size > 0 {
                    let _ = s.drop();
                }

                if stm.shutdown {
                    return;
                }
            }
        }

        fn write_data(_: &pulse::Stream, nbytes: usize, u: *mut c_void) {
            cubeb_alogv!("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;
            }

            let nframes = nbytes / stm.output_sample_spec.frame_size();
            let first_callback = stm.output_frame_count.fetch_add(nframes, Ordering::SeqCst) == 0;
            if stm.input_stream.is_some() {
                let nsamples_input = nframes * stm.input_sample_spec.channels as usize;
                let input_buffer_manager = stm.input_buffer_manager.as_mut().unwrap();

                if first_callback {
                    let buffered_input_frames = input_buffer_manager.available_samples()
                        / stm.input_sample_spec.channels as usize;
                    if buffered_input_frames > nframes {
                        // Trim the buffer to ensure minimal roundtrip latency
                        let popped_frames = buffered_input_frames - nframes;
                        input_buffer_manager
                            .trim(nframes * stm.input_sample_spec.channels as usize);
                        cubeb_alog!("Dropping {} frames in input buffer.", popped_frames);
                    }
                }

                let p = input_buffer_manager.get_linear_input_data(nsamples_input);
                stm.trigger_user_callback(p, nbytes);
            } else {
                // Output/playback only operation.
                // Write directly to output
                debug_assert!(stm.output_stream.is_some());
                stm.trigger_user_callback(ptr::null(), nbytes);
            }
        }

        let mut stm = Box::new(PulseStream {
            context,
            output_stream: None,
            input_stream: None,
            data_callback,
            state_callback,
            user_ptr,
            drain_timer: AtomicPtr::new(ptr::null_mut()),
            output_sample_spec: pulse::SampleSpec::default(),
            input_sample_spec: pulse::SampleSpec::default(),
            output_frame_count: AtomicUsize::new(0),
            shutdown: false,
            volume: PULSE_NO_GAIN,
            state: ffi::CUBEB_STATE_ERROR,
            input_buffer_manager: None,
        });

        if let Some(ref context) = stm.context.context {
            stm.context.mainloop.lock();

            // Setup output stream
            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 buffer_size_bytes =
                            latency_frames * stm.output_sample_spec.frame_size() as u32;

                        let battr = pa_buffer_attr {
                            maxlength: u32::MAX,
                            prebuf: u32::MAX,
                            fragsize: u32::MAX,
                            tlength: buffer_size_bytes * 2,
                            minreq: buffer_size_bytes / 4,
                        };
                        let device_name = super::try_cstr_from(output_device as *const _);
                        let mut stream_flags = pulse::StreamFlags::AUTO_TIMING_UPDATE
                            | pulse::StreamFlags::INTERPOLATE_TIMING
                            | pulse::StreamFlags::START_CORKED
                            | pulse::StreamFlags::ADJUST_LATENCY;
                        if device_name.is_some()
                            || stream_params
                                .prefs()
                                .contains(StreamPrefs::DISABLE_DEVICE_SWITCHING)
                        {
                            stream_flags |= pulse::StreamFlags::DONT_MOVE;
                        }
                        let _ = s.connect_playback(device_name, &battr, stream_flags, None, None);

                        stm.output_stream = Some(s);
                    }
                    Err(e) => {
                        cubeb_log!("Output stream initialization error");
                        stm.context.mainloop.unlock();
                        stm.destroy();
                        return Err(e);
                    }
                }
            }

            // Set up input stream
            if let Some(stream_params) = input_stream_params {
                match PulseStream::stream_init(context, stream_params, stream_name) {
                    Ok(s) => {
                        stm.input_sample_spec = *s.get_sample_spec();

                        s.set_state_callback(check_error, stm.as_mut() as *mut _ as *mut _);
                        s.set_read_callback(read_data, stm.as_mut() as *mut _ as *mut _);

                        let buffer_size_bytes =
                            latency_frames * stm.input_sample_spec.frame_size() as u32;
                        let battr = pa_buffer_attr {
                            maxlength: u32::MAX,
                            prebuf: u32::MAX,
                            fragsize: buffer_size_bytes,
                            tlength: buffer_size_bytes,
                            minreq: buffer_size_bytes,
                        };
                        let device_name = super::try_cstr_from(input_device as *const _);
                        let mut stream_flags = pulse::StreamFlags::AUTO_TIMING_UPDATE
                            | pulse::StreamFlags::INTERPOLATE_TIMING
                            | pulse::StreamFlags::START_CORKED
                            | pulse::StreamFlags::ADJUST_LATENCY;
                        if device_name.is_some()
                            || stream_params
                                .prefs()
                                .contains(StreamPrefs::DISABLE_DEVICE_SWITCHING)
                        {
                            stream_flags |= pulse::StreamFlags::DONT_MOVE;
                        }
                        let _ = s.connect_record(device_name, &battr, stream_flags);

                        stm.input_stream = Some(s);
                    }
                    Err(e) => {
                        cubeb_log!("Input stream initialization error");
                        stm.context.mainloop.unlock();
                        stm.destroy();
                        return Err(e);
                    }
                }
            }

            // Duplex, set up the ringbuffer
            if input_stream_params.is_some() && output_stream_params.is_some() {
                // A bit more room in case of output underrun.
                let buffer_size_bytes =
                    2 * latency_frames * stm.input_sample_spec.frame_size() as u32;
                stm.input_buffer_manager = Some(BufferManager::new(
                    buffer_size_bytes as usize,
                    &stm.input_sample_spec,
                ))
            }

            let r = if stm.wait_until_ready() {
                /* force a timing update now, otherwise timing info does not become valid
                until some point after initialization has completed. */
                stm.update_timing_info()
            } else {
                false
            };

            stm.context.mainloop.unlock();

            if !r {
                stm.destroy();
                cubeb_log!("Error while waiting for the stream to be ready");
                return Err(Error::error());
            }

            // TODO:
            if log_enabled() {
                if let Some(ref output_stream) = stm.output_stream {
                    let output_att = output_stream.get_buffer_attr();
                    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
                    );
                }

                if let Some(ref input_stream) = stm.input_stream {
                    let input_att = input_stream.get_buffer_attr();
                    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
                    );
                }
            }
        }

        Ok(stm)
    }

    fn destroy(&mut self) {
        self.cork(CorkState::cork());

        self.context.mainloop.lock();
        {
            if let Some(stm) = self.output_stream.take() {
                let drain_timer = self.drain_timer.load(Ordering::Acquire);
                if !drain_timer.is_null() {
                    /* there's no pa_rttime_free, so use this instead. */
                    self.context.mainloop.get_api().time_free(drain_timer);
                }
                stm.clear_state_callback();
                stm.clear_write_callback();
                let _ = stm.disconnect();
                stm.unref();
            }

            if let Some(stm) = self.input_stream.take() {
                stm.clear_state_callback();
                stm.clear_read_callback();
                let _ = stm.disconnect();
                stm.unref();
            }
        }
        self.context.mainloop.unlock();
    }
}

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 PulseStream) };
            if !stm.shutdown {
                let size = stm
                    .output_stream
                    .as_ref()
                    .map_or(0, |s| s.writable_size().unwrap_or(0));
                stm.trigger_user_callback(std::ptr::null(), size);
            }
        }
        self.shutdown = false;
        self.cork(CorkState::uncork() | CorkState::notify());

        if self.output_stream.is_some() {
            /* When doing output-only or duplex, we need to manually call user cb once in order to
             * make things roll. This is done via a defer event in order to execute it from PA
             * server thread. */
            self.context.mainloop.lock();
            self.context
                .mainloop
                .get_api()
                .once(output_preroll, self as *const _ as *mut _);
            self.context.mainloop.unlock();
        }

        Ok(())
    }

    fn stop(&mut self) -> Result<()> {
        {
            self.context.mainloop.lock();
            self.shutdown = true;
            // If draining is taking place wait to finish
            cubeb_log!("Stream stop: waiting for drain");
            while !self.drain_timer.load(Ordering::Acquire).is_null() {
                self.context.mainloop.wait();
            }
            cubeb_log!("Stream stop: waited for drain");
            self.context.mainloop.unlock();
        }
        self.cork(CorkState::cork() | CorkState::notify());

        Ok(())
    }

    fn position(&mut self) -> Result<u64> {
        let in_thread = self.context.mainloop.in_thread();

        if !in_thread {
            self.context.mainloop.lock();
        }

        if self.output_stream.is_none() {
            cubeb_log!("Calling position() on an input-only stream");
            return Err(Error::error());
        }

        let stm = self.output_stream.as_ref().unwrap();
        let r = match stm.get_time() {
            Ok(r_usec) => {
                let bytes = USecExt::to_bytes(r_usec, &self.output_sample_spec);
                Ok((bytes / self.output_sample_spec.frame_size()) as u64)
            }
            Err(_) => {
                cubeb_log!("Error: stm.get_time failed");
                Err(Error::error())
            }
        };

        if !in_thread {
            self.context.mainloop.unlock();
        }

        r
    }

    fn latency(&mut self) -> Result<u32> {
        match self.output_stream {
            None => {
                cubeb_log!("Error: calling latency() on an input-only stream");
                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(_) => {
                    cubeb_log!("Error: get_latency() failed for an output stream");
                    Err(Error::error())
                }
            },
        }
    }

    fn input_latency(&mut self) -> Result<u32> {
        match self.input_stream {
            None => {
                cubeb_log!("Error: calling input_latency() on an output-only stream");
                Err(Error::error())
            }
            Some(ref stm) => match stm.get_latency() {
                Ok(StreamLatency::Positive(w_usec)) => {
                    let latency = (w_usec * pa_usec_t::from(self.input_sample_spec.rate)
                        / PA_USEC_PER_SEC) as u32;
                    Ok(latency)
                }
                // Input stream can be negative only if it is attached to a
                // monitor source device
                Ok(StreamLatency::Negative(_)) => Ok(0),
                Err(_) => {
                    cubeb_log!("Error: stm.get_latency() failed for an input stream");
                    Err(Error::error())
                }
            },
        }
    }

    fn set_volume(&mut self, volume: f32) -> Result<()> {
        match self.output_stream {
            None => {
                cubeb_log!("Error: can't set volume on an input-only stream");
                Err(Error::error())
            }
            Some(ref stm) => {
                if let Some(ref context) = self.context.context {
                    self.context.mainloop.lock();

                    let mut cvol: pa_cvolume = Default::default();

                    /* if the pulse daemon is configured to use flat
                     * volumes, apply our own gain instead of changing
                     * the input volume on the sink. */
                    let flags = {
                        match self.context.default_sink_info {
                            Some(ref info) => info.flags,
                            _ => pulse::SinkFlags::empty(),
                        }
                    };

                    if flags.contains(pulse::SinkFlags::FLAT_VOLUME) {
                        self.volume = volume;
                    } else {
                        let channels = stm.get_sample_spec().channels;
                        let vol = pulse::sw_volume_from_linear(f64::from(volume));
                        cvol.set(u32::from(channels), vol);

                        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,
                        ) {
                            self.context.operation_wait(stm, &o);
                        }
                    }

                    self.context.mainloop.unlock();
                    Ok(())
                } else {
                    cubeb_log!("Error: set_volume: no context?");
                    Err(Error::error())
                }
            }
        }
    }

    fn set_name(&mut self, name: &CStr) -> Result<()> {
        match self.output_stream {
            None => {
                cubeb_log!("Error: can't set the name on a input-only stream.");
                Err(Error::error())
            }
            Some(ref stm) => {
                self.context.mainloop.lock();
                if let Ok(o) = stm.set_name(name, stream_success, self as *const _ as *mut _) {
                    self.context.operation_wait(stm, &o);
                }
                self.context.mainloop.unlock();
                Ok(())
            }
        }
    }

    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 let Some(ref stm) = self.input_stream {
                dev.input_name = match stm.get_device_name() {
                    Ok(name) => name.to_owned().into_raw(),
                    Err(_) => {
                        cubeb_log!("Error: couldn't get the input stream's device name");
                        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(_) => {
                        cubeb_log!("Error: couldn't get the output stream's device name");
                        return Err(Error::error());
                    }
                }
            }

            Ok(unsafe { DeviceRef::from_ptr(Box::into_raw(dev) as *mut _) })
        } else {
            cubeb_log!("Error: PulseAudio context too old");
            Err(not_supported())
        }
    }

    fn set_input_mute(&mut self, _mute: bool) -> Result<()> {
        Err(not_supported())
    }

    fn set_input_processing_params(&mut self, _params: InputProcessingParams) -> Result<()> {
        Err(not_supported())
    }

    fn device_destroy(&mut self, device: &DeviceRef) -> Result<()> {
        if device.as_ptr().is_null() {
            cubeb_log!("Error: can't destroy null device");
            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<()> {
        cubeb_log!("Error: register_device_change_callback unimplemented");
        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 {
            cubeb_log!("Error: StreamPref::LOOPBACK unimplemented");
            return Err(not_supported());
        }

        fn to_pulse_format(format: SampleFormat) -> pulse::SampleFormat {
            match format {
                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());
        if fmt == pulse::SampleFormat::Invalid {
            cubeb_log!("Error: invalid sample format");
            return Err(invalid_format());
        }

        let ss = pulse::SampleSpec {
            channels: stream_params.channels() as u8,
            format: fmt.into(),
            rate: stream_params.rate(),
        };

        let cm: Option<pa_channel_map> = match stream_params.layout() {
            ChannelLayout::UNDEFINED => {
                if stream_params.channels() <= 8
                    && pulse::ChannelMap::init_auto(
                        stream_params.channels(),
                        PA_CHANNEL_MAP_DEFAULT,
                    )
                    .is_none()
                {
                    cubeb_log!("Layout undefined and PulseAudio's default layout has not been configured, guess one.");
                    Some(layout_to_channel_map(default_layout_for_channels(
                        stream_params.channels(),
                    )))
                } else {
                    cubeb_log!("Layout undefined, PulseAudio will use its default.");
                    None
                }
            }
            _ => Some(layout_to_channel_map(stream_params.layout())),
        };

        let stream = pulse::Stream::new(context, stream_name.unwrap(), &ss, cm.as_ref());

        match stream {
            None => {
                cubeb_log!("Error: pulse::Stream::new failure");
                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 _,
            ) {
                self.context.operation_wait(stream, &o);
            }
        }
    }

    fn cork(&mut self, state: CorkState) {
        {
            self.context.mainloop.lock();
            self.cork_stream(self.output_stream.as_ref(), state);
            self.cork_stream(self.input_stream.as_ref(), state);
            self.context.mainloop.unlock()
        }

        if state.is_notify() {
            self.state_change_callback(if state.is_cork() {
                ffi::CUBEB_STATE_STOPPED
            } else {
                ffi::CUBEB_STATE_STARTED
            });
        }
    }

    fn update_timing_info(&self) -> bool {
        let mut r = false;

        if let Some(ref stm) = self.output_stream {
            if let Ok(o) = stm.update_timing_info(stream_success, self as *const _ as *mut _) {
                r = self.context.operation_wait(stm, &o);
            }

            if !r {
                return r;
            }
        }

        if let Some(ref stm) = self.input_stream {
            if let Ok(o) = stm.update_timing_info(stream_success, self as *const _ as *mut _) {
                r = self.context.operation_wait(stm, &o);
            }
        }

        r
    }

    pub fn state_change_callback(&mut self, s: ffi::cubeb_state) {
        self.state = s;
        unsafe {
            (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 {
            if mainloop.is_null() {
                return false;
            }

            loop {
                let state = stm.get_state();
                if !state.is_good() {
                    return false;
                }
                if state == pulse::StreamState::Ready {
                    break;
                }
                mainloop.wait();
            }

            true
        }

        if let Some(ref stm) = self.output_stream {
            if !wait_until_io_stream_ready(stm, &self.context.mainloop) {
                return false;
            }
        }

        if let Some(ref stm) = self.input_stream {
            if !wait_until_io_stream_ready(stm, &self.context.mainloop) {
                return false;
            }
        }

        true
    }

    #[allow(clippy::cognitive_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,
        ) {
            cubeb_logv!("Drain finished callback.");
            let stm = unsafe { &mut *(u as *mut PulseStream) };
            let drain_timer = stm.drain_timer.load(Ordering::Acquire);
            debug_assert_eq!(drain_timer, e);
            stm.state_change_callback(ffi::CUBEB_STATE_DRAINED);
            /* there's no pa_rttime_free, so use this instead. */
            a.time_free(drain_timer);
            stm.drain_timer.store(ptr::null_mut(), Ordering::Release);
            stm.context.mainloop.signal();
        }

        if let Some(ref stm) = self.output_stream {
            let frame_size = self.output_sample_spec.frame_size();
            debug_assert_eq!(nbytes % frame_size, 0);

            let mut towrite = nbytes;
            let mut read_offset = 0usize;
            while towrite > 0 {
                match stm.begin_write(towrite) {
                    Err(e) => {
                        cubeb_logv!("Error: failure to write data");
                        panic!("Failed to write data: {}", e);
                    }
                    Ok((buffer, size)) => {
                        debug_assert!(size > 0);
                        debug_assert_eq!(size % frame_size, 0);

                        cubeb_logv!(
                            "Trigger user callback with output buffer size={}, read_offset={}",
                            size,
                            read_offset
                        );
                        let read_ptr = unsafe { (input_data as *const u8).add(read_offset) };
                        #[allow(clippy::unnecessary_cast)]
                        let mut 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,
                            ) as i64
                        };
                        if got < 0 {
                            let _ = stm.cancel_write();
                            self.shutdown = true;
                            unsafe {
                                self.state_callback.unwrap()(
                                    self as *const _ as *mut _,
                                    self.user_ptr,
                                    ffi::CUBEB_STATE_ERROR,
                                );
                            }
                            return;
                        }

                        // If more iterations move offset of read buffer
                        if !input_data.is_null() {
                            let in_frame_size = self.input_sample_spec.frame_size();
                            read_offset += (size / frame_size) * in_frame_size;
                        }

                        if self.volume != PULSE_NO_GAIN {
                            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
                            {
                                let b = buffer as *mut i16;
                                for i in 0..samples {
                                    unsafe { *b.offset(i) *= self.volume as i16 };
                                }
                            } else {
                                let b = buffer as *mut f32;
                                for i in 0..samples {
                                    unsafe { *b.offset(i) *= self.volume };
                                }
                            }
                        }

                        let should_drain = (got as usize) < size / frame_size;

                        if should_drain && self.output_frame_count.load(Ordering::SeqCst) == 0 {
                            // Draining during preroll, ensure `prebuf` frames are written so
                            // the stream starts. If not, pad with a bit of silence.
                            let prebuf_size_bytes = stm.get_buffer_attr().prebuf as usize;
                            let got_bytes = got as usize * frame_size;
                            if prebuf_size_bytes > got_bytes {
                                let padding_bytes = prebuf_size_bytes - got_bytes;
                                if padding_bytes + got_bytes <= size {
                                    // A slice that starts after the data provided by the callback,
                                    // with just enough room to provide a final buffer big enough.
                                    let padding_buf: &mut [u8] = unsafe {
                                        slice::from_raw_parts_mut::<u8>(
                                            buffer.add(got_bytes) as *mut u8,
                                            padding_bytes,
                                        )
                                    };
                                    padding_buf.fill(0);
                                    got += (padding_bytes / frame_size) as i64;
                                }
                            } else {
                                cubeb_logv!(
                                    "Not enough room to pad up to prebuf when prebuffering."
                                )
                            }
                        }

                        let r = stm.write(
                            buffer,
                            got as usize * frame_size,
                            0,
                            pulse::SeekMode::Relative,
                        );

                        if should_drain {
                            cubeb_logv!("Draining {} < {}", got, size / frame_size);
                            let latency = match stm.get_latency() {
                                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)
                                    );
                                    /* this needs a better guess. */
                                    100 * PA_USEC_PER_MSEC
                                }
                            };

                            /* pa_stream_drain is useless, see PA bug# 866. this is a workaround. */
                            /* arbitrary safety margin: double the current latency. */
                            debug_assert!(self.drain_timer.load(Ordering::Acquire).is_null());
                            let stream_ptr = self as *const _ as *mut _;
                            if let Some(ref context) = self.context.context {
                                self.drain_timer.store(
                                    context.rttime_new(
                                        pulse::rtclock_now() + 2 * latency,
                                        drained_cb,
                                        stream_ptr,
                                    ),
                                    Ordering::Release,
                                );
                            }
                            self.shutdown = true;
                            return;
                        }

                        debug_assert!(r.is_ok());

                        towrite -= size;
                    }
                }
            }
            debug_assert_eq!(towrite, 0);
        }
    }
}

fn stream_success(_: &pulse::Stream, success: i32, u: *mut c_void) {
    let stm = unsafe { &*(u as *mut PulseStream) };
    if success != 1 {
        cubeb_log!("stream_success ignored failure: {}", success);
    }
    stm.context.mainloop.signal();
}

fn context_success(_: &pulse::Context, success: i32, u: *mut c_void) {
    let ctx = unsafe { &*(u as *mut PulseContext) };
    if success != 1 {
        cubeb_log!("context_success ignored failure: {}", success);
    }
    ctx.mainloop.signal();
}

fn invalid_format() -> Error {
    Error::from_raw(ffi::CUBEB_ERROR_INVALID_FORMAT)
}

fn not_supported() -> Error {
    Error::from_raw(ffi::CUBEB_ERROR_NOT_SUPPORTED)
}

#[cfg(all(test, not(feature = "pulse-dlopen")))]
mod test {
    use super::layout_to_channel_map;
    use cubeb_backend::ChannelLayout;
    use pulse_ffi::*;

    macro_rules! channel_tests {
        {$($name: ident, $layout: ident => [ $($channels: ident),* ]),+} => {
            $(
            #[test]
            fn $name() {
                let layout = ChannelLayout::$layout;
                let mut iter = super::channel_layout_iter(layout);
                $(
                assert_eq!(Some(ChannelLayout::$channels), iter.next());
                )*
                assert_eq!(None, iter.next());
            }

            )*
        }
    }

    channel_tests! {
        channels_unknown, UNDEFINED => [ ],
        channels_mono, MONO => [
            FRONT_CENTER
        ],
        channels_mono_lfe, MONO_LFE => [
            FRONT_CENTER,
            LOW_FREQUENCY
        ],
        channels_stereo, STEREO => [
            FRONT_LEFT,
            FRONT_RIGHT
        ],
        channels_stereo_lfe, STEREO_LFE => [
            FRONT_LEFT,
            FRONT_RIGHT,
            LOW_FREQUENCY
        ],
        channels_3f, _3F => [
            FRONT_LEFT,
            FRONT_RIGHT,
            FRONT_CENTER
        ],
        channels_3f_lfe, _3F_LFE => [
            FRONT_LEFT,
            FRONT_RIGHT,
            FRONT_CENTER,
            LOW_FREQUENCY
        ],
        channels_2f1, _2F1 => [
            FRONT_LEFT,
            FRONT_RIGHT,
            BACK_CENTER
        ],
        channels_2f1_lfe, _2F1_LFE => [
            FRONT_LEFT,
            FRONT_RIGHT,
            LOW_FREQUENCY,
            BACK_CENTER
        ],
        channels_3f1, _3F1 => [
            FRONT_LEFT,
            FRONT_RIGHT,
            FRONT_CENTER,
            BACK_CENTER
        ],
        channels_3f1_lfe, _3F1_LFE => [
            FRONT_LEFT,
            FRONT_RIGHT,
            FRONT_CENTER,
            LOW_FREQUENCY,
            BACK_CENTER
        ],
        channels_2f2, _2F2 => [
            FRONT_LEFT,
            FRONT_RIGHT,
            SIDE_LEFT,
            SIDE_RIGHT
        ],
        channels_2f2_lfe, _2F2_LFE => [
            FRONT_LEFT,
            FRONT_RIGHT,
            LOW_FREQUENCY,
            SIDE_LEFT,
            SIDE_RIGHT
        ],
        channels_quad, QUAD => [
            FRONT_LEFT,
            FRONT_RIGHT,
            BACK_LEFT,
            BACK_RIGHT
        ],
        channels_quad_lfe, QUAD_LFE => [
            FRONT_LEFT,
            FRONT_RIGHT,
            LOW_FREQUENCY,
            BACK_LEFT,
            BACK_RIGHT
        ],
        channels_3f2, _3F2 => [
            FRONT_LEFT,
            FRONT_RIGHT,
            FRONT_CENTER,
            SIDE_LEFT,
            SIDE_RIGHT
        ],
        channels_3f2_lfe, _3F2_LFE => [
            FRONT_LEFT,
            FRONT_RIGHT,
            FRONT_CENTER,
            LOW_FREQUENCY,
            SIDE_LEFT,
            SIDE_RIGHT
        ],
        channels_3f2_back, _3F2_BACK => [
            FRONT_LEFT,
            FRONT_RIGHT,
            FRONT_CENTER,
            BACK_LEFT,
            BACK_RIGHT
        ],
        channels_3f2_lfe_back, _3F2_LFE_BACK => [
            FRONT_LEFT,
            FRONT_RIGHT,
            FRONT_CENTER,
            LOW_FREQUENCY,
            BACK_LEFT,
            BACK_RIGHT
        ],
        channels_3f3r_lfe, _3F3R_LFE => [
            FRONT_LEFT,
            FRONT_RIGHT,
            FRONT_CENTER,
            LOW_FREQUENCY,
            BACK_CENTER,
            SIDE_LEFT,
            SIDE_RIGHT
        ],
        channels_3f4_lfe, _3F4_LFE => [
            FRONT_LEFT,
            FRONT_RIGHT,
            FRONT_CENTER,
            LOW_FREQUENCY,
            BACK_LEFT,
            BACK_RIGHT,
            SIDE_LEFT,
            SIDE_RIGHT
        ]
    }

    #[test]
    fn mono_channels_enumerate() {
        let layout = ChannelLayout::MONO;
        let mut iter = super::channel_layout_iter(layout).enumerate();
        assert_eq!(Some((0, ChannelLayout::FRONT_CENTER)), iter.next());
        assert_eq!(None, iter.next());
    }

    #[test]
    fn stereo_channels_enumerate() {
        let layout = ChannelLayout::STEREO;
        let mut iter = super::channel_layout_iter(layout).enumerate();
        assert_eq!(Some((0, ChannelLayout::FRONT_LEFT)), iter.next());
        assert_eq!(Some((1, ChannelLayout::FRONT_RIGHT)), iter.next());
        assert_eq!(None, iter.next());
    }

    #[test]
    fn quad_channels_enumerate() {
        let layout = ChannelLayout::QUAD;
        let mut iter = super::channel_layout_iter(layout).enumerate();
        assert_eq!(Some((0, ChannelLayout::FRONT_LEFT)), iter.next());
        assert_eq!(Some((1, ChannelLayout::FRONT_RIGHT)), iter.next());
        assert_eq!(Some((2, ChannelLayout::BACK_LEFT)), iter.next());
        assert_eq!(Some((3, ChannelLayout::BACK_RIGHT)), iter.next());
        assert_eq!(None, iter.next());
    }

    macro_rules! map_channel_tests {
        {$($name: ident, $layout: ident => [ $($channels: ident),* ]),+} => {
            $(
            #[test]
            fn $name() {
                let map = layout_to_channel_map(ChannelLayout::$layout);
                assert_eq!(map.channels, map_channel_tests!(__COUNT__ $($channels)*));
                map_channel_tests!(__EACH__ map, 0, $($channels)*);
            }

            )*
        };
        (__COUNT__) => (0u8);
        (__COUNT__ $x:ident $($xs: ident)*) => (1u8 + map_channel_tests!(__COUNT__ $($xs)*));
        (__EACH__ $map:expr, $i:expr, ) => {};
        (__EACH__ $map:expr, $i:expr, $x:ident $($xs: ident)*) => {
            assert_eq!($map.map[$i], $x);
            map_channel_tests!(__EACH__ $map, $i+1, $($xs)* );
        };
    }

    map_channel_tests! {
        map_channel_mono, MONO => [
            PA_CHANNEL_POSITION_MONO
        ],
        map_channel_mono_lfe, MONO_LFE => [
            PA_CHANNEL_POSITION_FRONT_CENTER,
            PA_CHANNEL_POSITION_LFE
        ],
        map_channel_stereo, STEREO => [
            PA_CHANNEL_POSITION_FRONT_LEFT,
            PA_CHANNEL_POSITION_FRONT_RIGHT
        ],
        map_channel_stereo_lfe, STEREO_LFE => [
            PA_CHANNEL_POSITION_FRONT_LEFT,
            PA_CHANNEL_POSITION_FRONT_RIGHT,
            PA_CHANNEL_POSITION_LFE
        ],
        map_channel_3f, _3F => [
            PA_CHANNEL_POSITION_FRONT_LEFT,
            PA_CHANNEL_POSITION_FRONT_RIGHT,
            PA_CHANNEL_POSITION_FRONT_CENTER
        ],
        map_channel_3f_lfe, _3F_LFE => [
            PA_CHANNEL_POSITION_FRONT_LEFT,
            PA_CHANNEL_POSITION_FRONT_RIGHT,
            PA_CHANNEL_POSITION_FRONT_CENTER,
            PA_CHANNEL_POSITION_LFE
        ],
        map_channel_2f1, _2F1 => [
            PA_CHANNEL_POSITION_FRONT_LEFT,
            PA_CHANNEL_POSITION_FRONT_RIGHT,
            PA_CHANNEL_POSITION_REAR_CENTER
        ],
        map_channel_2f1_lfe, _2F1_LFE => [
            PA_CHANNEL_POSITION_FRONT_LEFT,
            PA_CHANNEL_POSITION_FRONT_RIGHT,
            PA_CHANNEL_POSITION_LFE,
            PA_CHANNEL_POSITION_REAR_CENTER
        ],
        map_channel_3f1, _3F1 => [
            PA_CHANNEL_POSITION_FRONT_LEFT,
            PA_CHANNEL_POSITION_FRONT_RIGHT,
            PA_CHANNEL_POSITION_FRONT_CENTER,
            PA_CHANNEL_POSITION_REAR_CENTER
        ],
        map_channel_3f1_lfe, _3F1_LFE =>[
            PA_CHANNEL_POSITION_FRONT_LEFT,
            PA_CHANNEL_POSITION_FRONT_RIGHT,
            PA_CHANNEL_POSITION_FRONT_CENTER,
            PA_CHANNEL_POSITION_LFE,
            PA_CHANNEL_POSITION_REAR_CENTER
        ],
        map_channel_2f2, _2F2 => [
            PA_CHANNEL_POSITION_FRONT_LEFT,
            PA_CHANNEL_POSITION_FRONT_RIGHT,
            PA_CHANNEL_POSITION_SIDE_LEFT,
            PA_CHANNEL_POSITION_SIDE_RIGHT
        ],
        map_channel_2f2_lfe, _2F2_LFE => [
            PA_CHANNEL_POSITION_FRONT_LEFT,
            PA_CHANNEL_POSITION_FRONT_RIGHT,
            PA_CHANNEL_POSITION_LFE,
            PA_CHANNEL_POSITION_SIDE_LEFT,
            PA_CHANNEL_POSITION_SIDE_RIGHT
        ],
        map_channel_quad, QUAD => [
            PA_CHANNEL_POSITION_FRONT_LEFT,
            PA_CHANNEL_POSITION_FRONT_RIGHT,
            PA_CHANNEL_POSITION_REAR_LEFT,
            PA_CHANNEL_POSITION_REAR_RIGHT
        ],
        map_channel_quad_lfe, QUAD_LFE => [
            PA_CHANNEL_POSITION_FRONT_LEFT,
            PA_CHANNEL_POSITION_FRONT_RIGHT,
            PA_CHANNEL_POSITION_LFE,
            PA_CHANNEL_POSITION_REAR_LEFT,
            PA_CHANNEL_POSITION_REAR_RIGHT
        ],
        map_channel_3f2, _3F2 => [
            PA_CHANNEL_POSITION_FRONT_LEFT,
            PA_CHANNEL_POSITION_FRONT_RIGHT,
            PA_CHANNEL_POSITION_FRONT_CENTER,
            PA_CHANNEL_POSITION_SIDE_LEFT,
            PA_CHANNEL_POSITION_SIDE_RIGHT
        ],
        map_channel_3f2_lfe, _3F2_LFE => [
            PA_CHANNEL_POSITION_FRONT_LEFT,
            PA_CHANNEL_POSITION_FRONT_RIGHT,
            PA_CHANNEL_POSITION_FRONT_CENTER,
            PA_CHANNEL_POSITION_LFE,
            PA_CHANNEL_POSITION_SIDE_LEFT,
            PA_CHANNEL_POSITION_SIDE_RIGHT
        ],
        map_channel_3f2_back, _3F2_BACK => [
            PA_CHANNEL_POSITION_FRONT_LEFT,
            PA_CHANNEL_POSITION_FRONT_RIGHT,
            PA_CHANNEL_POSITION_FRONT_CENTER,
            PA_CHANNEL_POSITION_REAR_LEFT,
            PA_CHANNEL_POSITION_REAR_RIGHT
        ],
        map_channel_3f2_lfe_back, _3F2_LFE_BACK => [
            PA_CHANNEL_POSITION_FRONT_LEFT,
            PA_CHANNEL_POSITION_FRONT_RIGHT,
            PA_CHANNEL_POSITION_FRONT_CENTER,
            PA_CHANNEL_POSITION_LFE,
            PA_CHANNEL_POSITION_REAR_LEFT,
            PA_CHANNEL_POSITION_REAR_RIGHT
        ],
        map_channel_3f3r_lfe, _3F3R_LFE => [
            PA_CHANNEL_POSITION_FRONT_LEFT,
            PA_CHANNEL_POSITION_FRONT_RIGHT,
            PA_CHANNEL_POSITION_FRONT_CENTER,
            PA_CHANNEL_POSITION_LFE,
            PA_CHANNEL_POSITION_REAR_CENTER,
            PA_CHANNEL_POSITION_SIDE_LEFT,
            PA_CHANNEL_POSITION_SIDE_RIGHT
        ],
        map_channel_3f4_lfe, _3F4_LFE => [
            PA_CHANNEL_POSITION_FRONT_LEFT,
            PA_CHANNEL_POSITION_FRONT_RIGHT,
            PA_CHANNEL_POSITION_FRONT_CENTER,
            PA_CHANNEL_POSITION_LFE,
            PA_CHANNEL_POSITION_REAR_LEFT,
            PA_CHANNEL_POSITION_REAR_RIGHT,
            PA_CHANNEL_POSITION_SIDE_LEFT,
            PA_CHANNEL_POSITION_SIDE_RIGHT
        ]
    }
}

[ Dauer der Verarbeitung: 0.49 Sekunden  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge