mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 13:51:41 +00:00
Bug 1331869 - Update cubeb from upstream to d96e35f02d. r=kinetik,padenot
MozReview-Commit-ID: 9H3cKh82Jwv --HG-- extra : rebase_source : fc8f877e627aced0ff0fcc2d18766ba05560d6af
This commit is contained in:
parent
349ec8590a
commit
538d575a01
@ -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.
|
||||
|
@ -16,6 +16,13 @@
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
template<typename T, size_t N>
|
||||
constexpr size_t
|
||||
ARRAY_LENGTH(T(&)[N])
|
||||
{
|
||||
return N;
|
||||
}
|
||||
|
||||
void delay(unsigned int ms)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
@ -30,6 +37,34 @@ void delay(unsigned int ms)
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
char const * name;
|
||||
unsigned int const channels;
|
||||
cubeb_channel_layout const layout;
|
||||
} layout_info;
|
||||
|
||||
layout_info const layout_infos[CUBEB_LAYOUT_MAX] = {
|
||||
{ "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;
|
||||
|
@ -24,7 +24,6 @@
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
#define NELEMS(x) ((int) (sizeof(x) / sizeof(x[0])))
|
||||
#define VOLUME 0.2
|
||||
|
||||
float get_frequency(int channel_index)
|
||||
@ -118,7 +117,7 @@ int supports_channel_count(const char* backend_id, int nchannels)
|
||||
(strcmp(backend_id, "opensl") != 0 && strcmp(backend_id, "audiotrack") != 0);
|
||||
}
|
||||
|
||||
int run_test(int num_channels, int sampling_rate, int is_float)
|
||||
int run_test(int num_channels, layout_info layout, int sampling_rate, int is_float)
|
||||
{
|
||||
int r = CUBEB_OK;
|
||||
|
||||
@ -142,12 +141,13 @@ int run_test(int num_channels, int sampling_rate, int is_float)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Testing %d channel(s), %d Hz, %s (%s)\n", num_channels, sampling_rate, is_float ? "float" : "short", cubeb_get_backend_id(ctx));
|
||||
fprintf(stderr, "Testing %d channel(s), layout: %s, %d Hz, %s (%s)\n", num_channels, layout.name, sampling_rate, is_float ? "float" : "short", cubeb_get_backend_id(ctx));
|
||||
|
||||
cubeb_stream_params params;
|
||||
params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
|
||||
params.rate = sampling_rate;
|
||||
params.channels = num_channels;
|
||||
params.layout = layout.layout;
|
||||
|
||||
synth = synth_create(params.channels, params.rate);
|
||||
if (synth == NULL) {
|
||||
@ -200,6 +200,7 @@ int run_panning_volume_test(int is_float)
|
||||
params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
|
||||
params.rate = 44100;
|
||||
params.channels = 2;
|
||||
params.layout = CUBEB_LAYOUT_STEREO;
|
||||
|
||||
synth = synth_create(params.channels, params.rate);
|
||||
if (synth == NULL) {
|
||||
@ -259,7 +260,7 @@ TEST(cubeb, run_panning_volume_test_float)
|
||||
|
||||
TEST(cubeb, run_channel_rate_test)
|
||||
{
|
||||
int channel_values[] = {
|
||||
unsigned int channel_values[] = {
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
@ -274,13 +275,16 @@ TEST(cubeb, run_channel_rate_test)
|
||||
48000,
|
||||
};
|
||||
|
||||
for(int j = 0; j < NELEMS(channel_values); ++j) {
|
||||
for(int i = 0; i < NELEMS(freq_values); ++i) {
|
||||
for(unsigned int j = 0; j < ARRAY_LENGTH(channel_values); ++j) {
|
||||
for(unsigned int i = 0; i < ARRAY_LENGTH(freq_values); ++i) {
|
||||
ASSERT_TRUE(channel_values[j] < MAX_NUM_CHANNELS);
|
||||
fprintf(stderr, "--------------------------\n");
|
||||
ASSERT_EQ(run_test(channel_values[j], freq_values[i], 0), CUBEB_OK);
|
||||
ASSERT_EQ(run_test(channel_values[j], freq_values[i], 1), CUBEB_OK);
|
||||
for (unsigned int k = 0 ; k < ARRAY_LENGTH(layout_infos); ++k ) {
|
||||
if (layout_infos[k].channels == channel_values[j]) {
|
||||
ASSERT_EQ(run_test(channel_values[j], layout_infos[k], freq_values[i], 0), CUBEB_OK);
|
||||
ASSERT_EQ(run_test(channel_values[j], layout_infos[k], freq_values[i], 1), CUBEB_OK);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,9 +108,11 @@ TEST(cubeb, duplex)
|
||||
input_params.format = STREAM_FORMAT;
|
||||
input_params.rate = 48000;
|
||||
input_params.channels = 1;
|
||||
input_params.layout = CUBEB_LAYOUT_MONO;
|
||||
output_params.format = STREAM_FORMAT;
|
||||
output_params.rate = 48000;
|
||||
output_params.channels = 2;
|
||||
output_params.layout = CUBEB_LAYOUT_STEREO;
|
||||
|
||||
r = cubeb_get_min_latency(ctx, output_params, &latency_frames);
|
||||
|
||||
|
@ -9,6 +9,7 @@ TEST(cubeb, latency)
|
||||
uint32_t max_channels;
|
||||
uint32_t preferred_rate;
|
||||
uint32_t latency_frames;
|
||||
cubeb_channel_layout layout;
|
||||
|
||||
r = cubeb_init(&ctx, "Cubeb audio test");
|
||||
ASSERT_EQ(r, CUBEB_OK);
|
||||
@ -25,10 +26,17 @@ TEST(cubeb, latency)
|
||||
ASSERT_GT(preferred_rate, 0u);
|
||||
}
|
||||
|
||||
r = cubeb_get_preferred_channel_layout(ctx, &layout);
|
||||
ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
|
||||
|
||||
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);
|
||||
|
181
media/libcubeb/gtest/test_mixer.cpp
Normal file
181
media/libcubeb/gtest/test_mixer.cpp
Normal file
@ -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 <vector>
|
||||
|
||||
using std::vector;
|
||||
|
||||
#define STREAM_FREQUENCY 48000
|
||||
#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
|
||||
|
||||
float const M = 1.0f; // Mono
|
||||
float const L = 2.0f; // Left
|
||||
float const R = 3.0f; // Right
|
||||
float const C = 4.0f; // Center
|
||||
float const LS = 5.0f; // Left Surround
|
||||
float const RS = 6.0f; // Right Surround
|
||||
float const RLS = 7.0f; // Rear Left Surround
|
||||
float const RC = 8.0f; // Rear Center
|
||||
float const RRS = 9.0f; // Rear Right Surround
|
||||
float const LFE = 10.0f; // Low Frequency Effects
|
||||
|
||||
float const INV_SQRT_2 = 0.707106f; // 1/sqrt(2)
|
||||
static float const DOWNMIX_3F2_RESULTS[2][12][5] = {
|
||||
// 3F2
|
||||
{
|
||||
{ INV_SQRT_2*(L+R) + C + 0.5f*(LS+RS) }, // Mono
|
||||
{ INV_SQRT_2*(L+R) + C + 0.5f*(LS+RS), 0 }, // Mono-LFE
|
||||
{ L + INV_SQRT_2*(C+LS), R + INV_SQRT_2*(C+RS) }, // Stereo
|
||||
{ L + INV_SQRT_2*(C+LS), R + INV_SQRT_2*(C+RS), 0 }, // Stereo-LFE
|
||||
{ L + INV_SQRT_2*LS, R + INV_SQRT_2*RS, C }, // 3F
|
||||
{ L + INV_SQRT_2*LS, R + INV_SQRT_2*RS, C, 0 }, // 3F-LFE
|
||||
{ L + C*INV_SQRT_2, R + C*INV_SQRT_2, INV_SQRT_2*(LS+RS) }, // 2F1
|
||||
{ L + C*INV_SQRT_2, R + C*INV_SQRT_2, 0, INV_SQRT_2*(LS+RS) }, // 2F1-LFE
|
||||
{ L, R, C, INV_SQRT_2*(LS+RS) }, // 3F1
|
||||
{ L, R, C, 0, INV_SQRT_2*(LS+RS) }, // 3F1-LFE
|
||||
{ L + INV_SQRT_2*C, R + INV_SQRT_2*C, LS, RS }, // 2F2
|
||||
{ L + INV_SQRT_2*C, R + INV_SQRT_2*C, 0, LS, RS } // 2F2-LFE
|
||||
},
|
||||
// 3F2-LFE
|
||||
{
|
||||
{ INV_SQRT_2*(L+R) + C + 0.5f*(LS+RS) }, // Mono
|
||||
{ INV_SQRT_2*(L+R) + C + 0.5f*(LS+RS), LFE }, // Mono-LFE
|
||||
{ L + INV_SQRT_2*(C+LS), R + INV_SQRT_2*(C+RS) }, // Stereo
|
||||
{ L + INV_SQRT_2*(C+LS), R + INV_SQRT_2*(C+RS), LFE }, // Stereo-LFE
|
||||
{ L + INV_SQRT_2*LS, R + INV_SQRT_2*RS, C }, // 3F
|
||||
{ L + INV_SQRT_2*LS, R + INV_SQRT_2*RS, C, LFE }, // 3F-LFE
|
||||
{ L + C*INV_SQRT_2, R + C*INV_SQRT_2, INV_SQRT_2*(LS+RS) }, // 2F1
|
||||
{ L + C*INV_SQRT_2, R + C*INV_SQRT_2, LFE, INV_SQRT_2*(LS+RS) }, // 2F1-LFE
|
||||
{ L, R, C, INV_SQRT_2*(LS+RS) }, // 3F1
|
||||
{ L, R, C, LFE, INV_SQRT_2*(LS+RS) }, // 3F1-LFE
|
||||
{ L + INV_SQRT_2*C, R + INV_SQRT_2*C, LS, RS }, // 2F2
|
||||
{ L + INV_SQRT_2*C, R + INV_SQRT_2*C, LFE, LS, RS } // 2F2-LFE
|
||||
}
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
cubeb_channel_layout layout;
|
||||
float data[10];
|
||||
} audio_input;
|
||||
|
||||
audio_input audio_inputs[CUBEB_LAYOUT_MAX] = {
|
||||
{ CUBEB_LAYOUT_UNDEFINED, { } },
|
||||
{ CUBEB_LAYOUT_DUAL_MONO, { L, R } },
|
||||
{ CUBEB_LAYOUT_DUAL_MONO_LFE, { L, R, LFE } },
|
||||
{ CUBEB_LAYOUT_MONO, { M } },
|
||||
{ CUBEB_LAYOUT_MONO_LFE, { M, LFE } },
|
||||
{ CUBEB_LAYOUT_STEREO, { L, R } },
|
||||
{ CUBEB_LAYOUT_STEREO_LFE, { L, R, LFE } },
|
||||
{ CUBEB_LAYOUT_3F, { L, R, C } },
|
||||
{ CUBEB_LAYOUT_3F_LFE, { L, R, C, LFE } },
|
||||
{ CUBEB_LAYOUT_2F1, { L, R, RC } },
|
||||
{ CUBEB_LAYOUT_2F1_LFE, { L, R, LFE, RC } },
|
||||
{ CUBEB_LAYOUT_3F1, { L, R, C, RC } },
|
||||
{ CUBEB_LAYOUT_3F1_LFE, { L, R, C, LFE, RC } },
|
||||
{ CUBEB_LAYOUT_2F2, { L, R, LS, RS } },
|
||||
{ CUBEB_LAYOUT_2F2_LFE, { L, R, LFE, LS, RS } },
|
||||
{ CUBEB_LAYOUT_3F2, { L, R, C, LS, RS } },
|
||||
{ CUBEB_LAYOUT_3F2_LFE, { L, R, C, LFE, LS, RS } },
|
||||
{ CUBEB_LAYOUT_3F3R_LFE, { L, R, C, LFE, RC, LS, RS } },
|
||||
{ CUBEB_LAYOUT_3F4_LFE, { L, R, C, LFE, RLS, RRS, LS, RS } }
|
||||
};
|
||||
|
||||
void
|
||||
downmix_test(float const * data, cubeb_channel_layout in_layout, cubeb_channel_layout out_layout)
|
||||
{
|
||||
if (in_layout == CUBEB_LAYOUT_UNDEFINED) {
|
||||
return; // Only possible output layout would be 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<float> in(in_params.channels * inframes);
|
||||
vector<float> out(out_params.channels * inframes);
|
||||
|
||||
for (unsigned int offset = 0 ; offset < inframes * in_params.channels ; offset += in_params.channels) {
|
||||
for (unsigned int i = 0 ; i < in_params.channels ; ++i) {
|
||||
in[offset + i] = data[i];
|
||||
}
|
||||
}
|
||||
|
||||
cubeb_downmix_float(in.data(), inframes, out.data(), in_params.channels, out_params.channels, in_params.layout, out_params.layout);
|
||||
|
||||
uint32_t in_layout_mask = 0;
|
||||
for (unsigned int i = 0 ; i < in_params.channels; ++i) {
|
||||
in_layout_mask |= 1 << CHANNEL_INDEX_TO_ORDER[in_layout][i];
|
||||
}
|
||||
|
||||
uint32_t out_layout_mask = 0;
|
||||
for (unsigned int i = 0 ; out_layout != CUBEB_LAYOUT_UNDEFINED && i < out_params.channels; ++i) {
|
||||
out_layout_mask |= 1 << CHANNEL_INDEX_TO_ORDER[out_layout][i];
|
||||
}
|
||||
|
||||
for (unsigned int i = 0 ; i < inframes * out_params.channels ; ++i) {
|
||||
unsigned int index = i % out_params.channels;
|
||||
|
||||
// downmix_3f2
|
||||
if ((in_layout == CUBEB_LAYOUT_3F2 || in_layout == CUBEB_LAYOUT_3F2_LFE) &&
|
||||
out_layout >= CUBEB_LAYOUT_MONO && out_layout <= CUBEB_LAYOUT_2F2_LFE) {
|
||||
auto & downmix_results = DOWNMIX_3F2_RESULTS[in_layout - CUBEB_LAYOUT_3F2][out_layout - CUBEB_LAYOUT_MONO];
|
||||
fprintf(stderr, "[3f2] Expect: %lf, Get: %lf\n", downmix_results[index], out[index]);
|
||||
ASSERT_EQ(out[index], downmix_results[index]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// mix_remap
|
||||
if (out_layout_mask & in_layout_mask) {
|
||||
uint32_t mask = 1 << CHANNEL_INDEX_TO_ORDER[out_layout][index];
|
||||
fprintf(stderr, "[map channels] Expect: %lf, Get: %lf\n", (mask & in_layout_mask) ? audio_inputs[out_layout].data[index] : 0, out[index]);
|
||||
ASSERT_EQ(out[index], (mask & in_layout_mask) ? audio_inputs[out_layout].data[index] : 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
// downmix_fallback
|
||||
fprintf(stderr, "[fallback] Expect: %lf, Get: %lf\n", audio_inputs[in_layout].data[index], out[index]);
|
||||
ASSERT_EQ(out[index], audio_inputs[in_layout].data[index]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(cubeb, run_mixing_test)
|
||||
{
|
||||
for (unsigned int i = 0 ; i < ARRAY_LENGTH(audio_inputs) ; ++i) {
|
||||
for (unsigned int j = 0 ; j < ARRAY_LENGTH(layout_infos) ; ++j) {
|
||||
downmix_test(audio_inputs[i].data, audio_inputs[i].layout, layout_infos[j].layout);
|
||||
}
|
||||
}
|
||||
}
|
@ -95,6 +95,7 @@ TEST(cubeb, record)
|
||||
params.format = STREAM_FORMAT;
|
||||
params.rate = SAMPLE_FREQUENCY;
|
||||
params.channels = 1;
|
||||
params.layout = CUBEB_LAYOUT_MONO;
|
||||
|
||||
r = cubeb_stream_init(ctx, &stream, "Cubeb record (mono)", NULL, ¶ms, NULL, nullptr,
|
||||
4096, data_cb_record, state_cb_record, &stream_state);
|
||||
|
@ -286,7 +286,6 @@ long data_cb_resampler(cubeb_stream * /*stm*/, void * user_ptr,
|
||||
const float * in = reinterpret_cast<const float*>(input_buffer);
|
||||
float * out = reinterpret_cast<float*>(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);
|
||||
|
@ -18,19 +18,13 @@
|
||||
#define STREAM_RATE 44100
|
||||
#define STREAM_LATENCY 100 * STREAM_RATE / 1000
|
||||
#define STREAM_CHANNELS 1
|
||||
#define STREAM_LAYOUT CUBEB_LAYOUT_MONO
|
||||
#if (defined(_WIN32) || defined(__WIN32__))
|
||||
#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
|
||||
#else
|
||||
#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
|
||||
#endif
|
||||
|
||||
template<typename T, size_t N>
|
||||
constexpr size_t
|
||||
ARRAY_LENGTH(T(&)[N])
|
||||
{
|
||||
return N;
|
||||
}
|
||||
|
||||
int is_windows_7()
|
||||
{
|
||||
#ifdef __MINGW32__
|
||||
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -176,12 +176,71 @@ typedef enum {
|
||||
CUBEB_LOG_VERBOSE = 2, /**< Verbose logging of callbacks, can have performance implications. */
|
||||
} cubeb_log_level;
|
||||
|
||||
/** SMPTE channel layout (also known as wave order)
|
||||
* DUAL-MONO L R
|
||||
* DUAL-MONO-LFE L R LFE
|
||||
* MONO M
|
||||
* MONO-LFE M LFE
|
||||
* STEREO L R
|
||||
* STEREO-LFE L R LFE
|
||||
* 3F L R C
|
||||
* 3F-LFE L R C LFE
|
||||
* 2F1 L R S
|
||||
* 2F1-LFE L R LFE S
|
||||
* 3F1 L R C S
|
||||
* 3F1-LFE L R C LFE S
|
||||
* 2F2 L R LS RS
|
||||
* 2F2-LFE L R LFE LS RS
|
||||
* 3F2 L R C LS RS
|
||||
* 3F2-LFE L R C LFE LS RS
|
||||
* 3F3R-LFE L R C LFE RC LS RS
|
||||
* 3F4-LFE L R C LFE RLS RRS LS RS
|
||||
*
|
||||
* The abbreviation of channel name is defined in following table:
|
||||
* Abbr Channel name
|
||||
* ---------------------------
|
||||
* M Mono
|
||||
* L Left
|
||||
* R Right
|
||||
* C Center
|
||||
* LS Left Surround
|
||||
* RS Right Surround
|
||||
* RLS Rear Left Surround
|
||||
* RC Rear Center
|
||||
* RRS Rear Right Surround
|
||||
* LFE Low Frequency Effects
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
CUBEB_LAYOUT_UNDEFINED, // Indicate the speaker's layout is undefined.
|
||||
CUBEB_LAYOUT_DUAL_MONO,
|
||||
CUBEB_LAYOUT_DUAL_MONO_LFE,
|
||||
CUBEB_LAYOUT_MONO,
|
||||
CUBEB_LAYOUT_MONO_LFE,
|
||||
CUBEB_LAYOUT_STEREO,
|
||||
CUBEB_LAYOUT_STEREO_LFE,
|
||||
CUBEB_LAYOUT_3F,
|
||||
CUBEB_LAYOUT_3F_LFE,
|
||||
CUBEB_LAYOUT_2F1,
|
||||
CUBEB_LAYOUT_2F1_LFE,
|
||||
CUBEB_LAYOUT_3F1,
|
||||
CUBEB_LAYOUT_3F1_LFE,
|
||||
CUBEB_LAYOUT_2F2,
|
||||
CUBEB_LAYOUT_2F2_LFE,
|
||||
CUBEB_LAYOUT_3F2,
|
||||
CUBEB_LAYOUT_3F2_LFE,
|
||||
CUBEB_LAYOUT_3F3R_LFE,
|
||||
CUBEB_LAYOUT_3F4_LFE,
|
||||
CUBEB_LAYOUT_MAX
|
||||
} cubeb_channel_layout;
|
||||
|
||||
/** Stream format initialization parameters. */
|
||||
typedef struct {
|
||||
cubeb_sample_format format; /**< Requested sample format. One of
|
||||
#cubeb_sample_format. */
|
||||
unsigned int rate; /**< Requested sample rate. Valid range is [1000, 192000]. */
|
||||
unsigned int channels; /**< Requested channel count. Valid range is [1, 8]. */
|
||||
cubeb_sample_format format; /**< Requested sample format. One of
|
||||
#cubeb_sample_format. */
|
||||
unsigned int rate; /**< Requested sample rate. Valid range is [1000, 192000]. */
|
||||
unsigned int channels; /**< Requested channel count. Valid range is [1, 8]. */
|
||||
cubeb_channel_layout layout; /**< Requested channel layout. This must be consistent with the provided channels. */
|
||||
#if defined(__ANDROID__)
|
||||
cubeb_stream_type stream_type; /**< Used to map Android audio stream types */
|
||||
#endif
|
||||
@ -376,7 +435,7 @@ CUBEB_EXPORT int cubeb_get_max_channel_count(cubeb * context, uint32_t * max_cha
|
||||
|
||||
/** Get the minimal latency value, in frames, that is guaranteed to work
|
||||
when creating a stream for the specified sample rate. This is platform,
|
||||
hardware and backend dependant.
|
||||
hardware and backend dependent.
|
||||
@param context A pointer to the cubeb context.
|
||||
@param params On some backends, the minimum achievable latency depends on
|
||||
the characteristics of the stream.
|
||||
@ -390,7 +449,7 @@ CUBEB_EXPORT int cubeb_get_min_latency(cubeb * context,
|
||||
uint32_t * latency_frames);
|
||||
|
||||
/** Get the preferred sample rate for this backend: this is hardware and
|
||||
platform dependant, and can avoid resampling, and/or trigger fastpaths.
|
||||
platform dependent, and can avoid resampling, and/or trigger fastpaths.
|
||||
@param context A pointer to the cubeb context.
|
||||
@param rate The samplerate (in Hz) the current configuration prefers.
|
||||
@retval CUBEB_OK
|
||||
@ -398,6 +457,15 @@ CUBEB_EXPORT int cubeb_get_min_latency(cubeb * context,
|
||||
@retval CUBEB_ERROR_NOT_SUPPORTED */
|
||||
CUBEB_EXPORT int cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate);
|
||||
|
||||
/** Get the preferred layout for this backend: this is hardware and
|
||||
platform dependent.
|
||||
@param context A pointer to the cubeb context.
|
||||
@param layout The layout of the current speaker configuration.
|
||||
@retval CUBEB_OK
|
||||
@retval CUBEB_ERROR_INVALID_PARAMETER
|
||||
@retval CUBEB_ERROR_NOT_SUPPORTED */
|
||||
CUBEB_EXPORT int cubeb_get_preferred_channel_layout(cubeb * context, cubeb_channel_layout * layout);
|
||||
|
||||
/** Destroy an application context. This must be called after all stream have
|
||||
* been destroyed.
|
||||
@param context A pointer to the cubeb context.*/
|
||||
|
@ -35,6 +35,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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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<int> 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<AudioObjectID> 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<int> output_callback_in_a_row{ 0 };
|
||||
/* This is true if a device change callback is currently running. */
|
||||
std::atomic<bool> switching_device{ false };
|
||||
std::atomic<bool> 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<cubeb_stream *>(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<uintptr_t>(input_device);
|
||||
@ -1512,18 +1752,14 @@ audiounit_stream_init(cubeb * context,
|
||||
stm->output_device = reinterpret_cast<uintptr_t>(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,
|
||||
|
@ -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)
|
||||
{
|
||||
|
348
media/libcubeb/src/cubeb_mixer.cpp
Normal file
348
media/libcubeb/src/cubeb_mixer.cpp
Normal file
@ -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 <cassert>
|
||||
#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<typename T>
|
||||
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<class T>
|
||||
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<typename T>
|
||||
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<typename T>
|
||||
void
|
||||
cubeb_downmix(T const * const in, long inframes, T * out,
|
||||
unsigned int in_channels, unsigned int out_channels,
|
||||
cubeb_channel_layout in_layout, cubeb_channel_layout out_layout)
|
||||
{
|
||||
assert(in_channels >= out_channels && in_layout != CUBEB_LAYOUT_UNDEFINED);
|
||||
|
||||
// If the channel number is different from the layout's setting or it's not a
|
||||
// valid audio 5.1 downmix, then we use fallback downmix mechanism.
|
||||
if (out_channels == CUBEB_CHANNEL_LAYOUT_MAPS[out_layout].channels &&
|
||||
in_channels == CUBEB_CHANNEL_LAYOUT_MAPS[in_layout].channels) {
|
||||
if (downmix_3f2(in, inframes, out, in_layout, out_layout)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mix_remap(in, inframes, out, in_layout, out_layout)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
downmix_fallback(in, inframes, out, in_channels, out_channels);
|
||||
}
|
||||
|
||||
/* Upmix function, copies a mono channel into L and R. */
|
||||
template<typename T>
|
||||
void
|
||||
mono_to_stereo(T const * in, long insamples, T * out, unsigned int out_channels)
|
||||
{
|
||||
for (long i = 0, j = 0; i < insamples; ++i, j += out_channels) {
|
||||
out[j] = out[j + 1] = in[i];
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void
|
||||
cubeb_upmix(T const * in, long inframes, T * out,
|
||||
unsigned int in_channels, unsigned int out_channels)
|
||||
{
|
||||
assert(out_channels >= in_channels && in_channels > 0);
|
||||
|
||||
/* Either way, if we have 2 or more channels, the first two are L and R. */
|
||||
/* If we are playing a mono stream over stereo speakers, copy the data over. */
|
||||
if (in_channels == 1 && out_channels >= 2) {
|
||||
mono_to_stereo(in, inframes, out, out_channels);
|
||||
} else {
|
||||
/* Copy through. */
|
||||
for (unsigned int i = 0, o = 0; i < inframes * in_channels;
|
||||
i += in_channels, o += out_channels) {
|
||||
for (unsigned int j = 0; j < in_channels; ++j) {
|
||||
out[o + j] = in[i + j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if more channels. */
|
||||
if (out_channels <= 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Put silence in remaining channels. */
|
||||
for (long i = 0, o = 0; i < inframes; ++i, o += out_channels) {
|
||||
for (unsigned int j = 2; j < out_channels; ++j) {
|
||||
out[o + j] = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
cubeb_should_upmix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer)
|
||||
{
|
||||
return mixer->channels > stream->channels;
|
||||
}
|
||||
|
||||
bool
|
||||
cubeb_should_downmix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer)
|
||||
{
|
||||
if (mixer->channels > stream->channels || mixer->layout == stream->layout) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return mixer->channels < stream->channels ||
|
||||
// When mixer.channels == stream.channels
|
||||
mixer->layout == CUBEB_LAYOUT_UNDEFINED || // fallback downmix
|
||||
(stream->layout == CUBEB_LAYOUT_3F2 && // 3f2 downmix
|
||||
(mixer->layout == CUBEB_LAYOUT_2F2_LFE ||
|
||||
mixer->layout == CUBEB_LAYOUT_3F1_LFE));
|
||||
}
|
||||
|
||||
void
|
||||
cubeb_downmix_float(float * const in, long inframes, float * out,
|
||||
unsigned int in_channels, unsigned int out_channels,
|
||||
cubeb_channel_layout in_layout, cubeb_channel_layout out_layout)
|
||||
{
|
||||
cubeb_downmix(in, inframes, out, in_channels, out_channels, in_layout, out_layout);
|
||||
}
|
||||
|
||||
void
|
||||
cubeb_upmix_float(float * const in, long inframes, float * out,
|
||||
unsigned int in_channels, unsigned int out_channels)
|
||||
{
|
||||
cubeb_upmix(in, inframes, out, in_channels, out_channels);
|
||||
}
|
70
media/libcubeb/src/cubeb_mixer.h
Normal file
70
media/libcubeb/src/cubeb_mixer.h
Normal file
@ -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 <stdbool.h>
|
||||
|
||||
#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
|
@ -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,
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <string.h>
|
||||
#include "cubeb/cubeb.h"
|
||||
#include "cubeb-internal.h"
|
||||
#include "cubeb_mixer.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#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,
|
||||
|
@ -124,7 +124,6 @@ cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
|
||||
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<T, InputProcessor, OutputProcessor>
|
||||
return (*input_frames_count) * (got / resampled_frame_count);
|
||||
}
|
||||
|
||||
|
||||
template<typename T, typename InputProcessor, typename OutputProcessor>
|
||||
long
|
||||
cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
|
||||
|
@ -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,
|
||||
|
@ -46,6 +46,63 @@ void PodZero(T * destination, size_t count)
|
||||
memset(destination, 0, count * sizeof(T));
|
||||
}
|
||||
|
||||
namespace {
|
||||
template<typename T, typename Trait>
|
||||
void Copy(T * destination, const T * source, size_t count, Trait)
|
||||
{
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
destination[i] = source[i];
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
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<typename T>
|
||||
void Copy(T * destination, const T * source, size_t count)
|
||||
{
|
||||
assert(destination && source);
|
||||
Copy(destination, source, count, typename std::is_trivial<T>::type());
|
||||
}
|
||||
|
||||
namespace {
|
||||
template<typename T, typename Trait>
|
||||
void ConstructDefault(T * destination, size_t count, Trait)
|
||||
{
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
destination[i] = T();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
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<typename T>
|
||||
void ConstructDefault(T * destination, size_t count)
|
||||
{
|
||||
assert(destination);
|
||||
ConstructDefault(destination, count,
|
||||
typename std::is_arithmetic<T>::type());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
class auto_array
|
||||
{
|
||||
|
@ -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 <initguid.h>
|
||||
@ -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<const wchar_t[]> input_device;
|
||||
std::unique_ptr<const wchar_t[]> 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<typename T>
|
||||
void
|
||||
mono_to_stereo(T * in, long insamples, T * out, int32_t out_channels)
|
||||
{
|
||||
for (int i = 0, j = 0; i < insamples; ++i, j += out_channels) {
|
||||
out[j] = out[j + 1] = in[i];
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void
|
||||
upmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels)
|
||||
{
|
||||
XASSERT(out_channels >= in_channels && in_channels > 0);
|
||||
|
||||
/* Either way, if we have 2 or more channels, the first two are L and R. */
|
||||
/* If we are playing a mono stream over stereo speakers, copy the data over. */
|
||||
if (in_channels == 1 && out_channels >= 2) {
|
||||
mono_to_stereo(in, inframes, out, out_channels);
|
||||
} else {
|
||||
/* Copy through. */
|
||||
for (int i = 0, o = 0; i < inframes * in_channels;
|
||||
i += in_channels, o += out_channels) {
|
||||
for (int j = 0; j < in_channels; ++j) {
|
||||
out[o + j] = in[i + j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if more channels. */
|
||||
if (out_channels <= 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Put silence in remaining channels. */
|
||||
for (long i = 0, o = 0; i < inframes; ++i, o += out_channels) {
|
||||
for (int j = 2; j < out_channels; ++j) {
|
||||
out[o + j] = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void
|
||||
downmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels)
|
||||
{
|
||||
XASSERT(in_channels >= out_channels);
|
||||
/* We could use a downmix matrix here, applying mixing weight based on the
|
||||
channel, but directsound and winmm simply drop the channels that cannot be
|
||||
rendered by the hardware, so we do the same for consistency. */
|
||||
long out_index = 0;
|
||||
for (long i = 0; i < inframes * in_channels; i += in_channels) {
|
||||
for (int j = 0; j < out_channels; ++j) {
|
||||
out[out_index + j] = in[i + j];
|
||||
}
|
||||
out_index += out_channels;
|
||||
}
|
||||
}
|
||||
|
||||
/* This returns the size of a frame in the stream, before the eventual upmix
|
||||
occurs. */
|
||||
static size_t
|
||||
frames_to_bytes_before_mix(cubeb_stream * stm, size_t frames)
|
||||
{
|
||||
// This is called only when we has a output client.
|
||||
XASSERT(has_output(stm));
|
||||
size_t stream_frame_size = stm->output_stream_params.channels * sizeof(float);
|
||||
return stream_frame_size * frames;
|
||||
}
|
||||
@ -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<float*>(input_packet), packet_size,
|
||||
stm->linear_input_buffer.data() + stm->linear_input_buffer.length(),
|
||||
stm->input_mix_params.channels,
|
||||
stm->input_stream_params.channels);
|
||||
cubeb_upmix_float(reinterpret_cast<float*>(input_packet), packet_size,
|
||||
stm->linear_input_buffer.data() + stm->linear_input_buffer.length(),
|
||||
stm->input_mix_params.channels,
|
||||
stm->input_stream_params.channels);
|
||||
stm->linear_input_buffer.set_length(stm->linear_input_buffer.length() + packet_size * stm->input_stream_params.channels);
|
||||
} else if (should_downmix(stm->input_mix_params, stm->input_stream_params)) {
|
||||
} else if (cubeb_should_downmix(&stm->input_mix_params, &stm->input_stream_params)) {
|
||||
bool ok = stm->linear_input_buffer.reserve(stm->linear_input_buffer.length() +
|
||||
packet_size * stm->input_stream_params.channels);
|
||||
XASSERT(ok);
|
||||
downmix(reinterpret_cast<float*>(input_packet), packet_size,
|
||||
stm->linear_input_buffer.data() + stm->linear_input_buffer.length(),
|
||||
stm->input_mix_params.channels,
|
||||
stm->input_stream_params.channels);
|
||||
cubeb_downmix_float(reinterpret_cast<float*>(input_packet), packet_size,
|
||||
stm->linear_input_buffer.data() + stm->linear_input_buffer.length(),
|
||||
stm->input_mix_params.channels,
|
||||
stm->input_stream_params.channels,
|
||||
stm->input_mix_params.layout,
|
||||
stm->input_stream_params.layout);
|
||||
stm->linear_input_buffer.set_length(stm->linear_input_buffer.length() + packet_size * stm->input_stream_params.channels);
|
||||
} else {
|
||||
stm->linear_input_buffer.push(reinterpret_cast<float*>(input_packet),
|
||||
@ -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<IMMDevice> device;
|
||||
hr = get_default_endpoint(device, eRender);
|
||||
if (FAILED(hr)) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
com_ptr<IAudioClient> client;
|
||||
hr = device->Activate(__uuidof(IAudioClient),
|
||||
CLSCTX_INPROC_SERVER,
|
||||
NULL, client.receive_vpp());
|
||||
if (FAILED(hr)) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
WAVEFORMATEX * tmp = nullptr;
|
||||
hr = client->GetMixFormat(&tmp);
|
||||
if (FAILED(hr)) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
com_heap_ptr<WAVEFORMATEX> mix_format(tmp);
|
||||
|
||||
WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get());
|
||||
*layout = mask_to_channel_layout(format_pcm->dwChannelMask);
|
||||
|
||||
LOG("Preferred channel layout: %s", CUBEB_CHANNEL_LAYOUT_MAPS[*layout].name);
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
void wasapi_stream_destroy(cubeb_stream * stm);
|
||||
|
||||
/* Based on the mix format and the stream format, try to find a way to play
|
||||
@ -1366,12 +1428,7 @@ void wasapi_stream_destroy(cubeb_stream * stm);
|
||||
static void
|
||||
handle_channel_layout(cubeb_stream * stm, com_heap_ptr<WAVEFORMATEX> & mix_format, const cubeb_stream_params * stream_params)
|
||||
{
|
||||
/* Common case: the hardware is stereo. Up-mixing and down-mixing will be
|
||||
handled in the callback. */
|
||||
if (mix_format->nChannels <= 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
XASSERT(stream_params->layout != CUBEB_LAYOUT_UNDEFINED);
|
||||
/* The docs say that GetMixFormat is always of type WAVEFORMATEXTENSIBLE [1],
|
||||
so the reinterpret_cast below should be safe. In practice, this is not
|
||||
true, and we just want to bail out and let the rest of the code find a good
|
||||
@ -1386,19 +1443,10 @@ handle_channel_layout(cubeb_stream * stm, com_heap_ptr<WAVEFORMATEX> & mix_form
|
||||
/* Stash a copy of the original mix format in case we need to restore it later. */
|
||||
WAVEFORMATEXTENSIBLE hw_mix_format = *format_pcm;
|
||||
|
||||
/* The hardware is in surround mode, we want to only use front left and front
|
||||
right. Try that, and check if it works. */
|
||||
switch (stream_params->channels) {
|
||||
case 1: /* Mono */
|
||||
format_pcm->dwChannelMask = KSAUDIO_SPEAKER_MONO;
|
||||
break;
|
||||
case 2: /* Stereo */
|
||||
format_pcm->dwChannelMask = KSAUDIO_SPEAKER_STEREO;
|
||||
break;
|
||||
default:
|
||||
XASSERT(false && "Channel layout not supported.");
|
||||
break;
|
||||
}
|
||||
/* Get the channel mask by the channel layout.
|
||||
If the layout is not supported, we will get a closest settings below. */
|
||||
format_pcm->dwChannelMask = channel_layout_to_mask(stream_params->layout);
|
||||
|
||||
mix_format->nChannels = stream_params->channels;
|
||||
mix_format->nBlockAlign = mix_format->wBitsPerSample * mix_format->nChannels / 8;
|
||||
mix_format->nAvgBytesPerSec = mix_format->nSamplesPerSec * mix_format->nBlockAlign;
|
||||
@ -1499,16 +1547,39 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
|
||||
}
|
||||
com_heap_ptr<WAVEFORMATEX> mix_format(tmp);
|
||||
|
||||
handle_channel_layout(stm, mix_format, stream_params);
|
||||
/* Set channel layout only when there're more than two channels. Otherwise,
|
||||
* use the default setting retrieved from the stream format of the audio
|
||||
* engine's internal processing by GetMixFormat. */
|
||||
if (mix_format->nChannels > 2) {
|
||||
/* Currently, we only support mono and stereo for capture stream. */
|
||||
if (direction == eCapture) {
|
||||
XASSERT(false && "Multichannel recording is not supported.");
|
||||
}
|
||||
|
||||
handle_channel_layout(stm, mix_format, stream_params);
|
||||
}
|
||||
|
||||
/* Shared mode WASAPI always supports float32 sample format, so this
|
||||
* is safe. */
|
||||
mix_params->format = CUBEB_SAMPLE_FLOAT32NE;
|
||||
mix_params->rate = mix_format->nSamplesPerSec;
|
||||
mix_params->channels = mix_format->nChannels;
|
||||
LOG("Setup requested=[f=%d r=%u c=%u] mix=[f=%d r=%u c=%u]",
|
||||
WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get());
|
||||
mix_params->layout = mask_to_channel_layout(format_pcm->dwChannelMask);
|
||||
if (mix_params->layout == CUBEB_LAYOUT_UNDEFINED) {
|
||||
LOG("Output using undefined layout!\n");
|
||||
} else if (mix_format->nChannels != CUBEB_CHANNEL_LAYOUT_MAPS[mix_params->layout].channels) {
|
||||
// The CUBEB_CHANNEL_LAYOUT_MAPS[mix_params->layout].channels may be
|
||||
// different from the mix_params->channels. 6 channel ouput with stereo
|
||||
// layout is acceptable in Windows. If this happens, it should not downmix
|
||||
// audio according to layout.
|
||||
LOG("Channel count is different from the layout standard!\n");
|
||||
}
|
||||
LOG("Setup requested=[f=%d r=%u c=%u l=%s] mix=[f=%d r=%u c=%u l=%s]",
|
||||
stream_params->format, stream_params->rate, stream_params->channels,
|
||||
mix_params->format, mix_params->rate, mix_params->channels);
|
||||
CUBEB_CHANNEL_LAYOUT_MAPS[stream_params->layout].name,
|
||||
mix_params->format, mix_params->rate, mix_params->channels,
|
||||
CUBEB_CHANNEL_LAYOUT_MAPS[mix_params->layout].name);
|
||||
|
||||
hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED,
|
||||
AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
|
||||
@ -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<char const *>(input_device));
|
||||
// Make sure the layout matches the channel count.
|
||||
XASSERT(stm->input_stream_params.channels == CUBEB_CHANNEL_LAYOUT_MAPS[stm->input_stream_params.layout].channels);
|
||||
}
|
||||
if (output_stream_params) {
|
||||
stm->output_stream_params = *output_stream_params;
|
||||
stm->output_device = utf8_to_wstr(reinterpret_cast<char const *>(output_device));
|
||||
// Make sure the layout matches the channel count.
|
||||
XASSERT(stm->output_stream_params.channels == CUBEB_CHANNEL_LAYOUT_MAPS[stm->output_stream_params.layout].channels);
|
||||
}
|
||||
|
||||
stm->latency = latency_frames;
|
||||
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -10,6 +10,7 @@ Library('cubeb')
|
||||
|
||||
SOURCES += [
|
||||
'cubeb.c',
|
||||
'cubeb_mixer.cpp',
|
||||
'cubeb_panner.cpp'
|
||||
]
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user