зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
be0ce1cbb2
Коммит
1691d18053
|
@ -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, ¶ms, &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, ¶ms, 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, ¶ms, &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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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));
|
||||
|
|
Загрузка…
Ссылка в новой задаче