new file mode 100644
--- /dev/null
+++ b/media/libcubeb/audiounit-drift-fix.patch
@@ -0,0 +1,510 @@
+# HG changeset patch
+# User Alex Chronopoulos <achronop@gmail.com>
+# Parent d1c742cd2af9d3da481d227b4675279902259751
+Bug 1339816 - Uplift cubeb audiounit drift fix. r?padenot
+
+diff --git a/media/libcubeb/gtest/test_resampler.cpp b/media/libcubeb/gtest/test_resampler.cpp
+--- a/media/libcubeb/gtest/test_resampler.cpp
++++ b/media/libcubeb/gtest/test_resampler.cpp
+@@ -712,17 +712,18 @@ TEST(cubeb, resampler_passthrough_duplex
+ seq_idx = seq(input_buffer_prebuffer, input_channels, seq_idx,
+ prebuffer_frames);
+
+ long got = cubeb_resampler_fill(resampler, input_buffer_prebuffer, &prebuffer_frames,
+ output_buffer, BUF_BASE_SIZE);
+
+ output_seq_idx += BUF_BASE_SIZE;
+
+- ASSERT_EQ(prebuffer_frames, static_cast<long>(ARRAY_LENGTH(input_buffer_prebuffer) / input_params.channels));
++ // 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;
+ // Simulate that sometimes, we don't have the input callback on time
+ if (i != 0 && (i % 100) == 0) {
+ long zero = 0;
+ got = cubeb_resampler_fill(resampler, input_buffer_normal /* unused here */,
+diff --git a/media/libcubeb/src/cubeb_audiounit.cpp b/media/libcubeb/src/cubeb_audiounit.cpp
+--- a/media/libcubeb/src/cubeb_audiounit.cpp
++++ b/media/libcubeb/src/cubeb_audiounit.cpp
+@@ -41,16 +41,26 @@ typedef UInt32 AudioFormatFlags;
+ #define AU_IN_BUS 1
+
+ #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";
+
++#ifdef ALOGV
++#undef ALOGV
++#endif
++#define ALOGV(msg, ...) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{LOGV(msg, ##__VA_ARGS__);})
++
++#ifdef ALOG
++#undef ALOG
++#endif
++#define ALOG(msg, ...) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{LOG(msg, ##__VA_ARGS__);})
++
+ /* 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
+ * frames. */
+ const uint32_t SAFE_MIN_LATENCY_FRAMES = 256;
+ const uint32_t SAFE_MAX_LATENCY_FRAMES = 512;
+
+ void audiounit_stream_stop_internal(cubeb_stream * stm);
+ void audiounit_stream_start_internal(cubeb_stream * stm);
+@@ -156,35 +166,34 @@ struct cubeb_stream {
+ Float64 output_hw_rate = 0;
+ /* Expected I/O thread interleave,
+ * calculated from I/O hw rate. */
+ int expected_output_callbacks_in_a_row = 0;
+ owned_critical_section mutex;
+ /* Hold the input samples in every
+ * input callback iteration */
+ std::unique_ptr<auto_array_wrapper> input_linear_buffer;
++ // After the resampling some input data remains stored inside
++ // the resampler. This number is used in order to calculate
++ // the number of extra silence frames in input.
++ std::atomic<uint32_t> available_input_frames{ 0 };
+ /* Frames on input buffer */
+ std::atomic<uint32_t> input_buffer_frames{ 0 };
+ /* Frame counters */
+ std::atomic<uint64_t> frames_played{ 0 };
+ uint64_t frames_queued = 0;
+ std::atomic<int64_t> frames_read{ 0 };
+- std::atomic<bool> shutdown{ false };
++ std::atomic<bool> shutdown{ true };
+ std::atomic<bool> draining{ false };
+ /* Latency requested by the user. */
+ uint32_t latency_frames = 0;
+ std::atomic<uint64_t> current_latency_frames{ 0 };
+ uint64_t hw_latency_frames = UINT64_MAX;
+ std::atomic<float> panning{ 0 };
+ std::unique_ptr<cubeb_resampler, decltype(&cubeb_resampler_destroy)> resampler;
+- /* This is the number of output callback we got in a row. This is usually one,
+- * but can be two when the input and output rate are different, and more when
+- * a device has been plugged or unplugged, as there can be some time before
+- * the device is ready. */
+- 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)
+ {
+ return stm->input_stream_params.rate != 0;
+@@ -276,26 +285,28 @@ audiounit_render_input(cubeb_stream * st
+ PRINT_ERROR_CODE("AudioUnitRender", r);
+ return r;
+ }
+
+ /* Copy input data in linear buffer. */
+ stm->input_linear_buffer->push(input_buffer_list.mBuffers[0].mData,
+ input_frames * stm->input_desc.mChannelsPerFrame);
+
+- LOGV("(%p) input: buffers %u, size %u, channels %u, frames %d.",
++ /* Advance input frame counter. */
++ assert(input_frames > 0);
++ stm->frames_read += input_frames;
++ stm->available_input_frames += input_frames;
++
++ ALOGV("(%p) input: buffers %u, size %u, channels %u, rendered frames %d, total 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);
+- stm->frames_read += input_frames;
++ (unsigned int) input_frames,
++ stm->available_input_frames.load());
+
+ return noErr;
+ }
+
+ static OSStatus
+ audiounit_input_callback(void * user_ptr,
+ AudioUnitRenderActionFlags * flags,
+ AudioTimeStamp const * tstamp,
+@@ -304,37 +315,27 @@ audiounit_input_callback(void * user_ptr
+ AudioBufferList * /* bufs */)
+ {
+ cubeb_stream * stm = static_cast<cubeb_stream *>(user_ptr);
+
+ assert(stm->input_unit != NULL);
+ assert(AU_IN_BUS == bus);
+
+ if (stm->shutdown) {
+- LOG("(%p) input shutdown", stm);
++ ALOG("(%p) input shutdown", stm);
+ return noErr;
+ }
+
+- // This happens when we're finally getting a new input callback after having
+- // switched device, we can clear the input buffer now, only keeping the data
+- // we just got.
+- if (stm->output_callback_in_a_row > stm->expected_output_callbacks_in_a_row) {
+- stm->input_linear_buffer->pop(
+- stm->input_linear_buffer->length() -
+- input_frames * stm->input_stream_params.channels);
+- }
+-
+ OSStatus r = audiounit_render_input(stm, flags, tstamp, bus, input_frames);
+ if (r != noErr) {
+ return r;
+ }
+
+ // Full Duplex. We'll call data_callback in the AudioUnit output callback.
+ if (stm->output_unit != NULL) {
+- stm->output_callback_in_a_row = 0;
+ return noErr;
+ }
+
+ /* Input only. Call the user callback through resampler.
+ 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;
+ long outframes = cubeb_resampler_fill(stm->resampler.get(),
+@@ -348,64 +349,60 @@ audiounit_input_callback(void * user_ptr
+ if (outframes < 0 || (UInt32) outframes != input_frames) {
+ stm->shutdown = true;
+ return noErr;
+ }
+
+ return noErr;
+ }
+
++static uint32_t
++minimum_resampling_input_frames(cubeb_stream *stm)
++{
++ return ceilf(stm->input_hw_rate / stm->output_hw_rate * stm->input_buffer_frames);
++}
++
+ static bool
+ is_extra_input_needed(cubeb_stream * stm)
+ {
+ /* If the output callback came first and this is a duplex stream, we need to
+ * fill in some additional silence in the resampler.
+ * Otherwise, if we had more than expected callbacks in a row, or we're currently
+ * switching, we add some silence as well to compensate for the fact that
+ * we're lacking some input data. */
+-
+- /* If resampling is taking place after every output callback
+- * the input buffer expected to be empty. Any frame left over
+- * from resampling is stored inside the resampler available to
+- * be used in next iteration as needed.
+- * BUT when noop_resampler is operating we have left over
+- * frames since it does not store anything internally. */
+ return stm->frames_read == 0 ||
+- (stm->input_linear_buffer->length() == 0 &&
+- (stm->output_callback_in_a_row > stm->expected_output_callbacks_in_a_row ||
+- stm->switching_device));
++ stm->available_input_frames.load() < minimum_resampling_input_frames(stm);
+ }
+
+ static OSStatus
+ audiounit_output_callback(void * user_ptr,
+ AudioUnitRenderActionFlags * /* flags */,
+ AudioTimeStamp const * tstamp,
+ UInt32 bus,
+ UInt32 output_frames,
+ AudioBufferList * outBufferList)
+ {
+ assert(AU_OUT_BUS == bus);
+ assert(outBufferList->mNumberBuffers == 1);
+
+ cubeb_stream * stm = static_cast<cubeb_stream *>(user_ptr);
+
+- stm->output_callback_in_a_row++;
++ ALOGV("(%p) output: buffers %u, size %u, channels %u, frames %u, total input frames %d.",
++ stm,
++ (unsigned int) outBufferList->mNumberBuffers,
++ (unsigned int) outBufferList->mBuffers[0].mDataByteSize,
++ (unsigned int) outBufferList->mBuffers[0].mNumberChannels,
++ (unsigned int) output_frames,
++ stm->available_input_frames.load());
+
+- 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 input_frames = 0;
++ long input_frames = 0, input_frames_before_fill = 0;
+ void * output_buffer = NULL, * input_buffer = NULL;
+
+ if (stm->shutdown) {
+- LOG("(%p) output shutdown.", stm);
++ ALOG("(%p) output shutdown.", stm);
+ audiounit_make_silent(&outBufferList->mBuffers[0]);
+ return noErr;
+ }
+
+ stm->current_latency_frames = audiotimestamp_to_latency(tstamp, stm);
+ if (stm->draining) {
+ OSStatus r = AudioOutputUnitStop(stm->output_unit);
+ assert(r == 0);
+@@ -417,37 +414,44 @@ audiounit_output_callback(void * user_pt
+ audiounit_make_silent(&outBufferList->mBuffers[0]);
+ return noErr;
+ }
+ /* Get output buffer. */
+ output_buffer = outBufferList->mBuffers[0].mData;
+ /* If Full duplex get also input buffer */
+ if (stm->input_unit != NULL) {
+ if (is_extra_input_needed(stm)) {
+- uint32_t min_input_frames_required = ceilf(stm->input_hw_rate / stm->output_hw_rate *
+- stm->input_buffer_frames);
+- stm->input_linear_buffer->push_silence(min_input_frames_required * stm->input_desc.mChannelsPerFrame);
+- LOG("(%p) %s pushed %u frames of input silence.", stm, stm->frames_read == 0 ? "Input hasn't started," :
+- stm->switching_device ? "Device switching," : "Drop out,", min_input_frames_required);
++ uint32_t min_input_frames = minimum_resampling_input_frames(stm);
++ stm->input_linear_buffer->push_silence(min_input_frames * stm->input_desc.mChannelsPerFrame);
++ stm->available_input_frames += min_input_frames;
++
++ ALOG("(%p) %s pushed %u frames of input silence.", stm, stm->frames_read == 0 ? "Input hasn't started," :
++ stm->switching_device ? "Device switching," : "Drop out,", min_input_frames);
+ }
+- // The input buffer
+ input_buffer = stm->input_linear_buffer->data();
+- // Number of input frames in the buffer
++ // Number of input frames in the buffer. It will change to actually used frames
++ // inside fill
+ input_frames = stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame;
++ // Number of input frames pushed inside resampler.
++ input_frames_before_fill = input_frames;
+ }
+
+ /* Call user callback through resampler. */
+ 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);
++ // Decrease counter by the number of frames used by resampler
++ stm->available_input_frames -= input_frames;
++ assert(stm->available_input_frames.load() >= 0);
++ // Pop from the buffer the frames pushed to the resampler.
++ stm->input_linear_buffer->pop(input_frames_before_fill * stm->input_desc.mChannelsPerFrame);
+ }
+
+ if (outframes < 0) {
+ stm->shutdown = true;
+ return noErr;
+ }
+
+ size_t outbpf = stm->output_desc.mBytesPerFrame;
+@@ -613,16 +617,19 @@ audiounit_property_listener_callback(Aud
+ }
+ // Allow restart to choose the new default. Event register only for input.
+ stm->input_device = 0;
+ }
+ break;
+ case kAudioDevicePropertyDataSource:
+ LOG("Event[%u] - mSelector == kAudioHardwarePropertyDataSource", (unsigned int) i);
+ break;
++ default:
++ LOG("Event[%u] - mSelector == Unexpected Event id %d, return", (unsigned int) i, addresses[i].mSelector);
++ return noErr;
+ }
+ }
+
+ for (UInt32 i = 0; i < address_count; i++) {
+ switch(addresses[i].mSelector) {
+ case kAudioHardwarePropertyDefaultOutputDevice:
+ case kAudioHardwarePropertyDefaultInputDevice:
+ case kAudioDevicePropertyDeviceIsAlive:
+@@ -789,16 +796,26 @@ audiounit_uninstall_device_changed_callb
+ return CUBEB_ERROR;
+ }
+
+ r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice,
+ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
+ if (r != noErr) {
+ return CUBEB_ERROR;
+ }
++
++ /* Event to notify when the input is going away. */
++ AudioDeviceID dev = stm->input_device ? stm->input_device :
++ audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT);
++ r = audiounit_remove_listener(stm, dev, kAudioDevicePropertyDeviceIsAlive,
++ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
++ if (r != noErr) {
++ PRINT_ERROR_CODE("AudioObjectRemovePropertyListener/input/kAudioDevicePropertyDeviceIsAlive", r);
++ return CUBEB_ERROR;
++ }
+ }
+ return CUBEB_OK;
+ }
+
+ /* Get the acceptable buffer size (in frames) that this device can work with. */
+ static int
+ audiounit_get_acceptable_latency_range(AudioValueRange * latency_range)
+ {
+@@ -1106,28 +1123,18 @@ static int
+ audiounit_init_input_linear_buffer(cubeb_stream * stream, uint32_t capacity)
+ {
+ uint32_t size = capacity * stream->input_buffer_frames * stream->input_desc.mChannelsPerFrame;
+ if (stream->input_desc.mFormatFlags & kAudioFormatFlagIsSignedInteger) {
+ stream->input_linear_buffer.reset(new auto_array_wrapper_impl<short>(size));
+ } else {
+ stream->input_linear_buffer.reset(new auto_array_wrapper_impl<float>(size));
+ }
+-
+ assert(stream->input_linear_buffer->length() == 0);
+
+- // Pre-buffer silence if needed
+- if (capacity != 1) {
+- size_t silence_size = stream->input_buffer_frames *
+- stream->input_desc.mChannelsPerFrame;
+- stream->input_linear_buffer->push_silence(silence_size);
+-
+- assert(stream->input_linear_buffer->length() == silence_size);
+- }
+-
+ return CUBEB_OK;
+ }
+
+ static uint32_t
+ 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);
+diff --git a/media/libcubeb/src/cubeb_resampler.cpp b/media/libcubeb/src/cubeb_resampler.cpp
+--- a/media/libcubeb/src/cubeb_resampler.cpp
++++ b/media/libcubeb/src/cubeb_resampler.cpp
+@@ -67,16 +67,17 @@ long passthrough_resampler<T>::fill(void
+ frames_to_samples(*input_frames_count));
+ }
+
+ long rv = data_callback(stream, user_ptr, internal_input_buffer.data(),
+ output_buffer, output_frames);
+
+ if (input_buffer) {
+ internal_input_buffer.pop(nullptr, frames_to_samples(output_frames));
++ *input_frames_count = output_frames;
+ }
+
+ return rv;
+ }
+
+ template<typename T, typename InputProcessor, typename OutputProcessor>
+ cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
+ ::cubeb_resampler_speex(InputProcessor * input_processor,
+@@ -174,17 +175,17 @@ cubeb_resampler_speex<T, InputProcessor,
+
+ /* The input data, after eventual resampling. This is passed to the callback. */
+ T * resampled_input = nullptr;
+ uint32_t resampled_frame_count = input_processor->output_for_input(*input_frames_count);
+
+ /* process the input, and present exactly `output_frames_needed` in the
+ * callback. */
+ input_processor->input(input_buffer, *input_frames_count);
+- resampled_input = input_processor->output(resampled_frame_count);
++ resampled_input = input_processor->output(resampled_frame_count, (size_t*)input_frames_count);
+
+ long got = data_callback(stream, user_ptr,
+ resampled_input, nullptr, resampled_frame_count);
+
+ /* Return the number of initial input frames or part of it.
+ * Since output_frames_needed == 0 in input scenario, the only
+ * available number outside resampler is the initial number of frames. */
+ return (*input_frames_count) * (got / resampled_frame_count);
+@@ -221,17 +222,17 @@ cubeb_resampler_speex<T, InputProcessor,
+ out_unprocessed =
+ output_processor->input_buffer(output_frames_before_processing);
+
+ if (in_buffer) {
+ /* process the input, and present exactly `output_frames_needed` in the
+ * callback. */
+ input_processor->input(in_buffer, *input_frames_count);
+ resampled_input =
+- input_processor->output(output_frames_before_processing);
++ input_processor->output(output_frames_before_processing, (size_t*)input_frames_count);
+ } else {
+ resampled_input = nullptr;
+ }
+
+ got = data_callback(stream, user_ptr,
+ resampled_input, out_unprocessed,
+ output_frames_before_processing);
+
+diff --git a/media/libcubeb/src/cubeb_resampler_internal.h b/media/libcubeb/src/cubeb_resampler_internal.h
+--- a/media/libcubeb/src/cubeb_resampler_internal.h
++++ b/media/libcubeb/src/cubeb_resampler_internal.h
+@@ -217,33 +217,34 @@ public:
+ size_t output_for_input(uint32_t input_frames)
+ {
+ return (size_t)floorf((input_frames + samples_to_frames(resampling_in_buffer.length()))
+ / resampling_ratio);
+ }
+
+ /** Returns a buffer containing exactly `output_frame_count` resampled frames.
+ * The consumer should not hold onto the pointer. */
+- T * output(size_t output_frame_count)
++ T * output(size_t output_frame_count, size_t * input_frames_used)
+ {
+ if (resampling_out_buffer.capacity() < frames_to_samples(output_frame_count)) {
+ resampling_out_buffer.reserve(frames_to_samples(output_frame_count));
+ }
+
+ uint32_t in_len = samples_to_frames(resampling_in_buffer.length());
+ uint32_t out_len = output_frame_count;
+
+ speex_resample(resampling_in_buffer.data(), &in_len,
+ resampling_out_buffer.data(), &out_len);
+
+ assert(out_len == output_frame_count);
+
+ /* This shifts back any unresampled samples to the beginning of the input
+ buffer. */
+ resampling_in_buffer.pop(nullptr, frames_to_samples(in_len));
++ *input_frames_used = in_len;
+
+ return resampling_out_buffer.data();
+ }
+
+ /** Get the latency of the resampler, in output frames. */
+ uint32_t latency() const
+ {
+ /* The documentation of the resampler talks about "samples" here, but it
+@@ -371,26 +372,27 @@ public:
+ void input(T * buffer, uint32_t frame_count)
+ {
+ delay_input_buffer.push(buffer, frames_to_samples(frame_count));
+ }
+ /** Pop some frames from the internal buffer, into a internal output buffer.
+ * @parameter frames_needed the number of frames to be returned.
+ * @return a buffer containing the delayed frames. The consumer should not
+ * hold onto the pointer. */
+- T * output(uint32_t frames_needed)
++ T * output(uint32_t frames_needed, size_t * input_frames_used)
+ {
+ if (delay_output_buffer.capacity() < frames_to_samples(frames_needed)) {
+ delay_output_buffer.reserve(frames_to_samples(frames_needed));
+ }
+
+ delay_output_buffer.clear();
+ delay_output_buffer.push(delay_input_buffer.data(),
+ frames_to_samples(frames_needed));
+ delay_input_buffer.pop(nullptr, frames_to_samples(frames_needed));
++ *input_frames_used = frames_needed;
+
+ return delay_output_buffer.data();
+ }
+ /** Get a pointer to the first writable location in the input buffer>
+ * @parameter frames_needed the number of frames the user needs to write into
+ * the buffer.
+ * @returns a pointer to a location in the input buffer where #frames_needed
+ * can be writen. */
--- a/media/libcubeb/gtest/test_resampler.cpp
+++ b/media/libcubeb/gtest/test_resampler.cpp
@@ -712,17 +712,18 @@ TEST(cubeb, resampler_passthrough_duplex
seq_idx = seq(input_buffer_prebuffer, input_channels, seq_idx,
prebuffer_frames);
long got = cubeb_resampler_fill(resampler, input_buffer_prebuffer, &prebuffer_frames,
output_buffer, BUF_BASE_SIZE);
output_seq_idx += BUF_BASE_SIZE;
- ASSERT_EQ(prebuffer_frames, static_cast<long>(ARRAY_LENGTH(input_buffer_prebuffer) / input_params.channels));
+ // 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;
// Simulate that sometimes, we don't have the input callback on time
if (i != 0 && (i % 100) == 0) {
long zero = 0;
got = cubeb_resampler_fill(resampler, input_buffer_normal /* unused here */,
--- a/media/libcubeb/src/cubeb_audiounit.cpp
+++ b/media/libcubeb/src/cubeb_audiounit.cpp
@@ -41,16 +41,26 @@ typedef UInt32 AudioFormatFlags;
#define AU_IN_BUS 1
#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";
+#ifdef ALOGV
+#undef ALOGV
+#endif
+#define ALOGV(msg, ...) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{LOGV(msg, ##__VA_ARGS__);})
+
+#ifdef ALOG
+#undef ALOG
+#endif
+#define ALOG(msg, ...) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{LOG(msg, ##__VA_ARGS__);})
+
/* 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
* frames. */
const uint32_t SAFE_MIN_LATENCY_FRAMES = 256;
const uint32_t SAFE_MAX_LATENCY_FRAMES = 512;
void audiounit_stream_stop_internal(cubeb_stream * stm);
void audiounit_stream_start_internal(cubeb_stream * stm);
@@ -156,35 +166,34 @@ struct cubeb_stream {
Float64 output_hw_rate = 0;
/* Expected I/O thread interleave,
* calculated from I/O hw rate. */
int expected_output_callbacks_in_a_row = 0;
owned_critical_section mutex;
/* Hold the input samples in every
* input callback iteration */
std::unique_ptr<auto_array_wrapper> input_linear_buffer;
+ // After the resampling some input data remains stored inside
+ // the resampler. This number is used in order to calculate
+ // the number of extra silence frames in input.
+ std::atomic<uint32_t> available_input_frames{ 0 };
/* Frames on input buffer */
std::atomic<uint32_t> input_buffer_frames{ 0 };
/* Frame counters */
std::atomic<uint64_t> frames_played{ 0 };
uint64_t frames_queued = 0;
std::atomic<int64_t> frames_read{ 0 };
- std::atomic<bool> shutdown{ false };
+ std::atomic<bool> shutdown{ true };
std::atomic<bool> draining{ false };
/* Latency requested by the user. */
uint32_t latency_frames = 0;
std::atomic<uint64_t> current_latency_frames{ 0 };
uint64_t hw_latency_frames = UINT64_MAX;
std::atomic<float> panning{ 0 };
std::unique_ptr<cubeb_resampler, decltype(&cubeb_resampler_destroy)> resampler;
- /* This is the number of output callback we got in a row. This is usually one,
- * but can be two when the input and output rate are different, and more when
- * a device has been plugged or unplugged, as there can be some time before
- * the device is ready. */
- 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)
{
return stm->input_stream_params.rate != 0;
@@ -276,26 +285,28 @@ audiounit_render_input(cubeb_stream * st
PRINT_ERROR_CODE("AudioUnitRender", r);
return r;
}
/* Copy input data in linear buffer. */
stm->input_linear_buffer->push(input_buffer_list.mBuffers[0].mData,
input_frames * stm->input_desc.mChannelsPerFrame);
- LOGV("(%p) input: buffers %u, size %u, channels %u, frames %d.",
+ /* Advance input frame counter. */
+ assert(input_frames > 0);
+ stm->frames_read += input_frames;
+ stm->available_input_frames += input_frames;
+
+ ALOGV("(%p) input: buffers %u, size %u, channels %u, rendered frames %d, total 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);
- stm->frames_read += input_frames;
+ (unsigned int) input_frames,
+ stm->available_input_frames.load());
return noErr;
}
static OSStatus
audiounit_input_callback(void * user_ptr,
AudioUnitRenderActionFlags * flags,
AudioTimeStamp const * tstamp,
@@ -304,37 +315,27 @@ audiounit_input_callback(void * user_ptr
AudioBufferList * /* bufs */)
{
cubeb_stream * stm = static_cast<cubeb_stream *>(user_ptr);
assert(stm->input_unit != NULL);
assert(AU_IN_BUS == bus);
if (stm->shutdown) {
- LOG("(%p) input shutdown", stm);
+ ALOG("(%p) input shutdown", stm);
return noErr;
}
- // This happens when we're finally getting a new input callback after having
- // switched device, we can clear the input buffer now, only keeping the data
- // we just got.
- if (stm->output_callback_in_a_row > stm->expected_output_callbacks_in_a_row) {
- stm->input_linear_buffer->pop(
- stm->input_linear_buffer->length() -
- input_frames * stm->input_stream_params.channels);
- }
-
OSStatus r = audiounit_render_input(stm, flags, tstamp, bus, input_frames);
if (r != noErr) {
return r;
}
// Full Duplex. We'll call data_callback in the AudioUnit output callback.
if (stm->output_unit != NULL) {
- stm->output_callback_in_a_row = 0;
return noErr;
}
/* Input only. Call the user callback through resampler.
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;
long outframes = cubeb_resampler_fill(stm->resampler.get(),
@@ -348,64 +349,60 @@ audiounit_input_callback(void * user_ptr
if (outframes < 0 || (UInt32) outframes != input_frames) {
stm->shutdown = true;
return noErr;
}
return noErr;
}
+static uint32_t
+minimum_resampling_input_frames(cubeb_stream *stm)
+{
+ return ceilf(stm->input_hw_rate / stm->output_hw_rate * stm->input_buffer_frames);
+}
+
static bool
is_extra_input_needed(cubeb_stream * stm)
{
/* If the output callback came first and this is a duplex stream, we need to
* fill in some additional silence in the resampler.
* Otherwise, if we had more than expected callbacks in a row, or we're currently
* switching, we add some silence as well to compensate for the fact that
* we're lacking some input data. */
-
- /* If resampling is taking place after every output callback
- * the input buffer expected to be empty. Any frame left over
- * from resampling is stored inside the resampler available to
- * be used in next iteration as needed.
- * BUT when noop_resampler is operating we have left over
- * frames since it does not store anything internally. */
return stm->frames_read == 0 ||
- (stm->input_linear_buffer->length() == 0 &&
- (stm->output_callback_in_a_row > stm->expected_output_callbacks_in_a_row ||
- stm->switching_device));
+ stm->available_input_frames.load() < minimum_resampling_input_frames(stm);
}
static OSStatus
audiounit_output_callback(void * user_ptr,
AudioUnitRenderActionFlags * /* flags */,
AudioTimeStamp const * tstamp,
UInt32 bus,
UInt32 output_frames,
AudioBufferList * outBufferList)
{
assert(AU_OUT_BUS == bus);
assert(outBufferList->mNumberBuffers == 1);
cubeb_stream * stm = static_cast<cubeb_stream *>(user_ptr);
- stm->output_callback_in_a_row++;
+ ALOGV("(%p) output: buffers %u, size %u, channels %u, frames %u, total input frames %d.",
+ stm,
+ (unsigned int) outBufferList->mNumberBuffers,
+ (unsigned int) outBufferList->mBuffers[0].mDataByteSize,
+ (unsigned int) outBufferList->mBuffers[0].mNumberChannels,
+ (unsigned int) output_frames,
+ stm->available_input_frames.load());
- 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 input_frames = 0;
+ long input_frames = 0, input_frames_before_fill = 0;
void * output_buffer = NULL, * input_buffer = NULL;
if (stm->shutdown) {
- LOG("(%p) output shutdown.", stm);
+ ALOG("(%p) output shutdown.", stm);
audiounit_make_silent(&outBufferList->mBuffers[0]);
return noErr;
}
stm->current_latency_frames = audiotimestamp_to_latency(tstamp, stm);
if (stm->draining) {
OSStatus r = AudioOutputUnitStop(stm->output_unit);
assert(r == 0);
@@ -417,37 +414,44 @@ audiounit_output_callback(void * user_pt
audiounit_make_silent(&outBufferList->mBuffers[0]);
return noErr;
}
/* Get output buffer. */
output_buffer = outBufferList->mBuffers[0].mData;
/* If Full duplex get also input buffer */
if (stm->input_unit != NULL) {
if (is_extra_input_needed(stm)) {
- uint32_t min_input_frames_required = ceilf(stm->input_hw_rate / stm->output_hw_rate *
- stm->input_buffer_frames);
- stm->input_linear_buffer->push_silence(min_input_frames_required * stm->input_desc.mChannelsPerFrame);
- LOG("(%p) %s pushed %u frames of input silence.", stm, stm->frames_read == 0 ? "Input hasn't started," :
- stm->switching_device ? "Device switching," : "Drop out,", min_input_frames_required);
+ uint32_t min_input_frames = minimum_resampling_input_frames(stm);
+ stm->input_linear_buffer->push_silence(min_input_frames * stm->input_desc.mChannelsPerFrame);
+ stm->available_input_frames += min_input_frames;
+
+ ALOG("(%p) %s pushed %u frames of input silence.", stm, stm->frames_read == 0 ? "Input hasn't started," :
+ stm->switching_device ? "Device switching," : "Drop out,", min_input_frames);
}
- // The input buffer
input_buffer = stm->input_linear_buffer->data();
- // Number of input frames in the buffer
+ // Number of input frames in the buffer. It will change to actually used frames
+ // inside fill
input_frames = stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame;
+ // Number of input frames pushed inside resampler.
+ input_frames_before_fill = input_frames;
}
/* Call user callback through resampler. */
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);
+ // Decrease counter by the number of frames used by resampler
+ stm->available_input_frames -= input_frames;
+ assert(stm->available_input_frames.load() >= 0);
+ // Pop from the buffer the frames pushed to the resampler.
+ stm->input_linear_buffer->pop(input_frames_before_fill * stm->input_desc.mChannelsPerFrame);
}
if (outframes < 0) {
stm->shutdown = true;
return noErr;
}
size_t outbpf = stm->output_desc.mBytesPerFrame;
@@ -613,16 +617,19 @@ audiounit_property_listener_callback(Aud
}
// Allow restart to choose the new default. Event register only for input.
stm->input_device = 0;
}
break;
case kAudioDevicePropertyDataSource:
LOG("Event[%u] - mSelector == kAudioHardwarePropertyDataSource", (unsigned int) i);
break;
+ default:
+ LOG("Event[%u] - mSelector == Unexpected Event id %d, return", (unsigned int) i, addresses[i].mSelector);
+ return noErr;
}
}
for (UInt32 i = 0; i < address_count; i++) {
switch(addresses[i].mSelector) {
case kAudioHardwarePropertyDefaultOutputDevice:
case kAudioHardwarePropertyDefaultInputDevice:
case kAudioDevicePropertyDeviceIsAlive:
@@ -789,16 +796,26 @@ audiounit_uninstall_device_changed_callb
return CUBEB_ERROR;
}
r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice,
kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
if (r != noErr) {
return CUBEB_ERROR;
}
+
+ /* Event to notify when the input is going away. */
+ AudioDeviceID dev = stm->input_device ? stm->input_device :
+ audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT);
+ r = audiounit_remove_listener(stm, dev, kAudioDevicePropertyDeviceIsAlive,
+ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
+ if (r != noErr) {
+ PRINT_ERROR_CODE("AudioObjectRemovePropertyListener/input/kAudioDevicePropertyDeviceIsAlive", r);
+ return CUBEB_ERROR;
+ }
}
return CUBEB_OK;
}
/* Get the acceptable buffer size (in frames) that this device can work with. */
static int
audiounit_get_acceptable_latency_range(AudioValueRange * latency_range)
{
@@ -1106,28 +1123,18 @@ static int
audiounit_init_input_linear_buffer(cubeb_stream * stream, uint32_t capacity)
{
uint32_t size = capacity * stream->input_buffer_frames * stream->input_desc.mChannelsPerFrame;
if (stream->input_desc.mFormatFlags & kAudioFormatFlagIsSignedInteger) {
stream->input_linear_buffer.reset(new auto_array_wrapper_impl<short>(size));
} else {
stream->input_linear_buffer.reset(new auto_array_wrapper_impl<float>(size));
}
-
assert(stream->input_linear_buffer->length() == 0);
- // Pre-buffer silence if needed
- if (capacity != 1) {
- size_t silence_size = stream->input_buffer_frames *
- stream->input_desc.mChannelsPerFrame;
- stream->input_linear_buffer->push_silence(silence_size);
-
- assert(stream->input_linear_buffer->length() == silence_size);
- }
-
return CUBEB_OK;
}
static uint32_t
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);
--- a/media/libcubeb/src/cubeb_resampler.cpp
+++ b/media/libcubeb/src/cubeb_resampler.cpp
@@ -67,16 +67,17 @@ long passthrough_resampler<T>::fill(void
frames_to_samples(*input_frames_count));
}
long rv = data_callback(stream, user_ptr, internal_input_buffer.data(),
output_buffer, output_frames);
if (input_buffer) {
internal_input_buffer.pop(nullptr, frames_to_samples(output_frames));
+ *input_frames_count = output_frames;
}
return rv;
}
template<typename T, typename InputProcessor, typename OutputProcessor>
cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
::cubeb_resampler_speex(InputProcessor * input_processor,
@@ -174,17 +175,17 @@ cubeb_resampler_speex<T, InputProcessor,
/* The input data, after eventual resampling. This is passed to the callback. */
T * resampled_input = nullptr;
uint32_t resampled_frame_count = input_processor->output_for_input(*input_frames_count);
/* process the input, and present exactly `output_frames_needed` in the
* callback. */
input_processor->input(input_buffer, *input_frames_count);
- resampled_input = input_processor->output(resampled_frame_count);
+ resampled_input = input_processor->output(resampled_frame_count, (size_t*)input_frames_count);
long got = data_callback(stream, user_ptr,
resampled_input, nullptr, resampled_frame_count);
/* Return the number of initial input frames or part of it.
* Since output_frames_needed == 0 in input scenario, the only
* available number outside resampler is the initial number of frames. */
return (*input_frames_count) * (got / resampled_frame_count);
@@ -221,17 +222,17 @@ cubeb_resampler_speex<T, InputProcessor,
out_unprocessed =
output_processor->input_buffer(output_frames_before_processing);
if (in_buffer) {
/* process the input, and present exactly `output_frames_needed` in the
* callback. */
input_processor->input(in_buffer, *input_frames_count);
resampled_input =
- input_processor->output(output_frames_before_processing);
+ input_processor->output(output_frames_before_processing, (size_t*)input_frames_count);
} else {
resampled_input = nullptr;
}
got = data_callback(stream, user_ptr,
resampled_input, out_unprocessed,
output_frames_before_processing);
--- a/media/libcubeb/src/cubeb_resampler_internal.h
+++ b/media/libcubeb/src/cubeb_resampler_internal.h
@@ -217,33 +217,34 @@ public:
size_t output_for_input(uint32_t input_frames)
{
return (size_t)floorf((input_frames + samples_to_frames(resampling_in_buffer.length()))
/ resampling_ratio);
}
/** Returns a buffer containing exactly `output_frame_count` resampled frames.
* The consumer should not hold onto the pointer. */
- T * output(size_t output_frame_count)
+ T * output(size_t output_frame_count, size_t * input_frames_used)
{
if (resampling_out_buffer.capacity() < frames_to_samples(output_frame_count)) {
resampling_out_buffer.reserve(frames_to_samples(output_frame_count));
}
uint32_t in_len = samples_to_frames(resampling_in_buffer.length());
uint32_t out_len = output_frame_count;
speex_resample(resampling_in_buffer.data(), &in_len,
resampling_out_buffer.data(), &out_len);
assert(out_len == output_frame_count);
/* This shifts back any unresampled samples to the beginning of the input
buffer. */
resampling_in_buffer.pop(nullptr, frames_to_samples(in_len));
+ *input_frames_used = in_len;
return resampling_out_buffer.data();
}
/** Get the latency of the resampler, in output frames. */
uint32_t latency() const
{
/* The documentation of the resampler talks about "samples" here, but it
@@ -371,26 +372,27 @@ public:
void input(T * buffer, uint32_t frame_count)
{
delay_input_buffer.push(buffer, frames_to_samples(frame_count));
}
/** Pop some frames from the internal buffer, into a internal output buffer.
* @parameter frames_needed the number of frames to be returned.
* @return a buffer containing the delayed frames. The consumer should not
* hold onto the pointer. */
- T * output(uint32_t frames_needed)
+ T * output(uint32_t frames_needed, size_t * input_frames_used)
{
if (delay_output_buffer.capacity() < frames_to_samples(frames_needed)) {
delay_output_buffer.reserve(frames_to_samples(frames_needed));
}
delay_output_buffer.clear();
delay_output_buffer.push(delay_input_buffer.data(),
frames_to_samples(frames_needed));
delay_input_buffer.pop(nullptr, frames_to_samples(frames_needed));
+ *input_frames_used = frames_needed;
return delay_output_buffer.data();
}
/** Get a pointer to the first writable location in the input buffer>
* @parameter frames_needed the number of frames the user needs to write into
* the buffer.
* @returns a pointer to a location in the input buffer where #frames_needed
* can be writen. */
--- a/media/libcubeb/update.sh
+++ b/media/libcubeb/update.sh
@@ -57,8 +57,14 @@ if [ -n "$rev" ]; then
version=$version-dirty
echo "WARNING: updating from a dirty git repository."
fi
sed -i.bak -e "/The git commit ID used was/ s/[0-9a-f]\{40\}\(-dirty\)\{0,1\}\./$version./" README_MOZILLA
rm README_MOZILLA.bak
else
echo "Remember to update README_MOZILLA with the version details."
fi
+
+echo "Applying a patch on top of $version"
+patch -p1 < ./wasapi-drift-fix-passthrough-resampler.patch
+
+echo "Applying a patch on top of $version"
+patch -p1 < ./audiounit-drift-fix.patch
new file mode 100644
--- /dev/null
+++ b/media/libcubeb/wasapi-drift-fix-passthrough-resampler.patch
@@ -0,0 +1,548 @@
+# HG changeset patch
+# User Alex Chronopoulos <achronop@gmail.com>
+# Parent 35ef506e005a3bc7f42637debfada1d3e09d7e5a
+Bug 1339816 - Uplift cubeb wasapi drift fix and passthrough resampler. r?padenot
+
+diff --git a/media/libcubeb/gtest/common.h b/media/libcubeb/gtest/common.h
+--- a/media/libcubeb/gtest/common.h
++++ b/media/libcubeb/gtest/common.h
+@@ -11,16 +11,18 @@
+ #ifndef WIN32_LEAN_AND_MEAN
+ #define WIN32_LEAN_AND_MEAN
+ #endif
+ #include <windows.h>
+ #else
+ #include <unistd.h>
+ #endif
+
++#include "cubeb/cubeb.h"
++
+ template<typename T, size_t N>
+ constexpr size_t
+ ARRAY_LENGTH(T(&)[N])
+ {
+ return N;
+ }
+
+ void delay(unsigned int ms)
+diff --git a/media/libcubeb/gtest/test_resampler.cpp b/media/libcubeb/gtest/test_resampler.cpp
+--- a/media/libcubeb/gtest/test_resampler.cpp
++++ b/media/libcubeb/gtest/test_resampler.cpp
+@@ -3,16 +3,17 @@
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+ #ifndef NOMINMAX
+ #define NOMINMAX
+ #endif // NOMINMAX
+ #include "gtest/gtest.h"
++#include "common.h"
+ #include "cubeb_resampler_internal.h"
+ #include <stdio.h>
+ #include <algorithm>
+ #include <iostream>
+
+ /* Windows cmath USE_MATH_DEFINE thing... */
+ const float PI = 3.14159265359f;
+
+@@ -386,18 +387,21 @@ void test_resampler_duplex(uint32_t inpu
+ state.output.push(output_buffer.data(), got * state.output_channels);
+ }
+
+ dump("input_expected.raw", expected_resampled_input.data(), expected_resampled_input.length());
+ dump("output_expected.raw", expected_resampled_output.data(), expected_resampled_output.length());
+ dump("input.raw", state.input.data(), state.input.length());
+ dump("output.raw", state.output.data(), state.output.length());
+
+- ASSERT_TRUE(array_fuzzy_equal(state.input, expected_resampled_input, epsilon<T>(input_rate/target_rate)));
+- ASSERT_TRUE(array_fuzzy_equal(state.output, expected_resampled_output, epsilon<T>(output_rate/target_rate)));
++ // This is disabled because the latency estimation in the resampler code is
++ // slightly off so we can generate expected vectors.
++ // See https://github.com/kinetiknz/cubeb/issues/93
++ // ASSERT_TRUE(array_fuzzy_equal(state.input, expected_resampled_input, epsilon<T>(input_rate/target_rate)));
++ // ASSERT_TRUE(array_fuzzy_equal(state.output, expected_resampled_output, epsilon<T>(output_rate/target_rate)));
+
+ cubeb_resampler_destroy(resampler);
+ }
+
+ #define array_size(x) (sizeof(x) / sizeof(x[0]))
+
+ TEST(cubeb, resampler_one_way)
+ {
+@@ -411,19 +415,16 @@ TEST(cubeb, resampler_one_way)
+ test_resampler_one_way<float>(channels, sample_rates[source_rate],
+ sample_rates[dest_rate], chunk_duration);
+ }
+ }
+ }
+ }
+ }
+
+-// This is disabled because the latency estimation in the resampler code is
+-// slightly off so we can generate expected vectors.
+-// See https://github.com/kinetiknz/cubeb/issues/93
+ TEST(cubeb, DISABLED_resampler_duplex)
+ {
+ for (uint32_t input_channels = 1; input_channels <= max_channels; input_channels++) {
+ for (uint32_t output_channels = 1; output_channels <= max_channels; output_channels++) {
+ for (uint32_t source_rate_input = 0; source_rate_input < array_size(sample_rates); source_rate_input++) {
+ for (uint32_t source_rate_output = 0; source_rate_output < array_size(sample_rates); source_rate_output++) {
+ for (uint32_t dest_rate = 0; dest_rate < array_size(sample_rates); dest_rate++) {
+ for (uint32_t chunk_duration = min_chunks; chunk_duration < max_chunks; chunk_duration+=chunk_increment) {
+@@ -531,8 +532,219 @@ TEST(cubeb, resampler_drain)
+
+ /* If the above is not an infinite loop, the drain was a success, just mark
+ * this test as such. */
+ ASSERT_TRUE(true);
+
+ cubeb_resampler_destroy(resampler);
+ }
+
++// gtest does not support using ASSERT_EQ and friend in a function that returns
++// a value.
++void check_output(const void * input_buffer, void * output_buffer, long frame_count)
++{
++ ASSERT_EQ(input_buffer, nullptr);
++ ASSERT_EQ(frame_count, 256);
++ ASSERT_TRUE(!!output_buffer);
++}
++
++long cb_passthrough_resampler_output(cubeb_stream * /*stm*/, void * /*user_ptr*/,
++ const void * input_buffer,
++ void * output_buffer, long frame_count)
++{
++ check_output(input_buffer, output_buffer, frame_count);
++ return frame_count;
++}
++
++TEST(cubeb, resampler_passthrough_output_only)
++{
++ // Test that the passthrough resampler works when there is only an output stream.
++ cubeb_stream_params output_params;
++
++ const size_t output_channels = 2;
++ output_params.channels = output_channels;
++ output_params.rate = 44100;
++ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
++ int target_rate = output_params.rate;
++
++ cubeb_resampler * resampler =
++ cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params,
++ target_rate, cb_passthrough_resampler_output, nullptr,
++ CUBEB_RESAMPLER_QUALITY_VOIP);
++
++ float output_buffer[output_channels * 256];
++
++ long got;
++ for (uint32_t i = 0; i < 30; i++) {
++ got = cubeb_resampler_fill(resampler, nullptr, nullptr, output_buffer, 256);
++ ASSERT_EQ(got, 256);
++ }
++}
++
++// gtest does not support using ASSERT_EQ and friend in a function that returns
++// a value.
++void check_input(const void * input_buffer, void * output_buffer, long frame_count)
++{
++ ASSERT_EQ(output_buffer, nullptr);
++ ASSERT_EQ(frame_count, 256);
++ ASSERT_TRUE(!!input_buffer);
++}
++
++long cb_passthrough_resampler_input(cubeb_stream * /*stm*/, void * /*user_ptr*/,
++ const void * input_buffer,
++ void * output_buffer, long frame_count)
++{
++ check_input(input_buffer, output_buffer, frame_count);
++ return frame_count;
++}
++
++TEST(cubeb, resampler_passthrough_input_only)
++{
++ // Test that the passthrough resampler works when there is only an output stream.
++ cubeb_stream_params input_params;
++
++ const size_t input_channels = 2;
++ input_params.channels = input_channels;
++ input_params.rate = 44100;
++ input_params.format = CUBEB_SAMPLE_FLOAT32NE;
++ int target_rate = input_params.rate;
++
++ cubeb_resampler * resampler =
++ cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, nullptr,
++ target_rate, cb_passthrough_resampler_input, nullptr,
++ CUBEB_RESAMPLER_QUALITY_VOIP);
++
++ float input_buffer[input_channels * 256];
++
++ long got;
++ for (uint32_t i = 0; i < 30; i++) {
++ long int frames = 256;
++ got = cubeb_resampler_fill(resampler, input_buffer, &frames, nullptr, 0);
++ ASSERT_EQ(got, 256);
++ }
++}
++
++template<typename T>
++long seq(T* array, int stride, long start, long count)
++{
++ for(int i = 0; i < count; i++) {
++ for (int j = 0; j < stride; j++) {
++ array[i + j] = static_cast<T>(start + i);
++ }
++ }
++ return start + count;
++}
++
++template<typename T>
++void is_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_EQ(array[output_index + j], expected_start + i);
++ }
++ output_index += stride;
++ }
++}
++
++// 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)
++{
++ ASSERT_EQ(frame_count, 256);
++ // Silence scan-build warning.
++ ASSERT_TRUE(!!output_buffer); assert(output_buffer);
++ ASSERT_TRUE(!!input_buffer); assert(input_buffer);
++
++ int output_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_index += 2;
++ }
++}
++
++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);
++ return frame_count;
++}
++
++
++TEST(cubeb, resampler_passthrough_duplex_callback_reordering)
++{
++ // Test that when pre-buffering on resampler creation, we can survive an input
++ // callback being delayed.
++
++ cubeb_stream_params input_params;
++ cubeb_stream_params output_params;
++
++ const int input_channels = 1;
++ const int output_channels = 2;
++
++ input_params.channels = input_channels;
++ input_params.rate = 44100;
++ input_params.format = CUBEB_SAMPLE_FLOAT32NE;
++
++ output_params.channels = output_channels;
++ output_params.rate = input_params.rate;
++ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
++
++ int target_rate = input_params.rate;
++
++ cubeb_resampler * resampler =
++ cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params,
++ target_rate, cb_passthrough_resampler_duplex, nullptr,
++ CUBEB_RESAMPLER_QUALITY_VOIP);
++
++ const long BUF_BASE_SIZE = 256;
++ float input_buffer_prebuffer[input_channels * BUF_BASE_SIZE * 2];
++ float input_buffer_glitch[input_channels * BUF_BASE_SIZE * 2];
++ float input_buffer_normal[input_channels * BUF_BASE_SIZE];
++ float output_buffer[output_channels * BUF_BASE_SIZE];
++
++ long seq_idx = 0;
++ long output_seq_idx = 0;
++
++ long prebuffer_frames = ARRAY_LENGTH(input_buffer_prebuffer) / input_params.channels;
++ seq_idx = seq(input_buffer_prebuffer, input_channels, seq_idx,
++ prebuffer_frames);
++
++ long got = cubeb_resampler_fill(resampler, input_buffer_prebuffer, &prebuffer_frames,
++ output_buffer, BUF_BASE_SIZE);
++
++ output_seq_idx += BUF_BASE_SIZE;
++
++ ASSERT_EQ(prebuffer_frames, static_cast<long>(ARRAY_LENGTH(input_buffer_prebuffer) / input_params.channels));
++ ASSERT_EQ(got, BUF_BASE_SIZE);
++
++ for (uint32_t i = 0; i < 300; i++) {
++ long int frames = BUF_BASE_SIZE;
++ // Simulate that sometimes, we don't have the input callback on time
++ if (i != 0 && (i % 100) == 0) {
++ long zero = 0;
++ got = cubeb_resampler_fill(resampler, input_buffer_normal /* unused here */,
++ &zero, output_buffer, BUF_BASE_SIZE);
++ is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx);
++ output_seq_idx += BUF_BASE_SIZE;
++ } else if (i != 0 && (i % 100) == 1) {
++ // if this is the case, the on the next iteration, we'll have twice the
++ // amount of input frames
++ seq_idx = seq(input_buffer_glitch, input_channels, seq_idx, BUF_BASE_SIZE * 2);
++ frames = 2 * BUF_BASE_SIZE;
++ got = cubeb_resampler_fill(resampler, input_buffer_glitch, &frames, output_buffer, BUF_BASE_SIZE);
++ is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx);
++ output_seq_idx += BUF_BASE_SIZE;
++ } else {
++ // normal case
++ seq_idx = seq(input_buffer_normal, input_channels, seq_idx, BUF_BASE_SIZE);
++ long normal_input_frame_count = 256;
++ got = cubeb_resampler_fill(resampler, input_buffer_normal, &normal_input_frame_count, output_buffer, BUF_BASE_SIZE);
++ is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx);
++ output_seq_idx += BUF_BASE_SIZE;
++ }
++ ASSERT_EQ(got, BUF_BASE_SIZE);
++ }
++}
+diff --git a/media/libcubeb/src/cubeb_resampler.cpp b/media/libcubeb/src/cubeb_resampler.cpp
+--- a/media/libcubeb/src/cubeb_resampler.cpp
++++ b/media/libcubeb/src/cubeb_resampler.cpp
+@@ -30,39 +30,56 @@ to_speex_quality(cubeb_resampler_quality
+ case CUBEB_RESAMPLER_QUALITY_DESKTOP:
+ return SPEEX_RESAMPLER_QUALITY_DESKTOP;
+ default:
+ assert(false);
+ return 0XFFFFFFFF;
+ }
+ }
+
+-long noop_resampler::fill(void * input_buffer, long * input_frames_count,
+- void * output_buffer, long output_frames)
++template<typename T>
++passthrough_resampler<T>::passthrough_resampler(cubeb_stream * s,
++ cubeb_data_callback cb,
++ void * ptr,
++ uint32_t input_channels)
++ : processor(input_channels)
++ , stream(s)
++ , data_callback(cb)
++ , user_ptr(ptr)
++{
++}
++
++template<typename T>
++long passthrough_resampler<T>::fill(void * input_buffer, long * input_frames_count,
++ void * output_buffer, long output_frames)
+ {
+ if (input_buffer) {
+ assert(input_frames_count);
+ }
+ assert((input_buffer && output_buffer &&
+- *input_frames_count >= output_frames) ||
+- (!input_buffer && (!input_frames_count || *input_frames_count == 0)) ||
+- (!output_buffer && output_frames == 0));
++ *input_frames_count + static_cast<int>(samples_to_frames(internal_input_buffer.length())) >= output_frames) ||
++ (output_buffer && !input_buffer && (!input_frames_count || *input_frames_count == 0)) ||
++ (input_buffer && !output_buffer && output_frames == 0));
+
+- if (output_buffer == nullptr) {
+- assert(input_buffer);
+- output_frames = *input_frames_count;
++ if (input_buffer) {
++ if (!output_buffer) {
++ output_frames = *input_frames_count;
++ }
++ internal_input_buffer.push(static_cast<T*>(input_buffer),
++ frames_to_samples(*input_frames_count));
+ }
+
+- if (input_buffer && *input_frames_count != output_frames) {
+- assert(*input_frames_count > output_frames);
+- *input_frames_count = output_frames;
++ long rv = data_callback(stream, user_ptr, internal_input_buffer.data(),
++ output_buffer, output_frames);
++
++ if (input_buffer) {
++ internal_input_buffer.pop(nullptr, frames_to_samples(output_frames));
+ }
+
+- return data_callback(stream, user_ptr,
+- input_buffer, output_buffer, output_frames);
++ return rv;
+ }
+
+ template<typename T, typename InputProcessor, typename OutputProcessor>
+ cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
+ ::cubeb_resampler_speex(InputProcessor * input_processor,
+ OutputProcessor * output_processor,
+ cubeb_stream * s,
+ cubeb_data_callback cb,
+diff --git a/media/libcubeb/src/cubeb_resampler_internal.h b/media/libcubeb/src/cubeb_resampler_internal.h
+--- a/media/libcubeb/src/cubeb_resampler_internal.h
++++ b/media/libcubeb/src/cubeb_resampler_internal.h
+@@ -43,41 +43,16 @@ int to_speex_quality(cubeb_resampler_qua
+
+ struct cubeb_resampler {
+ virtual long fill(void * input_buffer, long * input_frames_count,
+ void * output_buffer, long frames_needed) = 0;
+ virtual long latency() = 0;
+ virtual ~cubeb_resampler() {}
+ };
+
+-class noop_resampler : public cubeb_resampler {
+-public:
+- noop_resampler(cubeb_stream * s,
+- cubeb_data_callback cb,
+- void * ptr)
+- : stream(s)
+- , data_callback(cb)
+- , user_ptr(ptr)
+- {
+- }
+-
+- virtual long fill(void * input_buffer, long * input_frames_count,
+- void * output_buffer, long output_frames);
+-
+- virtual long latency()
+- {
+- return 0;
+- }
+-
+-private:
+- cubeb_stream * const stream;
+- const cubeb_data_callback data_callback;
+- void * const user_ptr;
+-};
+-
+ /** Base class for processors. This is just used to share methods for now. */
+ class processor {
+ public:
+ explicit processor(uint32_t channels)
+ : channels(channels)
+ {}
+ protected:
+ size_t frames_to_samples(size_t frames)
+@@ -88,16 +63,42 @@ protected:
+ {
+ assert(!(samples % channels));
+ return samples / channels;
+ }
+ /** The number of channel of the audio buffers to be resampled. */
+ const uint32_t channels;
+ };
+
++template<typename T>
++class passthrough_resampler : public cubeb_resampler
++ , public processor {
++public:
++ passthrough_resampler(cubeb_stream * s,
++ cubeb_data_callback cb,
++ void * ptr,
++ uint32_t input_channels);
++
++ virtual long fill(void * input_buffer, long * input_frames_count,
++ void * output_buffer, long output_frames);
++
++ virtual long latency()
++ {
++ return 0;
++ }
++
++private:
++ cubeb_stream * const stream;
++ const cubeb_data_callback data_callback;
++ void * const user_ptr;
++ /* This allows to buffer some input to account for the fact that we buffer
++ * some inputs. */
++ auto_array<T> internal_input_buffer;
++};
++
+ /** Bidirectional resampler, can resample an input and an output stream, or just
+ * an input stream or output stream. In this case a delay is inserted in the
+ * opposite direction to keep the streams synchronized. */
+ template<typename T, typename InputProcessing, typename OutputProcessing>
+ class cubeb_resampler_speex : public cubeb_resampler {
+ public:
+ cubeb_resampler_speex(InputProcessing * input_processor,
+ OutputProcessing * output_processor,
+@@ -475,17 +476,19 @@ cubeb_resampler_create_internal(cubeb_st
+
+ /* All the streams we have have a sample rate that matches the target
+ sample rate, use a no-op resampler, that simply forwards the buffers to the
+ callback. */
+ if (((input_params && input_params->rate == target_rate) &&
+ (output_params && output_params->rate == target_rate)) ||
+ (input_params && !output_params && (input_params->rate == target_rate)) ||
+ (output_params && !input_params && (output_params->rate == target_rate))) {
+- return new noop_resampler(stream, callback, user_ptr);
++ return new passthrough_resampler<T>(stream, callback,
++ user_ptr,
++ input_params ? input_params->channels : 0);
+ }
+
+ /* Determine if we need to resampler one or both directions, and create the
+ resamplers. */
+ if (output_params && (output_params->rate != target_rate)) {
+ output_resampler.reset(
+ new cubeb_resampler_speex_one_way<T>(output_params->channels,
+ target_rate,
+diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp
+--- a/media/libcubeb/src/cubeb_wasapi.cpp
++++ b/media/libcubeb/src/cubeb_wasapi.cpp
+@@ -742,25 +742,16 @@ refill_callback_duplex(cubeb_stream * st
+ }
+
+ /* This can only happen when debugging, and having breakpoints set in the
+ * callback in a way that it makes the stream underrun. */
+ if (output_frames == 0) {
+ return true;
+ }
+
+- // When WASAPI has not filled the input buffer yet, send silence.
+- double output_duration = double(output_frames) / stm->output_mix_params.rate;
+- double input_duration = double(stm->linear_input_buffer.length() / stm->input_stream_params.channels) / stm->input_mix_params.rate;
+- if (input_duration < output_duration) {
+- size_t padding = size_t(round((output_duration - input_duration) * stm->input_mix_params.rate));
+- LOG("padding silence: out=%f in=%f pad=%u", output_duration, input_duration, padding);
+- stm->linear_input_buffer.push_front_silence(padding * stm->input_stream_params.channels);
+- }
+-
+ LOGV("Duplex callback: input frames: %Iu, output frames: %Iu",
+ stm->linear_input_buffer.length(), output_frames);
+
+ refill(stm,
+ stm->linear_input_buffer.data(),
+ stm->linear_input_buffer.length(),
+ output_buffer,
+ output_frames);
+@@ -1646,16 +1637,24 @@ int setup_wasapi_stream(cubeb_stream * s
+ stm->input_device.get(),
+ eCapture,
+ __uuidof(IAudioCaptureClient),
+ stm->input_client,
+ &stm->input_buffer_frame_count,
+ stm->input_available_event,
+ stm->capture_client,
+ &stm->input_mix_params);
++
++ // We initializing an input stream, buffer ahead two buffers worth of silence.
++ // This delays the input side slightly, but allow to not glitch when no input
++ // is available when calling into the resampler to call the callback: the input
++ // refill event will be set shortly after to compensate for this lack of data.
++ stm->linear_input_buffer.push_silence(stm->input_buffer_frame_count *
++ stm->input_stream_params.channels * 2);
++
+ if (rv != CUBEB_OK) {
+ LOG("Failure to open the input side.");
+ return rv;
+ }
+ }
+
+ if (has_output(stm)) {
+ LOG("(%p) Setup render: device=%p", stm, stm->output_device.get());