diff --git a/media/libcubeb/README_MOZILLA b/media/libcubeb/README_MOZILLA index 7e11e96c1f42..0a6b4f4f802f 100644 --- a/media/libcubeb/README_MOZILLA +++ b/media/libcubeb/README_MOZILLA @@ -5,4 +5,4 @@ Makefile.in build files for the Mozilla build system. The cubeb git repository is: git://github.com/kinetiknz/cubeb.git -The git commit ID used was 051cd8478dd48a18e2e91e5f07837640f40618c1. +The git commit ID used was d96e35f02dbb9a093e5bfdff4f2948b7a6e9d3f9. diff --git a/media/libcubeb/gtest/common.h b/media/libcubeb/gtest/common.h index a5a550008ce1..d6d6e3438266 100644 --- a/media/libcubeb/gtest/common.h +++ b/media/libcubeb/gtest/common.h @@ -16,6 +16,13 @@ #include #endif +template +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] = { + { "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 } +}; + int has_available_input_device(cubeb * ctx) { cubeb_device_collection * devices; diff --git a/media/libcubeb/gtest/test_audio.cpp b/media/libcubeb/gtest/test_audio.cpp index 77fdfb6656fd..f517490bc628 100644 --- a/media/libcubeb/gtest/test_audio.cpp +++ b/media/libcubeb/gtest/test_audio.cpp @@ -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); + } + } } } } - diff --git a/media/libcubeb/gtest/test_duplex.cpp b/media/libcubeb/gtest/test_duplex.cpp index 0289d9a094cb..3979002bc10b 100644 --- a/media/libcubeb/gtest/test_duplex.cpp +++ b/media/libcubeb/gtest/test_duplex.cpp @@ -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); diff --git a/media/libcubeb/gtest/test_latency.cpp b/media/libcubeb/gtest/test_latency.cpp index 9843557e9ec8..0a0e4c213a61 100644 --- a/media/libcubeb/gtest/test_latency.cpp +++ b/media/libcubeb/gtest/test_latency.cpp @@ -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); + cubeb_stream_params params = { CUBEB_SAMPLE_FLOAT32NE, preferred_rate, - max_channels + max_channels, + (r == CUBEB_OK) ? layout : CUBEB_LAYOUT_UNDEFINED +#if defined(__ANDROID__) + , CUBEB_STREAM_TYPE_MUSIC +#endif }; r = cubeb_get_min_latency(ctx, params, &latency_frames); ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED); diff --git a/media/libcubeb/gtest/test_mixer.cpp b/media/libcubeb/gtest/test_mixer.cpp new file mode 100644 index 000000000000..304518b7847e --- /dev/null +++ b/media/libcubeb/gtest/test_mixer.cpp @@ -0,0 +1,181 @@ +/* + * 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 + +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 UNDEFINED. + } + + cubeb_stream_params in_params = { + STREAM_FORMAT, + STREAM_FREQUENCY, + layout_infos[in_layout].channels, + in_layout +#if defined(__ANDROID__) + , CUBEB_STREAM_TYPE_MUSIC +#endif + }; + + cubeb_stream_params out_params = { + STREAM_FORMAT, + STREAM_FREQUENCY, + // To downmix audio data with undefined 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 defined(__ANDROID__) + , CUBEB_STREAM_TYPE_MUSIC +#endif + }; + + 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 in(in_params.channels * inframes); + vector 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); + } + } +} diff --git a/media/libcubeb/gtest/test_record.cpp b/media/libcubeb/gtest/test_record.cpp index 19f32383796f..639bbb27b889 100644 --- a/media/libcubeb/gtest/test_record.cpp +++ b/media/libcubeb/gtest/test_record.cpp @@ -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); diff --git a/media/libcubeb/gtest/test_resampler.cpp b/media/libcubeb/gtest/test_resampler.cpp index 48fc38a7ef7f..c4e179add18f 100644 --- a/media/libcubeb/gtest/test_resampler.cpp +++ b/media/libcubeb/gtest/test_resampler.cpp @@ -286,7 +286,6 @@ long data_cb_resampler(cubeb_stream * /*stm*/, void * user_ptr, const float * in = reinterpret_cast(input_buffer); float * out = reinterpret_cast(output_buffer); - state->input.push(in, frame_count * state->input_channels); /* Check how much output frames we need to write */ @@ -362,7 +361,6 @@ void test_resampler_duplex(uint32_t input_channels, uint32_t output_channels, fill_with_sine(expected_resampled_output.data() + latency, output_rate, output_channels, output_rate * duration_s - latency, 0); - while (state.output_phase_index != state.max_output_phase_index) { uint32_t leftover_samples = input_buffer.length() * input_channels; input_buffer.reserve(input_array_frame_count); diff --git a/media/libcubeb/gtest/test_sanity.cpp b/media/libcubeb/gtest/test_sanity.cpp index eb8a596768cd..688be8565158 100644 --- a/media/libcubeb/gtest/test_sanity.cpp +++ b/media/libcubeb/gtest/test_sanity.cpp @@ -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 -constexpr size_t -ARRAY_LENGTH(T(&)[N]) -{ - return N; -} - int is_windows_7() { #ifdef __MINGW32__ @@ -95,7 +89,6 @@ TEST(cubeb, init_destroy_context) ASSERT_EQ(r, CUBEB_OK); ASSERT_NE(ctx, nullptr); - backend_id = cubeb_get_backend_id(ctx); ASSERT_TRUE(backend_id); @@ -138,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 @@ -170,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 @@ -198,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 @@ -230,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 @@ -265,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 @@ -280,7 +278,6 @@ test_init_start_stop_destroy_multiple_streams(int early, int delay_ms) } } - if (!early) { for (i = 0; i < ARRAY_LENGTH(stream); ++i) { r = cubeb_stream_start(stream[i]); @@ -352,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 @@ -392,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 @@ -442,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 @@ -581,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 diff --git a/media/libcubeb/gtest/test_tone.cpp b/media/libcubeb/gtest/test_tone.cpp index 8ad0a269563d..12e64bbfe4d7 100644 --- a/media/libcubeb/gtest/test_tone.cpp +++ b/media/libcubeb/gtest/test_tone.cpp @@ -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) { diff --git a/media/libcubeb/include/cubeb.h b/media/libcubeb/include/cubeb.h index 7a36b8303890..b435d162405c 100644 --- a/media/libcubeb/include/cubeb.h +++ b/media/libcubeb/include/cubeb.h @@ -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.*/ diff --git a/media/libcubeb/src/cubeb-internal.h b/media/libcubeb/src/cubeb-internal.h index dfcc186c5700..962e9563e43e 100644 --- a/media/libcubeb/src/cubeb-internal.h +++ b/media/libcubeb/src/cubeb-internal.h @@ -35,6 +35,14 @@ void cubeb_crash() CLANG_ANALYZER_NORETURN; } #endif +typedef struct { + char const * name; + unsigned int const channels; + cubeb_channel_layout const layout; +} cubeb_layout_map; + +extern cubeb_layout_map const CUBEB_CHANNEL_LAYOUT_MAPS[CUBEB_LAYOUT_MAX]; + struct cubeb_ops { int (* init)(cubeb ** context, char const * context_name); char const * (* get_backend_id)(cubeb * context); @@ -43,6 +51,7 @@ struct cubeb_ops { cubeb_stream_params params, uint32_t * latency_ms); int (* get_preferred_sample_rate)(cubeb * context, uint32_t * rate); + int (* get_preferred_channel_layout)(cubeb * context, cubeb_channel_layout * layout); int (* enumerate_devices)(cubeb * context, cubeb_device_type type, cubeb_device_collection ** collection); void (* destroy)(cubeb * context); diff --git a/media/libcubeb/src/cubeb.c b/media/libcubeb/src/cubeb.c index 6a320296bd37..57bcb4c13652 100644 --- a/media/libcubeb/src/cubeb.c +++ b/media/libcubeb/src/cubeb.c @@ -55,7 +55,6 @@ int audiotrack_init(cubeb ** context, char const * context_name); int kai_init(cubeb ** context, char const * context_name); #endif - static int validate_stream_params(cubeb_stream_params * input_stream_params, cubeb_stream_params * output_stream_params) @@ -96,8 +95,6 @@ validate_stream_params(cubeb_stream_params * input_stream_params, return CUBEB_ERROR_INVALID_FORMAT; } - - static int validate_latency(int latency) { @@ -218,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) { @@ -565,4 +576,3 @@ cubeb_crash() abort(); *((volatile int *) NULL) = 0; } - diff --git a/media/libcubeb/src/cubeb_alsa.c b/media/libcubeb/src/cubeb_alsa.c index 1ea0961d089c..05ad27fef53b 100644 --- a/media/libcubeb/src/cubeb_alsa.c +++ b/media/libcubeb/src/cubeb_alsa.c @@ -82,7 +82,7 @@ struct cubeb_stream { cubeb_data_callback data_callback; cubeb_state_callback state_callback; void * user_ptr; - snd_pcm_uframes_t write_position; + snd_pcm_uframes_t stream_position; snd_pcm_uframes_t last_position; snd_pcm_uframes_t buffer_size; cubeb_stream_params params; @@ -107,6 +107,12 @@ struct cubeb_stream { being logically active and playing. */ struct timeval last_activity; float volume; + + char * buffer; + snd_pcm_uframes_t bufframes; + snd_pcm_stream_t stream_type; + + struct cubeb_stream * other_stream; }; static int @@ -234,6 +240,14 @@ set_timeout(struct timeval * timeout, unsigned int ms) timeout->tv_usec += (ms % 1000) * 1000; } +static void +stream_buffer_decrement(cubeb_stream * stm, long count) +{ + char * bufremains = stm->buffer + snd_pcm_frames_to_bytes(stm->pcm, count); + memmove(stm->buffer, bufremains, snd_pcm_frames_to_bytes(stm->pcm, stm->bufframes - count)); + stm->bufframes -= count; +} + static void alsa_set_stream_state(cubeb_stream * stm, enum stream_state state) { @@ -249,92 +263,173 @@ alsa_set_stream_state(cubeb_stream * stm, enum stream_state state) } static enum stream_state -alsa_refill_stream(cubeb_stream * stm) +alsa_process_stream(cubeb_stream * stm) { + unsigned short revents; snd_pcm_sframes_t avail; - long got; - void * p; int draining; draining = 0; pthread_mutex_lock(&stm->mutex); + /* Call _poll_descriptors_revents() even if we don't use it + to let underlying plugins clear null events. Otherwise poll() + may wake up again and again, producing unnecessary CPU usage. */ + snd_pcm_poll_descriptors_revents(stm->pcm, stm->fds, stm->nfds, &revents); + avail = snd_pcm_avail_update(stm->pcm); - if (avail < 0) { - snd_pcm_recover(stm->pcm, avail, 1); - avail = snd_pcm_avail_update(stm->pcm); - } - /* Failed to recover from an xrun, this stream must be broken. */ - if (avail < 0) { - pthread_mutex_unlock(&stm->mutex); - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - return ERROR; - } - - /* This should never happen. */ - if ((unsigned int) avail > stm->buffer_size) { - avail = stm->buffer_size; - } - - /* poll(2) claims this stream is active, so there should be some space - available to write. If avail is still zero here, the stream must be in - a funky state, bail and wait for another wakeup. */ + /* Got null event? Bail and wait for another wakeup. */ if (avail == 0) { pthread_mutex_unlock(&stm->mutex); return RUNNING; } - p = calloc(1, snd_pcm_frames_to_bytes(stm->pcm, avail)); - assert(p); - - pthread_mutex_unlock(&stm->mutex); - got = stm->data_callback(stm, stm->user_ptr, NULL, p, avail); - pthread_mutex_lock(&stm->mutex); - if (got < 0) { - pthread_mutex_unlock(&stm->mutex); - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - free(p); - return ERROR; + /* This could happen if we were suspended with SIGSTOP/Ctrl+Z for a long time. */ + if ((unsigned int) avail > stm->buffer_size) { + avail = stm->buffer_size; } - if (got > 0) { + + /* Capture: Read available frames */ + if (stm->stream_type == SND_PCM_STREAM_CAPTURE && avail > 0) { + snd_pcm_sframes_t got; + + if (avail + stm->bufframes > stm->buffer_size) { + /* Buffer overflow. Skip and overwrite with new data. */ + stm->bufframes = 0; + // TODO: should it be marked as DRAINING? + } + + got = snd_pcm_readi(stm->pcm, stm->buffer+stm->bufframes, avail); + + if (got < 0) { + avail = got; // the error handler below will recover us + } else { + stm->bufframes += got; + stm->stream_position += got; + + gettimeofday(&stm->last_activity, NULL); + } + } + + /* Capture: Pass read frames to callback function */ + if (stm->stream_type == SND_PCM_STREAM_CAPTURE && stm->bufframes > 0 && + (!stm->other_stream || stm->other_stream->bufframes < stm->other_stream->buffer_size)) { + long wrote = stm->bufframes; + struct cubeb_stream * mainstm = stm->other_stream ? stm->other_stream : stm; + void * other_buffer = stm->other_stream ? stm->other_stream->buffer + stm->other_stream->bufframes : NULL; + + /* Correct write size to the other stream available space */ + if (stm->other_stream && wrote > stm->other_stream->buffer_size - stm->other_stream->bufframes) { + wrote = stm->other_stream->buffer_size - stm->other_stream->bufframes; + } + + pthread_mutex_unlock(&stm->mutex); + wrote = stm->data_callback(mainstm, stm->user_ptr, stm->buffer, other_buffer, wrote); + pthread_mutex_lock(&stm->mutex); + + if (wrote < 0) { + avail = wrote; // the error handler below will recover us + } else { + stream_buffer_decrement(stm, wrote); + + if (stm->other_stream) { + stm->other_stream->bufframes += wrote; + } + } + } + + /* Playback: Don't have enough data? Let's ask for more. */ + if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > stm->bufframes && + (!stm->other_stream || stm->other_stream->bufframes > 0)) { + long got = avail - stm->bufframes; + void * other_buffer = stm->other_stream ? stm->other_stream->buffer : NULL; + char * buftail = stm->buffer + snd_pcm_frames_to_bytes(stm->pcm, stm->bufframes); + + /* Correct read size to the other stream available frames */ + if (stm->other_stream && got > stm->other_stream->bufframes) { + got = stm->other_stream->bufframes; + } + + pthread_mutex_unlock(&stm->mutex); + got = stm->data_callback(stm, stm->user_ptr, other_buffer, buftail, got); + pthread_mutex_lock(&stm->mutex); + + if (got < 0) { + avail = got; // the error handler below will recover us + } else { + stm->bufframes += got; + + if (stm->other_stream) { + stream_buffer_decrement(stm->other_stream, got); + } + } + } + + /* Playback: Still don't have enough data? Add some silence. */ + if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > stm->bufframes) { + long drain_frames = avail - stm->bufframes; + double drain_time = (double) drain_frames / stm->params.rate; + + char * buftail = stm->buffer + snd_pcm_frames_to_bytes(stm->pcm, stm->bufframes); + memset(buftail, 0, snd_pcm_frames_to_bytes(stm->pcm, drain_frames)); + stm->bufframes = avail; + + /* Mark as draining, unless we're waiting for capture */ + if (!stm->other_stream || stm->other_stream->bufframes > 0) { + set_timeout(&stm->drain_timeout, drain_time * 1000); + + draining = 1; + } + } + + /* Playback: Have enough data and no errors. Let's write it out. */ + if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > 0) { snd_pcm_sframes_t wrote; if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) { - float * b = (float *) p; - for (uint32_t i = 0; i < got * stm->params.channels; i++) { + float * b = (float *) stm->buffer; + for (uint32_t i = 0; i < avail * stm->params.channels; i++) { b[i] *= stm->volume; } } else { - short * b = (short *) p; - for (uint32_t i = 0; i < got * stm->params.channels; i++) { + short * b = (short *) stm->buffer; + for (uint32_t i = 0; i < avail * stm->params.channels; i++) { b[i] *= stm->volume; } } - wrote = snd_pcm_writei(stm->pcm, p, got); + + wrote = snd_pcm_writei(stm->pcm, stm->buffer, avail); if (wrote < 0) { - snd_pcm_recover(stm->pcm, wrote, 1); - wrote = snd_pcm_writei(stm->pcm, p, got); + avail = wrote; // the error handler below will recover us + } else { + stream_buffer_decrement(stm, wrote); + + stm->stream_position += wrote; + gettimeofday(&stm->last_activity, NULL); } - assert(wrote >= 0 && wrote == got); - stm->write_position += wrote; - gettimeofday(&stm->last_activity, NULL); - } - if (got != avail) { - long buffer_fill = stm->buffer_size - (avail - got); - double buffer_time = (double) buffer_fill / stm->params.rate; - - /* Fill the remaining buffer with silence to guarantee one full period - has been written. */ - snd_pcm_writei(stm->pcm, (char *) p + got, avail - got); - - set_timeout(&stm->drain_timeout, buffer_time * 1000); - - draining = 1; } - free(p); + /* Got some error? Let's try to recover the stream. */ + if (avail < 0) { + avail = snd_pcm_recover(stm->pcm, avail, 0); + + /* Capture pcm must be started after initial setup/recover */ + if (avail >= 0 && + stm->stream_type == SND_PCM_STREAM_CAPTURE && + snd_pcm_state(stm->pcm) == SND_PCM_STATE_PREPARED) { + avail = snd_pcm_start(stm->pcm); + } + } + + /* Failed to recover, this stream must be broken. */ + if (avail < 0) { + pthread_mutex_unlock(&stm->mutex); + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); + return ERROR; + } + pthread_mutex_unlock(&stm->mutex); return draining ? DRAINING : RUNNING; } @@ -390,7 +485,7 @@ alsa_run(cubeb * ctx) if (stm && stm->state == RUNNING && stm->fds && any_revents(stm->fds, stm->nfds)) { alsa_set_stream_state(stm, PROCESSING); pthread_mutex_unlock(&ctx->mutex); - state = alsa_refill_stream(stm); + state = alsa_process_stream(stm); pthread_mutex_lock(&ctx->mutex); alsa_set_stream_state(stm, state); } @@ -576,15 +671,15 @@ init_local_config_with_workaround(char const * pcm_name) } static int -alsa_locked_pcm_open(snd_pcm_t ** pcm, snd_pcm_stream_t stream, snd_config_t * local_config) +alsa_locked_pcm_open(snd_pcm_t ** pcm, char const * pcm_name, snd_pcm_stream_t stream, snd_config_t * local_config) { int r; pthread_mutex_lock(&cubeb_alsa_mutex); if (local_config) { - r = snd_pcm_open_lconf(pcm, CUBEB_ALSA_PCM_NAME, stream, SND_PCM_NONBLOCK, local_config); + r = snd_pcm_open_lconf(pcm, pcm_name, stream, SND_PCM_NONBLOCK, local_config); } else { - r = snd_pcm_open(pcm, CUBEB_ALSA_PCM_NAME, stream, SND_PCM_NONBLOCK); + r = snd_pcm_open(pcm, pcm_name, stream, SND_PCM_NONBLOCK); } pthread_mutex_unlock(&cubeb_alsa_mutex); @@ -707,7 +802,7 @@ alsa_init(cubeb ** context, char const * context_name) /* Open a dummy PCM to force the configuration space to be evaluated so that init_local_config_with_workaround can find and modify the default node. */ - r = alsa_locked_pcm_open(&dummy, SND_PCM_STREAM_PLAYBACK, NULL); + r = alsa_locked_pcm_open(&dummy, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, NULL); if (r >= 0) { alsa_locked_pcm_close(dummy); } @@ -717,7 +812,7 @@ alsa_init(cubeb ** context, char const * context_name) pthread_mutex_unlock(&cubeb_alsa_mutex); if (ctx->local_config) { ctx->is_pa = 1; - r = alsa_locked_pcm_open(&dummy, SND_PCM_STREAM_PLAYBACK, ctx->local_config); + r = alsa_locked_pcm_open(&dummy, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, ctx->local_config); /* If we got a local_config, we found a PA PCM. If opening a PCM with that config fails with EINVAL, the PA PCM is too old for this workaround. */ if (r == -EINVAL) { @@ -774,14 +869,14 @@ alsa_destroy(cubeb * ctx) static void alsa_stream_destroy(cubeb_stream * stm); static int -alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, - cubeb_devid input_device, - cubeb_stream_params * input_stream_params, - cubeb_devid output_device, - cubeb_stream_params * output_stream_params, - unsigned int latency_frames, - cubeb_data_callback data_callback, cubeb_state_callback state_callback, - void * user_ptr) +alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, + snd_pcm_stream_t stream_type, + cubeb_devid deviceid, + cubeb_stream_params * stream_params, + unsigned int latency_frames, + cubeb_data_callback data_callback, + cubeb_state_callback state_callback, + void * user_ptr) { (void)stream_name; cubeb_stream * stm; @@ -789,23 +884,13 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, snd_pcm_format_t format; snd_pcm_uframes_t period_size; int latency_us = 0; - + char const * pcm_name = deviceid ? (char const *) deviceid : CUBEB_ALSA_PCM_NAME; assert(ctx && stream); - if (input_stream_params) { - /* Capture support not yet implemented. */ - return CUBEB_ERROR_NOT_SUPPORTED; - } - - if (input_device || output_device) { - /* Device selection not yet implemented. */ - return CUBEB_ERROR_DEVICE_UNAVAILABLE; - } - *stream = NULL; - switch (output_stream_params->format) { + switch (stream_params->format) { case CUBEB_SAMPLE_S16LE: format = SND_PCM_FORMAT_S16_LE; break; @@ -837,14 +922,18 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, stm->data_callback = data_callback; stm->state_callback = state_callback; stm->user_ptr = user_ptr; - stm->params = *output_stream_params; + stm->params = *stream_params; stm->state = INACTIVE; stm->volume = 1.0; + stm->buffer = NULL; + stm->bufframes = 0; + stm->stream_type = stream_type; + stm->other_stream = NULL; r = pthread_mutex_init(&stm->mutex, NULL); assert(r == 0); - r = alsa_locked_pcm_open(&stm->pcm, SND_PCM_STREAM_PLAYBACK, ctx->local_config); + r = alsa_locked_pcm_open(&stm->pcm, pcm_name, stm->stream_type, ctx->local_config); if (r < 0) { alsa_stream_destroy(stm); return CUBEB_ERROR; @@ -874,6 +963,11 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, r = snd_pcm_get_params(stm->pcm, &stm->buffer_size, &period_size); assert(r == 0); + /* Double internal buffer size to have enough space when waiting for the other side of duplex connection */ + stm->buffer_size *= 2; + stm->buffer = calloc(1, snd_pcm_frames_to_bytes(stm->pcm, stm->buffer_size)); + assert(stm->buffer); + stm->nfds = snd_pcm_poll_descriptors_count(stm->pcm); assert(stm->nfds > 0); @@ -895,6 +989,45 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, return CUBEB_OK; } +static int +alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency_frames, + cubeb_data_callback data_callback, cubeb_state_callback state_callback, + void * user_ptr) +{ + int result = CUBEB_OK; + cubeb_stream * instm = NULL, * outstm = NULL; + + if (result == CUBEB_OK && input_stream_params) { + result = alsa_stream_init_single(ctx, &instm, stream_name, SND_PCM_STREAM_CAPTURE, + input_device, input_stream_params, latency_frames, + data_callback, state_callback, user_ptr); + } + + if (result == CUBEB_OK && output_stream_params) { + result = alsa_stream_init_single(ctx, &outstm, stream_name, SND_PCM_STREAM_PLAYBACK, + output_device, output_stream_params, latency_frames, + data_callback, state_callback, user_ptr); + } + + if (result == CUBEB_OK && input_stream_params && output_stream_params) { + instm->other_stream = outstm; + outstm->other_stream = instm; + } + + if (result != CUBEB_OK && instm) { + alsa_stream_destroy(instm); + } + + *stream = outstm ? outstm : instm; + + return result; +} + static void alsa_stream_destroy(cubeb_stream * stm) { @@ -907,6 +1040,11 @@ alsa_stream_destroy(cubeb_stream * stm) ctx = stm->context; + if (stm->other_stream) { + stm->other_stream->other_stream = NULL; // to stop infinite recursion + alsa_stream_destroy(stm->other_stream); + } + pthread_mutex_lock(&stm->mutex); if (stm->pcm) { if (stm->state == DRAINING) { @@ -929,6 +1067,8 @@ alsa_stream_destroy(cubeb_stream * stm) ctx->active_streams -= 1; pthread_mutex_unlock(&ctx->mutex); + free(stm->buffer); + free(stm); } @@ -952,6 +1092,8 @@ alsa_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) return CUBEB_ERROR; } + assert(stm); + r = snd_pcm_hw_params_any(stm->pcm, hw_params); if (r < 0) { return CUBEB_ERROR; @@ -1029,7 +1171,18 @@ alsa_stream_start(cubeb_stream * stm) assert(stm); ctx = stm->context; + if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && stm->other_stream) { + int r = alsa_stream_start(stm->other_stream); + if (r != CUBEB_OK) + return r; + } + pthread_mutex_lock(&stm->mutex); + /* Capture pcm must be started after initial setup/recover */ + if (stm->stream_type == SND_PCM_STREAM_CAPTURE && + snd_pcm_state(stm->pcm) == SND_PCM_STATE_PREPARED) { + snd_pcm_start(stm->pcm); + } snd_pcm_pause(stm->pcm, 0); gettimeofday(&stm->last_activity, NULL); pthread_mutex_unlock(&stm->mutex); @@ -1054,6 +1207,12 @@ alsa_stream_stop(cubeb_stream * stm) assert(stm); ctx = stm->context; + if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && stm->other_stream) { + int r = alsa_stream_stop(stm->other_stream); + if (r != CUBEB_OK) + return r; + } + pthread_mutex_lock(&ctx->mutex); while (stm->state == PROCESSING) { r = pthread_cond_wait(&stm->cond, &ctx->mutex); @@ -1090,8 +1249,8 @@ alsa_stream_get_position(cubeb_stream * stm, uint64_t * position) assert(delay >= 0); *position = 0; - if (stm->write_position >= (snd_pcm_uframes_t) delay) { - *position = stm->write_position - delay; + if (stm->stream_position >= (snd_pcm_uframes_t) delay) { + *position = stm->stream_position - delay; } stm->last_position = *position; @@ -1126,13 +1285,63 @@ alsa_stream_set_volume(cubeb_stream * stm, float volume) return CUBEB_OK; } +static int +alsa_enumerate_devices(cubeb * context, cubeb_device_type type, + cubeb_device_collection ** collection) +{ + if (!context) + return CUBEB_ERROR; + + uint32_t rate, max_channels; + int r; + + r = alsa_get_preferred_sample_rate(context, &rate); + if (r != CUBEB_OK) { + return CUBEB_ERROR; + } + + r = alsa_get_max_channel_count(context, &max_channels); + if (r != CUBEB_OK) { + return CUBEB_ERROR; + } + + *collection = (cubeb_device_collection *) calloc(1, sizeof(cubeb_device_collection) + 1*sizeof(cubeb_device_info *)); + assert(*collection); + + char const * a_name = "default"; + (*collection)->device[0] = (cubeb_device_info *) calloc(1, sizeof(cubeb_device_info)); + assert((*collection)->device[0]); + + (*collection)->device[0]->device_id = strdup(a_name); + (*collection)->device[0]->devid = (*collection)->device[0]->device_id; + (*collection)->device[0]->friendly_name = strdup(a_name); + (*collection)->device[0]->group_id = strdup(a_name); + (*collection)->device[0]->vendor_name = strdup(a_name); + (*collection)->device[0]->type = type; + (*collection)->device[0]->state = CUBEB_DEVICE_STATE_ENABLED; + (*collection)->device[0]->preferred = CUBEB_DEVICE_PREF_ALL; + (*collection)->device[0]->format = CUBEB_DEVICE_FMT_S16NE; + (*collection)->device[0]->default_format = CUBEB_DEVICE_FMT_S16NE; + (*collection)->device[0]->max_channels = max_channels; + (*collection)->device[0]->min_rate = rate; + (*collection)->device[0]->max_rate = rate; + (*collection)->device[0]->default_rate = rate; + (*collection)->device[0]->latency_lo = 0; + (*collection)->device[0]->latency_hi = 0; + + (*collection)->count = 1; + + return CUBEB_OK; +} + static struct cubeb_ops const alsa_ops = { .init = alsa_init, .get_backend_id = alsa_get_backend_id, .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, - .enumerate_devices = NULL, + .get_preferred_channel_layout = NULL, + .enumerate_devices = alsa_enumerate_devices, .destroy = alsa_destroy, .stream_init = alsa_stream_init, .stream_destroy = alsa_stream_destroy, diff --git a/media/libcubeb/src/cubeb_audiotrack.c b/media/libcubeb/src/cubeb_audiotrack.c index 047636eba1bd..c0455ae76cf0 100644 --- a/media/libcubeb/src/cubeb_audiotrack.c +++ b/media/libcubeb/src/cubeb_audiotrack.c @@ -421,6 +421,7 @@ static struct cubeb_ops const audiotrack_ops = { .get_max_channel_count = audiotrack_get_max_channel_count, .get_min_latency = audiotrack_get_min_latency, .get_preferred_sample_rate = audiotrack_get_preferred_sample_rate, + .get_preferred_channel_layout = NULL, .enumerate_devices = NULL, .destroy = audiotrack_destroy, .stream_init = audiotrack_stream_init, diff --git a/media/libcubeb/src/cubeb_audiounit.cpp b/media/libcubeb/src/cubeb_audiounit.cpp index a748041ec49c..25be61adf0a9 100644 --- a/media/libcubeb/src/cubeb_audiounit.cpp +++ b/media/libcubeb/src/cubeb_audiounit.cpp @@ -40,9 +40,11 @@ typedef UInt32 AudioFormatFlags; #define AU_OUT_BUS 0 #define AU_IN_BUS 1 -#define PRINT_ERROR_CODE(str, r) do { \ - LOG("System call failed: %s (rv: %d)", str, r); \ -} while(0) +#define PRINT_ERROR_CODE(str, r) do { \ + LOG("System call failed: %s (rv: %d)", str, (int) r); \ + } while(0) + +const char * DISPATCH_QUEUE_LABEL = "org.mozilla.cubeb"; /* Testing empirically, some headsets report a minimal latency that is very * low, but this does not work in practice. Lie and say the minimum is 256 @@ -52,8 +54,8 @@ const uint32_t SAFE_MAX_LATENCY_FRAMES = 512; void audiounit_stream_stop_internal(cubeb_stream * stm); void audiounit_stream_start_internal(cubeb_stream * stm); -static void close_audiounit_stream(cubeb_stream * stm); -static int setup_audiounit_stream(cubeb_stream * stm); +static void audiounit_close_stream(cubeb_stream *stm); +static int audiounit_setup_stream(cubeb_stream *stm); extern cubeb_ops const audiounit_ops; @@ -61,11 +63,14 @@ struct cubeb { cubeb_ops const * ops = &audiounit_ops; owned_critical_section mutex; std::atomic active_streams{ 0 }; + uint32_t global_latency_frames = 0; cubeb_device_collection_changed_callback collection_changed_callback = nullptr; void * collection_changed_user_ptr = nullptr; /* Differentiate input from output devices. */ cubeb_device_type collection_changed_devtype = CUBEB_DEVICE_TYPE_UNKNOWN; std::vector devtype_device_array; + // The queue is asynchronously deallocated once all references to it are released + dispatch_queue_t serial_queue = dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL); }; struct auto_array_wrapper { @@ -133,8 +138,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; @@ -177,6 +182,7 @@ struct cubeb_stream { std::atomic output_callback_in_a_row{ 0 }; /* This is true if a device change callback is currently running. */ std::atomic switching_device{ false }; + std::atomic buffer_size_change_state{ false }; }; bool has_input(cubeb_stream * stm) @@ -227,6 +233,14 @@ audiotimestamp_to_latency(AudioTimeStamp const * tstamp, cubeb_stream * stream) return ((pres - now) * stream->output_desc.mSampleRate) / 1000000000LL; } +static void +audiounit_set_global_latency(cubeb_stream * stm, uint32_t latency_frames) +{ + stm->mutex.assert_current_thread_owns(); + assert(stm->context->active_streams == 1); + stm->context->global_latency_frames = latency_frames; +} + static void audiounit_make_silent(AudioBuffer * ioData) { @@ -267,11 +281,12 @@ audiounit_render_input(cubeb_stream * stm, stm->input_linear_buffer->push(input_buffer_list.mBuffers[0].mData, input_frames * stm->input_desc.mChannelsPerFrame); - LOGV("(%p) input: buffers %d, size %d, channels %d, frames %d.", - stm, input_buffer_list.mNumberBuffers, - input_buffer_list.mBuffers[0].mDataByteSize, - input_buffer_list.mBuffers[0].mNumberChannels, - input_frames); + LOGV("(%p) input: buffers %u, size %u, channels %u, frames %d.", + stm, + (unsigned int) input_buffer_list.mNumberBuffers, + (unsigned int) input_buffer_list.mBuffers[0].mDataByteSize, + (unsigned int) input_buffer_list.mBuffers[0].mNumberChannels, + (unsigned int) input_frames); /* Advance input frame counter. */ assert(input_frames > 0); @@ -289,7 +304,6 @@ audiounit_input_callback(void * user_ptr, AudioBufferList * /* bufs */) { cubeb_stream * stm = static_cast(user_ptr); - long outframes; assert(stm->input_unit != NULL); assert(AU_IN_BUS == bus); @@ -323,15 +337,15 @@ audiounit_input_callback(void * user_ptr, Resampler will deliver input buffer in the correct rate. */ assert(input_frames <= stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame); long total_input_frames = stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame; - outframes = cubeb_resampler_fill(stm->resampler.get(), - stm->input_linear_buffer->data(), - &total_input_frames, - NULL, - 0); + long outframes = cubeb_resampler_fill(stm->resampler.get(), + stm->input_linear_buffer->data(), + &total_input_frames, + NULL, + 0); // Reset input buffer stm->input_linear_buffer->clear(); - if (outframes < 0 || outframes != input_frames) { + if (outframes < 0 || (UInt32) outframes != input_frames) { stm->shutdown = true; return noErr; } @@ -375,12 +389,14 @@ audiounit_output_callback(void * user_ptr, stm->output_callback_in_a_row++; - LOGV("(%p) output: buffers %d, size %d, channels %d, frames %d.", - stm, outBufferList->mNumberBuffers, - outBufferList->mBuffers[0].mDataByteSize, - outBufferList->mBuffers[0].mNumberChannels, output_frames); + LOGV("(%p) output: buffers %u, size %u, channels %u, frames %u.", + stm, + (unsigned int) outBufferList->mNumberBuffers, + (unsigned int) outBufferList->mBuffers[0].mDataByteSize, + (unsigned int) outBufferList->mBuffers[0].mNumberChannels, + (unsigned int) output_frames); - long outframes = 0, input_frames = 0; + long input_frames = 0; void * output_buffer = NULL, * input_buffer = NULL; if (stm->shutdown) { @@ -419,11 +435,11 @@ audiounit_output_callback(void * user_ptr, } /* Call user callback through resampler. */ - outframes = cubeb_resampler_fill(stm->resampler.get(), - input_buffer, - input_buffer ? &input_frames : NULL, - output_buffer, - output_frames); + long outframes = cubeb_resampler_fill(stm->resampler.get(), + input_buffer, + input_buffer ? &input_frames : NULL, + output_buffer, + output_frames); if (input_buffer) { stm->input_linear_buffer->pop(input_frames * stm->input_desc.mChannelsPerFrame); @@ -435,7 +451,7 @@ audiounit_output_callback(void * user_ptr, } size_t outbpf = stm->output_desc.mBytesPerFrame; - stm->draining = outframes < output_frames; + stm->draining = (UInt32) outframes < output_frames; stm->frames_played = stm->frames_queued; stm->frames_queued += outframes; @@ -533,37 +549,62 @@ audiounit_get_input_device_id(AudioDeviceID * device_id) return CUBEB_OK; } +static int +audiounit_reinit_stream(cubeb_stream * stm, bool is_started) +{ + if (is_started) { + audiounit_stream_stop_internal(stm); + } + + { + auto_lock lock(stm->mutex); + + audiounit_close_stream(stm); + + if (audiounit_setup_stream(stm) != CUBEB_OK) { + LOG("(%p) Stream reinit failed.", stm); + return CUBEB_ERROR; + } + + // Reset input frames to force new stream pre-buffer + // silence if needed, check `is_extra_input_needed()` + stm->frames_read = 0; + + // If the stream was running, start it again. + if (is_started) { + audiounit_stream_start_internal(stm); + } + } + return CUBEB_OK; +} + static OSStatus audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_count, const AudioObjectPropertyAddress * addresses, void * user) { cubeb_stream * stm = (cubeb_stream*) user; - int rv; - bool was_running = false; - stm->switching_device = true; - // Note if the stream was running or not - was_running = !stm->shutdown; + bool was_running = !stm->shutdown; - LOG("(%p) Audio device changed, %d events.", stm, address_count); + LOG("(%p) Audio device changed, %u events.", stm, (unsigned int) address_count); for (UInt32 i = 0; i < address_count; i++) { switch(addresses[i].mSelector) { case kAudioHardwarePropertyDefaultOutputDevice: { - LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultOutputDevice", i); + LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultOutputDevice", (unsigned int) i); // Allow restart to choose the new default stm->output_device = 0; } break; case kAudioHardwarePropertyDefaultInputDevice: { - LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultInputDevice", i); + LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultInputDevice", (unsigned int) i); // Allow restart to choose the new default stm->input_device = 0; } break; case kAudioDevicePropertyDeviceIsAlive: { - LOG("Event[%d] - mSelector == kAudioDevicePropertyDeviceIsAlive", i); + LOG("Event[%u] - mSelector == kAudioDevicePropertyDeviceIsAlive", (unsigned int) i); // If this is the default input device ignore the event, // kAudioHardwarePropertyDefaultInputDevice will take care of the switch if (stm->is_default_input) { @@ -575,7 +616,7 @@ audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_coun } break; case kAudioDevicePropertyDataSource: - LOG("Event[%d] - mSelector == kAudioHardwarePropertyDataSource", i); + LOG("Event[%u] - mSelector == kAudioHardwarePropertyDataSource", (unsigned int) i); break; } } @@ -596,28 +637,15 @@ audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_coun } } - // This means the callback won't be called again. - audiounit_stream_stop_internal(stm); - - { - auto_lock lock(stm->mutex); - close_audiounit_stream(stm); - rv = setup_audiounit_stream(stm); - if (rv != CUBEB_OK) { - LOG("(%p) Could not reopen a stream after switching.", stm); + // Use a new thread, through the queue, to avoid deadlock when calling + // Get/SetProperties method from inside notify callback + dispatch_async(stm->context->serial_queue, ^() { + if (audiounit_reinit_stream(stm, was_running) != CUBEB_OK) { stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); - return noErr; + LOG("(%p) Could not reopen the stream after switching.", stm); } - - stm->frames_read = 0; - - // If the stream was running, start it again. - if (was_running) { - audiounit_stream_start_internal(stm); - } - } - - stm->switching_device = false; + stm->switching_device = false; + }); return noErr; } @@ -1099,8 +1127,7 @@ audiounit_init_input_linear_buffer(cubeb_stream * stream, uint32_t capacity) } static uint32_t -audiounit_clamp_latency(cubeb_stream * stm, - uint32_t latency_frames) +audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames) { // For the 1st stream set anything within safe min-max assert(stm->context->active_streams > 0); @@ -1163,16 +1190,364 @@ audiounit_clamp_latency(cubeb_stream * stm, SAFE_MIN_LATENCY_FRAMES); } -static int -setup_audiounit_stream(cubeb_stream * stm) +/* + * Change buffer size is prone to deadlock thus we change it + * following the steps: + * - register a listener for the buffer size property + * - change the property + * - wait until the listener is executed + * - property has changed, remove the listener + * */ +static void +buffer_size_changed_callback(void * inClientData, + AudioUnit inUnit, + AudioUnitPropertyID inPropertyID, + AudioUnitScope inScope, + AudioUnitElement inElement) { - stm->mutex.assert_current_thread_owns(); + cubeb_stream * stm = (cubeb_stream *)inClientData; - int r; + AudioUnit au = inUnit; + AudioUnitScope au_scope = kAudioUnitScope_Input; + AudioUnitElement au_element = inElement; + const char * au_type = "output"; + + if (au == stm->input_unit) { + au_scope = kAudioUnitScope_Output; + au_type = "input"; + } + + switch (inPropertyID) { + + case kAudioDevicePropertyBufferFrameSize: { + if (inScope != au_scope) { + break; + } + UInt32 new_buffer_size; + UInt32 outSize = sizeof(UInt32); + OSStatus r = AudioUnitGetProperty(au, + kAudioDevicePropertyBufferFrameSize, + au_scope, + au_element, + &new_buffer_size, + &outSize); + if (r != noErr) { + LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: Cannot get current buffer size", stm); + } else { + LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: New %s buffer size = %d for scope %d", stm, + au_type, new_buffer_size, inScope); + } + stm->buffer_size_change_state = true; + break; + } + } +} + +enum set_buffer_size_side { + INPUT, + OUTPUT, +}; + +static int +audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, set_buffer_size_side set_side) +{ + AudioUnit au = stm->output_unit; + AudioUnitScope au_scope = kAudioUnitScope_Input; + AudioUnitElement au_element = AU_OUT_BUS; + const char * au_type = "output"; + + if (set_side == INPUT) { + au = stm->input_unit; + au_scope = kAudioUnitScope_Output; + au_element = AU_IN_BUS; + au_type = "input"; + } + + uint32_t buffer_frames = 0; + UInt32 size = sizeof(buffer_frames); + int r = AudioUnitGetProperty(au, + kAudioDevicePropertyBufferFrameSize, + au_scope, + au_element, + &buffer_frames, + &size); + if (r != noErr) { + if (set_side == INPUT) { + PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize", r); + } else { + PRINT_ERROR_CODE("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize", r); + } + return CUBEB_ERROR; + } + + if (new_size_frames == buffer_frames) { + LOG("(%p) No need to update %s buffer size already %u frames", stm, au_type, buffer_frames); + return CUBEB_OK; + } + + r = AudioUnitAddPropertyListener(au, + kAudioDevicePropertyBufferFrameSize, + buffer_size_changed_callback, + stm); + if (r != noErr) { + if (set_side == INPUT) { + PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r); + } else { + PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r); + } + return CUBEB_ERROR; + } + + stm->buffer_size_change_state = false; + + r = AudioUnitSetProperty(au, + kAudioDevicePropertyBufferFrameSize, + au_scope, + au_element, + &new_size_frames, + sizeof(new_size_frames)); + if (r != noErr) { + if (set_side == INPUT) { + PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioDevicePropertyBufferFrameSize", r); + } else { + PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioDevicePropertyBufferFrameSize", r); + } + + r = AudioUnitRemovePropertyListenerWithUserData(au, + kAudioDevicePropertyBufferFrameSize, + buffer_size_changed_callback, + stm); + if (r != noErr) { + if (set_side == INPUT) { + PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r); + } else { + PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r); + } + } + + return CUBEB_ERROR; + } + + int count = 0; + while (!stm->buffer_size_change_state && count++ < 30) { + struct timespec req, rem; + req.tv_sec = 0; + req.tv_nsec = 100000000L; // 0.1 sec + if (nanosleep(&req , &rem) < 0 ) { + LOG("(%p) Warning: nanosleep call failed or interrupted. Remaining time %ld nano secs \n", stm, rem.tv_nsec); + } + LOG("(%p) audiounit_set_buffer_size : wait count = %d", stm, count); + } + + r = AudioUnitRemovePropertyListenerWithUserData(au, + kAudioDevicePropertyBufferFrameSize, + buffer_size_changed_callback, + stm); + if (r != noErr) { + if (set_side == INPUT) { + PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r); + } else { + PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r); + } + return CUBEB_ERROR; + } + + if (!stm->buffer_size_change_state && count >= 30) { + LOG("(%p) Error, did not get buffer size change callback ...", stm); + return CUBEB_ERROR; + } + + LOG("(%p) %s buffer size changed to %u frames.", stm, au_type, new_size_frames); + return CUBEB_OK; +} + +static int +audiounit_configure_input(cubeb_stream * stm) +{ + int r = 0; + UInt32 size; AURenderCallbackStruct aurcbs_in; + + LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in frames %u.", + stm, stm->input_stream_params.rate, stm->input_stream_params.channels, + stm->input_stream_params.format, stm->latency_frames); + + /* Get input device sample rate. */ + AudioStreamBasicDescription input_hw_desc; + size = sizeof(AudioStreamBasicDescription); + r = AudioUnitGetProperty(stm->input_unit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + AU_IN_BUS, + &input_hw_desc, + &size); + if (r != noErr) { + PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat", r); + return CUBEB_ERROR; + } + stm->input_hw_rate = input_hw_desc.mSampleRate; + LOG("(%p) Input device sampling rate: %.2f", stm, stm->input_hw_rate); + + /* Set format description according to the input params. */ + r = audio_stream_desc_init(&stm->input_desc, &stm->input_stream_params); + if (r != CUBEB_OK) { + LOG("(%p) Setting format description for input failed.", stm); + return r; + } + + // Use latency to set buffer size + stm->input_buffer_frames = stm->latency_frames; + r = audiounit_set_buffer_size(stm, stm->input_buffer_frames, INPUT); + if (r != CUBEB_OK) { + LOG("(%p) Error in change input buffer size.", stm); + return CUBEB_ERROR; + } + + AudioStreamBasicDescription src_desc = stm->input_desc; + /* Input AudioUnit must be configured with device's sample rate. + we will resample inside input callback. */ + src_desc.mSampleRate = stm->input_hw_rate; + + r = AudioUnitSetProperty(stm->input_unit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + AU_IN_BUS, + &src_desc, + sizeof(AudioStreamBasicDescription)); + if (r != noErr) { + PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat", r); + return CUBEB_ERROR; + } + + /* Frames per buffer in the input callback. */ + r = AudioUnitSetProperty(stm->input_unit, + kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Global, + AU_IN_BUS, + &stm->input_buffer_frames, + sizeof(UInt32)); + if (r != noErr) { + PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice", r); + return CUBEB_ERROR; + } + + // Input only capacity + unsigned int array_capacity = 1; + if (has_output(stm)) { + // Full-duplex increase capacity + array_capacity = 8; + } + if (audiounit_init_input_linear_buffer(stm, array_capacity) != CUBEB_OK) { + return CUBEB_ERROR; + } + + assert(stm->input_unit != NULL); + aurcbs_in.inputProc = audiounit_input_callback; + aurcbs_in.inputProcRefCon = stm; + + r = AudioUnitSetProperty(stm->input_unit, + kAudioOutputUnitProperty_SetInputCallback, + kAudioUnitScope_Global, + AU_OUT_BUS, + &aurcbs_in, + sizeof(aurcbs_in)); + if (r != noErr) { + PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback", r); + return CUBEB_ERROR; + } + LOG("(%p) Input audiounit init successfully.", stm); + + return CUBEB_OK; +} + +static int +audiounit_configure_output(cubeb_stream * stm) +{ + int r; AURenderCallbackStruct aurcbs_out; UInt32 size; + + LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in frames %u.", + stm, stm->output_stream_params.rate, stm->output_stream_params.channels, + stm->output_stream_params.format, stm->latency_frames); + + r = audio_stream_desc_init(&stm->output_desc, &stm->output_stream_params); + if (r != CUBEB_OK) { + LOG("(%p) Could not initialize the audio stream description.", stm); + return r; + } + + /* Get output device sample rate. */ + AudioStreamBasicDescription output_hw_desc; + size = sizeof(AudioStreamBasicDescription); + memset(&output_hw_desc, 0, size); + r = AudioUnitGetProperty(stm->output_unit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + AU_OUT_BUS, + &output_hw_desc, + &size); + if (r != noErr) { + PRINT_ERROR_CODE("AudioUnitGetProperty/output/tkAudioUnitProperty_StreamFormat", r); + return CUBEB_ERROR; + } + stm->output_hw_rate = output_hw_desc.mSampleRate; + LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate); + + r = AudioUnitSetProperty(stm->output_unit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + AU_OUT_BUS, + &stm->output_desc, + sizeof(AudioStreamBasicDescription)); + if (r != noErr) { + PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat", r); + return CUBEB_ERROR; + } + + r = audiounit_set_buffer_size(stm, stm->latency_frames, OUTPUT); + if (r != CUBEB_OK) { + LOG("(%p) Error in change output buffer size.", stm); + return CUBEB_ERROR; + } + + /* Frames per buffer in the input callback. */ + r = AudioUnitSetProperty(stm->output_unit, + kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Global, + AU_OUT_BUS, + &stm->latency_frames, + sizeof(UInt32)); + if (r != noErr) { + PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_MaximumFramesPerSlice", r); + return CUBEB_ERROR; + } + + assert(stm->output_unit != NULL); + aurcbs_out.inputProc = audiounit_output_callback; + aurcbs_out.inputProcRefCon = stm; + r = AudioUnitSetProperty(stm->output_unit, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Global, + AU_OUT_BUS, + &aurcbs_out, + sizeof(aurcbs_out)); + if (r != noErr) { + PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback", r); + return CUBEB_ERROR; + } + + LOG("(%p) Output audiounit init successfully.", stm); + return CUBEB_OK; +} + +static int +audiounit_setup_stream(cubeb_stream * stm) +{ + stm->mutex.assert_current_thread_owns(); + + int r = 0; if (has_input(stm)) { r = audiounit_create_unit(&stm->input_unit, true, &stm->input_stream_params, @@ -1193,170 +1568,36 @@ setup_audiounit_stream(cubeb_stream * stm) } } + /* Latency cannot change if another stream is operating in parallel. In this case + * latecy is set to the other stream value. */ + if (stm->context->active_streams > 1) { + LOG("(%p) More than one active stream, use global latency.", stm); + stm->latency_frames = stm->context->global_latency_frames; + } else { + /* Silently clamp the latency down to the platform default, because we + * synthetize the clock from the callbacks, and we want the clock to update + * often. */ + stm->latency_frames = audiounit_clamp_latency(stm, stm->latency_frames); + assert(stm->latency_frames); // Ungly error check + audiounit_set_global_latency(stm, stm->latency_frames); + } + /* Setup Input Stream! */ if (has_input(stm)) { - LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in frames %u.", - stm, stm->input_stream_params.rate, stm->input_stream_params.channels, - stm->input_stream_params.format, stm->latency_frames); - /* Get input device sample rate. */ - AudioStreamBasicDescription input_hw_desc; - size = sizeof(AudioStreamBasicDescription); - r = AudioUnitGetProperty(stm->input_unit, - kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Input, - AU_IN_BUS, - &input_hw_desc, - &size); - if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat", r); - return CUBEB_ERROR; - } - stm->input_hw_rate = input_hw_desc.mSampleRate; - LOG("(%p) Input device sampling rate: %.2f", stm, stm->input_hw_rate); - - /* Set format description according to the input params. */ - r = audio_stream_desc_init(&stm->input_desc, &stm->input_stream_params); + r = audiounit_configure_input(stm); if (r != CUBEB_OK) { - LOG("(%p) Setting format description for input failed.", stm); + LOG("(%p) Configure audiounit input failed.", stm); return r; } - - // Use latency to set buffer size - stm->input_buffer_frames = stm->latency_frames; - LOG("(%p) Input buffer frame count %u.", stm, unsigned(stm->input_buffer_frames)); - r = AudioUnitSetProperty(stm->input_unit, - kAudioDevicePropertyBufferFrameSize, - kAudioUnitScope_Output, - AU_IN_BUS, - &stm->input_buffer_frames, - sizeof(UInt32)); - if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioDevicePropertyBufferFrameSize", r); - return CUBEB_ERROR; - } - - AudioStreamBasicDescription src_desc = stm->input_desc; - /* Input AudioUnit must be configured with device's sample rate. - we will resample inside input callback. */ - src_desc.mSampleRate = stm->input_hw_rate; - - r = AudioUnitSetProperty(stm->input_unit, - kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Output, - AU_IN_BUS, - &src_desc, - sizeof(AudioStreamBasicDescription)); - if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat", r); - return CUBEB_ERROR; - } - - /* Frames per buffer in the input callback. */ - r = AudioUnitSetProperty(stm->input_unit, - kAudioUnitProperty_MaximumFramesPerSlice, - kAudioUnitScope_Output, - AU_IN_BUS, - &stm->input_buffer_frames, - sizeof(UInt32)); - if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice", r); - return CUBEB_ERROR; - } - - // Input only capacity - unsigned int array_capacity = 1; - if (has_output(stm)) { - // Full-duplex increase capacity - array_capacity = 8; - } - if (audiounit_init_input_linear_buffer(stm, array_capacity) != CUBEB_OK) { - return CUBEB_ERROR; - } - - assert(stm->input_unit != NULL); - aurcbs_in.inputProc = audiounit_input_callback; - aurcbs_in.inputProcRefCon = stm; - - r = AudioUnitSetProperty(stm->input_unit, - kAudioOutputUnitProperty_SetInputCallback, - kAudioUnitScope_Global, - AU_OUT_BUS, - &aurcbs_in, - sizeof(aurcbs_in)); - if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback", r); - return CUBEB_ERROR; - } - LOG("(%p) Input audiounit init successfully.", stm); } /* Setup Output Stream! */ if (has_output(stm)) { - LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in frames %u.", - stm, stm->output_stream_params.rate, stm->output_stream_params.channels, - stm->output_stream_params.format, stm->latency_frames); - r = audio_stream_desc_init(&stm->output_desc, &stm->output_stream_params); + r = audiounit_configure_output(stm); if (r != CUBEB_OK) { - LOG("(%p) Could not initialize the audio stream description.", stm); + LOG("(%p) Configure audiounit output failed.", stm); return r; } - - /* Get output device sample rate. */ - AudioStreamBasicDescription output_hw_desc; - size = sizeof(AudioStreamBasicDescription); - memset(&output_hw_desc, 0, size); - r = AudioUnitGetProperty(stm->output_unit, - kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Output, - AU_OUT_BUS, - &output_hw_desc, - &size); - if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitGetProperty/output/tkAudioUnitProperty_StreamFormat", r); - return CUBEB_ERROR; - } - stm->output_hw_rate = output_hw_desc.mSampleRate; - LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate); - - r = AudioUnitSetProperty(stm->output_unit, - kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Input, - AU_OUT_BUS, - &stm->output_desc, - sizeof(AudioStreamBasicDescription)); - if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat", r); - return CUBEB_ERROR; - } - - // Use latency to calculate buffer size - uint32_t output_buffer_frames = stm->latency_frames; - LOG("(%p) Output buffer frame count %u.", stm, output_buffer_frames); - r = AudioUnitSetProperty(stm->output_unit, - kAudioDevicePropertyBufferFrameSize, - kAudioUnitScope_Input, - AU_OUT_BUS, - &output_buffer_frames, - sizeof(output_buffer_frames)); - if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioDevicePropertyBufferFrameSize", r); - return CUBEB_ERROR; - } - - assert(stm->output_unit != NULL); - aurcbs_out.inputProc = audiounit_output_callback; - aurcbs_out.inputProcRefCon = stm; - r = AudioUnitSetProperty(stm->output_unit, - kAudioUnitProperty_SetRenderCallback, - kAudioUnitScope_Global, - AU_OUT_BUS, - &aurcbs_out, - sizeof(aurcbs_out)); - if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback", r); - return CUBEB_ERROR; - } - LOG("(%p) Output audiounit init successfully.", stm); } // Setting the latency doesn't work well for USB headsets (eg. plantronics). @@ -1486,14 +1727,12 @@ audiounit_stream_init(cubeb * context, assert(context); *stream = NULL; - + assert(latency_frames > 0); if ((input_device && !input_stream_params) || (output_device && !output_stream_params)) { return CUBEB_ERROR_INVALID_PARAMETER; } - context->active_streams += 1; - stm.reset(new cubeb_stream(context)); /* These could be different in the future if we have both @@ -1501,6 +1740,7 @@ audiounit_stream_init(cubeb * context, stm->data_callback = data_callback; stm->state_callback = state_callback; stm->user_ptr = user_ptr; + stm->latency_frames = latency_frames; if (input_stream_params) { stm->input_stream_params = *input_stream_params; stm->input_device = reinterpret_cast(input_device); @@ -1512,18 +1752,14 @@ audiounit_stream_init(cubeb * context, stm->output_device = reinterpret_cast(output_device); } - /* Silently clamp the latency down to the platform default, because we - * synthetize the clock from the callbacks, and we want the clock to update - * often. */ - stm->latency_frames = audiounit_clamp_latency(stm.get(), latency_frames); - assert(latency_frames > 0); - + auto_lock context_lock(context->mutex); { // It's not critical to lock here, because no other thread has been started // yet, but it allows to assert that the lock has been taken in - // `setup_audiounit_stream`. + // `audiounit_setup_stream`. + context->active_streams += 1; auto_lock lock(stm->mutex); - r = setup_audiounit_stream(stm.get()); + r = audiounit_setup_stream(stm.get()); } if (r != CUBEB_OK) { @@ -1543,7 +1779,7 @@ audiounit_stream_init(cubeb * context, } static void -close_audiounit_stream(cubeb_stream * stm) +audiounit_close_stream(cubeb_stream *stm) { stm->mutex.assert_current_thread_owns(); if (stm->input_unit) { @@ -1566,11 +1802,12 @@ audiounit_stream_destroy(cubeb_stream * stm) { stm->shutdown = true; + auto_lock context_locl(stm->context->mutex); audiounit_stream_stop_internal(stm); { auto_lock lock(stm->mutex); - close_audiounit_stream(stm); + audiounit_close_stream(stm); } #if !TARGET_OS_IPHONE @@ -1583,6 +1820,7 @@ audiounit_stream_destroy(cubeb_stream * stm) assert(stm->context->active_streams >= 1); stm->context->active_streams -= 1; + LOG("Cubeb stream (%p) destroyed successful.", stm); delete stm; } @@ -1606,6 +1844,7 @@ audiounit_stream_start(cubeb_stream * stm) stm->shutdown = false; stm->draining = false; + auto_lock context_locl(stm->context->mutex); audiounit_stream_start_internal(stm); stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); @@ -1633,6 +1872,7 @@ audiounit_stream_stop(cubeb_stream * stm) { stm->shutdown = true; + auto_lock context_locl(stm->context->mutex); audiounit_stream_stop_internal(stm); stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); @@ -2294,6 +2534,7 @@ cubeb_ops const audiounit_ops = { /*.get_max_channel_count =*/ audiounit_get_max_channel_count, /*.get_min_latency =*/ audiounit_get_min_latency, /*.get_preferred_sample_rate =*/ audiounit_get_preferred_sample_rate, + /*.get_preferred_channel_layout =*/ nullptr, /*.enumerate_devices =*/ audiounit_enumerate_devices, /*.destroy =*/ audiounit_destroy, /*.stream_init =*/ audiounit_stream_init, diff --git a/media/libcubeb/src/cubeb_jack.cpp b/media/libcubeb/src/cubeb_jack.cpp index 5d1ae964cc20..cf98b832fa28 100644 --- a/media/libcubeb/src/cubeb_jack.cpp +++ b/media/libcubeb/src/cubeb_jack.cpp @@ -114,6 +114,7 @@ static struct cubeb_ops const cbjack_ops = { .get_max_channel_count = cbjack_get_max_channel_count, .get_min_latency = cbjack_get_min_latency, .get_preferred_sample_rate = cbjack_get_preferred_sample_rate, + .get_preferred_channel_layout = NULL, .enumerate_devices = cbjack_enumerate_devices, .destroy = cbjack_destroy, .stream_init = cbjack_stream_init, @@ -420,7 +421,6 @@ cbjack_process(jack_nframes_t nframes, void * arg) return 0; } - static void cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in, float ** bufs_out, jack_nframes_t nframes) { @@ -433,7 +433,6 @@ cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in, fl long done_frames = 0; long input_frames_count = (in != NULL) ? nframes : 0; - done_frames = cubeb_resampler_fill(stream->resampler, inptr, &input_frames_count, @@ -934,7 +933,6 @@ cbjack_stream_set_volume(cubeb_stream * stm, float volume) return CUBEB_OK; } - static int cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device) { diff --git a/media/libcubeb/src/cubeb_mixer.cpp b/media/libcubeb/src/cubeb_mixer.cpp new file mode 100644 index 000000000000..843c58040aef --- /dev/null +++ b/media/libcubeb/src/cubeb_mixer.cpp @@ -0,0 +1,348 @@ +/* + * Copyright © 2016 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +#include +#include "cubeb-internal.h" +#include "cubeb_mixer.h" + +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 } +}; + +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 }, // UNDEFINED + { -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 +bool +downmix_3f2(T const * const in, unsigned 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 (unsigned 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 +bool +mix_remap(T const * const in, unsigned 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 (unsigned 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 +void +downmix_fallback(T const * const in, unsigned long inframes, T * out, unsigned int in_channels, unsigned int out_channels) +{ + assert(in_channels >= out_channels); + long out_index = 0; + for (unsigned 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 +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 +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 +void +cubeb_upmix(T const * in, long inframes, T * out, + unsigned int in_channels, unsigned int out_channels) +{ + assert(out_channels >= in_channels && in_channels > 0); + + /* Either way, if we have 2 or more channels, the first two are L and R. */ + /* If we are playing a mono stream over stereo speakers, copy the data over. */ + if (in_channels == 1 && out_channels >= 2) { + mono_to_stereo(in, inframes, out, out_channels); + } else { + /* Copy through. */ + for (unsigned int i = 0, o = 0; i < inframes * in_channels; + i += in_channels, o += out_channels) { + for (unsigned int j = 0; j < in_channels; ++j) { + out[o + j] = in[i + j]; + } + } + } + + /* Check if more channels. */ + if (out_channels <= 2) { + return; + } + + /* Put silence in remaining channels. */ + for (long i = 0, o = 0; i < inframes; ++i, o += out_channels) { + for (unsigned int j = 2; j < out_channels; ++j) { + out[o + j] = 0.0; + } + } +} + +bool +cubeb_should_upmix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer) +{ + return mixer->channels > stream->channels; +} + +bool +cubeb_should_downmix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer) +{ + if (mixer->channels > stream->channels || mixer->layout == stream->layout) { + return false; + } + + return mixer->channels < stream->channels || + // When mixer.channels == stream.channels + mixer->layout == CUBEB_LAYOUT_UNDEFINED || // fallback downmix + (stream->layout == CUBEB_LAYOUT_3F2 && // 3f2 downmix + (mixer->layout == CUBEB_LAYOUT_2F2_LFE || + mixer->layout == CUBEB_LAYOUT_3F1_LFE)); +} + +void +cubeb_downmix_float(float * const in, long inframes, float * out, + unsigned int in_channels, unsigned int out_channels, + cubeb_channel_layout in_layout, cubeb_channel_layout out_layout) +{ + cubeb_downmix(in, inframes, out, in_channels, out_channels, in_layout, out_layout); +} + +void +cubeb_upmix_float(float * const in, long inframes, float * out, + unsigned int in_channels, unsigned int out_channels) +{ + cubeb_upmix(in, inframes, out, in_channels, out_channels); +} diff --git a/media/libcubeb/src/cubeb_mixer.h b/media/libcubeb/src/cubeb_mixer.h new file mode 100644 index 000000000000..1527483d28e4 --- /dev/null +++ b/media/libcubeb/src/cubeb_mixer.h @@ -0,0 +1,70 @@ +/* + * Copyright © 2016 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +#ifndef CUBEB_MIXER +#define CUBEB_MIXER + +#include "cubeb/cubeb.h" // for cubeb_channel_layout ,CUBEB_CHANNEL_LAYOUT_MAPS and cubeb_stream_params. +#include + +#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 }, // UNDEFINED + { 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_MIXER diff --git a/media/libcubeb/src/cubeb_opensl.c b/media/libcubeb/src/cubeb_opensl.c index f12f88cda848..24dd8e486667 100644 --- a/media/libcubeb/src/cubeb_opensl.c +++ b/media/libcubeb/src/cubeb_opensl.c @@ -1740,6 +1740,7 @@ static struct cubeb_ops const opensl_ops = { .get_max_channel_count = opensl_get_max_channel_count, .get_min_latency = opensl_get_min_latency, .get_preferred_sample_rate = opensl_get_preferred_sample_rate, + .get_preferred_channel_layout = NULL, .enumerate_devices = NULL, .destroy = opensl_destroy, .stream_init = opensl_stream_init, diff --git a/media/libcubeb/src/cubeb_pulse.c b/media/libcubeb/src/cubeb_pulse.c index 7bb64918dc5a..c9de4fc390bd 100644 --- a/media/libcubeb/src/cubeb_pulse.c +++ b/media/libcubeb/src/cubeb_pulse.c @@ -12,6 +12,7 @@ #include #include "cubeb/cubeb.h" #include "cubeb-internal.h" +#include "cubeb_mixer.h" #include #ifdef DISABLE_LIBPULSE_DLOPEN @@ -20,7 +21,7 @@ #define WRAP(x) cubeb_##x #define LIBPULSE_API_VISIT(X) \ X(pa_channel_map_can_balance) \ - X(pa_channel_map_init_auto) \ + X(pa_channel_map_init) \ X(pa_context_connect) \ X(pa_context_disconnect) \ X(pa_context_drain) \ @@ -468,6 +469,113 @@ stream_update_timing_info(cubeb_stream * stm) return r; } +static pa_channel_position_t +cubeb_channel_to_pa_channel(cubeb_channel channel) +{ + assert(channel != CHANNEL_INVALID); + + // 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 pa_channel_position_t map[CHANNEL_MAX] = { + // PA_CHANNEL_POSITION_INVALID, // CHANNEL_INVALID + PA_CHANNEL_POSITION_MONO, // CHANNEL_MONO + PA_CHANNEL_POSITION_FRONT_LEFT, // CHANNEL_LEFT + PA_CHANNEL_POSITION_FRONT_RIGHT, // CHANNEL_RIGHT + PA_CHANNEL_POSITION_FRONT_CENTER, // CHANNEL_CENTER + PA_CHANNEL_POSITION_SIDE_LEFT, // CHANNEL_LS + PA_CHANNEL_POSITION_SIDE_RIGHT, // CHANNEL_RS + PA_CHANNEL_POSITION_REAR_LEFT, // CHANNEL_RLS + PA_CHANNEL_POSITION_REAR_CENTER, // CHANNEL_RCENTER + PA_CHANNEL_POSITION_REAR_RIGHT, // CHANNEL_RRS + PA_CHANNEL_POSITION_LFE // CHANNEL_LFE + }; + + return map[channel]; +} + +static cubeb_channel +pa_channel_to_cubeb_channel(pa_channel_position_t channel) +{ + assert(channel != PA_CHANNEL_POSITION_INVALID); + switch(channel) { + case PA_CHANNEL_POSITION_MONO: return CHANNEL_MONO; + case PA_CHANNEL_POSITION_FRONT_LEFT: return CHANNEL_LEFT; + case PA_CHANNEL_POSITION_FRONT_RIGHT: return CHANNEL_RIGHT; + case PA_CHANNEL_POSITION_FRONT_CENTER: return CHANNEL_CENTER; + case PA_CHANNEL_POSITION_SIDE_LEFT: return CHANNEL_LS; + case PA_CHANNEL_POSITION_SIDE_RIGHT: return CHANNEL_RS; + case PA_CHANNEL_POSITION_REAR_LEFT: return CHANNEL_RLS; + case PA_CHANNEL_POSITION_REAR_CENTER: return CHANNEL_RCENTER; + case PA_CHANNEL_POSITION_REAR_RIGHT: return CHANNEL_RRS; + case PA_CHANNEL_POSITION_LFE: return CHANNEL_LFE; + default: return CHANNEL_INVALID; + } +} + +static void +layout_to_channel_map(cubeb_channel_layout layout, pa_channel_map * cm) +{ + assert(cm && layout != CUBEB_LAYOUT_UNDEFINED); + + WRAP(pa_channel_map_init)(cm); + cm->channels = CUBEB_CHANNEL_LAYOUT_MAPS[layout].channels; + for (uint8_t i = 0 ; i < cm->channels ; ++i) { + cm->map[i] = cubeb_channel_to_pa_channel(CHANNEL_INDEX_TO_ORDER[layout][i]); + } +} + +// DUAL_MONO(_LFE) is same as STEREO(_LFE). +#define MASK_MONO (1 << CHANNEL_MONO) +#define MASK_MONO_LFE (MASK_MONO | (1 << CHANNEL_LFE)) +#define MASK_STEREO ((1 << CHANNEL_LEFT) | (1 << CHANNEL_RIGHT)) +#define MASK_STEREO_LFE (MASK_STEREO | (1 << CHANNEL_LFE)) +#define MASK_3F (MASK_STEREO | (1 << CHANNEL_CENTER)) +#define MASK_3F_LFE (MASK_3F | (1 << CHANNEL_LFE)) +#define MASK_2F1 (MASK_STEREO | (1 << CHANNEL_RCENTER)) +#define MASK_2F1_LFE (MASK_2F1 | (1 << CHANNEL_LFE)) +#define MASK_3F1 (MASK_3F | (1 << CHANNEL_RCENTER)) +#define MASK_3F1_LFE (MASK_3F1 | (1 << CHANNEL_LFE)) +#define MASK_2F2 (MASK_STEREO | (1 << CHANNEL_LS) | (1 << CHANNEL_RS)) +#define MASK_2F2_LFE (MASK_2F2 | (1 << CHANNEL_LFE)) +#define MASK_3F2 (MASK_2F2 | (1 << CHANNEL_CENTER)) +#define MASK_3F2_LFE (MASK_3F2 | (1 << CHANNEL_LFE)) +#define MASK_3F3R_LFE (MASK_3F2_LFE | (1 << CHANNEL_RCENTER)) +#define MASK_3F4_LFE (MASK_3F2_LFE | (1 << CHANNEL_RLS) | (1 << CHANNEL_RRS)) + +static cubeb_channel_layout +channel_map_to_layout(pa_channel_map * cm) +{ + uint32_t channel_mask = 0; + for (uint8_t i = 0 ; i < cm->channels ; ++i) { + cubeb_channel channel = pa_channel_to_cubeb_channel(cm->map[i]); + if (channel == CHANNEL_INVALID) { + return CUBEB_LAYOUT_UNDEFINED; + } + channel_mask |= 1 << channel; + } + + switch(channel_mask) { + 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; + } +} + static void pulse_context_destroy(cubeb * ctx); static void pulse_destroy(cubeb * ctx); @@ -596,6 +704,23 @@ pulse_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) return CUBEB_OK; } +static int +pulse_get_preferred_channel_layout(cubeb * ctx, cubeb_channel_layout * layout) +{ + assert(ctx && layout); + (void)ctx; + + WRAP(pa_threaded_mainloop_lock)(ctx->mainloop); + while (!ctx->default_sink_info) { + WRAP(pa_threaded_mainloop_wait)(ctx->mainloop); + } + WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop); + + *layout = channel_map_to_layout(&ctx->default_sink_info->channel_map); + + return CUBEB_OK; +} + static int pulse_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames) { @@ -672,7 +797,8 @@ create_pa_stream(cubeb_stream * stm, cubeb_stream_params * stream_params, char const * stream_name) { - assert(stm && stream_params); + assert(stm && stream_params && stream_params->layout != CUBEB_LAYOUT_UNDEFINED && + CUBEB_CHANNEL_LAYOUT_MAPS[stream_params->layout].channels == stream_params->channels); *pa_stm = NULL; pa_sample_spec ss; ss.format = to_pulse_format(stream_params->format); @@ -681,7 +807,10 @@ create_pa_stream(cubeb_stream * stm, ss.rate = stream_params->rate; ss.channels = stream_params->channels; - *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL); + pa_channel_map cm; + layout_to_channel_map(stream_params->layout, &cm); + + *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, &cm); return (*pa_stm == NULL) ? CUBEB_ERROR : CUBEB_OK; } @@ -1410,6 +1539,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 = pulse_get_preferred_channel_layout, .enumerate_devices = pulse_enumerate_devices, .destroy = pulse_destroy, .stream_init = pulse_stream_init, diff --git a/media/libcubeb/src/cubeb_resampler.cpp b/media/libcubeb/src/cubeb_resampler.cpp index f6676946c0f4..91fa35ffa63a 100644 --- a/media/libcubeb/src/cubeb_resampler.cpp +++ b/media/libcubeb/src/cubeb_resampler.cpp @@ -124,7 +124,6 @@ cubeb_resampler_speex T * out_unprocessed = nullptr; long output_frames_before_processing = 0; - /* fill directly the input buffer of the output processor to save a copy */ output_frames_before_processing = output_processor->input_needed_for_output(output_frames_needed); @@ -174,7 +173,6 @@ cubeb_resampler_speex return (*input_frames_count) * (got / resampled_frame_count); } - template long cubeb_resampler_speex diff --git a/media/libcubeb/src/cubeb_sndio.c b/media/libcubeb/src/cubeb_sndio.c index 793789765833..7aae5d820a87 100644 --- a/media/libcubeb/src/cubeb_sndio.c +++ b/media/libcubeb/src/cubeb_sndio.c @@ -366,6 +366,7 @@ static struct cubeb_ops const sndio_ops = { .get_max_channel_count = sndio_get_max_channel_count, .get_min_latency = sndio_get_min_latency, .get_preferred_sample_rate = sndio_get_preferred_sample_rate, + .get_preferred_channel_layout = NULL, .enumerate_devices = NULL, .destroy = sndio_destroy, .stream_init = sndio_stream_init, diff --git a/media/libcubeb/src/cubeb_utils.h b/media/libcubeb/src/cubeb_utils.h index 94e5ab056f5a..a7f4c8cfb441 100644 --- a/media/libcubeb/src/cubeb_utils.h +++ b/media/libcubeb/src/cubeb_utils.h @@ -46,6 +46,63 @@ void PodZero(T * destination, size_t count) memset(destination, 0, count * sizeof(T)); } +namespace { +template +void Copy(T * destination, const T * source, size_t count, Trait) +{ + for (size_t i = 0; i < count; i++) { + destination[i] = source[i]; + } +} + +template +void Copy(T * destination, const T * source, size_t count, std::true_type) +{ + PodCopy(destination, source, count); +} +} + +/** + * This allows copying a number of elements from a `source` pointer to a + * `destination` pointer, using `memcpy` if it is safe to do so, or a loop that + * calls the constructors and destructors otherwise. + */ +template +void Copy(T * destination, const T * source, size_t count) +{ + assert(destination && source); + Copy(destination, source, count, typename std::is_trivial::type()); +} + +namespace { +template +void ConstructDefault(T * destination, size_t count, Trait) +{ + for (size_t i = 0; i < count; i++) { + destination[i] = T(); + } +} + +template +void ConstructDefault(T * destination, + size_t count, std::true_type) +{ + PodZero(destination, count); +} +} + +/** + * This allows zeroing (using memset) or default-constructing a number of + * elements calling the constructors and destructors if necessary. + */ +template +void ConstructDefault(T * destination, size_t count) +{ + assert(destination); + ConstructDefault(destination, count, + typename std::is_arithmetic::type()); +} + template class auto_array { diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp index af0a0ca367cc..2920b5dfda75 100644 --- a/media/libcubeb/src/cubeb_wasapi.cpp +++ b/media/libcubeb/src/cubeb_wasapi.cpp @@ -4,6 +4,11 @@ * This program is made available under an ISC-style license. See the * accompanying file LICENSE for details. */ +// Explicitly define NTDDI_VERSION rather than letting the value be derived +// from _WIN32_WINNT because we depend on values defined for XP SP2 and WS03 +// SP1. +#define _WIN32_WINNT 0x0502 +#define NTDDI_VERSION 0x05020100 #define NOMINMAX #include @@ -27,6 +32,7 @@ #include "cubeb/cubeb.h" #include "cubeb-internal.h" +#include "cubeb_mixer.h" #include "cubeb_resampler.h" #include "cubeb_utils.h" @@ -198,12 +204,12 @@ struct cubeb_stream { /* Mixer pameters. We need to convert the input stream to this samplerate/channel layout, as WASAPI does not resample nor upmix itself. */ - cubeb_stream_params input_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0 }; - cubeb_stream_params output_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0 }; + cubeb_stream_params input_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED }; + cubeb_stream_params output_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED }; /* Stream parameters. This is what the client requested, * and what will be presented in the callback. */ - 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 }; /* The input and output device, or NULL for default. */ std::unique_ptr input_device; std::unique_ptr output_device; @@ -392,21 +398,89 @@ 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) @@ -439,71 +513,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 -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 -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 -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; } @@ -519,8 +535,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; @@ -551,12 +567,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); } } @@ -615,23 +632,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(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(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(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(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(input_packet), @@ -781,7 +800,8 @@ refill_callback_input(cubeb_stream * stm) nullptr, 0); - consumed_all_buffer = read == stm->linear_input_buffer.length(); + XASSERT(read >= 0); + consumed_all_buffer = (unsigned long) read == stm->linear_input_buffer.length(); stm->linear_input_buffer.clear(); @@ -817,7 +837,7 @@ refill_callback_output(cubeb_stream * stm) output_frames, got); XASSERT(got >= 0); - XASSERT(got == output_frames || stm->draining); + XASSERT((unsigned long) got == output_frames || stm->draining); hr = stm->render_client->ReleaseBuffer(got, 0); if (FAILED(hr)) { @@ -825,7 +845,7 @@ refill_callback_output(cubeb_stream * stm) return false; } - return got == output_frames || stm->draining; + return (unsigned long) got == output_frames || stm->draining; } static unsigned int __stdcall @@ -860,7 +880,6 @@ wasapi_stream_render_loop(LPVOID stream) LOG("Unable to use mmcss to bump the render thread priority: %lx", GetLastError()); } - /* WaitForMultipleObjects timeout can trigger in cases where we don't want to treat it as a timeout, such as across a system sleep/wake cycle. Trigger the timeout error handling only when the timeout_limit is reached, which is @@ -1211,13 +1230,18 @@ bool stop_and_join_render_thread(cubeb_stream * stm) rv = false; } - LOG("Closing thread."); - CloseHandle(stm->thread); - stm->thread = NULL; + // Only attempts to close and null out the thread and event if the + // WaitForSingleObject above succeeded, so that calling this function again + // attemps to clean up the thread and event each time. + if (rv) { + LOG("Closing thread."); + CloseHandle(stm->thread); + stm->thread = NULL; - CloseHandle(stm->shutdown_event); - stm->shutdown_event = 0; + CloseHandle(stm->shutdown_event); + stm->shutdown_event = 0; + } return rv; } @@ -1359,6 +1383,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 device; + hr = get_default_endpoint(device, eRender); + if (FAILED(hr)) { + return CUBEB_ERROR; + } + + com_ptr 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 mix_format(tmp); + + WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast(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 @@ -1366,12 +1428,7 @@ void wasapi_stream_destroy(cubeb_stream * stm); static void handle_channel_layout(cubeb_stream * stm, com_heap_ptr & 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 @@ -1386,19 +1443,10 @@ handle_channel_layout(cubeb_stream * stm, com_heap_ptr & 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; @@ -1499,16 +1547,39 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm, } com_heap_ptr 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(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 | @@ -1530,8 +1601,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)); } @@ -1712,10 +1783,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(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(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; @@ -1921,7 +1996,6 @@ int wasapi_stream_stop(cubeb_stream * stm) } } - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); } @@ -2252,6 +2326,7 @@ cubeb_ops const wasapi_ops = { /*.get_max_channel_count =*/ wasapi_get_max_channel_count, /*.get_min_latency =*/ wasapi_get_min_latency, /*.get_preferred_sample_rate =*/ wasapi_get_preferred_sample_rate, + /*.get_preferred_channel_layout =*/ wasapi_get_preferred_channel_layout, /*.enumerate_devices =*/ wasapi_enumerate_devices, /*.destroy =*/ wasapi_destroy, /*.stream_init =*/ wasapi_stream_init, diff --git a/media/libcubeb/src/cubeb_winmm.c b/media/libcubeb/src/cubeb_winmm.c index d2bafaeafdd9..9be73a413bb9 100644 --- a/media/libcubeb/src/cubeb_winmm.c +++ b/media/libcubeb/src/cubeb_winmm.c @@ -512,7 +512,6 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n return CUBEB_ERROR; } - for (i = 0; i < NBUFS; ++i) { WAVEHDR * hdr = &stm->buffers[i]; @@ -769,7 +768,6 @@ winmm_calculate_device_rate(cubeb_device_info * info, DWORD formats) } } - #define MM_S16_MASK (WAVE_FORMAT_1M16 | WAVE_FORMAT_1S16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4M16 | \ WAVE_FORMAT_4S16 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S16 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S16) static int @@ -1050,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, diff --git a/media/libcubeb/src/moz.build b/media/libcubeb/src/moz.build index 78121462065c..fc9c79198e30 100644 --- a/media/libcubeb/src/moz.build +++ b/media/libcubeb/src/moz.build @@ -10,6 +10,7 @@ Library('cubeb') SOURCES += [ 'cubeb.c', + 'cubeb_mixer.cpp', 'cubeb_panner.cpp' ] diff --git a/media/libcubeb/update.sh b/media/libcubeb/update.sh index 0a6d1816ce39..0eb4e8c5b110 100755 --- a/media/libcubeb/update.sh +++ b/media/libcubeb/update.sh @@ -31,6 +31,8 @@ cp $1/src/cubeb_utils_unix.h src cp $1/src/cubeb_utils_win.h src cp $1/src/cubeb_wasapi.cpp src cp $1/src/cubeb_winmm.c src +cp $1/src/cubeb_mixer.h src +cp $1/src/cubeb_mixer.cpp src cp $1/test/common.h gtest cp $1/test/test_audio.cpp gtest cp $1/test/test_devices.cpp gtest @@ -42,6 +44,7 @@ cp $1/test/test_ring_array.cpp gtest cp $1/test/test_sanity.cpp gtest cp $1/test/test_tone.cpp gtest cp $1/test/test_utils.cpp gtest +cp $1/test/test_mixer.cpp gtest if [ -d $1/.git ]; then rev=$(cd $1 && git rev-parse --verify HEAD)