Bug 1432869 - Update cubeb from upstream to 4c18a84. r=achronop

This brings across the WASAPI loopback stream functionality recently
implemented in cubeb.

The README_MOZILLA is reverted to its previous format. Previously a couple of
patches were highlighted as needing application, however these have now been
applied upstream.

MozReview-Commit-ID: CV6FHWXZBK0

--HG--
extra : rebase_source : 2dff33fcff6960d1d5b7d8dfaf35023b9c44d16e
This commit is contained in:
Bryce Van Dyk 2017-12-05 13:11:01 -05:00
Родитель be0ce1cbb2
Коммит 1691d18053
21 изменённых файлов: 776 добавлений и 60 удалений

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

@ -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)

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

@ -123,6 +123,7 @@ int run_test(int num_channels, layout_info layout, int sampling_rate, int is_flo
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);
@ -171,6 +172,7 @@ int run_panning_volume_test(int is_float)
params.rate = 44100;
params.channels = 2;
params.layout = CUBEB_LAYOUT_STEREO;
params.prefs = CUBEB_STREAM_PREF_NONE;
synth_state synth(params.channels, params.rate);

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

@ -190,6 +190,7 @@ TEST(cubeb, enumerate_devices)
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,

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

@ -101,10 +101,12 @@ TEST(cubeb, duplex)
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";

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

@ -34,7 +34,8 @@ TEST(cubeb, latency)
CUBEB_SAMPLE_FLOAT32NE,
preferred_rate,
max_channels,
CUBEB_LAYOUT_UNDEFINED
CUBEB_LAYOUT_UNDEFINED,
CUBEB_STREAM_PREF_NONE
};
r = cubeb_get_min_latency(ctx, &params, &latency_frames);
ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);

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

@ -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);
}

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

@ -114,7 +114,8 @@ downmix_test(float const * data, cubeb_channel_layout in_layout, cubeb_channel_l
STREAM_FORMAT,
STREAM_FREQUENCY,
layout_infos[in_layout].channels,
in_layout
in_layout,
CUBEB_STREAM_PREF_NONE
};
cubeb_stream_params out_params = {
@ -124,7 +125,8 @@ downmix_test(float const * data, cubeb_channel_layout in_layout, cubeb_channel_l
// 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)) {

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

@ -68,6 +68,7 @@ TEST(cubeb, overload_callback)
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);

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

@ -94,6 +94,7 @@ TEST(cubeb, record)
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, &params, NULL, nullptr,
4096, data_cb_record, state_cb_record, &stream_state);

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

@ -333,6 +333,7 @@ void test_resampler_duplex(uint32_t input_channels, uint32_t 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 =

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

@ -125,6 +125,8 @@ TEST(cubeb, context_variables)
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.layout = STREAM_LAYOUT;
params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_get_min_latency(ctx, &params, &value);
ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
if (r == CUBEB_OK) {
@ -160,6 +162,7 @@ TEST(cubeb, init_destroy_stream)
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, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
@ -186,6 +189,7 @@ TEST(cubeb, init_destroy_multiple_streams)
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, &params, STREAM_LATENCY,
@ -216,6 +220,7 @@ TEST(cubeb, configure_stream)
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, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
@ -247,6 +252,7 @@ TEST(cubeb, configure_stream_undefined_layout)
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, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
@ -282,6 +288,7 @@ test_init_start_stop_destroy_multiple_streams(int early, int delay_ms)
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, &params, STREAM_LATENCY,
@ -366,6 +373,7 @@ TEST(cubeb, init_destroy_multiple_contexts_and_streams)
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");
@ -404,6 +412,7 @@ TEST(cubeb, basic_stream_operations)
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, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
@ -452,6 +461,7 @@ TEST(cubeb, stream_position)
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, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
@ -585,6 +595,7 @@ TEST(cubeb, drain)
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, &params, STREAM_LATENCY,
test_drain_data_callback, test_drain_state_callback, &dummy);

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

@ -95,6 +95,7 @@ TEST(cubeb, tone)
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";

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

@ -53,16 +53,18 @@ extern "C" {
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);
@ -213,6 +215,15 @@ typedef enum {
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
@ -220,6 +231,7 @@ typedef struct {
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 */

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

@ -890,6 +890,10 @@ alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * 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;

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

@ -135,8 +135,8 @@ struct cubeb_stream {
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 */
@ -2360,6 +2360,12 @@ 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;

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

@ -743,6 +743,12 @@ cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_
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.

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

@ -1291,6 +1291,11 @@ opensl_validate_stream_param(cubeb_stream_params * 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;
}

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

@ -807,6 +807,9 @@ create_pa_stream(cubeb_stream * 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);

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

@ -279,11 +279,19 @@ sndio_stream_init(cubeb * context,
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;

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

@ -193,12 +193,12 @@ struct cubeb_stream {
/* Mixer pameters. We need to convert the input stream to this
samplerate/channel layout, as WASAPI does not resample nor upmix
itself. */
cubeb_stream_params input_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_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;
@ -207,6 +207,10 @@ struct cubeb_stream {
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
@ -564,9 +568,9 @@ refill(cubeb_stream * stm, void * input_buffer, long input_frames_count,
{
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 {
@ -595,9 +599,10 @@ refill(cubeb_stream * stm, void * input_buffer, long input_frames_count,
/* 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);
@ -612,22 +617,12 @@ refill(cubeb_stream * stm, void * input_buffer, long input_frames_count,
/* 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;
@ -635,17 +630,18 @@ bool get_input_buffer(cubeb_stream * stm)
/* 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,
@ -658,6 +654,21 @@ bool get_input_buffer(cubeb_stream * stm)
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);
@ -687,8 +698,7 @@ bool get_input_buffer(cubeb_stream * stm)
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;
}
@ -774,18 +784,36 @@ refill_callback_duplex(cubeb_stream * stm)
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;
@ -1517,6 +1545,11 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
{
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;
@ -1530,9 +1563,16 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
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;
}
}
@ -1603,9 +1643,18 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
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(),
@ -1626,11 +1675,14 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
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());
@ -1691,6 +1743,27 @@ int setup_wasapi_stream(cubeb_stream * stm)
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,

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

@ -414,6 +414,11 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n
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));