mozilla / cubeb-rs

ISC License
61 stars 19 forks source link

Multi Channel Audio #80

Closed sainteckes closed 1 year ago

sainteckes commented 1 year ago

Can you get an arbitrary channel count in the data callback? As far as I see you can only get a mono or stereo stream.

padenot commented 1 year ago

The underlying implementations generally support a large number of channels (the exact number depends on the backend / OS / hardware), but frame types are only provided with meaningful attributes for mono and stereo as of now.

It's however possible to implement any layout, as long as you know the channel count, here's for example a patch that modifies the example tone.rs to output to 4 speakers, in quad layout:

diff --git a/cubeb-api/examples/tone.rs b/cubeb-api/examples/tone.rs
index 97e940b..aa03f72 100644
--- a/cubeb-api/examples/tone.rs
+++ b/cubeb-api/examples/tone.rs
@@ -8,7 +8,7 @@ extern crate cubeb;

 mod common;

-use cubeb::{MonoFrame, Sample};
+use cubeb::Sample;
 use std::f32::consts::PI;
 use std::thread;
 use std::time::Duration;
@@ -16,7 +16,12 @@ use std::time::Duration;
 const SAMPLE_FREQUENCY: u32 = 48_000;
 const STREAM_FORMAT: cubeb::SampleFormat = cubeb::SampleFormat::S16LE;

-type Frame = MonoFrame<i16>;
+pub struct MultichannelFrame<T, const N: usize>
+{
+    pub data: [T; N],
+}
+
+type Frame = MultichannelFrame<i16, 4>;

 fn main() {
     let ctx = common::init("Cubeb tone example").expect("Failed to create cubeb context");
@@ -24,8 +29,8 @@ fn main() {
     let params = cubeb::StreamParamsBuilder::new()
         .format(STREAM_FORMAT)
         .rate(SAMPLE_FREQUENCY)
-        .channels(1)
-        .layout(cubeb::ChannelLayout::MONO)
+        .channels(4)
+        .layout(cubeb::ChannelLayout::QUAD)
         .take();

     let mut position = 0u32;
@@ -42,7 +47,9 @@ fn main() {
                 let t1 = (2.0 * PI * 350.0 * position as f32 / SAMPLE_FREQUENCY as f32).sin();
                 let t2 = (2.0 * PI * 440.0 * position as f32 / SAMPLE_FREQUENCY as f32).sin();

-                f.m = i16::from_float(0.5 * (t1 + t2));
+                for sample in &mut f.data {
+                    *sample = i16::from_float(0.5 * (t1 + t2));
+                }

                 position += 1;
             }

If you're working with a layout where the speaker positions are well-defined and have meaningful names, e.g. 5.1, it could be:

pub struct MultichannelFrame<T, const N: usize>
{
    pub fl: u16;
    pub fr: u16;
    pub c: u16;
    pub bl: u16;
    pub br: u16;
    pub lfe: u16;
}

cubeb always gets the data in SMPTE ordering (see the ChannelLayout type), and then figures out how to best give it to the host. It will reorder channels or remix the audio if needed to match the output device, or simply forward the data if they're already in the right order (this is the common case).

If dealing with what's often called "discrete channels", e.g. a setup for an art installation with 16 speakers in a line or circle or whatnot, without a well known layout, UNDEFINED layout is what's usually preferred. The backends will just forward the data in this case, and pad with silence / drop channels if the output device doesn't have the same number of channels as the cubeb stream. In general, in a setup like this the hardware setup is under control so it's not a problem.

sainteckes commented 1 year ago

Wow thank you very much! That is enough to start 👍