diff options
author | Chun-Min Chang <[email protected]> | 2016-12-21 13:18:07 +0800 |
---|---|---|
committer | Matthew Gregan <[email protected]> | 2016-12-20 19:18:07 -1000 |
commit | a900d6e511c3286956c38ca60162790ca6e8e18d (patch) | |
tree | 12969af1e27b0d0d127127a60d770b52100f76fb /src | |
parent | c1e1e45dee4367c6f839b903417998049ad1baeb (diff) | |
download | cubeb-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.h | 29 | ||||
-rw-r--r-- | src/cubeb.c | 15 | ||||
-rw-r--r-- | src/cubeb_alsa.c | 1 | ||||
-rw-r--r-- | src/cubeb_audiotrack.c | 1 | ||||
-rw-r--r-- | src/cubeb_audiounit.cpp | 5 | ||||
-rw-r--r-- | src/cubeb_jack.cpp | 1 | ||||
-rw-r--r-- | src/cubeb_kai.c | 1 | ||||
-rw-r--r-- | src/cubeb_mixer.cpp | 326 | ||||
-rw-r--r-- | src/cubeb_mixer.h | 69 | ||||
-rw-r--r-- | src/cubeb_opensl.c | 1 | ||||
-rw-r--r-- | src/cubeb_pulse.c | 1 | ||||
-rw-r--r-- | src/cubeb_sndio.c | 1 | ||||
-rw-r--r-- | src/cubeb_wasapi.cpp | 283 | ||||
-rw-r--r-- | src/cubeb_winmm.c | 1 |
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, |