зеркало из https://github.com/mozilla/cubeb.git
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:
Родитель
c1e1e45dee
Коммит
a900d6e511
|
@ -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);
|
||||
|
|
15
src/cubeb.c
15
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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, ¶ms, 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) {
|
||||
|
|
Загрузка…
Ссылка в новой задаче