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
This commit is contained in:
Chun-Min Chang 2016-12-21 13:18:07 +08:00 коммит произвёл Matthew Gregan
Родитель c1e1e45dee
Коммит a900d6e511
24 изменённых файлов: 955 добавлений и 138 удалений

Просмотреть файл

@ -33,6 +33,7 @@ endif()
add_library(cubeb
src/cubeb.c
src/cubeb_mixer.cpp
src/cubeb_resampler.cpp
src/cubeb_panner.cpp
$<TARGET_OBJECTS:speex>)
@ -199,6 +200,12 @@ target_include_directories(test_ring_array PRIVATE src)
target_link_libraries(test_ring_array PRIVATE cubeb gtest_main)
add_test(ring_array test_ring_array)
add_executable(test_mixer test/test_mixer.cpp)
target_include_directories(test_mixer PRIVATE ${gtest_SOURCE_DIR}/include)
target_include_directories(test_mixer PRIVATE src)
target_link_libraries(test_mixer PRIVATE cubeb gtest_main)
add_test(mixer test_mixer)
add_executable(test_utils test/test_utils.cpp)
target_include_directories(test_utils PRIVATE ${gtest_SOURCE_DIR}/include)
target_include_directories(test_utils PRIVATE src)

Просмотреть файл

@ -176,12 +176,71 @@ typedef enum {
CUBEB_LOG_VERBOSE = 2, /**< Verbose logging of callbacks, can have performance implications. */
} cubeb_log_level;
/** SMPTE channel layout (also known as wave order)
* DUAL-MONO L R
* DUAL-MONO-LFE L R LFE
* MONO M
* MONO-LFE M LFE
* STEREO L R
* STEREO-LFE L R LFE
* 3F L R C
* 3F-LFE L R C LFE
* 2F1 L R S
* 2F1-LFE L R LFE S
* 3F1 L R C S
* 3F1-LFE L R C LFE S
* 2F2 L R LS RS
* 2F2-LFE L R LFE LS RS
* 3F2 L R C LS RS
* 3F2-LFE L R C LFE LS RS
* 3F3R-LFE L R C LFE RC LS RS
* 3F4-LFE L R C LFE RLS RRS LS RS
*
* The abbreviation of channel name is defined in following table:
* Abbr Channel name
* ---------------------------
* M Mono
* L Left
* R Right
* C Center
* LS Left Surround
* RS Right Surround
* RLS Rear Left Surround
* RC Rear Center
* RRS Rear Right Surround
* LFE Low Frequency Effects
*/
typedef enum {
CUBEB_LAYOUT_UNDEFINED, // Indicate the speaker's layout is undefined.
CUBEB_LAYOUT_DUAL_MONO,
CUBEB_LAYOUT_DUAL_MONO_LFE,
CUBEB_LAYOUT_MONO,
CUBEB_LAYOUT_MONO_LFE,
CUBEB_LAYOUT_STEREO,
CUBEB_LAYOUT_STEREO_LFE,
CUBEB_LAYOUT_3F,
CUBEB_LAYOUT_3F_LFE,
CUBEB_LAYOUT_2F1,
CUBEB_LAYOUT_2F1_LFE,
CUBEB_LAYOUT_3F1,
CUBEB_LAYOUT_3F1_LFE,
CUBEB_LAYOUT_2F2,
CUBEB_LAYOUT_2F2_LFE,
CUBEB_LAYOUT_3F2,
CUBEB_LAYOUT_3F2_LFE,
CUBEB_LAYOUT_3F3R_LFE,
CUBEB_LAYOUT_3F4_LFE,
CUBEB_LAYOUT_MAX
} cubeb_channel_layout;
/** Stream format initialization parameters. */
typedef struct {
cubeb_sample_format format; /**< Requested sample format. One of
#cubeb_sample_format. */
unsigned int rate; /**< Requested sample rate. Valid range is [1000, 192000]. */
unsigned int channels; /**< Requested channel count. Valid range is [1, 8]. */
cubeb_sample_format format; /**< Requested sample format. One of
#cubeb_sample_format. */
unsigned int rate; /**< Requested sample rate. Valid range is [1000, 192000]. */
unsigned int channels; /**< Requested channel count. Valid range is [1, 8]. */
cubeb_channel_layout layout; /**< Requested channel layout. This must be consistent with the provided channels. */
#if defined(__ANDROID__)
cubeb_stream_type stream_type; /**< Used to map Android audio stream types */
#endif
@ -376,7 +435,7 @@ CUBEB_EXPORT int cubeb_get_max_channel_count(cubeb * context, uint32_t * max_cha
/** Get the minimal latency value, in frames, that is guaranteed to work
when creating a stream for the specified sample rate. This is platform,
hardware and backend dependant.
hardware and backend dependent.
@param context A pointer to the cubeb context.
@param params On some backends, the minimum achievable latency depends on
the characteristics of the stream.
@ -390,7 +449,7 @@ CUBEB_EXPORT int cubeb_get_min_latency(cubeb * context,
uint32_t * latency_frames);
/** Get the preferred sample rate for this backend: this is hardware and
platform dependant, and can avoid resampling, and/or trigger fastpaths.
platform dependent, and can avoid resampling, and/or trigger fastpaths.
@param context A pointer to the cubeb context.
@param rate The samplerate (in Hz) the current configuration prefers.
@retval CUBEB_OK
@ -398,6 +457,15 @@ CUBEB_EXPORT int cubeb_get_min_latency(cubeb * context,
@retval CUBEB_ERROR_NOT_SUPPORTED */
CUBEB_EXPORT int cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate);
/** Get the preferred layout for this backend: this is hardware and
platform dependent.
@param context A pointer to the cubeb context.
@param layout The layout of the current speaker configuration.
@retval CUBEB_OK
@retval CUBEB_ERROR_INVALID_PARAMETER
@retval CUBEB_ERROR_NOT_SUPPORTED */
CUBEB_EXPORT int cubeb_get_preferred_channel_layout(cubeb * context, cubeb_channel_layout * layout);
/** Destroy an application context. This must be called after all stream have
* been destroyed.
@param context A pointer to the cubeb context.*/

Просмотреть файл

@ -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);

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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,

Просмотреть файл

@ -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,

Просмотреть файл

@ -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,

Просмотреть файл

@ -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,

Просмотреть файл

@ -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,

326
src/cubeb_mixer.cpp Normal file
Просмотреть файл

@ -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);
}

69
src/cubeb_mixer.h Normal file
Просмотреть файл

@ -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

Просмотреть файл

@ -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,

Просмотреть файл

@ -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,

Просмотреть файл

@ -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,

Просмотреть файл

@ -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,21 +393,90 @@ bool has_output(cubeb_stream * stm)
return stm->output_stream_params.rate != 0;
}
bool should_upmix(cubeb_stream_params & stream, cubeb_stream_params & mixer)
{
return mixer.channels > stream.channels;
}
bool should_downmix(cubeb_stream_params & stream, cubeb_stream_params & mixer)
{
return mixer.channels < stream.channels;
}
double stream_to_mix_samplerate_ratio(cubeb_stream_params & stream, cubeb_stream_params & mixer)
{
return double(stream.rate) / mixer.rate;
}
/* 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)
{
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];
}
cubeb_channel_layout
mask_to_channel_layout(DWORD mask)
{
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
get_rate(cubeb_stream * stm)
{
@ -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,

Просмотреть файл

@ -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,

Просмотреть файл

@ -16,6 +16,13 @@
#include <unistd.h>
#endif
template<typename T, size_t N>
constexpr size_t
ARRAY_LENGTH(T(&)[N])
{
return N;
}
void delay(unsigned int ms)
{
#if defined(_WIN32)
@ -30,6 +37,34 @@ void delay(unsigned int ms)
#define M_PI 3.14159265358979323846
#endif
typedef struct {
char const * name;
unsigned int const channels;
cubeb_channel_layout const layout;
} layout_info;
layout_info const layout_infos[CUBEB_LAYOUT_MAX] = {
{ "unsupported", 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 }
};
int has_available_input_device(cubeb * ctx)
{
cubeb_device_collection * devices;

Просмотреть файл

@ -24,7 +24,6 @@
#define M_PI 3.14159265358979323846
#endif
#define NELEMS(x) ((int) (sizeof(x) / sizeof(x[0])))
#define VOLUME 0.2
float get_frequency(int channel_index)
@ -118,7 +117,7 @@ int supports_channel_count(const char* backend_id, int nchannels)
(strcmp(backend_id, "opensl") != 0 && strcmp(backend_id, "audiotrack") != 0);
}
int run_test(int num_channels, int sampling_rate, int is_float)
int run_test(int num_channels, layout_info layout, int sampling_rate, int is_float)
{
int r = CUBEB_OK;
@ -142,12 +141,13 @@ int run_test(int num_channels, int sampling_rate, int is_float)
goto cleanup;
}
fprintf(stderr, "Testing %d channel(s), %d Hz, %s (%s)\n", num_channels, sampling_rate, is_float ? "float" : "short", cubeb_get_backend_id(ctx));
fprintf(stderr, "Testing %d channel(s), layout: %s, %d Hz, %s (%s)\n", num_channels, layout.name, sampling_rate, is_float ? "float" : "short", cubeb_get_backend_id(ctx));
cubeb_stream_params params;
params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
params.rate = sampling_rate;
params.channels = num_channels;
params.layout = layout.layout;
synth = synth_create(params.channels, params.rate);
if (synth == NULL) {
@ -200,6 +200,7 @@ int run_panning_volume_test(int is_float)
params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
params.rate = 44100;
params.channels = 2;
params.layout = CUBEB_LAYOUT_STEREO;
synth = synth_create(params.channels, params.rate);
if (synth == NULL) {
@ -259,7 +260,7 @@ TEST(cubeb, run_panning_volume_test_float)
TEST(cubeb, run_channel_rate_test)
{
int channel_values[] = {
unsigned int channel_values[] = {
1,
2,
3,
@ -274,13 +275,16 @@ TEST(cubeb, run_channel_rate_test)
48000,
};
for(int j = 0; j < NELEMS(channel_values); ++j) {
for(int i = 0; i < NELEMS(freq_values); ++i) {
for(unsigned int j = 0; j < ARRAY_LENGTH(channel_values); ++j) {
for(unsigned int i = 0; i < ARRAY_LENGTH(freq_values); ++i) {
ASSERT_TRUE(channel_values[j] < MAX_NUM_CHANNELS);
fprintf(stderr, "--------------------------\n");
ASSERT_EQ(run_test(channel_values[j], freq_values[i], 0), CUBEB_OK);
ASSERT_EQ(run_test(channel_values[j], freq_values[i], 1), CUBEB_OK);
for (unsigned int k = 0 ; k < ARRAY_LENGTH(layout_infos); ++k ) {
if (layout_infos[k].channels == channel_values[j]) {
ASSERT_EQ(run_test(channel_values[j], layout_infos[k], freq_values[i], 0), CUBEB_OK);
ASSERT_EQ(run_test(channel_values[j], layout_infos[k], freq_values[i], 1), CUBEB_OK);
}
}
}
}
}

Просмотреть файл

@ -108,9 +108,11 @@ TEST(cubeb, duplex)
input_params.format = STREAM_FORMAT;
input_params.rate = 48000;
input_params.channels = 1;
input_params.layout = CUBEB_LAYOUT_MONO;
output_params.format = STREAM_FORMAT;
output_params.rate = 48000;
output_params.channels = 2;
output_params.layout = CUBEB_LAYOUT_STEREO;
r = cubeb_get_min_latency(ctx, output_params, &latency_frames);

Просмотреть файл

@ -9,6 +9,7 @@ TEST(cubeb, latency)
uint32_t max_channels;
uint32_t preferred_rate;
uint32_t latency_frames;
cubeb_channel_layout layout;
r = cubeb_init(&ctx, "Cubeb audio test");
ASSERT_EQ(r, CUBEB_OK);
@ -25,10 +26,17 @@ TEST(cubeb, latency)
ASSERT_GT(preferred_rate, 0u);
}
r = cubeb_get_preferred_channel_layout(ctx, &layout);
ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
if (r == CUBEB_OK) {
ASSERT_NE(layout, CUBEB_LAYOUT_UNDEFINED);
}
cubeb_stream_params params = {
CUBEB_SAMPLE_FLOAT32NE,
preferred_rate,
max_channels
max_channels,
(r == CUBEB_OK) ? layout : CUBEB_LAYOUT_UNDEFINED
};
r = cubeb_get_min_latency(ctx, params, &latency_frames);
ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);

175
test/test_mixer.cpp Normal file
Просмотреть файл

@ -0,0 +1,175 @@
/*
* Copyright © 2016 Mozilla Foundation
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
#include "gtest/gtest.h"
#include "cubeb/cubeb.h"
#include "cubeb_mixer.h"
#include "common.h"
#include <vector>
using std::vector;
#define STREAM_FREQUENCY 48000
#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
float const M = 1.0f; // Mono
float const L = 2.0f; // Left
float const R = 3.0f; // Right
float const C = 4.0f; // Center
float const LS = 5.0f; // Left Surround
float const RS = 6.0f; // Right Surround
float const RLS = 7.0f; // Rear Left Surround
float const RC = 8.0f; // Rear Center
float const RRS = 9.0f; // Rear Right Surround
float const LFE = 10.0f; // Low Frequency Effects
float const INV_SQRT_2 = 0.707106f; // 1/sqrt(2)
static float const DOWNMIX_3F2_RESULTS[2][12][5] = {
// 3F2
{
{ INV_SQRT_2*(L+R) + C + 0.5f*(LS+RS) }, // Mono
{ INV_SQRT_2*(L+R) + C + 0.5f*(LS+RS), 0 }, // Mono-LFE
{ L + INV_SQRT_2*(C+LS), R + INV_SQRT_2*(C+RS) }, // Stereo
{ L + INV_SQRT_2*(C+LS), R + INV_SQRT_2*(C+RS), 0 }, // Stereo-LFE
{ L + INV_SQRT_2*LS, R + INV_SQRT_2*RS, C }, // 3F
{ L + INV_SQRT_2*LS, R + INV_SQRT_2*RS, C, 0 }, // 3F-LFE
{ L + C*INV_SQRT_2, R + C*INV_SQRT_2, INV_SQRT_2*(LS+RS) }, // 2F1
{ L + C*INV_SQRT_2, R + C*INV_SQRT_2, 0, INV_SQRT_2*(LS+RS) }, // 2F1-LFE
{ L, R, C, INV_SQRT_2*(LS+RS) }, // 3F1
{ L, R, C, 0, INV_SQRT_2*(LS+RS) }, // 3F1-LFE
{ L + INV_SQRT_2*C, R + INV_SQRT_2*C, LS, RS }, // 2F2
{ L + INV_SQRT_2*C, R + INV_SQRT_2*C, 0, LS, RS } // 2F2-LFE
},
// 3F2-LFE
{
{ INV_SQRT_2*(L+R) + C + 0.5f*(LS+RS) }, // Mono
{ INV_SQRT_2*(L+R) + C + 0.5f*(LS+RS), LFE }, // Mono-LFE
{ L + INV_SQRT_2*(C+LS), R + INV_SQRT_2*(C+RS) }, // Stereo
{ L + INV_SQRT_2*(C+LS), R + INV_SQRT_2*(C+RS), LFE }, // Stereo-LFE
{ L + INV_SQRT_2*LS, R + INV_SQRT_2*RS, C }, // 3F
{ L + INV_SQRT_2*LS, R + INV_SQRT_2*RS, C, LFE }, // 3F-LFE
{ L + C*INV_SQRT_2, R + C*INV_SQRT_2, INV_SQRT_2*(LS+RS) }, // 2F1
{ L + C*INV_SQRT_2, R + C*INV_SQRT_2, LFE, INV_SQRT_2*(LS+RS) }, // 2F1-LFE
{ L, R, C, INV_SQRT_2*(LS+RS) }, // 3F1
{ L, R, C, LFE, INV_SQRT_2*(LS+RS) }, // 3F1-LFE
{ L + INV_SQRT_2*C, R + INV_SQRT_2*C, LS, RS }, // 2F2
{ L + INV_SQRT_2*C, R + INV_SQRT_2*C, LFE, LS, RS } // 2F2-LFE
}
};
typedef struct {
cubeb_channel_layout layout;
float data[10];
} audio_input;
audio_input audio_inputs[CUBEB_LAYOUT_MAX] = {
{ CUBEB_LAYOUT_UNDEFINED, { } },
{ CUBEB_LAYOUT_DUAL_MONO, { L, R } },
{ CUBEB_LAYOUT_DUAL_MONO_LFE, { L, R, LFE } },
{ CUBEB_LAYOUT_MONO, { M } },
{ CUBEB_LAYOUT_MONO_LFE, { M, LFE } },
{ CUBEB_LAYOUT_STEREO, { L, R } },
{ CUBEB_LAYOUT_STEREO_LFE, { L, R, LFE } },
{ CUBEB_LAYOUT_3F, { L, R, C } },
{ CUBEB_LAYOUT_3F_LFE, { L, R, C, LFE } },
{ CUBEB_LAYOUT_2F1, { L, R, RC } },
{ CUBEB_LAYOUT_2F1_LFE, { L, R, LFE, RC } },
{ CUBEB_LAYOUT_3F1, { L, R, C, RC } },
{ CUBEB_LAYOUT_3F1_LFE, { L, R, C, LFE, RC } },
{ CUBEB_LAYOUT_2F2, { L, R, LS, RS } },
{ CUBEB_LAYOUT_2F2_LFE, { L, R, LFE, LS, RS } },
{ CUBEB_LAYOUT_3F2, { L, R, C, LS, RS } },
{ CUBEB_LAYOUT_3F2_LFE, { L, R, C, LFE, LS, RS } },
{ CUBEB_LAYOUT_3F3R_LFE, { L, R, C, LFE, RC, LS, RS } },
{ CUBEB_LAYOUT_3F4_LFE, { L, R, C, LFE, RLS, RRS, LS, RS } }
};
void
downmix_test(float const * data, cubeb_channel_layout in_layout, cubeb_channel_layout out_layout)
{
if (in_layout == CUBEB_LAYOUT_UNDEFINED) {
return; // Only possible output layout would be UNSUPPORTED.
}
cubeb_stream_params in_params = {
STREAM_FORMAT,
STREAM_FREQUENCY,
layout_infos[in_layout].channels,
in_layout
};
cubeb_stream_params out_params = {
STREAM_FORMAT,
STREAM_FREQUENCY,
// To downmix audio data with unsupported layout, its channel number must be
// smaller than or equal to the input channels.
(out_layout == CUBEB_LAYOUT_UNDEFINED) ?
layout_infos[in_layout].channels : layout_infos[out_layout].channels,
out_layout
};
if (!cubeb_should_downmix(&in_params, &out_params)) {
return;
}
fprintf(stderr, "Downmix from %s to %s\n", layout_infos[in_layout].name, layout_infos[out_layout].name);
unsigned int const inframes = 10;
vector<float> in(in_params.channels * inframes);
vector<float> out(out_params.channels * inframes);
for (unsigned int offset = 0 ; offset < inframes * in_params.channels ; offset += in_params.channels) {
for (unsigned int i = 0 ; i < in_params.channels ; ++i) {
in[offset + i] = data[i];
}
}
cubeb_downmix_float(in.data(), inframes, out.data(), in_params.channels, out_params.channels, in_params.layout, out_params.layout);
uint32_t in_layout_mask = 0;
for (unsigned int i = 0 ; i < in_params.channels; ++i) {
in_layout_mask |= 1 << CHANNEL_INDEX_TO_ORDER[in_layout][i];
}
uint32_t out_layout_mask = 0;
for (unsigned int i = 0 ; out_layout != CUBEB_LAYOUT_UNDEFINED && i < out_params.channels; ++i) {
out_layout_mask |= 1 << CHANNEL_INDEX_TO_ORDER[out_layout][i];
}
for (unsigned int i = 0 ; i < inframes * out_params.channels ; ++i) {
unsigned int index = i % out_params.channels;
// downmix_3f2
if ((in_layout == CUBEB_LAYOUT_3F2 || in_layout == CUBEB_LAYOUT_3F2_LFE) &&
out_layout >= CUBEB_LAYOUT_MONO && out_layout <= CUBEB_LAYOUT_2F2_LFE) {
auto & downmix_results = DOWNMIX_3F2_RESULTS[in_layout - CUBEB_LAYOUT_3F2][out_layout - CUBEB_LAYOUT_MONO];
fprintf(stderr, "[3f2] Expect: %lf, Get: %lf\n", downmix_results[index], out[index]);
ASSERT_EQ(out[index], downmix_results[index]);
continue;
}
// mix_remap
if (out_layout_mask & in_layout_mask) {
uint32_t mask = 1 << CHANNEL_INDEX_TO_ORDER[out_layout][index];
fprintf(stderr, "[map channels] Expect: %lf, Get: %lf\n", (mask & in_layout_mask) ? audio_inputs[out_layout].data[index] : 0, out[index]);
ASSERT_EQ(out[index], (mask & in_layout_mask) ? audio_inputs[out_layout].data[index] : 0);
continue;
}
// downmix_fallback
fprintf(stderr, "[fallback] Expect: %lf, Get: %lf\n", audio_inputs[in_layout].data[index], out[index]);
ASSERT_EQ(out[index], audio_inputs[in_layout].data[index]);
}
}
TEST(cubeb, run_mixing_test)
{
for (unsigned int i = 0 ; i < ARRAY_LENGTH(audio_inputs) ; ++i) {
for (unsigned int j = 0 ; j < ARRAY_LENGTH(layout_infos) ; ++j) {
downmix_test(audio_inputs[i].data, audio_inputs[i].layout, layout_infos[j].layout);
}
}
}

Просмотреть файл

@ -95,6 +95,7 @@ TEST(cubeb, record)
params.format = STREAM_FORMAT;
params.rate = SAMPLE_FREQUENCY;
params.channels = 1;
params.layout = CUBEB_LAYOUT_MONO;
r = cubeb_stream_init(ctx, &stream, "Cubeb record (mono)", NULL, &params, NULL, nullptr,
4096, data_cb_record, state_cb_record, &stream_state);

Просмотреть файл

@ -18,19 +18,13 @@
#define STREAM_RATE 44100
#define STREAM_LATENCY 100 * STREAM_RATE / 1000
#define STREAM_CHANNELS 1
#define STREAM_LAYOUT CUBEB_LAYOUT_MONO
#if (defined(_WIN32) || defined(__WIN32__))
#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
#else
#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
#endif
template<typename T, size_t N>
constexpr size_t
ARRAY_LENGTH(T(&)[N])
{
return N;
}
int is_windows_7()
{
#ifdef __MINGW32__
@ -137,6 +131,7 @@ TEST(cubeb, context_variables)
params.channels = STREAM_CHANNELS;
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.layout = STREAM_LAYOUT;
#if defined(__ANDROID__)
params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
#endif
@ -169,6 +164,7 @@ TEST(cubeb, init_destroy_stream)
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
params.layout = STREAM_LAYOUT;
#if defined(__ANDROID__)
params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
#endif
@ -197,6 +193,7 @@ TEST(cubeb, init_destroy_multiple_streams)
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
params.layout = STREAM_LAYOUT;
#if defined(__ANDROID__)
params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
#endif
@ -229,6 +226,7 @@ TEST(cubeb, configure_stream)
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = 2; // panning
params.layout = CUBEB_LAYOUT_STEREO;
#if defined(__ANDROID__)
params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
#endif
@ -264,6 +262,7 @@ test_init_start_stop_destroy_multiple_streams(int early, int delay_ms)
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
params.layout = STREAM_LAYOUT;
#if defined(__ANDROID__)
params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
#endif
@ -350,6 +349,7 @@ TEST(cubeb, init_destroy_multiple_contexts_and_streams)
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
params.layout = STREAM_LAYOUT;
#if defined(__ANDROID__)
params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
#endif
@ -390,6 +390,7 @@ TEST(cubeb, basic_stream_operations)
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
params.layout = STREAM_LAYOUT;
#if defined(__ANDROID__)
params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
#endif
@ -440,6 +441,7 @@ TEST(cubeb, stream_position)
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
params.layout = STREAM_LAYOUT;
#if defined(__ANDROID__)
params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
#endif
@ -579,6 +581,7 @@ TEST(cubeb, drain)
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
params.layout = STREAM_LAYOUT;
#if defined(__ANDROID__)
params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
#endif

Просмотреть файл

@ -110,6 +110,7 @@ TEST(cubeb, tone)
params.format = STREAM_FORMAT;
params.rate = SAMPLE_FREQUENCY;
params.channels = 1;
params.layout = CUBEB_LAYOUT_MONO;
user_data = (struct cb_user_data *) malloc(sizeof(*user_data));
if (user_data == NULL) {