--- a/media/libcubeb/README_MOZILLA
+++ b/media/libcubeb/README_MOZILLA
@@ -1,12 +1,8 @@
The source from this directory was copied from the cubeb
git repository using the update.sh script. The only changes
-made were those applied by update.sh, the addition of
-Makefile.in build files for the Mozilla build system,
-and the following patches, which may be overwritten when
-included upstream.
-https://github.com/kinetiknz/cubeb/pull/398/commits/c8e66dee61a35e6a6d54e3630e1668bdbd6984b4
-https://github.com/kinetiknz/cubeb/pull/398/commits/2ed979bc891cf1a7822799947a357d4d3b625964
+made were those applied by update.sh and the addition of
+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 2b98e3d3279322ce0764ae2b9f28a9ea9000418b (2018-01-19 10:17:28 +1300)
+The git commit ID used was 4c18a84a4573a23a1c39ccf6c70f1a6c328cb110 (2018-01-23 08:50:28 +1300)
--- a/media/libcubeb/gtest/test_audio.cpp
+++ b/media/libcubeb/gtest/test_audio.cpp
@@ -118,16 +118,17 @@ int run_test(int num_channels, layout_in
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;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
synth_state synth(params.channels, params.rate);
cubeb_stream *stream = NULL;
r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, ¶ms,
4096, is_float ? &data_cb<float> : &data_cb<short>, state_cb_audio, &synth);
if (r != CUBEB_OK) {
fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
@@ -166,16 +167,17 @@ int run_panning_volume_test(int is_float
return CUBEB_OK;
}
cubeb_stream_params params;
params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
params.rate = 44100;
params.channels = 2;
params.layout = CUBEB_LAYOUT_STEREO;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
synth_state synth(params.channels, params.rate);
cubeb_stream *stream = NULL;
r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, ¶ms,
4096, is_float ? &data_cb<float> : &data_cb<short>,
state_cb_audio, &synth);
if (r != CUBEB_OK) {
--- a/media/libcubeb/gtest/test_devices.cpp
+++ b/media/libcubeb/gtest/test_devices.cpp
@@ -185,16 +185,17 @@ TEST(cubeb, enumerate_devices)
cubeb_stream * stream;
cubeb_stream_params input_params;
cubeb_stream_params output_params;
input_params.format = output_params.format = CUBEB_SAMPLE_FLOAT32NE;
input_params.rate = output_params.rate = 48000;
input_params.channels = output_params.channels = 1;
input_params.layout = output_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = output_params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
NULL, &input_params, NULL, &output_params,
1024, data_cb_duplex, state_cb_duplex, nullptr);
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
--- a/media/libcubeb/gtest/test_duplex.cpp
+++ b/media/libcubeb/gtest/test_duplex.cpp
@@ -96,20 +96,22 @@ TEST(cubeb, duplex)
return;
}
/* typical user-case: mono input, stereo output, low latency. */
input_params.format = STREAM_FORMAT;
input_params.rate = 48000;
input_params.channels = 1;
input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_NONE;
output_params.format = STREAM_FORMAT;
output_params.rate = 48000;
output_params.channels = 2;
output_params.layout = CUBEB_LAYOUT_STEREO;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
NULL, &input_params, NULL, &output_params,
latency_frames, data_cb_duplex, state_cb_duplex, &stream_state);
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
--- a/media/libcubeb/gtest/test_latency.cpp
+++ b/media/libcubeb/gtest/test_latency.cpp
@@ -29,16 +29,17 @@ TEST(cubeb, latency)
if (r == CUBEB_OK) {
ASSERT_GT(preferred_rate, 0u);
}
cubeb_stream_params params = {
CUBEB_SAMPLE_FLOAT32NE,
preferred_rate,
max_channels,
- CUBEB_LAYOUT_UNDEFINED
+ CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE
};
r = cubeb_get_min_latency(ctx, ¶ms, &latency_frames);
ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
if (r == CUBEB_OK) {
ASSERT_GT(latency_frames, 0u);
}
}
new file mode 100644
--- /dev/null
+++ b/media/libcubeb/gtest/test_loopback.cpp
@@ -0,0 +1,575 @@
+/*
+ * Copyright © 2017 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+ /* libcubeb api/function test. Requests a loopback device and checks that
+ output is being looped back to input. NOTE: Usage of output devices while
+ performing this test will cause flakey results! */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <algorithm>
+#include <memory>
+#include <mutex>
+#include <string>
+#include "cubeb/cubeb.h"
+#include "common.h"
+
+const uint32_t SAMPLE_FREQUENCY = 48000;
+const uint32_t TONE_FREQUENCY = 440;
+const double OUTPUT_AMPLITUDE = 0.25;
+const uint32_t NUM_FRAMES_TO_OUTPUT = SAMPLE_FREQUENCY / 20; /* play ~50ms of samples */
+
+template<typename T> T ConvertSampleToOutput(double input);
+template<> float ConvertSampleToOutput(double input) { return float(input); }
+template<> short ConvertSampleToOutput(double input) { return short(input * 32767.0f); }
+
+template<typename T> double ConvertSampleFromOutput(T sample);
+template<> double ConvertSampleFromOutput(float sample) { return double(sample); }
+template<> double ConvertSampleFromOutput(short sample) { return double(sample / 32767.0); }
+
+/* Simple cross correlation to help find phase shift. Not a performant impl */
+std::vector<double> cross_correlate(std::vector<double> & f,
+ std::vector<double> & g,
+ size_t signal_length)
+{
+ /* the length we sweep our window through to find the cross correlation */
+ size_t sweep_length = f.size() - signal_length + 1;
+ std::vector<double> correlation;
+ correlation.reserve(sweep_length);
+ for (size_t i = 0; i < sweep_length; i++) {
+ double accumulator = 0.0;
+ for (size_t j = 0; j < signal_length; j++) {
+ accumulator += f.at(j) * g.at(i + j);
+ }
+ correlation.push_back(accumulator);
+ }
+ return correlation;
+}
+
+/* best effort discovery of phase shift between output and (looped) input*/
+size_t find_phase(std::vector<double> & output_frames,
+ std::vector<double> & input_frames,
+ size_t signal_length)
+{
+ std::vector<double> correlation = cross_correlate(output_frames, input_frames, signal_length);
+ size_t phase = 0;
+ double max_correlation = correlation.at(0);
+ for (size_t i = 1; i < correlation.size(); i++) {
+ if (correlation.at(i) > max_correlation) {
+ max_correlation = correlation.at(i);
+ phase = i;
+ }
+ }
+ return phase;
+}
+
+std::vector<double> normalize_frames(std::vector<double> & frames) {
+ double max = abs(*std::max_element(frames.begin(), frames.end(),
+ [](double a, double b) { return abs(a) < abs(b); }));
+ std::vector<double> normalized_frames;
+ normalized_frames.reserve(frames.size());
+ for (const double frame : frames) {
+ normalized_frames.push_back(frame / max);
+ }
+ return normalized_frames;
+}
+
+/* heuristic comparison of aligned output and input signals, gets flaky if TONE_FREQUENCY is too high */
+void compare_signals(std::vector<double> & output_frames,
+ std::vector<double> & input_frames)
+{
+ ASSERT_EQ(output_frames.size(), input_frames.size()) << "#Output frames != #input frames";
+ size_t num_frames = output_frames.size();
+ std::vector<double> normalized_output_frames = normalize_frames(output_frames);
+ std::vector<double> normalized_input_frames = normalize_frames(input_frames);
+
+ /* calculate mean absolute errors */
+ /* mean absolute errors between output and input */
+ double io_mas = 0.0;
+ /* mean absolute errors between output and silence */
+ double output_silence_mas = 0.0;
+ /* mean absolute errors between input and silence */
+ double input_silence_mas = 0.0;
+ for (size_t i = 0; i < num_frames; i++) {
+ io_mas += abs(normalized_output_frames.at(i) - normalized_input_frames.at(i));
+ output_silence_mas += abs(normalized_output_frames.at(i));
+ input_silence_mas += abs(normalized_input_frames.at(i));
+ }
+ io_mas /= num_frames;
+ output_silence_mas /= num_frames;
+ input_silence_mas /= num_frames;
+
+ ASSERT_LT(io_mas, output_silence_mas)
+ << "Error between output and input should be less than output and silence!";
+ ASSERT_LT(io_mas, input_silence_mas)
+ << "Error between output and input should be less than output and silence!";
+
+ /* make sure extrema are in (roughly) correct location */
+ /* number of maxima + minama expected in the frames*/
+ const long NUM_EXTREMA = 2 * TONE_FREQUENCY * NUM_FRAMES_TO_OUTPUT / SAMPLE_FREQUENCY;
+ /* expected index of first maxima */
+ const long FIRST_MAXIMUM_INDEX = SAMPLE_FREQUENCY / TONE_FREQUENCY / 4;
+ /* Threshold we expect all maxima and minima to be above or below. Ideally
+ the extrema would be 1 or -1, but particularly at the start of loopback
+ the values seen can be significantly lower. */
+ const double THRESHOLD = 0.5;
+
+ for (size_t i = 0; i < NUM_EXTREMA; i++) {
+ bool is_maximum = i % 2 == 0;
+ /* expected offset to current extreme: i * stide between extrema */
+ size_t offset = i * SAMPLE_FREQUENCY / TONE_FREQUENCY / 2;
+ if (is_maximum) {
+ ASSERT_GT(normalized_output_frames.at(FIRST_MAXIMUM_INDEX + offset), THRESHOLD)
+ << "Output frames have unexpected missing maximum!";
+ ASSERT_GT(normalized_input_frames.at(FIRST_MAXIMUM_INDEX + offset), THRESHOLD)
+ << "Input frames have unexpected missing maximum!";
+ } else {
+ ASSERT_LT(normalized_output_frames.at(FIRST_MAXIMUM_INDEX + offset), -THRESHOLD)
+ << "Output frames have unexpected missing minimum!";
+ ASSERT_LT(normalized_input_frames.at(FIRST_MAXIMUM_INDEX + offset), -THRESHOLD)
+ << "Input frames have unexpected missing minimum!";
+ }
+ }
+}
+
+struct user_state_loopback {
+ std::mutex user_state_mutex;
+ long position = 0;
+ /* track output */
+ std::vector<double> output_frames;
+ /* track input */
+ std::vector<double> input_frames;
+};
+
+template<typename T>
+long data_cb_loop_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ struct user_state_loopback * u = (struct user_state_loopback *) user;
+ T * ib = (T *) inputbuffer;
+ T * ob = (T *) outputbuffer;
+
+ if (stream == NULL || inputbuffer == NULL || outputbuffer == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ std::lock_guard<std::mutex> lock(u->user_state_mutex);
+ /* generate our test tone on the fly */
+ for (int i = 0; i < nframes; i++) {
+ double tone = 0.0;
+ if (u->position + i < NUM_FRAMES_TO_OUTPUT) {
+ /* generate sine wave */
+ tone = sin(2 * M_PI*(i + u->position) * TONE_FREQUENCY / SAMPLE_FREQUENCY);
+ tone *= OUTPUT_AMPLITUDE;
+ }
+ ob[i] = ConvertSampleToOutput<T>(tone);
+ u->output_frames.push_back(tone);
+ /* store any looped back output, may be silence */
+ u->input_frames.push_back(ConvertSampleFromOutput(ib[i]));
+ }
+
+ u->position += nframes;
+
+ return nframes;
+}
+
+template<typename T>
+long data_cb_loop_input_only(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ struct user_state_loopback * u = (struct user_state_loopback *) user;
+ T * ib = (T *) inputbuffer;
+
+ if (outputbuffer != NULL) {
+ // Can't assert as it needs to return, so expect to fail instead
+ EXPECT_EQ(outputbuffer, (void *) NULL) << "outputbuffer should be null in input only callback";
+ return CUBEB_ERROR;
+ }
+
+ if (stream == NULL || inputbuffer == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ std::lock_guard<std::mutex> lock(u->user_state_mutex);
+ for (int i = 0; i < nframes; i++) {
+ u->input_frames.push_back(ConvertSampleFromOutput(ib[i]));
+ }
+
+ return nframes;
+}
+
+template<typename T>
+long data_cb_playback(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ struct user_state_loopback * u = (struct user_state_loopback *) user;
+ T * ob = (T *) outputbuffer;
+
+ if (stream == NULL || outputbuffer == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ std::lock_guard<std::mutex> lock(u->user_state_mutex);
+ /* generate our test tone on the fly */
+ for (int i = 0; i < nframes; i++) {
+ double tone = 0.0;
+ if (u->position + i < NUM_FRAMES_TO_OUTPUT) {
+ /* generate sine wave */
+ tone = sin(2 * M_PI*(i + u->position) * TONE_FREQUENCY / SAMPLE_FREQUENCY);
+ tone *= OUTPUT_AMPLITUDE;
+ }
+ ob[i] = ConvertSampleToOutput<T>(tone);
+ u->output_frames.push_back(tone);
+ }
+
+ u->position += nframes;
+
+ return nframes;
+}
+
+void state_cb_loop(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ if (stream == NULL)
+ return;
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n"); break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n"); break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n"); break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+
+ return;
+}
+
+void run_loopback_duplex_test(bool is_float)
+{
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb loopback example: duplex stream");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+ output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = 1;
+ output_params.layout = CUBEB_LAYOUT_MONO;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ /* setup a duplex stream with loopback */
+ r = cubeb_stream_init(ctx, &stream, "Cubeb loopback",
+ NULL, &input_params, NULL, &output_params, latency_frames,
+ is_float ? data_cb_loop_duplex<float> : data_cb_loop_duplex<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(150);
+ cubeb_stream_stop(stream);
+
+ /* access after stop should not happen, but lock just in case and to appease sanitization tools */
+ std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+ std::vector<double> & output_frames = user_data->output_frames;
+ std::vector<double> & input_frames = user_data->input_frames;
+ ASSERT_EQ(output_frames.size(), input_frames.size())
+ << "#Output frames != #input frames";
+
+ size_t phase = find_phase(user_data->output_frames, user_data->input_frames, NUM_FRAMES_TO_OUTPUT);
+
+ /* extract vectors of just the relevant signal from output and input */
+ auto output_frames_signal_start = output_frames.begin();
+ auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_output_frames(output_frames_signal_start, output_frames_signal_end);
+ auto input_frames_signal_start = input_frames.begin() + phase;
+ auto input_frames_signal_end = input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_input_frames(input_frames_signal_start, input_frames_signal_end);
+
+ compare_signals(trimmed_output_frames, trimmed_input_frames);
+}
+
+TEST(cubeb, loopback_duplex)
+{
+ run_loopback_duplex_test(true);
+ run_loopback_duplex_test(false);
+}
+
+void run_loopback_separate_streams_test(bool is_float)
+{
+ cubeb * ctx;
+ cubeb_stream * input_stream;
+ cubeb_stream * output_stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb loopback example: separate streams");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+ output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = 1;
+ output_params.layout = CUBEB_LAYOUT_MONO;
+
+ std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ /* setup an input stream with loopback */
+ r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only",
+ NULL, &input_params, NULL, NULL, latency_frames,
+ is_float ? data_cb_loop_input_only<float> : data_cb_loop_input_only<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
+
+ /* setup an output stream */
+ r = cubeb_stream_init(ctx, &output_stream, "Cubeb loopback output only",
+ NULL, NULL, NULL, &output_params, latency_frames,
+ is_float ? data_cb_playback<float> : data_cb_playback<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_output_stream_at_exit(output_stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(input_stream);
+ cubeb_stream_start(output_stream);
+ delay(150);
+ cubeb_stream_stop(output_stream);
+ cubeb_stream_stop(input_stream);
+
+ /* access after stop should not happen, but lock just in case and to appease sanitization tools */
+ std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+ std::vector<double> & output_frames = user_data->output_frames;
+ std::vector<double> & input_frames = user_data->input_frames;
+ ASSERT_LE(output_frames.size(), input_frames.size())
+ << "#Output frames should be less or equal to #input frames";
+
+ size_t phase = find_phase(user_data->output_frames, user_data->input_frames, NUM_FRAMES_TO_OUTPUT);
+
+ /* extract vectors of just the relevant signal from output and input */
+ auto output_frames_signal_start = output_frames.begin();
+ auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_output_frames(output_frames_signal_start, output_frames_signal_end);
+ auto input_frames_signal_start = input_frames.begin() + phase;
+ auto input_frames_signal_end = input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_input_frames(input_frames_signal_start, input_frames_signal_end);
+
+ compare_signals(trimmed_output_frames, trimmed_input_frames);
+}
+
+TEST(cubeb, loopback_separate_streams)
+{
+ run_loopback_separate_streams_test(true);
+ run_loopback_separate_streams_test(false);
+}
+
+void run_loopback_silence_test(bool is_float)
+{
+ cubeb * ctx;
+ cubeb_stream * input_stream;
+ cubeb_stream_params input_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb loopback example: record silence");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+
+ std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+ r = cubeb_get_min_latency(ctx, &input_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ /* setup an input stream with loopback */
+ r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only",
+ NULL, &input_params, NULL, NULL, latency_frames,
+ is_float ? data_cb_loop_input_only<float> : data_cb_loop_input_only<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(input_stream);
+ delay(50);
+ cubeb_stream_stop(input_stream);
+
+ /* access after stop should not happen, but lock just in case and to appease sanitization tools */
+ std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+ std::vector<double> & input_frames = user_data->input_frames;
+
+ /* expect to have at least ~50ms of frames */
+ ASSERT_GE(input_frames.size(), SAMPLE_FREQUENCY / 20);
+ double EPISILON = 0.000001;
+ /* frames should be 0.0, but use epsilon to avoid possible issues with impls
+ that may use ~0.0 silence values. */
+ for (double frame : input_frames) {
+ ASSERT_LT(abs(frame), EPISILON);
+ }
+}
+
+TEST(cubeb, loopback_silence)
+{
+ run_loopback_silence_test(true);
+ run_loopback_silence_test(false);
+}
+
+void run_loopback_device_selection_test(bool is_float)
+{
+ cubeb * ctx;
+ cubeb_device_collection collection;
+ cubeb_stream * input_stream;
+ cubeb_stream * output_stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb loopback example: device selection, separate streams");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
+ if (r == CUBEB_ERROR_NOT_SUPPORTED) {
+ fprintf(stderr, "Device enumeration not supported"
+ " for this backend, skipping this test.\n");
+ return;
+ }
+
+ ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
+ /* get first preferred output device id */
+ std::string device_id;
+ for (size_t i = 0; i < collection.count; i++) {
+ if (collection.device[i].preferred) {
+ device_id = collection.device[i].device_id;
+ break;
+ }
+ }
+ cubeb_device_collection_destroy(ctx, &collection);
+ if (device_id.empty()) {
+ fprintf(stderr, "Could not find preferred device, aborting test.\n");
+ return;
+ }
+
+ input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+ output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = 1;
+ output_params.layout = CUBEB_LAYOUT_MONO;
+
+ std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ /* setup an input stream with loopback */
+ r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only",
+ device_id.c_str(), &input_params, NULL, NULL, latency_frames,
+ is_float ? data_cb_loop_input_only<float> : data_cb_loop_input_only<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
+
+ /* setup an output stream */
+ r = cubeb_stream_init(ctx, &output_stream, "Cubeb loopback output only",
+ NULL, NULL, device_id.c_str(), &output_params, latency_frames,
+ is_float ? data_cb_playback<float> : data_cb_playback<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_output_stream_at_exit(output_stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(input_stream);
+ cubeb_stream_start(output_stream);
+ delay(150);
+ cubeb_stream_stop(output_stream);
+ cubeb_stream_stop(input_stream);
+
+ /* access after stop should not happen, but lock just in case and to appease sanitization tools */
+ std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+ std::vector<double> & output_frames = user_data->output_frames;
+ std::vector<double> & input_frames = user_data->input_frames;
+ ASSERT_LE(output_frames.size(), input_frames.size())
+ << "#Output frames should be less or equal to #input frames";
+
+ size_t phase = find_phase(user_data->output_frames, user_data->input_frames, NUM_FRAMES_TO_OUTPUT);
+
+ /* extract vectors of just the relevant signal from output and input */
+ auto output_frames_signal_start = output_frames.begin();
+ auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_output_frames(output_frames_signal_start, output_frames_signal_end);
+ auto input_frames_signal_start = input_frames.begin() + phase;
+ auto input_frames_signal_end = input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_input_frames(input_frames_signal_start, input_frames_signal_end);
+
+ compare_signals(trimmed_output_frames, trimmed_input_frames);
+}
+
+TEST(cubeb, loopback_device_selection)
+{
+ run_loopback_device_selection_test(true);
+ run_loopback_device_selection_test(false);
+}
--- a/media/libcubeb/gtest/test_mixer.cpp
+++ b/media/libcubeb/gtest/test_mixer.cpp
@@ -109,27 +109,29 @@ downmix_test(float const * data, cubeb_c
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
+ in_layout,
+ CUBEB_STREAM_PREF_NONE
};
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
+ out_layout,
+ CUBEB_STREAM_PREF_NONE
};
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);
--- a/media/libcubeb/gtest/test_overload_callback.cpp
+++ b/media/libcubeb/gtest/test_overload_callback.cpp
@@ -63,16 +63,17 @@ TEST(cubeb, overload_callback)
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
output_params.format = STREAM_FORMAT;
output_params.rate = 48000;
output_params.channels = 2;
output_params.layout = CUBEB_LAYOUT_STEREO;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
ASSERT_EQ(r, CUBEB_OK);
r = cubeb_stream_init(ctx, &stream, "Cubeb",
NULL, NULL, NULL, &output_params,
latency_frames, data_cb, state_cb, NULL);
ASSERT_EQ(r, CUBEB_OK);
--- a/media/libcubeb/gtest/test_record.cpp
+++ b/media/libcubeb/gtest/test_record.cpp
@@ -89,16 +89,17 @@ TEST(cubeb, record)
if (!has_available_input_device(ctx)) {
return;
}
params.format = STREAM_FORMAT;
params.rate = SAMPLE_FREQUENCY;
params.channels = 1;
params.layout = CUBEB_LAYOUT_UNDEFINED;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "Cubeb record (mono)", NULL, ¶ms, NULL, nullptr,
4096, data_cb_record, state_cb_record, &stream_state);
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
cleanup_stream_at_exit(stream, cubeb_stream_destroy);
--- a/media/libcubeb/gtest/test_resampler.cpp
+++ b/media/libcubeb/gtest/test_resampler.cpp
@@ -328,16 +328,17 @@ void test_resampler_duplex(uint32_t inpu
osc_state state;
input_params.format = output_params.format = cubeb_format<T>();
state.input_channels = input_params.channels = input_channels;
state.output_channels = output_params.channels = output_channels;
input_params.rate = input_rate;
state.output_rate = output_params.rate = output_rate;
state.target_rate = target_rate;
+ input_params.prefs = output_params.prefs = CUBEB_STREAM_PREF_NONE;
long got;
cubeb_resampler * resampler =
cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params, target_rate,
data_cb_resampler, (void*)&state, CUBEB_RESAMPLER_QUALITY_VOIP);
long latency = cubeb_resampler_latency(resampler);
--- a/media/libcubeb/gtest/test_sanity.cpp
+++ b/media/libcubeb/gtest/test_sanity.cpp
@@ -120,16 +120,18 @@ TEST(cubeb, context_variables)
r = common_init(&ctx, "test_context_variables");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx, nullptr);
params.channels = STREAM_CHANNELS;
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
r = cubeb_get_min_latency(ctx, ¶ms, &value);
ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
if (r == CUBEB_OK) {
ASSERT_TRUE(value > 0);
}
r = cubeb_get_preferred_sample_rate(ctx, &value);
ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
@@ -155,16 +157,17 @@ TEST(cubeb, init_destroy_stream)
r = common_init(&ctx, "test_sanity");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(stream, nullptr);
cubeb_stream_destroy(stream);
cubeb_destroy(ctx);
@@ -181,16 +184,17 @@ TEST(cubeb, init_destroy_multiple_stream
r = common_init(&ctx, "test_sanity");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
r = cubeb_stream_init(ctx, &stream[i], "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(stream[i], nullptr);
}
@@ -211,16 +215,17 @@ TEST(cubeb, configure_stream)
r = common_init(&ctx, "test_sanity");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = 2; // panning
params.layout = CUBEB_LAYOUT_STEREO;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(stream, nullptr);
r = cubeb_stream_set_volume(stream, 1.0f);
ASSERT_TRUE(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED);
@@ -242,16 +247,17 @@ TEST(cubeb, configure_stream_undefined_l
r = common_init(&ctx, "test_sanity");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = 2; // panning
params.layout = CUBEB_LAYOUT_UNDEFINED;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(stream, nullptr);
r = cubeb_stream_start(stream);
ASSERT_EQ(r, CUBEB_OK);
@@ -277,16 +283,17 @@ test_init_start_stop_destroy_multiple_st
r = common_init(&ctx, "test_sanity");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
r = cubeb_stream_init(ctx, &stream[i], "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(stream[i], nullptr);
if (early) {
r = cubeb_stream_start(stream[i]);
@@ -361,16 +368,17 @@ TEST(cubeb, init_destroy_multiple_contex
* try to limit the number of streams we create in this test. */
if (is_windows_7())
return;
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
r = common_init(&ctx[i], "test_sanity");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx[i], nullptr);
for (j = 0; j < streams_per_ctx; ++j) {
r = cubeb_stream_init(ctx[i], &stream[i * streams_per_ctx + j], "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY,
@@ -399,16 +407,17 @@ TEST(cubeb, basic_stream_operations)
r = common_init(&ctx, "test_sanity");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(stream, nullptr);
/* position and volume before stream has started */
r = cubeb_stream_get_position(stream, &position);
@@ -447,16 +456,17 @@ TEST(cubeb, stream_position)
r = common_init(&ctx, "test_sanity");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(stream, nullptr);
/* stream position should not advance before starting playback */
r = cubeb_stream_get_position(stream, &position);
@@ -580,16 +590,17 @@ TEST(cubeb, drain)
r = common_init(&ctx, "test_sanity");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY,
test_drain_data_callback, test_drain_state_callback, &dummy);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(stream, nullptr);
r = cubeb_stream_start(stream);
ASSERT_EQ(r, CUBEB_OK);
--- a/media/libcubeb/gtest/test_tone.cpp
+++ b/media/libcubeb/gtest/test_tone.cpp
@@ -90,16 +90,17 @@ TEST(cubeb, tone)
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
params.format = STREAM_FORMAT;
params.rate = SAMPLE_FREQUENCY;
params.channels = 1;
params.layout = CUBEB_LAYOUT_MONO;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
std::unique_ptr<cb_user_data> user_data(new cb_user_data());
ASSERT_TRUE(!!user_data) << "Error allocating user data";
user_data->position = 0;
r = cubeb_stream_init(ctx, &stream, "Cubeb tone (mono)", NULL, NULL, NULL, ¶ms,
4096, data_cb_tone, state_cb_tone, user_data.get());
--- a/media/libcubeb/include/cubeb.h
+++ b/media/libcubeb/include/cubeb.h
@@ -48,26 +48,28 @@ extern "C" {
fprintf(stderr, "Could not get preferred sample-rate");
return rv;
}
cubeb_stream_params output_params;
output_params.format = CUBEB_SAMPLE_FLOAT32NE;
output_params.rate = rate;
output_params.channels = 2;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
cubeb_stream_params input_params;
input_params.format = CUBEB_SAMPLE_FLOAT32NE;
input_params.rate = rate;
input_params.channels = 1;
+ input_params.prefs = CUBEB_STREAM_PREF_NONE;
cubeb_stream * stm;
rv = cubeb_stream_init(app_ctx, &stm, "Example Stream 1",
- NULL, input_params,
- NULL, output_params,
+ NULL, &input_params,
+ NULL, &output_params,
latency_frames,
data_cb, state_cb,
NULL);
if (rv != CUBEB_OK) {
fprintf(stderr, "Could not open the stream");
return rv;
}
@@ -208,23 +210,33 @@ typedef enum {
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;
+/** Miscellaneous stream preferences. */
+typedef enum {
+ CUBEB_STREAM_PREF_NONE = 0x00, /**< No stream preferences are requested. */
+ CUBEB_STREAM_PREF_LOOPBACK = 0x01 /**< Request a loopback stream. Should be
+ specified on the input params and an
+ output device to loopback from should
+ be passed in place of an input device. */
+} cubeb_stream_prefs;
+
/** Stream format initialization parameters. */
typedef struct {
cubeb_sample_format format; /**< Requested sample format. One of
#cubeb_sample_format. */
uint32_t rate; /**< Requested sample rate. Valid range is [1000, 192000]. */
uint32_t channels; /**< Requested channel count. Valid range is [1, 8]. */
cubeb_channel_layout layout; /**< Requested channel layout. This must be consistent with the provided channels. */
+ cubeb_stream_prefs prefs; /**< Requested preferences. */
} cubeb_stream_params;
/** Audio device description */
typedef struct {
char * output_name; /**< The name of the output device */
char * input_name; /**< The name of the input device */
} cubeb_device;
--- a/media/libcubeb/src/cubeb_alsa.c
+++ b/media/libcubeb/src/cubeb_alsa.c
@@ -885,16 +885,20 @@ alsa_stream_init_single(cubeb * ctx, cub
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);
*stream = NULL;
+ if (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
switch (stream_params->format) {
case CUBEB_SAMPLE_S16LE:
format = SND_PCM_FORMAT_S16_LE;
break;
case CUBEB_SAMPLE_S16BE:
format = SND_PCM_FORMAT_S16_BE;
break;
case CUBEB_SAMPLE_FLOAT32LE:
--- a/media/libcubeb/src/cubeb_audiounit.cpp
+++ b/media/libcubeb/src/cubeb_audiounit.cpp
@@ -130,18 +130,18 @@ struct cubeb_stream {
explicit cubeb_stream(cubeb * context);
cubeb * context;
cubeb_data_callback data_callback = nullptr;
cubeb_state_callback state_callback = nullptr;
cubeb_device_changed_callback device_changed_callback = nullptr;
owned_critical_section device_changed_callback_lock;
/* Stream creation parameters */
- 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 };
+ cubeb_stream_params input_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE };
+ cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE };
device_info input_device;
device_info output_device;
/* User pointer of data_callback */
void * user_ptr = nullptr;
/* Format descriptions */
AudioStreamBasicDescription input_desc;
AudioStreamBasicDescription output_desc;
/* I/O AudioUnits */
@@ -2355,16 +2355,22 @@ audiounit_configure_output(cubeb_stream
return CUBEB_OK;
}
static int
audiounit_setup_stream(cubeb_stream * stm)
{
stm->mutex.assert_current_thread_owns();
+ if ((stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) ||
+ (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK)) {
+ LOG("(%p) Loopback not supported for audiounit.", stm);
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
int r = 0;
device_info in_dev_info = stm->input_device;
device_info out_dev_info = stm->output_device;
if (has_input(stm) && has_output(stm) &&
stm->input_device.id != stm->output_device.id) {
r = audiounit_create_aggregate_device(stm);
--- a/media/libcubeb/src/cubeb_jack.cpp
+++ b/media/libcubeb/src/cubeb_jack.cpp
@@ -738,16 +738,22 @@ cbjack_stream_init(cubeb * context, cube
input_stream_params->format != CUBEB_SAMPLE_S16NE)
) {
return CUBEB_ERROR_INVALID_FORMAT;
}
if (input_device || output_device)
return CUBEB_ERROR_NOT_SUPPORTED;
+ // Loopback is unsupported
+ if ((input_stream_params && (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK)) ||
+ (output_stream_params && (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
*stream = NULL;
// Find a free stream.
pthread_mutex_lock(&context->mutex);
cubeb_stream * stm = context_alloc_stream(context, stream_name);
// No free stream?
if (stm == NULL) {
--- a/media/libcubeb/src/cubeb_opensl.c
+++ b/media/libcubeb/src/cubeb_opensl.c
@@ -1286,16 +1286,21 @@ opensl_configure_playback(cubeb_stream *
static int
opensl_validate_stream_param(cubeb_stream_params * stream_params)
{
if ((stream_params &&
(stream_params->channels < 1 || stream_params->channels > 32))) {
return CUBEB_ERROR_INVALID_FORMAT;
}
+ if ((stream_params &&
+ (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) {
+ LOG("Loopback is not supported");
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
return CUBEB_OK;
}
static int
opensl_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,
--- a/media/libcubeb/src/cubeb_pulse.c
+++ b/media/libcubeb/src/cubeb_pulse.c
@@ -802,16 +802,19 @@ create_pa_stream(cubeb_stream * stm,
cubeb_stream_params * stream_params,
char const * stream_name)
{
assert(stm && stream_params);
assert(&stm->input_stream == pa_stm || (&stm->output_stream == pa_stm &&
(stream_params->layout == CUBEB_LAYOUT_UNDEFINED ||
(stream_params->layout != CUBEB_LAYOUT_UNDEFINED &&
CUBEB_CHANNEL_LAYOUT_MAPS[stream_params->layout].channels == stream_params->channels))));
+ if (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
*pa_stm = NULL;
pa_sample_spec ss;
ss.format = to_pulse_format(stream_params->format);
if (ss.format == PA_SAMPLE_INVALID)
return CUBEB_ERROR_INVALID_FORMAT;
ss.rate = stream_params->rate;
ss.channels = stream_params->channels;
--- a/media/libcubeb/src/cubeb_sndio.c
+++ b/media/libcubeb/src/cubeb_sndio.c
@@ -274,21 +274,29 @@ sndio_stream_init(cubeb * context,
DPR("sndio_stream_init(%s)\n", stream_name);
s = malloc(sizeof(cubeb_stream));
if (s == NULL)
return CUBEB_ERROR;
memset(s, 0, sizeof(cubeb_stream));
s->mode = 0;
if (input_stream_params) {
+ if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ DPR("sndio_stream_init(), loopback not supported\n");
+ goto err;
+ }
s->mode |= SIO_REC;
format = input_stream_params->format;
rate = input_stream_params->rate;
}
if (output_stream_params) {
+ if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ DPR("sndio_stream_init(), loopback not supported\n");
+ goto err;
+ }
s->mode |= SIO_PLAY;
format = output_stream_params->format;
rate = output_stream_params->rate;
}
if (s->mode == 0) {
DPR("sndio_stream_init(), neither playing nor recording\n");
goto err;
}
--- a/media/libcubeb/src/cubeb_wasapi.cpp
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
@@ -188,30 +188,34 @@ class wasapi_endpoint_notification_clien
*/
typedef bool (*wasapi_refill_callback)(cubeb_stream * stm);
struct cubeb_stream {
cubeb * context = nullptr;
/* 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_LAYOUT_UNDEFINED };
- cubeb_stream_params output_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED };
+ cubeb_stream_params input_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE };
+ cubeb_stream_params output_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE };
/* 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_LAYOUT_UNDEFINED };
- cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED };
+ cubeb_stream_params input_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE };
+ cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE };
/* 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;
/* The latency initially requested for this stream, in frames. */
unsigned latency = 0;
cubeb_state_callback state_callback = nullptr;
cubeb_data_callback data_callback = nullptr;
wasapi_refill_callback refill_callback = nullptr;
+ /* True when a loopback device is requested with no output device. In this
+ case a dummy output device is opened to drive the loopback, but should not
+ be exposed. */
+ bool has_dummy_output = false;
void * user_ptr = nullptr;
/* Lifetime considerations:
- client, render_client, audio_clock and audio_stream_volume are interface
pointer to the IAudioClient.
- The lifetime for device_enumerator and notification_client, resampler,
mix_buffer are the same as the cubeb_stream instance. */
/* Main handle on the WASAPI stream. */
@@ -559,19 +563,19 @@ frames_to_bytes_before_mix(cubeb_stream
* converting it to rate and channel layout specified at initialization.
* It then calls the data callback, via the resampler. */
long
refill(cubeb_stream * stm, void * input_buffer, long input_frames_count,
void * output_buffer, long output_frames_needed)
{
XASSERT(!stm->draining);
/* If we need to upmix after resampling, resample into the mix buffer to
- avoid a copy. */
+ avoid a copy. Avoid exposing output if it is a dummy stream. */
void * dest = nullptr;
- if (has_output(stm)) {
+ if (has_output(stm) && !stm->has_dummy_output) {
if (cubeb_should_mix(&stm->output_stream_params, &stm->output_mix_params)) {
dest = stm->mix_buffer.data();
} else {
dest = output_buffer;
}
}
long out_frames = cubeb_resampler_fill(stm->resampler.get(),
@@ -590,79 +594,86 @@ refill(cubeb_stream * stm, void * input_
/* Go in draining mode if we got fewer frames than requested. */
if (out_frames < output_frames_needed) {
LOG("start draining.");
stm->draining = true;
}
/* If this is not true, there will be glitches.
It is alright to have produced less frames if we are draining, though. */
- XASSERT(out_frames == output_frames_needed || stm->draining || !has_output(stm));
+ XASSERT(out_frames == output_frames_needed || stm->draining || !has_output(stm) || stm->has_dummy_output);
- if (has_output(stm) && cubeb_should_mix(&stm->output_stream_params, &stm->output_mix_params)) {
+ // We don't bother mixing dummy output as it will be silenced, otherwise mix output if needed
+ if (!stm->has_dummy_output && has_output(stm) && cubeb_should_mix(&stm->output_stream_params, &stm->output_mix_params)) {
XASSERT(dest == stm->mix_buffer.data());
unsigned long dest_len = out_frames * stm->output_stream_params.channels;
XASSERT(dest_len <= stm->mix_buffer.size() / stm->bytes_per_sample);
unsigned long output_buffer_len = out_frames * stm->output_mix_params.channels;
cubeb_mixer_mix(stm->mixer.get(), out_frames,
dest, dest_len, output_buffer, output_buffer_len,
&stm->output_stream_params, &stm->output_mix_params);
}
return out_frames;
}
/* This helper grabs all the frames available from a capture client, put them in
* linear_input_buffer. linear_input_buffer should be cleared before the
- * callback exits. */
+ * callback exits. This helper does not work with exclusive mode streams. */
bool get_input_buffer(cubeb_stream * stm)
{
- HRESULT hr;
- UINT32 padding_in;
-
XASSERT(has_input(stm));
- hr = stm->input_client->GetCurrentPadding(&padding_in);
- if (FAILED(hr)) {
- LOG("Failed to get padding");
- return false;
- }
- XASSERT(padding_in <= stm->input_buffer_frame_count);
- UINT32 total_available_input = padding_in;
-
+ HRESULT hr;
BYTE * input_packet = NULL;
DWORD flags;
UINT64 dev_pos;
UINT32 next;
/* Get input packets until we have captured enough frames, and put them in a
* contiguous buffer. */
uint32_t offset = 0;
- while (offset != total_available_input) {
- hr = stm->capture_client->GetNextPacketSize(&next);
+ // If the input stream is event driven we should only ever expect to read a
+ // single packet each time. However, if we're pulling from the stream we may
+ // need to grab multiple packets worth of frames that have accumulated (so
+ // need a loop).
+ for (hr = stm->capture_client->GetNextPacketSize(&next);
+ next > 0;
+ hr = stm->capture_client->GetNextPacketSize(&next)) {
+
if (FAILED(hr)) {
LOG("cannot get next packet size: %lx", hr);
return false;
}
- /* This can happen if the capture stream has stopped. Just return in this
- * case. */
- if (!next) {
- break;
- }
UINT32 packet_size;
hr = stm->capture_client->GetBuffer(&input_packet,
&packet_size,
&flags,
&dev_pos,
NULL);
if (FAILED(hr)) {
LOG("GetBuffer failed for capture: %lx", hr);
return false;
}
XASSERT(packet_size == next);
+ // We do not explicitly handle the AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY
+ // flag. There a two primary (non exhaustive) scenarios we anticipate this
+ // flag being set in:
+ // - The first GetBuffer after Start has this flag undefined. In this
+ // case the flag may be set but is meaningless and can be ignored.
+ // - If a glitch is introduced into the input. This should not happen
+ // for event based inputs, and should be mitigated by using a dummy
+ // stream to drive input in the case of input only loopback. Without
+ // a dummy output, input only loopback would glitch on silence. However,
+ // the dummy input should push silence to the loopback and prevent
+ // discontinuities. See https://blogs.msdn.microsoft.com/matthew_van_eerde/2008/12/16/sample-wasapi-loopback-capture-record-what-you-hear/
+ // As the first scenario can be ignored, and we anticipate the second
+ // scenario is mitigated, we ignore the flag.
+ // For more info: https://msdn.microsoft.com/en-us/library/windows/desktop/dd370859(v=vs.85).aspx,
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd371458(v=vs.85).aspx
if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
LOG("insert silence: ps=%u", packet_size);
stm->linear_input_buffer->push_silence(packet_size * stm->input_stream_params.channels);
} else {
if (cubeb_should_mix(&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);
@@ -682,18 +693,17 @@ bool get_input_buffer(cubeb_stream * stm
hr = stm->capture_client->ReleaseBuffer(packet_size);
if (FAILED(hr)) {
LOG("FAILED to release intput buffer");
return false;
}
offset += packet_size;
}
- XASSERT(stm->linear_input_buffer->length() >= total_available_input &&
- offset == total_available_input);
+ XASSERT(stm->linear_input_buffer->length() >= offset);
return true;
}
/* Get an output buffer from the render_client. It has to be released before
* exiting the callback. */
bool get_output_buffer(cubeb_stream * stm, void *& buffer, size_t & frame_count)
{
@@ -769,28 +779,46 @@ refill_callback_duplex(cubeb_stream * st
return true;
}
/* Wait for draining is not important on duplex. */
if (stm->draining) {
return false;
}
- ALOGV("Duplex callback: input frames: %Iu, output frames: %Iu",
- input_frames, output_frames);
+ if (stm->has_dummy_output) {
+ ALOGV("Duplex callback (dummy output): input frames: %Iu, output frames: %Iu",
+ input_frames, output_frames);
- refill(stm,
- stm->linear_input_buffer->data(),
- input_frames,
- output_buffer,
- output_frames);
+ // We don't want to expose the dummy output to the callback so don't pass
+ // the output buffer (it will be released later with silence in it)
+ refill(stm,
+ stm->linear_input_buffer->data(),
+ input_frames,
+ nullptr,
+ 0);
+ } else {
+ ALOGV("Duplex callback: input frames: %Iu, output frames: %Iu",
+ input_frames, output_frames);
+
+ refill(stm,
+ stm->linear_input_buffer->data(),
+ input_frames,
+ output_buffer,
+ output_frames);
+ }
stm->linear_input_buffer->clear();
- hr = stm->render_client->ReleaseBuffer(output_frames, 0);
+ if (stm->has_dummy_output) {
+ // If output is a dummy output, make sure it's silent
+ hr = stm->render_client->ReleaseBuffer(output_frames, AUDCLNT_BUFFERFLAGS_SILENT);
+ } else {
+ hr = stm->render_client->ReleaseBuffer(output_frames, 0);
+ }
if (FAILED(hr)) {
LOG("failed to release buffer: %lx", hr);
return false;
}
return true;
}
bool
@@ -1512,32 +1540,44 @@ int setup_wasapi_stream_one_side(cubeb_s
com_ptr<IAudioClient> & audio_client,
uint32_t * buffer_frame_count,
HANDLE & event,
T & render_or_capture_client,
cubeb_stream_params * mix_params)
{
com_ptr<IMMDevice> device;
HRESULT hr;
+ bool is_loopback = stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK;
+ if (is_loopback && direction != eCapture) {
+ LOG("Loopback pref can only be used with capture streams!\n");
+ return CUBEB_ERROR;
+ }
stm->stream_reset_lock.assert_current_thread_owns();
bool try_again = false;
// This loops until we find a device that works, or we've exhausted all
// possibilities.
do {
if (devid) {
hr = get_endpoint(device, devid);
if (FAILED(hr)) {
LOG("Could not get %s endpoint, error: %lx\n", DIRECTION_NAME, hr);
return CUBEB_ERROR;
}
} else {
- hr = get_default_endpoint(device, direction);
+ // If caller has requested loopback but not specified a device, look for
+ // the default render device. Otherwise look for the default device
+ // appropriate to the direction.
+ hr = get_default_endpoint(device, is_loopback ? eRender : direction);
if (FAILED(hr)) {
- LOG("Could not get default %s endpoint, error: %lx\n", DIRECTION_NAME, hr);
+ if (is_loopback) {
+ LOG("Could not get default render endpoint for loopback, error: %lx\n", hr);
+ } else {
+ LOG("Could not get default %s endpoint, error: %lx\n", DIRECTION_NAME, hr);
+ }
return CUBEB_ERROR;
}
}
/* Get a client. We will get all other interfaces we need from
* this pointer. */
hr = device->Activate(__uuidof(IAudioClient),
CLSCTX_INPROC_SERVER,
@@ -1598,19 +1638,28 @@ int setup_wasapi_stream_one_side(cubeb_s
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,
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);
+ DWORD flags = AUDCLNT_STREAMFLAGS_NOPERSIST;
+
+ // Check if a loopback device should be requested. Note that event callbacks
+ // do not work with loopback devices, so only request these if not looping.
+ if (is_loopback) {
+ flags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
+ } else {
+ flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
+ }
+
hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED,
- AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
- AUDCLNT_STREAMFLAGS_NOPERSIST,
+ flags,
frames_to_hns(stm, stm->latency),
0,
mix_format.get(),
NULL);
if (FAILED(hr)) {
LOG("Unable to initialize audio client for %s: %lx.", DIRECTION_NAME, hr);
return CUBEB_ERROR;
}
@@ -1621,21 +1670,24 @@ int setup_wasapi_stream_one_side(cubeb_s
" for %s %lx.", DIRECTION_NAME, hr);
return CUBEB_ERROR;
}
// Input is up/down mixed when depacketized in get_input_buffer.
if (has_output(stm) && cubeb_should_mix(stream_params, mix_params)) {
stm->mix_buffer.resize(frames_to_bytes_before_mix(stm, *buffer_frame_count));
}
- hr = audio_client->SetEventHandle(event);
- if (FAILED(hr)) {
- LOG("Could set the event handle for the %s client %lx.",
- DIRECTION_NAME, hr);
- return CUBEB_ERROR;
+ // Events are used if not looping back
+ if (!is_loopback) {
+ hr = audio_client->SetEventHandle(event);
+ if (FAILED(hr)) {
+ LOG("Could set the event handle for the %s client %lx.",
+ DIRECTION_NAME, hr);
+ return CUBEB_ERROR;
+ }
}
hr = audio_client->GetService(riid, render_or_capture_client.receive_vpp());
if (FAILED(hr)) {
LOG("Could not get the %s client %lx.", DIRECTION_NAME, hr);
return CUBEB_ERROR;
}
@@ -1686,16 +1738,37 @@ int setup_wasapi_stream(cubeb_stream * s
#else
const int silent_buffer_count = 6;
#endif
stm->linear_input_buffer->push_silence(stm->input_buffer_frame_count *
stm->input_stream_params.channels *
silent_buffer_count);
}
+ // If we don't have an output device but are requesting a loopback device,
+ // we attempt to open that same device in output mode in order to drive the
+ // loopback via the output events.
+ stm->has_dummy_output = false;
+ if (!has_output(stm) && stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ stm->output_stream_params.rate = stm->input_stream_params.rate;
+ stm->output_stream_params.channels = stm->input_stream_params.channels;
+ stm->output_stream_params.layout = stm->input_stream_params.layout;
+ if (stm->input_device) {
+ size_t len = wcslen(stm->input_device.get());
+ std::unique_ptr<wchar_t[]> tmp(new wchar_t[len + 1]);
+ if (wcsncpy_s(tmp.get(), len + 1, stm->input_device.get(), len) != 0) {
+ LOG("Failed to copy device identifier while copying input stream"
+ " configuration to output stream configuration to drive loopback.");
+ return CUBEB_ERROR;
+ }
+ stm->output_device = move(tmp);
+ }
+ stm->has_dummy_output = true;
+ }
+
if (has_output(stm)) {
LOG("(%p) Setup render: device=%p", stm, stm->output_device.get());
rv = setup_wasapi_stream_one_side(stm,
&stm->output_stream_params,
stm->output_device.get(),
eRender,
__uuidof(IAudioRenderClient),
stm->output_client,
--- a/media/libcubeb/src/cubeb_winmm.c
+++ b/media/libcubeb/src/cubeb_winmm.c
@@ -409,16 +409,21 @@ winmm_stream_init(cubeb * context, cubeb
return CUBEB_ERROR_NOT_SUPPORTED;
}
if (input_device || output_device) {
/* Device selection not yet implemented. */
return CUBEB_ERROR_DEVICE_UNAVAILABLE;
}
+ if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ /* Loopback is not supported */
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
*stream = NULL;
memset(&wfx, 0, sizeof(wfx));
if (output_stream_params->channels > 2) {
wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
wfx.Format.cbSize = sizeof(wfx) - sizeof(wfx.Format);
} else {
wfx.Format.wFormatTag = WAVE_FORMAT_PCM;