aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorChun-Min Chang <[email protected]>2016-12-21 13:18:07 +0800
committerMatthew Gregan <[email protected]>2016-12-20 19:18:07 -1000
commita900d6e511c3286956c38ca60162790ca6e8e18d (patch)
tree12969af1e27b0d0d127127a60d770b52100f76fb /src
parentc1e1e45dee4367c6f839b903417998049ad1baeb (diff)
downloadcubeb-a900d6e511c3286956c38ca60162790ca6e8e18d.tar.gz
cubeb-a900d6e511c3286956c38ca60162790ca6e8e18d.zip
Support multiple channels on Windows (#171)
* Multiple channel support on Windows * Move up/down mixing code from cubeb_wasapi.cpp to standalone cubeb_mixer.cpp
Diffstat (limited to 'src')
-rw-r--r--src/cubeb-internal.h29
-rw-r--r--src/cubeb.c15
-rw-r--r--src/cubeb_alsa.c1
-rw-r--r--src/cubeb_audiotrack.c1
-rw-r--r--src/cubeb_audiounit.cpp5
-rw-r--r--src/cubeb_jack.cpp1
-rw-r--r--src/cubeb_kai.c1
-rw-r--r--src/cubeb_mixer.cpp326
-rw-r--r--src/cubeb_mixer.h69
-rw-r--r--src/cubeb_opensl.c1
-rw-r--r--src/cubeb_pulse.c1
-rw-r--r--src/cubeb_sndio.c1
-rw-r--r--src/cubeb_wasapi.cpp283
-rw-r--r--src/cubeb_winmm.c1
14 files changed, 624 insertions, 111 deletions
diff --git a/src/cubeb-internal.h b/src/cubeb-internal.h
index dfcc186..083a02a 100644
--- a/src/cubeb-internal.h
+++ b/src/cubeb-internal.h
@@ -35,6 +35,34 @@ void cubeb_crash() CLANG_ANALYZER_NORETURN;
}
#endif
+typedef struct {
+ char const * name;
+ unsigned int const channels;
+ cubeb_channel_layout const layout;
+} cubeb_layout_map;
+
+static cubeb_layout_map const CUBEB_CHANNEL_LAYOUT_MAPS[CUBEB_LAYOUT_MAX] = {
+ { "undefined", 0, CUBEB_LAYOUT_UNDEFINED },
+ { "dual mono", 2, CUBEB_LAYOUT_DUAL_MONO },
+ { "dual mono lfe", 3, CUBEB_LAYOUT_DUAL_MONO_LFE },
+ { "mono", 1, CUBEB_LAYOUT_MONO },
+ { "mono lfe", 2, CUBEB_LAYOUT_MONO_LFE },
+ { "stereo", 2, CUBEB_LAYOUT_STEREO },
+ { "stereo lfe", 3, CUBEB_LAYOUT_STEREO_LFE },
+ { "3f", 3, CUBEB_LAYOUT_3F },
+ { "3f lfe", 4, CUBEB_LAYOUT_3F_LFE },
+ { "2f1", 3, CUBEB_LAYOUT_2F1 },
+ { "2f1 lfe", 4, CUBEB_LAYOUT_2F1_LFE },
+ { "3f1", 4, CUBEB_LAYOUT_3F1 },
+ { "3f1 lfe", 5, CUBEB_LAYOUT_3F1_LFE },
+ { "2f2", 4, CUBEB_LAYOUT_2F2 },
+ { "2f2 lfe", 5, CUBEB_LAYOUT_2F2_LFE },
+ { "3f2", 5, CUBEB_LAYOUT_3F2 },
+ { "3f2 lfe", 6, CUBEB_LAYOUT_3F2_LFE },
+ { "3f3r lfe", 7, CUBEB_LAYOUT_3F3R_LFE },
+ { "3f4 lfe", 8, CUBEB_LAYOUT_3F4_LFE }
+};
+
struct cubeb_ops {
int (* init)(cubeb ** context, char const * context_name);
char const * (* get_backend_id)(cubeb * context);
@@ -43,6 +71,7 @@ struct cubeb_ops {
cubeb_stream_params params,
uint32_t * latency_ms);
int (* get_preferred_sample_rate)(cubeb * context, uint32_t * rate);
+ int (* get_preferred_channel_layout)(cubeb * context, cubeb_channel_layout * layout);
int (* enumerate_devices)(cubeb * context, cubeb_device_type type,
cubeb_device_collection ** collection);
void (* destroy)(cubeb * context);
diff --git a/src/cubeb.c b/src/cubeb.c
index e239b05..57bcb4c 100644
--- a/src/cubeb.c
+++ b/src/cubeb.c
@@ -215,6 +215,20 @@ cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate)
return context->ops->get_preferred_sample_rate(context, rate);
}
+int
+cubeb_get_preferred_channel_layout(cubeb * context, cubeb_channel_layout * layout)
+{
+ if (!context || !layout) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!context->ops->get_preferred_channel_layout) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return context->ops->get_preferred_channel_layout(context, layout);
+}
+
void
cubeb_destroy(cubeb * context)
{
@@ -562,4 +576,3 @@ cubeb_crash()
abort();
*((volatile int *) NULL) = 0;
}
-
diff --git a/src/cubeb_alsa.c b/src/cubeb_alsa.c
index bd34a07..6403dcc 100644
--- a/src/cubeb_alsa.c
+++ b/src/cubeb_alsa.c
@@ -1131,6 +1131,7 @@ static struct cubeb_ops const alsa_ops = {
.get_max_channel_count = alsa_get_max_channel_count,
.get_min_latency = alsa_get_min_latency,
.get_preferred_sample_rate = alsa_get_preferred_sample_rate,
+ .get_preferred_channel_layout = NULL,
.enumerate_devices = NULL,
.destroy = alsa_destroy,
.stream_init = alsa_stream_init,
diff --git a/src/cubeb_audiotrack.c b/src/cubeb_audiotrack.c
index 047636e..c0455ae 100644
--- a/src/cubeb_audiotrack.c
+++ b/src/cubeb_audiotrack.c
@@ -421,6 +421,7 @@ static struct cubeb_ops const audiotrack_ops = {
.get_max_channel_count = audiotrack_get_max_channel_count,
.get_min_latency = audiotrack_get_min_latency,
.get_preferred_sample_rate = audiotrack_get_preferred_sample_rate,
+ .get_preferred_channel_layout = NULL,
.enumerate_devices = NULL,
.destroy = audiotrack_destroy,
.stream_init = audiotrack_stream_init,
diff --git a/src/cubeb_audiounit.cpp b/src/cubeb_audiounit.cpp
index fc67ab8..5cba07c 100644
--- a/src/cubeb_audiounit.cpp
+++ b/src/cubeb_audiounit.cpp
@@ -133,8 +133,8 @@ struct cubeb_stream {
cubeb_state_callback state_callback = nullptr;
cubeb_device_changed_callback device_changed_callback = nullptr;
/* Stream creation parameters */
- cubeb_stream_params input_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0 };
- cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0 };
+ cubeb_stream_params input_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED };
+ cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED };
bool is_default_input;
AudioDeviceID input_device = 0;
AudioDeviceID output_device = 0;
@@ -2296,6 +2296,7 @@ cubeb_ops const audiounit_ops = {
/*.get_max_channel_count =*/ audiounit_get_max_channel_count,
/*.get_min_latency =*/ audiounit_get_min_latency,
/*.get_preferred_sample_rate =*/ audiounit_get_preferred_sample_rate,
+ /*.get_preferred_channel_layout =*/ nullptr,
/*.enumerate_devices =*/ audiounit_enumerate_devices,
/*.destroy =*/ audiounit_destroy,
/*.stream_init =*/ audiounit_stream_init,
diff --git a/src/cubeb_jack.cpp b/src/cubeb_jack.cpp
index d29f857..cf98b83 100644
--- a/src/cubeb_jack.cpp
+++ b/src/cubeb_jack.cpp
@@ -114,6 +114,7 @@ static struct cubeb_ops const cbjack_ops = {
.get_max_channel_count = cbjack_get_max_channel_count,
.get_min_latency = cbjack_get_min_latency,
.get_preferred_sample_rate = cbjack_get_preferred_sample_rate,
+ .get_preferred_channel_layout = NULL,
.enumerate_devices = cbjack_enumerate_devices,
.destroy = cbjack_destroy,
.stream_init = cbjack_stream_init,
diff --git a/src/cubeb_kai.c b/src/cubeb_kai.c
index e0531b3..eee9c95 100644
--- a/src/cubeb_kai.c
+++ b/src/cubeb_kai.c
@@ -343,6 +343,7 @@ static struct cubeb_ops const kai_ops = {
/*.get_max_channel_count=*/ kai_get_max_channel_count,
/*.get_min_latency=*/ kai_get_min_latency,
/*.get_preferred_sample_rate =*/ kai_get_preferred_sample_rate,
+ /*.get_preferred_channel_layout =*/ NULL,
/*.enumerate_devices =*/ NULL,
/*.destroy =*/ kai_destroy,
/*.stream_init =*/ kai_stream_init,
diff --git a/src/cubeb_mixer.cpp b/src/cubeb_mixer.cpp
new file mode 100644
index 0000000..241698a
--- /dev/null
+++ b/src/cubeb_mixer.cpp
@@ -0,0 +1,326 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#include <cassert>
+#include "cubeb-internal.h"
+#include "cubeb_mixer.h"
+
+static int const CHANNEL_ORDER_TO_INDEX[CUBEB_LAYOUT_MAX][CHANNEL_MAX] = {
+ // M | L | R | C | LS | RS | RLS | RC | RRS | LFE
+ { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // UNSUPPORTED
+ { -1, 0, 1, -1, -1, -1, -1, -1, -1, -1 }, // DUAL_MONO
+ { -1, 0, 1, -1, -1, -1, -1, -1, -1, 2 }, // DUAL_MONO_LFE
+ { 0, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // MONO
+ { 0, -1, -1, -1, -1, -1, -1, -1, -1, 1 }, // MONO_LFE
+ { -1, 0, 1, -1, -1, -1, -1, -1, -1, -1 }, // STEREO
+ { -1, 0, 1, -1, -1, -1, -1, -1, -1, 2 }, // STEREO_LFE
+ { -1, 0, 1, 2, -1, -1, -1, -1, -1, -1 }, // 3F
+ { -1, 0, 1, 2, -1, -1, -1, -1, -1, 3 }, // 3F_LFE
+ { -1, 0, 1, -1, -1, -1, -1, 2, -1, -1 }, // 2F1
+ { -1, 0, 1, -1, -1, -1, -1, 3, -1, 2 }, // 2F1_LFE
+ { -1, 0, 1, 2, -1, -1, -1, 3, -1, -1 }, // 3F1
+ { -1, 0, 1, 2, -1, -1, -1, 4, -1, 3 }, // 3F1_LFE
+ { -1, 0, 1, -1, 2, 3, -1, -1, -1, -1 }, // 2F2
+ { -1, 0, 1, -1, 3, 4, -1, -1, -1, 2 }, // 2F2_LFE
+ { -1, 0, 1, 2, 3, 4, -1, -1, -1, -1 }, // 3F2
+ { -1, 0, 1, 2, 4, 5, -1, -1, -1, 3 }, // 3F2_LFE
+ { -1, 0, 1, 2, 5, 6, -1, 4, -1, 3 }, // 3F3R_LFE
+ { -1, 0, 1, 2, 6, 7, 4, -1, 5, 3 }, // 3F4_LFE
+};
+
+// The downmix matrix from TABLE 2 in the ITU-R BS.775-3[1] defines a way to
+// convert 3F2 input data to 1F, 2F, 3F, 2F1, 3F1, 2F2 output data. We extend it
+// to convert 3F2-LFE input data to 1F, 2F, 3F, 2F1, 3F1, 2F2 and their LFEs
+// output data.
+// [1] https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.775-3-201208-I!!PDF-E.pdf
+
+// Number of converted layouts: 1F, 2F, 3F, 2F1, 3F1, 2F2 and their LFEs.
+unsigned int const SUPPORTED_LAYOUT_NUM = 12;
+// Number of input channel for downmix conversion.
+unsigned int const INPUT_CHANNEL_NUM = 6; // 3F2-LFE
+// Max number of possible output channels.
+unsigned int const MAX_OUTPUT_CHANNEL_NUM = 5; // 2F2-LFE or 3F1-LFE
+float const INV_SQRT_2 = 0.707106f; // 1/sqrt(2)
+// Each array contains coefficients that will be multiplied with
+// { L, R, C, LFE, LS, RS } channels respectively.
+static float const DOWNMIX_MATRIX_3F2_LFE[SUPPORTED_LAYOUT_NUM][MAX_OUTPUT_CHANNEL_NUM][INPUT_CHANNEL_NUM] =
+{
+// 1F Mono
+ {
+ { INV_SQRT_2, INV_SQRT_2, 1, 0, 0.5, 0.5 }, // M
+ },
+// 1F Mono-LFE
+ {
+ { INV_SQRT_2, INV_SQRT_2, 1, 0, 0.5, 0.5 }, // M
+ { 0, 0, 0, 1, 0, 0 } // LFE
+ },
+// 2F Stereo
+ {
+ { 1, 0, INV_SQRT_2, 0, INV_SQRT_2, 0 }, // L
+ { 0, 1, INV_SQRT_2, 0, 0, INV_SQRT_2 } // R
+ },
+// 2F Stereo-LFE
+ {
+ { 1, 0, INV_SQRT_2, 0, INV_SQRT_2, 0 }, // L
+ { 0, 1, INV_SQRT_2, 0, 0, INV_SQRT_2 }, // R
+ { 0, 0, 0, 1, 0, 0 } // LFE
+ },
+// 3F
+ {
+ { 1, 0, 0, 0, INV_SQRT_2, 0 }, // L
+ { 0, 1, 0, 0, 0, INV_SQRT_2 }, // R
+ { 0, 0, 1, 0, 0, 0 } // C
+ },
+// 3F-LFE
+ {
+ { 1, 0, 0, 0, INV_SQRT_2, 0 }, // L
+ { 0, 1, 0, 0, 0, INV_SQRT_2 }, // R
+ { 0, 0, 1, 0, 0, 0 }, // C
+ { 0, 0, 0, 1, 0, 0 } // LFE
+ },
+// 2F1
+ {
+ { 1, 0, INV_SQRT_2, 0, 0, 0 }, // L
+ { 0, 1, INV_SQRT_2, 0, 0, 0 }, // R
+ { 0, 0, 0, 0, INV_SQRT_2, INV_SQRT_2 } // S
+ },
+// 2F1-LFE
+ {
+ { 1, 0, INV_SQRT_2, 0, 0, 0 }, // L
+ { 0, 1, INV_SQRT_2, 0, 0, 0 }, // R
+ { 0, 0, 0, 1, 0, 0 }, // LFE
+ { 0, 0, 0, 0, INV_SQRT_2, INV_SQRT_2 } // S
+ },
+// 3F1
+ {
+ { 1, 0, 0, 0, 0, 0 }, // L
+ { 0, 1, 0, 0, 0, 0 }, // R
+ { 0, 0, 1, 0, 0, 0 }, // C
+ { 0, 0, 0, 0, INV_SQRT_2, INV_SQRT_2 } // S
+ },
+// 3F1-LFE
+ {
+ { 1, 0, 0, 0, 0, 0 }, // L
+ { 0, 1, 0, 0, 0, 0 }, // R
+ { 0, 0, 1, 0, 0, 0 }, // C
+ { 0, 0, 0, 1, 0, 0 }, // LFE
+ { 0, 0, 0, 0, INV_SQRT_2, INV_SQRT_2 } // S
+ },
+// 2F2
+ {
+ { 1, 0, INV_SQRT_2, 0, 0, 0 }, // L
+ { 0, 1, INV_SQRT_2, 0, 0, 0 }, // R
+ { 0, 0, 0, 0, 1, 0 }, // LS
+ { 0, 0, 0, 0, 0, 1 } // RS
+ },
+// 2F2-LFE
+ {
+ { 1, 0, INV_SQRT_2, 0, 0, 0 }, // L
+ { 0, 1, INV_SQRT_2, 0, 0, 0 }, // R
+ { 0, 0, 0, 1, 0, 0 }, // LFE
+ { 0, 0, 0, 0, 1, 0 }, // LS
+ { 0, 0, 0, 0, 0, 1 } // RS
+ }
+};
+
+/* Convert audio data from 3F2(-LFE) to 1F, 2F, 3F, 2F1, 3F1, 2F2 and their LFEs. */
+template<typename T>
+bool
+downmix_3f2(T const * const in, long inframes, T * out, cubeb_channel_layout in_layout, cubeb_channel_layout out_layout)
+{
+ if ((in_layout != CUBEB_LAYOUT_3F2 && in_layout != CUBEB_LAYOUT_3F2_LFE) ||
+ out_layout < CUBEB_LAYOUT_MONO || out_layout > CUBEB_LAYOUT_2F2_LFE) {
+ return false;
+ }
+
+ unsigned int in_channels = CUBEB_CHANNEL_LAYOUT_MAPS[in_layout].channels;
+ unsigned int out_channels = CUBEB_CHANNEL_LAYOUT_MAPS[out_layout].channels;
+
+ // Conversion from 3F2 to 2F2-LFE or 3F1-LFE is allowed, so we use '<=' instead of '<'.
+ assert(out_channels <= in_channels);
+
+ long out_index = 0;
+ auto & downmix_matrix = DOWNMIX_MATRIX_3F2_LFE[out_layout - CUBEB_LAYOUT_MONO]; // The matrix is started from mono.
+ for (long i = 0; i < inframes * in_channels; i += in_channels) {
+ for (unsigned int j = 0; j < out_channels; ++j) {
+ out[out_index + j] = 0; // Clear its value.
+ for (unsigned int k = 0 ; k < INPUT_CHANNEL_NUM ; ++k) {
+ // 3F2-LFE has 6 channels: L, R, C, LFE, LS, RS, while 3F2 has only 5
+ // channels: L, R, C, LS, RS. Thus, we need to append 0 to LFE(index 3)
+ // to simulate a 3F2-LFE data when input layout is 3F2.
+ T data = (in_layout == CUBEB_LAYOUT_3F2_LFE) ? in[i + k] : (k == 3) ? 0 : in[i + ((k < 3) ? k : k - 1)];
+ out[out_index + j] += downmix_matrix[j][k] * data;
+ }
+ }
+ out_index += out_channels;
+ }
+
+ return true;
+}
+
+/* Map the audio data by channel name. */
+template<class T>
+bool
+mix_remap(T const * const in, long inframes, T * out, cubeb_channel_layout in_layout, cubeb_channel_layout out_layout) {
+ assert(in_layout != out_layout);
+ unsigned int in_channels = CUBEB_CHANNEL_LAYOUT_MAPS[in_layout].channels;
+ unsigned int out_channels = CUBEB_CHANNEL_LAYOUT_MAPS[out_layout].channels;
+
+ uint32_t in_layout_mask = 0;
+ for (unsigned int i = 0 ; i < in_channels ; ++i) {
+ in_layout_mask |= 1 << CHANNEL_INDEX_TO_ORDER[in_layout][i];
+ }
+
+ uint32_t out_layout_mask = 0;
+ for (unsigned int i = 0 ; i < out_channels ; ++i) {
+ out_layout_mask |= 1 << CHANNEL_INDEX_TO_ORDER[out_layout][i];
+ }
+
+ // If there is no matched channel, then do nothing.
+ if (!(out_layout_mask & in_layout_mask)) {
+ return false;
+ }
+
+ long out_index = 0;
+ for (long i = 0; i < inframes * in_channels; i += in_channels) {
+ for (unsigned int j = 0; j < out_channels; ++j) {
+ cubeb_channel channel = CHANNEL_INDEX_TO_ORDER[out_layout][j];
+ uint32_t channel_mask = 1 << channel;
+ int channel_index = CHANNEL_ORDER_TO_INDEX[in_layout][channel];
+ if (in_layout_mask & channel_mask) {
+ assert(channel_index != -1);
+ out[out_index + j] = in[i + channel_index];
+ } else {
+ assert(channel_index == -1);
+ out[out_index + j] = 0;
+ }
+ }
+ out_index += out_channels;
+ }
+
+ return true;
+}
+
+/* Drop the extra channels beyond the provided output channels. */
+template<typename T>
+void
+downmix_fallback(T const * const in, long inframes, T * out, unsigned int in_channels, unsigned int out_channels)
+{
+ assert(in_channels >= out_channels);
+ long out_index = 0;
+ for (long i = 0; i < inframes * in_channels; i += in_channels) {
+ for (unsigned int j = 0; j < out_channels; ++j) {
+ out[out_index + j] = in[i + j];
+ }
+ out_index += out_channels;
+ }
+}
+
+
+template<typename T>
+void
+cubeb_downmix(T const * const in, long inframes, T * out,
+ unsigned int in_channels, unsigned int out_channels,
+ cubeb_channel_layout in_layout, cubeb_channel_layout out_layout)
+{
+ assert(in_channels >= out_channels && in_layout != CUBEB_LAYOUT_UNDEFINED);
+
+ // If the channel number is different from the layout's setting or it's not a
+ // valid audio 5.1 downmix, then we use fallback downmix mechanism.
+ if (out_channels == CUBEB_CHANNEL_LAYOUT_MAPS[out_layout].channels &&
+ in_channels == CUBEB_CHANNEL_LAYOUT_MAPS[in_layout].channels) {
+ if (downmix_3f2(in, inframes, out, in_layout, out_layout)) {
+ return;
+ }
+
+ if (mix_remap(in, inframes, out, in_layout, out_layout)) {
+ return;
+ }
+ }
+
+ downmix_fallback(in, inframes, out, in_channels, out_channels);
+}
+
+/* Upmix function, copies a mono channel into L and R. */
+template<typename T>
+void
+mono_to_stereo(T const * in, long insamples, T * out, unsigned int out_channels)
+{
+ for (long i = 0, j = 0; i < insamples; ++i, j += out_channels) {
+ out[j] = out[j + 1] = in[i];
+ }
+}
+
+template<typename T>
+void
+cubeb_upmix(T const * in, long inframes, T * out,
+ unsigned int in_channels, unsigned int out_channels)
+{
+ assert(out_channels >= in_channels && in_channels > 0);
+
+ /* Either way, if we have 2 or more channels, the first two are L and R. */
+ /* If we are playing a mono stream over stereo speakers, copy the data over. */
+ if (in_channels == 1 && out_channels >= 2) {
+ mono_to_stereo(in, inframes, out, out_channels);
+ } else {
+ /* Copy through. */
+ for (unsigned int i = 0, o = 0; i < inframes * in_channels;
+ i += in_channels, o += out_channels) {
+ for (unsigned int j = 0; j < in_channels; ++j) {
+ out[o + j] = in[i + j];
+ }
+ }
+ }
+
+ /* Check if more channels. */
+ if (out_channels <= 2) {
+ return;
+ }
+
+ /* Put silence in remaining channels. */
+ for (long i = 0, o = 0; i < inframes; ++i, o += out_channels) {
+ for (unsigned int j = 2; j < out_channels; ++j) {
+ out[o + j] = 0.0;
+ }
+ }
+}
+
+bool
+cubeb_should_upmix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer)
+{
+ return mixer->channels > stream->channels;
+}
+
+bool
+cubeb_should_downmix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer)
+{
+ if (mixer->channels > stream->channels || mixer->layout == stream->layout) {
+ return false;
+ }
+
+ return mixer->channels < stream->channels ||
+ // When mixer.channels == stream.channels
+ mixer->layout == CUBEB_LAYOUT_UNDEFINED || // fallback downmix
+ (stream->layout == CUBEB_LAYOUT_3F2 && // 3f2 downmix
+ (mixer->layout == CUBEB_LAYOUT_2F2_LFE ||
+ mixer->layout == CUBEB_LAYOUT_3F1_LFE));
+}
+
+void
+cubeb_downmix_float(float * const in, long inframes, float * out,
+ unsigned int in_channels, unsigned int out_channels,
+ cubeb_channel_layout in_layout, cubeb_channel_layout out_layout)
+{
+ cubeb_downmix(in, inframes, out, in_channels, out_channels, in_layout, out_layout);
+}
+
+void
+cubeb_upmix_float(float * const in, long inframes, float * out,
+ unsigned int in_channels, unsigned int out_channels)
+{
+ cubeb_upmix(in, inframes, out, in_channels, out_channels);
+}
diff --git a/src/cubeb_mixer.h b/src/cubeb_mixer.h
new file mode 100644
index 0000000..dc90410
--- /dev/null
+++ b/src/cubeb_mixer.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_MIXING
+#define CUBEB_MIXING
+
+#include "cubeb/cubeb.h" // for cubeb_channel_layout ,CUBEB_CHANNEL_LAYOUT_MAPS and cubeb_stream_params.
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef enum {
+ CHANNEL_INVALID = -1,
+ CHANNEL_MONO = 0,
+ CHANNEL_LEFT,
+ CHANNEL_RIGHT,
+ CHANNEL_CENTER,
+ CHANNEL_LS,
+ CHANNEL_RS,
+ CHANNEL_RLS,
+ CHANNEL_RCENTER,
+ CHANNEL_RRS,
+ CHANNEL_LFE,
+ CHANNEL_MAX // Max number of supported channels.
+} cubeb_channel;
+
+static cubeb_channel const CHANNEL_INDEX_TO_ORDER[CUBEB_LAYOUT_MAX][CHANNEL_MAX] = {
+ { CHANNEL_INVALID }, // UNSUPPORTED
+ { CHANNEL_LEFT, CHANNEL_RIGHT }, // DUAL_MONO
+ { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE }, // DUAL_MONO_LFE
+ { CHANNEL_MONO }, // MONO
+ { CHANNEL_MONO, CHANNEL_LFE }, // MONO_LFE
+ { CHANNEL_LEFT, CHANNEL_RIGHT }, // STEREO
+ { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE }, // STEREO_LFE
+ { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER }, // 3F
+ { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE }, // 3F_LFE
+ { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_RCENTER }, // 2F1
+ { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE, CHANNEL_RCENTER }, // 2F1_LFE
+ { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_RCENTER }, // 3F1
+ { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_RCENTER }, // 3F1_LFE
+ { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LS, CHANNEL_RS }, // 2F2
+ { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE, CHANNEL_LS, CHANNEL_RS }, // 2F2_LFE
+ { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LS, CHANNEL_RS }, // 3F2
+ { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_LS, CHANNEL_RS }, // 3F2_LFE
+ { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_RCENTER, CHANNEL_LS, CHANNEL_RS }, // 3F3R_LFE
+ { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_RLS, CHANNEL_RRS, CHANNEL_LS, CHANNEL_RS } // 3F4_LFE
+};
+
+bool cubeb_should_upmix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer);
+
+bool cubeb_should_downmix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer);
+
+void cubeb_downmix_float(float * const in, long inframes, float * out,
+ unsigned int in_channels, unsigned int out_channels,
+ cubeb_channel_layout in_layout, cubeb_channel_layout out_layout);
+
+void cubeb_upmix_float(float * const in, long inframes, float * out,
+ unsigned int in_channels, unsigned int out_channels);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif // CUBEB_MIXING
diff --git a/src/cubeb_opensl.c b/src/cubeb_opensl.c
index f12f88c..24dd8e4 100644
--- a/src/cubeb_opensl.c
+++ b/src/cubeb_opensl.c
@@ -1740,6 +1740,7 @@ static struct cubeb_ops const opensl_ops = {
.get_max_channel_count = opensl_get_max_channel_count,
.get_min_latency = opensl_get_min_latency,
.get_preferred_sample_rate = opensl_get_preferred_sample_rate,
+ .get_preferred_channel_layout = NULL,
.enumerate_devices = NULL,
.destroy = opensl_destroy,
.stream_init = opensl_stream_init,
diff --git a/src/cubeb_pulse.c b/src/cubeb_pulse.c
index 7bb6491..fc33937 100644
--- a/src/cubeb_pulse.c
+++ b/src/cubeb_pulse.c
@@ -1410,6 +1410,7 @@ static struct cubeb_ops const pulse_ops = {
.get_max_channel_count = pulse_get_max_channel_count,
.get_min_latency = pulse_get_min_latency,
.get_preferred_sample_rate = pulse_get_preferred_sample_rate,
+ .get_preferred_channel_layout = NULL,
.enumerate_devices = pulse_enumerate_devices,
.destroy = pulse_destroy,
.stream_init = pulse_stream_init,
diff --git a/src/cubeb_sndio.c b/src/cubeb_sndio.c
index 7937897..7aae5d8 100644
--- a/src/cubeb_sndio.c
+++ b/src/cubeb_sndio.c
@@ -366,6 +366,7 @@ static struct cubeb_ops const sndio_ops = {
.get_max_channel_count = sndio_get_max_channel_count,
.get_min_latency = sndio_get_min_latency,
.get_preferred_sample_rate = sndio_get_preferred_sample_rate,
+ .get_preferred_channel_layout = NULL,
.enumerate_devices = NULL,
.destroy = sndio_destroy,
.stream_init = sndio_stream_init,
diff --git a/src/cubeb_wasapi.cpp b/src/cubeb_wasapi.cpp
index 4aa2eae..cea4e8b 100644
--- a/src/cubeb_wasapi.cpp
+++ b/src/cubeb_wasapi.cpp
@@ -27,6 +27,7 @@
#include "cubeb/cubeb.h"
#include "cubeb-internal.h"
+#include "cubeb_mixer.h"
#include "cubeb_resampler.h"
#include "cubeb_utils.h"
@@ -392,19 +393,88 @@ bool has_output(cubeb_stream * stm)
return stm->output_stream_params.rate != 0;
}
-bool should_upmix(cubeb_stream_params & stream, cubeb_stream_params & mixer)
+double stream_to_mix_samplerate_ratio(cubeb_stream_params & stream, cubeb_stream_params & mixer)
{
- return mixer.channels > stream.channels;
+ return double(stream.rate) / mixer.rate;
}
-bool should_downmix(cubeb_stream_params & stream, cubeb_stream_params & mixer)
+/* Convert the channel layout into the corresponding KSAUDIO_CHANNEL_CONFIG.
+ See more: https://msdn.microsoft.com/en-us/library/windows/hardware/ff537083(v=vs.85).aspx */
+#define MASK_DUAL_MONO (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT)
+#define MASK_DUAL_MONO_LFE (MASK_DUAL_MONO | SPEAKER_LOW_FREQUENCY)
+#define MASK_MONO (KSAUDIO_SPEAKER_MONO)
+#define MASK_MONO_LFE (MASK_MONO | SPEAKER_LOW_FREQUENCY)
+#define MASK_STEREO (KSAUDIO_SPEAKER_STEREO)
+#define MASK_STEREO_LFE (MASK_STEREO | SPEAKER_LOW_FREQUENCY)
+#define MASK_3F (MASK_STEREO | SPEAKER_FRONT_CENTER)
+#define MASK_3F_LFE (MASK_3F | SPEAKER_LOW_FREQUENCY)
+#define MASK_2F1 (MASK_STEREO | SPEAKER_BACK_CENTER)
+#define MASK_2F1_LFE (MASK_2F1 | SPEAKER_LOW_FREQUENCY)
+#define MASK_3F1 (KSAUDIO_SPEAKER_SURROUND)
+#define MASK_3F1_LFE (MASK_3F1 | SPEAKER_LOW_FREQUENCY)
+#define MASK_2F2 (MASK_STEREO | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT)
+#define MASK_2F2_LFE (MASK_2F2 | SPEAKER_LOW_FREQUENCY)
+#define MASK_3F2 (MASK_3F | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT)
+#define MASK_3F2_LFE (KSAUDIO_SPEAKER_5POINT1_SURROUND)
+#define MASK_3F3R_LFE (MASK_3F2_LFE | SPEAKER_BACK_CENTER)
+#define MASK_3F4_LFE (KSAUDIO_SPEAKER_7POINT1_SURROUND)
+
+static DWORD
+channel_layout_to_mask(cubeb_channel_layout layout)
{
- return mixer.channels < stream.channels;
+ XASSERT(layout > CUBEB_LAYOUT_UNDEFINED && layout < CUBEB_LAYOUT_MAX &&
+ "This mask conversion is not allowed.");
+
+ // 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 DWORD map[CUBEB_LAYOUT_MAX] = {
+ 0, // CUBEB_LAYOUT_UNDEFINED (this won't be used.)
+ MASK_DUAL_MONO, // CUBEB_LAYOUT_DUAL_MONO
+ MASK_DUAL_MONO_LFE, // CUBEB_LAYOUT_DUAL_MONO_LFE
+ MASK_MONO, // CUBEB_LAYOUT_MONO
+ MASK_MONO_LFE, // CUBEB_LAYOUT_MONO_LFE
+ MASK_STEREO, // CUBEB_LAYOUT_STEREO
+ MASK_STEREO_LFE, // CUBEB_LAYOUT_STEREO_LFE
+ MASK_3F, // CUBEB_LAYOUT_3F
+ MASK_3F_LFE, // CUBEB_LAYOUT_3F_LFE
+ MASK_2F1, // CUBEB_LAYOUT_2F1
+ MASK_2F1_LFE, // CUBEB_LAYOUT_2F1_LFE
+ MASK_3F1, // CUBEB_LAYOUT_3F1
+ MASK_3F1_LFE, // CUBEB_LAYOUT_3F1_LFE
+ MASK_2F2, // CUBEB_LAYOUT_2F2
+ MASK_2F2_LFE, // CUBEB_LAYOUT_2F2_LFE
+ MASK_3F2, // CUBEB_LAYOUT_3F2
+ MASK_3F2_LFE, // CUBEB_LAYOUT_3F2_LFE
+ MASK_3F3R_LFE, // CUBEB_LAYOUT_3F3R_LFE
+ MASK_3F4_LFE, // CUBEB_LAYOUT_3F4_LFE
+ };
+ return map[layout];
}
-double stream_to_mix_samplerate_ratio(cubeb_stream_params & stream, cubeb_stream_params & mixer)
+cubeb_channel_layout
+mask_to_channel_layout(DWORD mask)
{
- return double(stream.rate) / mixer.rate;
+ switch (mask) {
+ // MASK_DUAL_MONO(_LFE) is same as STEREO(_LFE), so we skip it.
+ case MASK_MONO: return CUBEB_LAYOUT_MONO;
+ case MASK_MONO_LFE: return CUBEB_LAYOUT_MONO_LFE;
+ case MASK_STEREO: return CUBEB_LAYOUT_STEREO;
+ case MASK_STEREO_LFE: return CUBEB_LAYOUT_STEREO_LFE;
+ case MASK_3F: return CUBEB_LAYOUT_3F;
+ case MASK_3F_LFE: return CUBEB_LAYOUT_3F_LFE;
+ case MASK_2F1: return CUBEB_LAYOUT_2F1;
+ case MASK_2F1_LFE: return CUBEB_LAYOUT_2F1_LFE;
+ case MASK_3F1: return CUBEB_LAYOUT_3F1;
+ case MASK_3F1_LFE: return CUBEB_LAYOUT_3F1_LFE;
+ case MASK_2F2: return CUBEB_LAYOUT_2F2;
+ case MASK_2F2_LFE: return CUBEB_LAYOUT_2F2_LFE;
+ case MASK_3F2: return CUBEB_LAYOUT_3F2;
+ case MASK_3F2_LFE: return CUBEB_LAYOUT_3F2_LFE;
+ case MASK_3F3R_LFE: return CUBEB_LAYOUT_3F3R_LFE;
+ case MASK_3F4_LFE: return CUBEB_LAYOUT_3F4_LFE;
+ default: return CUBEB_LAYOUT_UNDEFINED;
+ }
}
uint32_t
@@ -438,71 +508,13 @@ frames_to_hns(cubeb_stream * stm, uint32_t frames)
return frames * 1000 / get_rate(stm);
}
-/* Upmix function, copies a mono channel into L and R */
-template<typename T>
-void
-mono_to_stereo(T * in, long insamples, T * out, int32_t out_channels)
-{
- for (int i = 0, j = 0; i < insamples; ++i, j += out_channels) {
- out[j] = out[j + 1] = in[i];
- }
-}
-
-template<typename T>
-void
-upmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels)
-{
- XASSERT(out_channels >= in_channels && in_channels > 0);
-
- /* Either way, if we have 2 or more channels, the first two are L and R. */
- /* If we are playing a mono stream over stereo speakers, copy the data over. */
- if (in_channels == 1 && out_channels >= 2) {
- mono_to_stereo(in, inframes, out, out_channels);
- } else {
- /* Copy through. */
- for (int i = 0, o = 0; i < inframes * in_channels;
- i += in_channels, o += out_channels) {
- for (int j = 0; j < in_channels; ++j) {
- out[o + j] = in[i + j];
- }
- }
- }
-
- /* Check if more channels. */
- if (out_channels <= 2) {
- return;
- }
-
- /* Put silence in remaining channels. */
- for (long i = 0, o = 0; i < inframes; ++i, o += out_channels) {
- for (int j = 2; j < out_channels; ++j) {
- out[o + j] = 0.0;
- }
- }
-}
-
-template<typename T>
-void
-downmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels)
-{
- XASSERT(in_channels >= out_channels);
- /* We could use a downmix matrix here, applying mixing weight based on the
- channel, but directsound and winmm simply drop the channels that cannot be
- rendered by the hardware, so we do the same for consistency. */
- long out_index = 0;
- for (long i = 0; i < inframes * in_channels; i += in_channels) {
- for (int j = 0; j < out_channels; ++j) {
- out[out_index + j] = in[i + j];
- }
- out_index += out_channels;
- }
-}
-
/* This returns the size of a frame in the stream, before the eventual upmix
occurs. */
static size_t
frames_to_bytes_before_mix(cubeb_stream * stm, size_t frames)
{
+ // This is called only when we has a output client.
+ XASSERT(has_output(stm));
size_t stream_frame_size = stm->output_stream_params.channels * sizeof(float);
return stream_frame_size * frames;
}
@@ -518,8 +530,8 @@ refill(cubeb_stream * stm, float * input_buffer, long input_frames_count,
avoid a copy. */
float * dest = nullptr;
if (has_output(stm)) {
- if (should_upmix(stm->output_stream_params, stm->output_mix_params) ||
- should_downmix(stm->output_stream_params, stm->output_mix_params)) {
+ if (cubeb_should_upmix(&stm->output_stream_params, &stm->output_mix_params) ||
+ cubeb_should_downmix(&stm->output_stream_params, &stm->output_mix_params)) {
dest = stm->mix_buffer.data();
} else {
dest = output_buffer;
@@ -550,12 +562,13 @@ refill(cubeb_stream * stm, float * input_buffer, long input_frames_count,
XASSERT(out_frames == output_frames_needed || stm->draining || !has_output(stm));
if (has_output(stm)) {
- if (should_upmix(stm->output_stream_params, stm->output_mix_params)) {
- upmix(dest, out_frames, output_buffer,
- stm->output_stream_params.channels, stm->output_mix_params.channels);
- } else if (should_downmix(stm->output_stream_params, stm->output_mix_params)) {
- downmix(dest, out_frames, output_buffer,
- stm->output_stream_params.channels, stm->output_mix_params.channels);
+ if (cubeb_should_upmix(&stm->output_stream_params, &stm->output_mix_params)) {
+ cubeb_upmix_float(dest, out_frames, output_buffer,
+ stm->output_stream_params.channels, stm->output_mix_params.channels);
+ } else if (cubeb_should_downmix(&stm->output_stream_params, &stm->output_mix_params)) {
+ cubeb_downmix_float(dest, out_frames, output_buffer,
+ stm->output_stream_params.channels, stm->output_mix_params.channels,
+ stm->output_stream_params.layout, stm->output_mix_params.layout);
}
}
@@ -614,23 +627,25 @@ bool get_input_buffer(cubeb_stream * stm)
LOG("insert silence: ps=%u", packet_size);
stm->linear_input_buffer.push_silence(packet_size * stm->input_stream_params.channels);
} else {
- if (should_upmix(stm->input_mix_params, stm->input_stream_params)) {
+ if (cubeb_should_upmix(&stm->input_mix_params, &stm->input_stream_params)) {
bool ok = stm->linear_input_buffer.reserve(stm->linear_input_buffer.length() +
packet_size * stm->input_stream_params.channels);
XASSERT(ok);
- upmix(reinterpret_cast<float*>(input_packet), packet_size,
- stm->linear_input_buffer.data() + stm->linear_input_buffer.length(),
- stm->input_mix_params.channels,
- stm->input_stream_params.channels);
+ cubeb_upmix_float(reinterpret_cast<float*>(input_packet), packet_size,
+ stm->linear_input_buffer.data() + stm->linear_input_buffer.length(),
+ stm->input_mix_params.channels,
+ stm->input_stream_params.channels);
stm->linear_input_buffer.set_length(stm->linear_input_buffer.length() + packet_size * stm->input_stream_params.channels);
- } else if (should_downmix(stm->input_mix_params, stm->input_stream_params)) {
+ } else if (cubeb_should_downmix(&stm->input_mix_params, &stm->input_stream_params)) {
bool ok = stm->linear_input_buffer.reserve(stm->linear_input_buffer.length() +
packet_size * stm->input_stream_params.channels);
XASSERT(ok);
- downmix(reinterpret_cast<float*>(input_packet), packet_size,
- stm->linear_input_buffer.data() + stm->linear_input_buffer.length(),
- stm->input_mix_params.channels,
- stm->input_stream_params.channels);
+ cubeb_downmix_float(reinterpret_cast<float*>(input_packet), packet_size,
+ stm->linear_input_buffer.data() + stm->linear_input_buffer.length(),
+ stm->input_mix_params.channels,
+ stm->input_stream_params.channels,
+ stm->input_mix_params.layout,
+ stm->input_stream_params.layout);
stm->linear_input_buffer.set_length(stm->linear_input_buffer.length() + packet_size * stm->input_stream_params.channels);
} else {
stm->linear_input_buffer.push(reinterpret_cast<float*>(input_packet),
@@ -1357,6 +1372,44 @@ wasapi_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
return CUBEB_OK;
}
+int
+wasapi_get_preferred_channel_layout(cubeb * context, cubeb_channel_layout * layout)
+{
+ HRESULT hr;
+ auto_com com;
+ if (!com.ok()) {
+ return CUBEB_ERROR;
+ }
+
+ com_ptr<IMMDevice> device;
+ hr = get_default_endpoint(device, eRender);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+
+ com_ptr<IAudioClient> client;
+ hr = device->Activate(__uuidof(IAudioClient),
+ CLSCTX_INPROC_SERVER,
+ NULL, client.receive_vpp());
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+
+ WAVEFORMATEX * tmp = nullptr;
+ hr = client->GetMixFormat(&tmp);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+ com_heap_ptr<WAVEFORMATEX> mix_format(tmp);
+
+ WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get());
+ *layout = mask_to_channel_layout(format_pcm->dwChannelMask);
+
+ LOG("Preferred channel layout: %s", CUBEB_CHANNEL_LAYOUT_MAPS[*layout].name);
+
+ return CUBEB_OK;
+}
+
void wasapi_stream_destroy(cubeb_stream * stm);
/* Based on the mix format and the stream format, try to find a way to play
@@ -1364,12 +1417,7 @@ void wasapi_stream_destroy(cubeb_stream * stm);
static void
handle_channel_layout(cubeb_stream * stm, com_heap_ptr<WAVEFORMATEX> & mix_format, const cubeb_stream_params * stream_params)
{
- /* Common case: the hardware is stereo. Up-mixing and down-mixing will be
- handled in the callback. */
- if (mix_format->nChannels <= 2) {
- return;
- }
-
+ XASSERT(stream_params->layout != CUBEB_LAYOUT_UNDEFINED);
/* The docs say that GetMixFormat is always of type WAVEFORMATEXTENSIBLE [1],
so the reinterpret_cast below should be safe. In practice, this is not
true, and we just want to bail out and let the rest of the code find a good
@@ -1384,19 +1432,10 @@ handle_channel_layout(cubeb_stream * stm, com_heap_ptr<WAVEFORMATEX> & mix_form
/* Stash a copy of the original mix format in case we need to restore it later. */
WAVEFORMATEXTENSIBLE hw_mix_format = *format_pcm;
- /* The hardware is in surround mode, we want to only use front left and front
- right. Try that, and check if it works. */
- switch (stream_params->channels) {
- case 1: /* Mono */
- format_pcm->dwChannelMask = KSAUDIO_SPEAKER_MONO;
- break;
- case 2: /* Stereo */
- format_pcm->dwChannelMask = KSAUDIO_SPEAKER_STEREO;
- break;
- default:
- XASSERT(false && "Channel layout not supported.");
- break;
- }
+ /* Get the channel mask by the channel layout.
+ If the layout is not supported, we will get a closest settings below. */
+ format_pcm->dwChannelMask = channel_layout_to_mask(stream_params->layout);
+
mix_format->nChannels = stream_params->channels;
mix_format->nBlockAlign = mix_format->wBitsPerSample * mix_format->nChannels / 8;
mix_format->nAvgBytesPerSec = mix_format->nSamplesPerSec * mix_format->nBlockAlign;
@@ -1497,16 +1536,39 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
}
com_heap_ptr<WAVEFORMATEX> mix_format(tmp);
- handle_channel_layout(stm, mix_format, stream_params);
+ /* Set channel layout only when there're more than two channels. Otherwise,
+ * use the default setting retrieved from the stream format of the audio
+ * engine's internal processing by GetMixFormat. */
+ if (mix_format->nChannels > 2) {
+ /* Currently, we only support mono and stereo for capture stream. */
+ if (direction == eCapture) {
+ XASSERT(false && "Multichannel recording is not supported.");
+ }
+
+ handle_channel_layout(stm, mix_format, stream_params);
+ }
/* Shared mode WASAPI always supports float32 sample format, so this
* is safe. */
mix_params->format = CUBEB_SAMPLE_FLOAT32NE;
mix_params->rate = mix_format->nSamplesPerSec;
mix_params->channels = mix_format->nChannels;
- LOG("Setup requested=[f=%d r=%u c=%u] mix=[f=%d r=%u c=%u]",
+ WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get());
+ mix_params->layout = mask_to_channel_layout(format_pcm->dwChannelMask);
+ if (mix_params->layout == CUBEB_LAYOUT_UNDEFINED) {
+ LOG("Output using undefined layout!\n");
+ } else if (mix_format->nChannels != CUBEB_CHANNEL_LAYOUT_MAPS[mix_params->layout].channels) {
+ // The CUBEB_CHANNEL_LAYOUT_MAPS[mix_params->layout].channels may be
+ // different from the mix_params->channels. 6 channel ouput with stereo
+ // layout is acceptable in Windows. If this happens, it should not downmix
+ // audio according to layout.
+ LOG("Channel count is different from the layout standard!\n");
+ }
+ LOG("Setup requested=[f=%d r=%u c=%u l=%s] mix=[f=%d r=%u c=%u l=%s]",
stream_params->format, stream_params->rate, stream_params->channels,
- mix_params->format, mix_params->rate, mix_params->channels);
+ CUBEB_CHANNEL_LAYOUT_MAPS[stream_params->layout].name,
+ mix_params->format, mix_params->rate, mix_params->channels,
+ CUBEB_CHANNEL_LAYOUT_MAPS[mix_params->layout].name);
hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
@@ -1528,8 +1590,8 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
}
// Input is up/down mixed when depacketized in get_input_buffer.
if (has_output(stm) &&
- (should_upmix(*stream_params, *mix_params) ||
- should_downmix(*stream_params, *mix_params))) {
+ (cubeb_should_upmix(stream_params, mix_params) ||
+ cubeb_should_downmix(stream_params, mix_params))) {
stm->mix_buffer.resize(frames_to_bytes_before_mix(stm, *buffer_frame_count));
}
@@ -1710,10 +1772,14 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
if (input_stream_params) {
stm->input_stream_params = *input_stream_params;
stm->input_device = utf8_to_wstr(reinterpret_cast<char const *>(input_device));
+ // Make sure the layout matches the channel count.
+ XASSERT(stm->input_stream_params.channels == CUBEB_CHANNEL_LAYOUT_MAPS[stm->input_stream_params.layout].channels);
}
if (output_stream_params) {
stm->output_stream_params = *output_stream_params;
stm->output_device = utf8_to_wstr(reinterpret_cast<char const *>(output_device));
+ // Make sure the layout matches the channel count.
+ XASSERT(stm->output_stream_params.channels == CUBEB_CHANNEL_LAYOUT_MAPS[stm->output_stream_params.layout].channels);
}
stm->latency = latency_frames;
@@ -2249,6 +2315,7 @@ cubeb_ops const wasapi_ops = {
/*.get_max_channel_count =*/ wasapi_get_max_channel_count,
/*.get_min_latency =*/ wasapi_get_min_latency,
/*.get_preferred_sample_rate =*/ wasapi_get_preferred_sample_rate,
+ /*.get_preferred_channel_layout =*/ wasapi_get_preferred_channel_layout,
/*.enumerate_devices =*/ wasapi_enumerate_devices,
/*.destroy =*/ wasapi_destroy,
/*.stream_init =*/ wasapi_stream_init,
diff --git a/src/cubeb_winmm.c b/src/cubeb_winmm.c
index e21a014..9be73a4 100644
--- a/src/cubeb_winmm.c
+++ b/src/cubeb_winmm.c
@@ -1048,6 +1048,7 @@ static struct cubeb_ops const winmm_ops = {
/*.get_max_channel_count=*/ winmm_get_max_channel_count,
/*.get_min_latency=*/ winmm_get_min_latency,
/*.get_preferred_sample_rate =*/ winmm_get_preferred_sample_rate,
+ /*.get_preferred_channel_layout =*/ NULL,
/*.enumerate_devices =*/ winmm_enumerate_devices,
/*.destroy =*/ winmm_destroy,
/*.stream_init =*/ winmm_stream_init,