Bug 1405258 - Update libcubeb to revision ba2a89611875cd9f2dabae99a362461b03c0dd3d

MozReview-Commit-ID: PngLiVneAh

--HG--
extra : rebase_source : 9f604112fad6cafb0883e3248823f78106ec5e19
This commit is contained in:
Paul Adenot 2017-10-03 12:18:07 +02:00
Родитель f23193be11
Коммит 37bfa43273
6 изменённых файлов: 254 добавлений и 34 удалений

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

@ -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 ac532ad4f0defaf7a397db64c2c42d2665cd06e9 (2017-09-17 08:23:45 +1200)
The git commit ID used was ba2a89611875cd9f2dabae99a362461b03c0dd3d (2017-10-03 11:34:20 +0200)

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

@ -125,7 +125,7 @@ void test_delay_lines(uint32_t delay_frames, uint32_t channels, uint32_t chunk_m
const size_t length_s = 2;
const size_t rate = 44100;
const size_t length_frames = rate * length_s;
delay_line<float> delay(delay_frames, channels);
delay_line<float> delay(delay_frames, channels, rate);
auto_array<float> input;
auto_array<float> output;
uint32_t chunk_length = channels * chunk_ms * rate / 1000;
@ -629,10 +629,12 @@ TEST(cubeb, resampler_passthrough_input_only)
template<typename T>
long seq(T* array, int stride, long start, long count)
{
uint32_t output_idx = 0;
for(int i = 0; i < count; i++) {
for (int j = 0; j < stride; j++) {
array[i + j] = static_cast<T>(start + i);
array[output_idx + j] = static_cast<T>(start + i);
}
output_idx += stride;
}
return start + count;
}
@ -649,11 +651,28 @@ void is_seq(T * array, int stride, long count, long expected_start)
}
}
template<typename T>
void is_not_seq(T * array, int stride, long count, long expected_start)
{
uint32_t output_index = 0;
for (long i = 0; i < count; i++) {
for (int j = 0; j < stride; j++) {
ASSERT_NE(array[output_index + j], expected_start + i);
}
output_index += stride;
}
}
struct closure {
int input_channel_count;
};
// gtest does not support using ASSERT_EQ and friend in a function that returns
// a value.
template<typename T>
void check_duplex(const T * input_buffer,
T * output_buffer, long frame_count)
T * output_buffer, long frame_count,
int input_channel_count)
{
ASSERT_EQ(frame_count, 256);
// Silence scan-build warning.
@ -661,18 +680,28 @@ void check_duplex(const T * input_buffer,
ASSERT_TRUE(!!input_buffer); assert(input_buffer);
int output_index = 0;
int input_index = 0;
for (int i = 0; i < frame_count; i++) {
// output is two channels, input is one channel, we upmix.
output_buffer[output_index] = output_buffer[output_index+1] = input_buffer[i];
// output is two channels, input one or two channels.
if (input_channel_count == 1) {
output_buffer[output_index] = output_buffer[output_index + 1] = input_buffer[i];
} else if (input_channel_count == 2) {
output_buffer[output_index] = input_buffer[input_index];
output_buffer[output_index + 1] = input_buffer[input_index + 1];
}
output_index += 2;
input_index += input_channel_count;
}
}
long cb_passthrough_resampler_duplex(cubeb_stream * /*stm*/, void * /*user_ptr*/,
long cb_passthrough_resampler_duplex(cubeb_stream * /*stm*/, void * user_ptr,
const void * input_buffer,
void * output_buffer, long frame_count)
{
check_duplex<float>(static_cast<const float*>(input_buffer), static_cast<float*>(output_buffer), frame_count);
closure * c = reinterpret_cast<closure*>(user_ptr);
check_duplex<float>(static_cast<const float*>(input_buffer),
static_cast<float*>(output_buffer),
frame_count, c->input_channel_count);
return frame_count;
}
@ -698,9 +727,12 @@ TEST(cubeb, resampler_passthrough_duplex_callback_reordering)
int target_rate = input_params.rate;
closure c;
c.input_channel_count = input_channels;
cubeb_resampler * resampler =
cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params,
target_rate, cb_passthrough_resampler_duplex, nullptr,
target_rate, cb_passthrough_resampler_duplex, &c,
CUBEB_RESAMPLER_QUALITY_VOIP);
const long BUF_BASE_SIZE = 256;
@ -755,3 +787,101 @@ TEST(cubeb, resampler_passthrough_duplex_callback_reordering)
cubeb_resampler_destroy(resampler);
}
// Artificially simulate output thread underruns,
// by building up artificial delay in the input.
// Check that the frame drop logic kicks in.
TEST(cubeb, resampler_drift_drop_data)
{
for (uint32_t input_channels = 1; input_channels < 3; input_channels++) {
cubeb_stream_params input_params;
cubeb_stream_params output_params;
const int output_channels = 2;
const int sample_rate = 44100;
input_params.channels = input_channels;
input_params.rate = sample_rate;
input_params.format = CUBEB_SAMPLE_FLOAT32NE;
output_params.channels = output_channels;
output_params.rate = sample_rate;
output_params.format = CUBEB_SAMPLE_FLOAT32NE;
int target_rate = input_params.rate;
closure c;
c.input_channel_count = input_channels;
cubeb_resampler * resampler =
cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params,
target_rate, cb_passthrough_resampler_duplex, &c,
CUBEB_RESAMPLER_QUALITY_VOIP);
const long BUF_BASE_SIZE = 256;
// The factor by which the deadline is missed. This is intentionally
// kind of large to trigger the frame drop quickly. In real life, multiple
// smaller under-runs would accumulate.
const long UNDERRUN_FACTOR = 10;
// Number buffer used for pre-buffering, that some backends do.
const long PREBUFFER_FACTOR = 2;
std::vector<float> input_buffer_prebuffer(input_channels * BUF_BASE_SIZE * PREBUFFER_FACTOR);
std::vector<float> input_buffer_glitch(input_channels * BUF_BASE_SIZE * UNDERRUN_FACTOR);
std::vector<float> input_buffer_normal(input_channels * BUF_BASE_SIZE);
std::vector<float> output_buffer(output_channels * BUF_BASE_SIZE);
long seq_idx = 0;
long output_seq_idx = 0;
long prebuffer_frames = input_buffer_prebuffer.size() / input_params.channels;
seq_idx = seq(input_buffer_prebuffer.data(), input_channels, seq_idx,
prebuffer_frames);
long got = cubeb_resampler_fill(resampler, input_buffer_prebuffer.data(), &prebuffer_frames,
output_buffer.data(), BUF_BASE_SIZE);
output_seq_idx += BUF_BASE_SIZE;
// prebuffer_frames will hold the frames used by the resampler.
ASSERT_EQ(prebuffer_frames, BUF_BASE_SIZE);
ASSERT_EQ(got, BUF_BASE_SIZE);
for (uint32_t i = 0; i < 300; i++) {
long int frames = BUF_BASE_SIZE;
if (i != 0 && (i % 100) == 1) {
// Once in a while, the output thread misses its deadline.
// The input thread still produces data, so it ends up accumulating. Simulate this by providing a
// much bigger input buffer. Check that the sequence is now unaligned, meaning we've dropped data
// to keep everything in sync.
seq_idx = seq(input_buffer_glitch.data(), input_channels, seq_idx, BUF_BASE_SIZE * UNDERRUN_FACTOR);
frames = BUF_BASE_SIZE * UNDERRUN_FACTOR;
got = cubeb_resampler_fill(resampler, input_buffer_glitch.data(), &frames, output_buffer.data(), BUF_BASE_SIZE);
is_seq(output_buffer.data(), 2, BUF_BASE_SIZE, output_seq_idx);
output_seq_idx += BUF_BASE_SIZE;
}
else if (i != 0 && (i % 100) == 2) {
// On the next iteration, the sequence should be broken
seq_idx = seq(input_buffer_normal.data(), input_channels, seq_idx, BUF_BASE_SIZE);
long normal_input_frame_count = 256;
got = cubeb_resampler_fill(resampler, input_buffer_normal.data(), &normal_input_frame_count, output_buffer.data(), BUF_BASE_SIZE);
is_not_seq(output_buffer.data(), output_channels, BUF_BASE_SIZE, output_seq_idx);
// Reclock so that we can use is_seq again.
output_seq_idx = output_buffer[BUF_BASE_SIZE * output_channels - 1] + 1;
}
else {
// normal case
seq_idx = seq(input_buffer_normal.data(), input_channels, seq_idx, BUF_BASE_SIZE);
long normal_input_frame_count = 256;
got = cubeb_resampler_fill(resampler, input_buffer_normal.data(), &normal_input_frame_count, output_buffer.data(), BUF_BASE_SIZE);
is_seq(output_buffer.data(), output_channels, BUF_BASE_SIZE, output_seq_idx);
output_seq_idx += BUF_BASE_SIZE;
}
ASSERT_EQ(got, BUF_BASE_SIZE);
}
cubeb_resampler_destroy(resampler);
}
}

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

@ -576,7 +576,7 @@ static int audiounit_stream_set_volume(cubeb_stream * stm, float volume);
static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm);
static AudioObjectID audiounit_get_default_device_id(cubeb_device_type type);
static void
static int
audiounit_set_device_info(cubeb_stream * stm, AudioDeviceID id, io_side side)
{
assert(stm);
@ -601,6 +601,9 @@ audiounit_set_device_info(cubeb_stream * stm, AudioDeviceID id, io_side side)
}
AudioDeviceID default_device_id = audiounit_get_default_device_id(type);
if (default_device_id == kAudioObjectUnknown) {
return CUBEB_ERROR;
}
if (id == kAudioObjectUnknown) {
info->id = default_device_id;
info->flags |= DEV_SELECTED_DEFAULT;
@ -613,6 +616,8 @@ audiounit_set_device_info(cubeb_stream * stm, AudioDeviceID id, io_side side)
assert(info->id);
assert(info->flags & DEV_INPUT && !(info->flags & DEV_OUTPUT) ||
!(info->flags & DEV_INPUT) && info->flags & DEV_OUTPUT);
return CUBEB_OK;
}
@ -645,12 +650,14 @@ audiounit_reinit_stream(cubeb_stream * stm, device_flags_value flags)
* default system device change. In both cases cubeb switch on the new default
* device. This is considered the most expected behavior for the user. */
if (flags & DEV_INPUT) {
audiounit_set_device_info(stm, 0, INPUT);
r = audiounit_set_device_info(stm, 0, INPUT);
assert(r == CUBEB_OK);
}
/* Always use the default output on reinit. This is not correct in every case
* but it is sufficient for Firefox and prevent reinit from reporting failures.
* It will change soon when reinit mechanism will be updated. */
audiounit_set_device_info(stm, 0, OUTPUT);
r = audiounit_set_device_info(stm, 0, OUTPUT);
assert(r == CUBEB_OK);
if (audiounit_setup_stream(stm) != CUBEB_OK) {
LOG("(%p) Stream reinit failed.", stm);
@ -2512,11 +2519,19 @@ audiounit_stream_init(cubeb * context,
stm->latency_frames = latency_frames;
if (input_stream_params) {
stm->input_stream_params = *input_stream_params;
audiounit_set_device_info(stm.get(), reinterpret_cast<uintptr_t>(input_device), INPUT);
r = audiounit_set_device_info(stm.get(), reinterpret_cast<uintptr_t>(input_device), INPUT);
if (r != CUBEB_OK) {
LOG("(%p) Fail to set device info for input.", stm.get());
return r;
}
}
if (output_stream_params) {
stm->output_stream_params = *output_stream_params;
audiounit_set_device_info(stm.get(), reinterpret_cast<uintptr_t>(output_device), OUTPUT);
r = audiounit_set_device_info(stm.get(), reinterpret_cast<uintptr_t>(output_device), OUTPUT);
if (r != CUBEB_OK) {
LOG("(%p) Fail to set device info for output.", stm.get());
return r;
}
}
auto_lock context_lock(context->mutex);

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

@ -653,7 +653,6 @@ pulse_init(cubeb ** context, char const * context_name)
WRAP(pa_operation_unref)(o);
}
WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
assert(ctx->default_sink_info);
*context = ctx;
@ -673,6 +672,9 @@ pulse_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
(void)ctx;
assert(ctx && max_channels);
if (!ctx->default_sink_info)
return CUBEB_ERROR;
*max_channels = ctx->default_sink_info->channel_map.channels;
return CUBEB_OK;
@ -684,6 +686,9 @@ pulse_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
assert(ctx && rate);
(void)ctx;
if (!ctx->default_sink_info)
return CUBEB_ERROR;
*rate = ctx->default_sink_info->sample_spec.rate;
return CUBEB_OK;
@ -695,6 +700,9 @@ pulse_get_preferred_channel_layout(cubeb * ctx, cubeb_channel_layout * layout)
assert(ctx && layout);
(void)ctx;
if (!ctx->default_sink_info)
return CUBEB_ERROR;
*layout = channel_map_to_layout(&ctx->default_sink_info->channel_map);
return CUBEB_OK;
@ -1077,6 +1085,7 @@ pulse_stream_set_volume(cubeb_stream * stm, float volume)
pa_volume_t vol;
pa_cvolume cvol;
const pa_sample_spec * ss;
cubeb * ctx;
if (!stm->output_stream) {
return CUBEB_ERROR;
@ -1086,7 +1095,9 @@ pulse_stream_set_volume(cubeb_stream * stm, float volume)
/* if the pulse daemon is configured to use flat volumes,
* apply our own gain instead of changing the input volume on the sink. */
if (stm->context->default_sink_info->flags & PA_SINK_FLAT_VOLUME) {
ctx = stm->context;
if (ctx->default_sink_info &&
(ctx->default_sink_info->flags & PA_SINK_FLAT_VOLUME)) {
stm->volume = volume;
} else {
ss = WRAP(pa_stream_get_sample_spec)(stm->output_stream);
@ -1096,16 +1107,16 @@ pulse_stream_set_volume(cubeb_stream * stm, float volume)
index = WRAP(pa_stream_get_index)(stm->output_stream);
op = WRAP(pa_context_set_sink_input_volume)(stm->context->context,
op = WRAP(pa_context_set_sink_input_volume)(ctx->context,
index, &cvol, volume_success,
stm);
if (op) {
operation_wait(stm->context, stm->output_stream, op);
operation_wait(ctx, stm->output_stream, op);
WRAP(pa_operation_unref)(op);
}
}
WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
return CUBEB_OK;
}
@ -1235,7 +1246,12 @@ pulse_sink_info_cb(pa_context * context, const pa_sink_info * info,
(void)context;
if (eol || info == NULL)
if (eol) {
WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
return;
}
if (info == NULL)
return;
device_id = info->name;
@ -1274,8 +1290,6 @@ pulse_sink_info_cb(pa_context * context, const pa_sink_info * info,
devinfo->latency_hi = 0;
list_data->count += 1;
WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
}
static cubeb_device_state
@ -1304,8 +1318,10 @@ pulse_source_info_cb(pa_context * context, const pa_source_info * info,
(void)context;
if (eol)
if (eol) {
WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
return;
}
device_id = info->name;
if (intern_device_id(list_data->context, &device_id) != CUBEB_OK) {
@ -1343,7 +1359,6 @@ pulse_source_info_cb(pa_context * context, const pa_source_info * info,
devinfo->latency_hi = 0;
list_data->count += 1;
WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
}
static void
@ -1355,8 +1370,10 @@ pulse_server_info_cb(pa_context * c, const pa_server_info * i, void * userdata)
free(list_data->default_sink_name);
free(list_data->default_source_name);
list_data->default_sink_name = strdup(i->default_sink_name);
list_data->default_source_name = strdup(i->default_source_name);
list_data->default_sink_name =
i->default_sink_name ? strdup(i->default_sink_name) : NULL;
list_data->default_source_name =
i->default_source_name ? strdup(i->default_source_name) : NULL;
WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
}

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

@ -39,11 +39,13 @@ template<typename T>
passthrough_resampler<T>::passthrough_resampler(cubeb_stream * s,
cubeb_data_callback cb,
void * ptr,
uint32_t input_channels)
uint32_t input_channels,
uint32_t sample_rate)
: processor(input_channels)
, stream(s)
, data_callback(cb)
, user_ptr(ptr)
, sample_rate(sample_rate)
{
}
@ -72,6 +74,7 @@ long passthrough_resampler<T>::fill(void * input_buffer, long * input_frames_cou
if (input_buffer) {
internal_input_buffer.pop(nullptr, frames_to_samples(output_frames));
*input_frames_count = output_frames;
drop_audio_if_needed();
}
return rv;
@ -241,9 +244,15 @@ cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
output_processor->written(got);
input_processor->drop_audio_if_needed();
/* Process the output. If not enough frames have been returned from the
* callback, drain the processors. */
return output_processor->output(out_buffer, output_frames_needed);
got = output_processor->output(out_buffer, output_frames_needed);
output_processor->drop_audio_if_needed();
return got;
}
/* Resampler C API */

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

@ -38,6 +38,17 @@ MOZ_END_STD_NAMESPACE
#include <stdio.h>
/* This header file contains the internal C++ API of the resamplers, for testing. */
namespace {
// When dropping audio input frames to prevent building
// an input delay, this function returns the number of frames
// to keep in the buffer.
// @parameter sample_rate The sample rate of the stream.
// @return A number of frames to keep.
uint32_t min_buffered_audio_frame(uint32_t sample_rate)
{
return sample_rate / 20;
}
}
int to_speex_quality(cubeb_resampler_quality q);
@ -75,7 +86,8 @@ public:
passthrough_resampler(cubeb_stream * s,
cubeb_data_callback cb,
void * ptr,
uint32_t input_channels);
uint32_t input_channels,
uint32_t sample_rate);
virtual long fill(void * input_buffer, long * input_frames_count,
void * output_buffer, long output_frames);
@ -85,6 +97,15 @@ public:
return 0;
}
void drop_audio_if_needed()
{
uint32_t to_keep = min_buffered_audio_frame(sample_rate);
uint32_t available = samples_to_frames(internal_input_buffer.length());
if (available > to_keep) {
internal_input_buffer.pop(nullptr, frames_to_samples(available - to_keep));
}
}
private:
cubeb_stream * const stream;
const cubeb_data_callback data_callback;
@ -92,6 +113,7 @@ private:
/* This allows to buffer some input to account for the fact that we buffer
* some inputs. */
auto_array<T> internal_input_buffer;
uint32_t sample_rate;
};
/** Bidirectional resampler, can resample an input and an output stream, or just
@ -164,6 +186,7 @@ public:
int quality)
: processor(channels)
, resampling_ratio(static_cast<float>(source_rate) / target_rate)
, source_rate(source_rate)
, additional_latency(0)
, leftover_samples(0)
{
@ -296,6 +319,16 @@ public:
resampling_in_buffer.set_length(leftover_samples +
frames_to_samples(written_frames));
}
void drop_audio_if_needed()
{
// Keep at most 100ms buffered.
uint32_t available = samples_to_frames(resampling_in_buffer.length());
uint32_t to_keep = min_buffered_audio_frame(source_rate);
if (available > to_keep) {
resampling_in_buffer.pop(nullptr, frames_to_samples(available - to_keep));
}
}
private:
/** Wrapper for the speex resampling functions to have a typed
* interface. */
@ -332,6 +365,7 @@ private:
SpeexResamplerState * speex_resampler;
/** Source rate / target rate. */
const float resampling_ratio;
const uint32_t source_rate;
/** Storage for the input frames, to be resampled. Also contains
* any unresampled frames after resampling. */
auto_array<T> resampling_in_buffer;
@ -350,11 +384,13 @@ class delay_line : public processor {
public:
/** Constructor
* @parameter frames the number of frames of delay.
* @parameter channels the number of channels of this delay line. */
delay_line(uint32_t frames, uint32_t channels)
* @parameter channels the number of channels of this delay line.
* @parameter sample_rate sample-rate of the audio going through this delay line */
delay_line(uint32_t frames, uint32_t channels, uint32_t sample_rate)
: processor(channels)
, length(frames)
, leftover_samples(0)
, sample_rate(sample_rate)
{
/* Fill the delay line with some silent frames to add latency. */
delay_input_buffer.push_silence(frames * channels);
@ -444,6 +480,15 @@ public:
{
return length;
}
void drop_audio_if_needed()
{
size_t available = samples_to_frames(delay_input_buffer.length());
uint32_t to_keep = min_buffered_audio_frame(sample_rate);
if (available > to_keep) {
delay_input_buffer.pop(nullptr, frames_to_samples(available - to_keep));
}
}
private:
/** The length, in frames, of this delay line */
uint32_t length;
@ -455,6 +500,7 @@ private:
/** The output buffer. This is only ever used if using the ::output with a
* single argument. */
auto_array<T> delay_output_buffer;
uint32_t sample_rate;
};
/** This sits behind the C API and is more typed. */
@ -485,7 +531,8 @@ cubeb_resampler_create_internal(cubeb_stream * stream,
(output_params && !input_params && (output_params->rate == target_rate))) {
return new passthrough_resampler<T>(stream, callback,
user_ptr,
input_params ? input_params->channels : 0);
input_params ? input_params->channels : 0,
target_rate);
}
/* Determine if we need to resampler one or both directions, and create the
@ -517,13 +564,15 @@ cubeb_resampler_create_internal(cubeb_stream * stream,
* other direction so that the streams are synchronized. */
if (input_resampler && !output_resampler && input_params && output_params) {
output_delay.reset(new delay_line<T>(input_resampler->latency(),
output_params->channels));
output_params->channels,
output_params->rate));
if (!output_delay) {
return NULL;
}
} else if (output_resampler && !input_resampler && input_params && output_params) {
input_delay.reset(new delay_line<T>(output_resampler->latency(),
input_params->channels));
input_params->channels,
output_params->rate));
if (!input_delay) {
return NULL;
}