зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1337328 - Uplift cubeb to revision 927877. r=achronop
MozReview-Commit-ID: 1VxCdWZQtR4 --HG-- extra : rebase_source : 5430bd5c97d4cbd3854e0089bac324b5a4b134f7
This commit is contained in:
Родитель
cf1f2ae0a3
Коммит
42b97bbf05
|
@ -5,4 +5,4 @@ Makefile.in build files for the Mozilla build system.
|
|||
|
||||
The cubeb git repository is: git://github.com/kinetiknz/cubeb.git
|
||||
|
||||
The git commit ID used was d96e35f02dbb9a093e5bfdff4f2948b7a6e9d3f9.
|
||||
The git commit ID used was 927877c3204d6b8467b6dc782ca2aa740d240d41.
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <cstdarg>
|
||||
#include "cubeb/cubeb.h"
|
||||
|
||||
template<typename T, size_t N>
|
||||
constexpr size_t
|
||||
ARRAY_LENGTH(T(&)[N])
|
||||
|
@ -96,4 +99,12 @@ int has_available_input_device(cubeb * ctx)
|
|||
return 1;
|
||||
}
|
||||
|
||||
void print_log(const char * msg, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, msg);
|
||||
vprintf(msg, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
#endif /* TEST_COMMON */
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
#include <string.h>
|
||||
#include "cubeb/cubeb.h"
|
||||
#include "common.h"
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define MAX_NUM_CHANNELS 32
|
||||
|
||||
|
@ -31,90 +34,83 @@ float get_frequency(int channel_index)
|
|||
return 220.0f * (channel_index+1);
|
||||
}
|
||||
|
||||
template<typename T> T ConvertSample(double input);
|
||||
template<> float ConvertSample(double input) { return input; }
|
||||
template<> short ConvertSample(double input) { return short(input * 32767.0f); }
|
||||
|
||||
/* store the phase of the generated waveform */
|
||||
typedef struct {
|
||||
struct synth_state {
|
||||
synth_state(int num_channels_, float sample_rate_)
|
||||
: num_channels(num_channels_),
|
||||
sample_rate(sample_rate_)
|
||||
{
|
||||
for(int i=0;i < MAX_NUM_CHANNELS;++i)
|
||||
phase[i] = 0.0f;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void run(T* audiobuffer, long nframes)
|
||||
{
|
||||
for(int c=0;c < num_channels;++c) {
|
||||
float freq = get_frequency(c);
|
||||
float phase_inc = 2.0 * M_PI * freq / sample_rate;
|
||||
for(long n=0;n < nframes;++n) {
|
||||
audiobuffer[n*num_channels+c] = ConvertSample<T>(sin(phase[c]) * VOLUME);
|
||||
phase[c] += phase_inc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int num_channels;
|
||||
float phase[MAX_NUM_CHANNELS];
|
||||
float sample_rate;
|
||||
} synth_state;
|
||||
};
|
||||
|
||||
synth_state* synth_create(int num_channels, float sample_rate)
|
||||
{
|
||||
synth_state* synth = (synth_state *) malloc(sizeof(synth_state));
|
||||
if (!synth)
|
||||
return NULL;
|
||||
for(int i=0;i < MAX_NUM_CHANNELS;++i)
|
||||
synth->phase[i] = 0.0f;
|
||||
synth->num_channels = num_channels;
|
||||
synth->sample_rate = sample_rate;
|
||||
return synth;
|
||||
}
|
||||
|
||||
void synth_destroy(synth_state* synth)
|
||||
{
|
||||
free(synth);
|
||||
}
|
||||
|
||||
void synth_run_float(synth_state* synth, float* audiobuffer, long nframes)
|
||||
{
|
||||
for(int c=0;c < synth->num_channels;++c) {
|
||||
float freq = get_frequency(c);
|
||||
float phase_inc = 2.0 * M_PI * freq / synth->sample_rate;
|
||||
for(long n=0;n < nframes;++n) {
|
||||
audiobuffer[n*synth->num_channels+c] = sin(synth->phase[c]) * VOLUME;
|
||||
synth->phase[c] += phase_inc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long data_cb_float(cubeb_stream * /*stream*/, void * user, const void * /*inputbuffer*/, void * outputbuffer, long nframes)
|
||||
template<typename T>
|
||||
long data_cb(cubeb_stream * /*stream*/, void * user, const void * /*inputbuffer*/, void * outputbuffer, long nframes)
|
||||
{
|
||||
synth_state *synth = (synth_state *)user;
|
||||
synth_run_float(synth, (float*)outputbuffer, nframes);
|
||||
synth->run((T*)outputbuffer, nframes);
|
||||
return nframes;
|
||||
}
|
||||
|
||||
void synth_run_16bit(synth_state* synth, short* audiobuffer, long nframes)
|
||||
struct CubebCleaner
|
||||
{
|
||||
for(int c=0;c < synth->num_channels;++c) {
|
||||
float freq = get_frequency(c);
|
||||
float phase_inc = 2.0 * M_PI * freq / synth->sample_rate;
|
||||
for(long n=0;n < nframes;++n) {
|
||||
audiobuffer[n*synth->num_channels+c] = sin(synth->phase[c]) * VOLUME * 32767.0f;
|
||||
synth->phase[c] += phase_inc;
|
||||
}
|
||||
}
|
||||
}
|
||||
CubebCleaner(cubeb* ctx_) : ctx(ctx_) {}
|
||||
~CubebCleaner() { cubeb_destroy(ctx); }
|
||||
cubeb* ctx;
|
||||
};
|
||||
|
||||
long data_cb_short(cubeb_stream * /*stream*/, void * user, const void * /*inputbuffer*/, void * outputbuffer, long nframes)
|
||||
struct CubebStreamCleaner
|
||||
{
|
||||
synth_state *synth = (synth_state *)user;
|
||||
synth_run_16bit(synth, (short*)outputbuffer, nframes);
|
||||
return nframes;
|
||||
}
|
||||
CubebStreamCleaner(cubeb_stream* ctx_) : ctx(ctx_) {}
|
||||
~CubebStreamCleaner() { cubeb_stream_destroy(ctx); }
|
||||
cubeb_stream* ctx;
|
||||
};
|
||||
|
||||
void state_cb_audio(cubeb_stream * /*stream*/, void * /*user*/, cubeb_state /*state*/)
|
||||
{
|
||||
}
|
||||
|
||||
/* Our android backends don't support float, only int16. */
|
||||
int supports_float32(const char* backend_id)
|
||||
int supports_float32(string backend_id)
|
||||
{
|
||||
return (strcmp(backend_id, "opensl") != 0 &&
|
||||
strcmp(backend_id, "audiotrack") != 0);
|
||||
return backend_id != "opensl"
|
||||
&& backend_id != "audiotrack";
|
||||
}
|
||||
|
||||
/* The WASAPI backend only supports float. */
|
||||
int supports_int16(const char* backend_id)
|
||||
int supports_int16(string backend_id)
|
||||
{
|
||||
return strcmp(backend_id, "wasapi") != 0;
|
||||
return backend_id != "wasapi";
|
||||
}
|
||||
|
||||
/* Some backends don't have code to deal with more than mono or stereo. */
|
||||
int supports_channel_count(const char* backend_id, int nchannels)
|
||||
int supports_channel_count(string backend_id, int nchannels)
|
||||
{
|
||||
return nchannels <= 2 ||
|
||||
(strcmp(backend_id, "opensl") != 0 && strcmp(backend_id, "audiotrack") != 0);
|
||||
(backend_id != "opensl" && backend_id != "audiotrack");
|
||||
}
|
||||
|
||||
int run_test(int num_channels, layout_info layout, int sampling_rate, int is_float)
|
||||
|
@ -122,23 +118,21 @@ int run_test(int num_channels, layout_info layout, int sampling_rate, int is_flo
|
|||
int r = CUBEB_OK;
|
||||
|
||||
cubeb *ctx = NULL;
|
||||
synth_state* synth = NULL;
|
||||
cubeb_stream *stream = NULL;
|
||||
const char * backend_id = NULL;
|
||||
|
||||
r = cubeb_init(&ctx, "Cubeb audio test: channels");
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Error initializing cubeb library\n");
|
||||
goto cleanup;
|
||||
return r;
|
||||
}
|
||||
CubebCleaner cleanup_cubeb_at_exit(ctx);
|
||||
|
||||
backend_id = cubeb_get_backend_id(ctx);
|
||||
const char * backend_id = cubeb_get_backend_id(ctx);
|
||||
|
||||
if ((is_float && !supports_float32(backend_id)) ||
|
||||
(!is_float && !supports_int16(backend_id)) ||
|
||||
!supports_channel_count(backend_id, num_channels)) {
|
||||
/* don't treat this as a test failure. */
|
||||
goto cleanup;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Testing %d channel(s), layout: %s, %d Hz, %s (%s)\n", num_channels, layout.name, sampling_rate, is_float ? "float" : "short", cubeb_get_backend_id(ctx));
|
||||
|
@ -149,28 +143,22 @@ int run_test(int num_channels, layout_info layout, int sampling_rate, int is_flo
|
|||
params.channels = num_channels;
|
||||
params.layout = layout.layout;
|
||||
|
||||
synth = synth_create(params.channels, params.rate);
|
||||
if (synth == NULL) {
|
||||
fprintf(stderr, "Out of memory\n");
|
||||
goto cleanup;
|
||||
}
|
||||
synth_state synth(params.channels, params.rate);
|
||||
|
||||
cubeb_stream *stream = NULL;
|
||||
r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, ¶ms,
|
||||
4096, is_float ? data_cb_float : data_cb_short, state_cb_audio, synth);
|
||||
4096, is_float ? &data_cb<float> : &data_cb<short>, state_cb_audio, &synth);
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
|
||||
goto cleanup;
|
||||
return r;
|
||||
}
|
||||
|
||||
CubebStreamCleaner cleanup_stream_at_exit(stream);
|
||||
|
||||
cubeb_stream_start(stream);
|
||||
delay(200);
|
||||
cubeb_stream_stop(stream);
|
||||
|
||||
cleanup:
|
||||
cubeb_stream_destroy(stream);
|
||||
cubeb_destroy(ctx);
|
||||
synth_destroy(synth);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
@ -179,21 +167,21 @@ int run_panning_volume_test(int is_float)
|
|||
int r = CUBEB_OK;
|
||||
|
||||
cubeb *ctx = NULL;
|
||||
synth_state* synth = NULL;
|
||||
cubeb_stream *stream = NULL;
|
||||
const char * backend_id = NULL;
|
||||
|
||||
r = cubeb_init(&ctx, "Cubeb audio test");
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Error initializing cubeb library\n");
|
||||
goto cleanup;
|
||||
return r;
|
||||
}
|
||||
backend_id = cubeb_get_backend_id(ctx);
|
||||
|
||||
CubebCleaner cleanup_cubeb_at_exit(ctx);
|
||||
|
||||
const char * backend_id = cubeb_get_backend_id(ctx);
|
||||
|
||||
if ((is_float && !supports_float32(backend_id)) ||
|
||||
(!is_float && !supports_int16(backend_id))) {
|
||||
/* don't treat this as a test failure. */
|
||||
goto cleanup;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
cubeb_stream_params params;
|
||||
|
@ -202,20 +190,19 @@ int run_panning_volume_test(int is_float)
|
|||
params.channels = 2;
|
||||
params.layout = CUBEB_LAYOUT_STEREO;
|
||||
|
||||
synth = synth_create(params.channels, params.rate);
|
||||
if (synth == NULL) {
|
||||
fprintf(stderr, "Out of memory\n");
|
||||
goto cleanup;
|
||||
}
|
||||
synth_state synth(params.channels, params.rate);
|
||||
|
||||
cubeb_stream *stream = NULL;
|
||||
r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, ¶ms,
|
||||
4096, is_float ? data_cb_float : data_cb_short,
|
||||
state_cb_audio, synth);
|
||||
4096, is_float ? &data_cb<float> : &data_cb<short>,
|
||||
state_cb_audio, &synth);
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
|
||||
goto cleanup;
|
||||
return r;
|
||||
}
|
||||
|
||||
CubebStreamCleaner cleanup_stream_at_exit(stream);
|
||||
|
||||
fprintf(stderr, "Testing: volume\n");
|
||||
for(int i=0;i <= 4; ++i)
|
||||
{
|
||||
|
@ -240,11 +227,6 @@ int run_panning_volume_test(int is_float)
|
|||
delay(100);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
cubeb_stream_destroy(stream);
|
||||
cubeb_destroy(ctx);
|
||||
synth_destroy(synth);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
@ -275,14 +257,14 @@ TEST(cubeb, run_channel_rate_test)
|
|||
48000,
|
||||
};
|
||||
|
||||
for(unsigned int j = 0; j < ARRAY_LENGTH(channel_values); ++j) {
|
||||
for(unsigned int i = 0; i < ARRAY_LENGTH(freq_values); ++i) {
|
||||
ASSERT_TRUE(channel_values[j] < MAX_NUM_CHANNELS);
|
||||
for(auto channels : channel_values) {
|
||||
for(auto freq : freq_values) {
|
||||
ASSERT_TRUE(channels < MAX_NUM_CHANNELS);
|
||||
fprintf(stderr, "--------------------------\n");
|
||||
for (unsigned int k = 0 ; k < ARRAY_LENGTH(layout_infos); ++k ) {
|
||||
if (layout_infos[k].channels == channel_values[j]) {
|
||||
ASSERT_EQ(run_test(channel_values[j], layout_infos[k], freq_values[i], 0), CUBEB_OK);
|
||||
ASSERT_EQ(run_test(channel_values[j], layout_infos[k], freq_values[i], 1), CUBEB_OK);
|
||||
for (auto layout : layout_infos) {
|
||||
if (layout.channels == channels) {
|
||||
ASSERT_EQ(run_test(channels, layout, freq, 0), CUBEB_OK);
|
||||
ASSERT_EQ(run_test(channels, layout, freq, 1), CUBEB_OK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,30 +18,19 @@
|
|||
#include "common.h"
|
||||
|
||||
#define SAMPLE_FREQUENCY 48000
|
||||
#if (defined(_WIN32) || defined(__WIN32__))
|
||||
#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
|
||||
#define SILENT_SAMPLE 0.0f
|
||||
#else
|
||||
#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
|
||||
#define SILENT_SAMPLE 0
|
||||
#endif
|
||||
|
||||
struct user_state_duplex
|
||||
{
|
||||
bool seen_noise;
|
||||
bool seen_audio;
|
||||
};
|
||||
|
||||
long data_cb_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
|
||||
{
|
||||
user_state_duplex * u = reinterpret_cast<user_state_duplex*>(user);
|
||||
#if (defined(_WIN32) || defined(__WIN32__))
|
||||
float *ib = (float *)inputbuffer;
|
||||
float *ob = (float *)outputbuffer;
|
||||
#else
|
||||
short *ib = (short *)inputbuffer;
|
||||
short *ob = (short *)outputbuffer;
|
||||
#endif
|
||||
bool seen_noise = false;
|
||||
bool seen_audio = true;
|
||||
|
||||
if (stream == NULL || inputbuffer == NULL || outputbuffer == NULL) {
|
||||
return CUBEB_ERROR;
|
||||
|
@ -51,14 +40,15 @@ long data_cb_duplex(cubeb_stream * stream, void * user, const void * inputbuffer
|
|||
// checking if there is noise in the process.
|
||||
long output_index = 0;
|
||||
for (long i = 0; i < nframes; i++) {
|
||||
if (ib[i] != SILENT_SAMPLE) {
|
||||
seen_noise = true;
|
||||
if (ib[i] <= -1.0 && ib[i] >= 1.0) {
|
||||
seen_audio = false;
|
||||
break;
|
||||
}
|
||||
ob[output_index] = ob[output_index + 1] = ib[i];
|
||||
output_index += 2;
|
||||
}
|
||||
|
||||
u->seen_noise |= seen_noise;
|
||||
u->seen_audio |= seen_audio;
|
||||
|
||||
return nframes;
|
||||
}
|
||||
|
@ -136,5 +126,5 @@ TEST(cubeb, duplex)
|
|||
cubeb_stream_destroy(stream);
|
||||
cubeb_destroy(ctx);
|
||||
|
||||
ASSERT_TRUE(stream_state.seen_noise);
|
||||
ASSERT_TRUE(stream_state.seen_audio);
|
||||
}
|
||||
|
|
|
@ -17,38 +17,31 @@
|
|||
#include "common.h"
|
||||
|
||||
#define SAMPLE_FREQUENCY 48000
|
||||
#if (defined(_WIN32) || defined(__WIN32__))
|
||||
#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
|
||||
#else
|
||||
#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
|
||||
#endif
|
||||
|
||||
struct user_state_record
|
||||
{
|
||||
bool seen_noise;
|
||||
bool seen_audio;
|
||||
};
|
||||
|
||||
long data_cb_record(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
|
||||
{
|
||||
user_state_record * u = reinterpret_cast<user_state_record*>(user);
|
||||
#if STREAM_FORMAT != CUBEB_SAMPLE_FLOAT32LE
|
||||
short *b = (short *)inputbuffer;
|
||||
#else
|
||||
float *b = (float *)inputbuffer;
|
||||
#endif
|
||||
|
||||
if (stream == NULL || inputbuffer == NULL || outputbuffer != NULL) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
bool seen_noise = false;
|
||||
bool seen_audio = true;
|
||||
for (long i = 0; i < nframes; i++) {
|
||||
if (b[i] != 0.0) {
|
||||
seen_noise = true;
|
||||
if (b[i] <= -1.0 && b[i] >= 1.0) {
|
||||
seen_audio = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
u->seen_noise |= seen_noise;
|
||||
u->seen_audio |= seen_audio;
|
||||
|
||||
return nframes;
|
||||
}
|
||||
|
@ -74,6 +67,9 @@ void state_cb_record(cubeb_stream * stream, void * /*user*/, cubeb_state state)
|
|||
|
||||
TEST(cubeb, record)
|
||||
{
|
||||
if (cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr /*print_log*/) != CUBEB_OK) {
|
||||
printf("Set log callback failed\n");
|
||||
}
|
||||
cubeb *ctx;
|
||||
cubeb_stream *stream;
|
||||
cubeb_stream_params params;
|
||||
|
@ -111,5 +107,10 @@ TEST(cubeb, record)
|
|||
cubeb_stream_destroy(stream);
|
||||
cubeb_destroy(ctx);
|
||||
|
||||
ASSERT_TRUE(stream_state.seen_noise);
|
||||
#ifdef __linux__
|
||||
// user callback does not arrive in Linux, silence the error
|
||||
printf("Check is disabled in Linux\n");
|
||||
#else
|
||||
ASSERT_TRUE(stream_state.seen_audio);
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#define NOMINMAX
|
||||
#endif // NOMINMAX
|
||||
#include "gtest/gtest.h"
|
||||
#include "common.h"
|
||||
#include "cubeb_resampler_internal.h"
|
||||
#include <stdio.h>
|
||||
#include <algorithm>
|
||||
|
@ -391,8 +392,11 @@ void test_resampler_duplex(uint32_t input_channels, uint32_t output_channels,
|
|||
dump("input.raw", state.input.data(), state.input.length());
|
||||
dump("output.raw", state.output.data(), state.output.length());
|
||||
|
||||
ASSERT_TRUE(array_fuzzy_equal(state.input, expected_resampled_input, epsilon<T>(input_rate/target_rate)));
|
||||
ASSERT_TRUE(array_fuzzy_equal(state.output, expected_resampled_output, epsilon<T>(output_rate/target_rate)));
|
||||
// This is disabled because the latency estimation in the resampler code is
|
||||
// slightly off so we can generate expected vectors.
|
||||
// See https://github.com/kinetiknz/cubeb/issues/93
|
||||
// ASSERT_TRUE(array_fuzzy_equal(state.input, expected_resampled_input, epsilon<T>(input_rate/target_rate)));
|
||||
// ASSERT_TRUE(array_fuzzy_equal(state.output, expected_resampled_output, epsilon<T>(output_rate/target_rate)));
|
||||
|
||||
cubeb_resampler_destroy(resampler);
|
||||
}
|
||||
|
@ -416,9 +420,6 @@ TEST(cubeb, resampler_one_way)
|
|||
}
|
||||
}
|
||||
|
||||
// This is disabled because the latency estimation in the resampler code is
|
||||
// slightly off so we can generate expected vectors.
|
||||
// See https://github.com/kinetiknz/cubeb/issues/93
|
||||
TEST(cubeb, DISABLED_resampler_duplex)
|
||||
{
|
||||
for (uint32_t input_channels = 1; input_channels <= max_channels; input_channels++) {
|
||||
|
@ -536,3 +537,214 @@ TEST(cubeb, resampler_drain)
|
|||
cubeb_resampler_destroy(resampler);
|
||||
}
|
||||
|
||||
// gtest does not support using ASSERT_EQ and friend in a function that returns
|
||||
// a value.
|
||||
void check_output(const void * input_buffer, void * output_buffer, long frame_count)
|
||||
{
|
||||
ASSERT_EQ(input_buffer, nullptr);
|
||||
ASSERT_EQ(frame_count, 256);
|
||||
ASSERT_TRUE(!!output_buffer);
|
||||
}
|
||||
|
||||
long cb_passthrough_resampler_output(cubeb_stream * /*stm*/, void * /*user_ptr*/,
|
||||
const void * input_buffer,
|
||||
void * output_buffer, long frame_count)
|
||||
{
|
||||
check_output(input_buffer, output_buffer, frame_count);
|
||||
return frame_count;
|
||||
}
|
||||
|
||||
TEST(cubeb, resampler_passthrough_output_only)
|
||||
{
|
||||
// Test that the passthrough resampler works when there is only an output stream.
|
||||
cubeb_stream_params output_params;
|
||||
|
||||
const size_t output_channels = 2;
|
||||
output_params.channels = output_channels;
|
||||
output_params.rate = 44100;
|
||||
output_params.format = CUBEB_SAMPLE_FLOAT32NE;
|
||||
int target_rate = output_params.rate;
|
||||
|
||||
cubeb_resampler * resampler =
|
||||
cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params,
|
||||
target_rate, cb_passthrough_resampler_output, nullptr,
|
||||
CUBEB_RESAMPLER_QUALITY_VOIP);
|
||||
|
||||
float output_buffer[output_channels * 256];
|
||||
|
||||
long got;
|
||||
for (uint32_t i = 0; i < 30; i++) {
|
||||
got = cubeb_resampler_fill(resampler, nullptr, nullptr, output_buffer, 256);
|
||||
ASSERT_EQ(got, 256);
|
||||
}
|
||||
}
|
||||
|
||||
// gtest does not support using ASSERT_EQ and friend in a function that returns
|
||||
// a value.
|
||||
void check_input(const void * input_buffer, void * output_buffer, long frame_count)
|
||||
{
|
||||
ASSERT_EQ(output_buffer, nullptr);
|
||||
ASSERT_EQ(frame_count, 256);
|
||||
ASSERT_TRUE(!!input_buffer);
|
||||
}
|
||||
|
||||
long cb_passthrough_resampler_input(cubeb_stream * /*stm*/, void * /*user_ptr*/,
|
||||
const void * input_buffer,
|
||||
void * output_buffer, long frame_count)
|
||||
{
|
||||
check_input(input_buffer, output_buffer, frame_count);
|
||||
return frame_count;
|
||||
}
|
||||
|
||||
TEST(cubeb, resampler_passthrough_input_only)
|
||||
{
|
||||
// Test that the passthrough resampler works when there is only an output stream.
|
||||
cubeb_stream_params input_params;
|
||||
|
||||
const size_t input_channels = 2;
|
||||
input_params.channels = input_channels;
|
||||
input_params.rate = 44100;
|
||||
input_params.format = CUBEB_SAMPLE_FLOAT32NE;
|
||||
int target_rate = input_params.rate;
|
||||
|
||||
cubeb_resampler * resampler =
|
||||
cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, nullptr,
|
||||
target_rate, cb_passthrough_resampler_input, nullptr,
|
||||
CUBEB_RESAMPLER_QUALITY_VOIP);
|
||||
|
||||
float input_buffer[input_channels * 256];
|
||||
|
||||
long got;
|
||||
for (uint32_t i = 0; i < 30; i++) {
|
||||
long int frames = 256;
|
||||
got = cubeb_resampler_fill(resampler, input_buffer, &frames, nullptr, 0);
|
||||
ASSERT_EQ(got, 256);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
long seq(T* array, int stride, long start, long count)
|
||||
{
|
||||
for(int i = 0; i < count; i++) {
|
||||
for (int j = 0; j < stride; j++) {
|
||||
array[i + j] = static_cast<T>(start + i);
|
||||
}
|
||||
}
|
||||
return start + count;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void is_seq(T * array, int stride, long count, long expected_start)
|
||||
{
|
||||
uint32_t output_index = 0;
|
||||
for (long i = 0; i < count; i++) {
|
||||
for (int j = 0; j < stride; j++) {
|
||||
ASSERT_EQ(array[output_index + j], expected_start + i);
|
||||
}
|
||||
output_index += stride;
|
||||
}
|
||||
}
|
||||
|
||||
// gtest does not support using ASSERT_EQ and friend in a function that returns
|
||||
// a value.
|
||||
template<typename T>
|
||||
void check_duplex(const T * input_buffer,
|
||||
T * output_buffer, long frame_count)
|
||||
{
|
||||
ASSERT_EQ(frame_count, 256);
|
||||
// Silence scan-build warning.
|
||||
ASSERT_TRUE(!!output_buffer); assert(output_buffer);
|
||||
ASSERT_TRUE(!!input_buffer); assert(input_buffer);
|
||||
|
||||
int output_index = 0;
|
||||
for (int i = 0; i < frame_count; i++) {
|
||||
// output is two channels, input is one channel, we upmix.
|
||||
output_buffer[output_index] = output_buffer[output_index+1] = input_buffer[i];
|
||||
output_index += 2;
|
||||
}
|
||||
}
|
||||
|
||||
long cb_passthrough_resampler_duplex(cubeb_stream * /*stm*/, void * /*user_ptr*/,
|
||||
const void * input_buffer,
|
||||
void * output_buffer, long frame_count)
|
||||
{
|
||||
check_duplex<float>(static_cast<const float*>(input_buffer), static_cast<float*>(output_buffer), frame_count);
|
||||
return frame_count;
|
||||
}
|
||||
|
||||
|
||||
TEST(cubeb, resampler_passthrough_duplex_callback_reordering)
|
||||
{
|
||||
// Test that when pre-buffering on resampler creation, we can survive an input
|
||||
// callback being delayed.
|
||||
|
||||
cubeb_stream_params input_params;
|
||||
cubeb_stream_params output_params;
|
||||
|
||||
const int input_channels = 1;
|
||||
const int output_channels = 2;
|
||||
|
||||
input_params.channels = input_channels;
|
||||
input_params.rate = 44100;
|
||||
input_params.format = CUBEB_SAMPLE_FLOAT32NE;
|
||||
|
||||
output_params.channels = output_channels;
|
||||
output_params.rate = input_params.rate;
|
||||
output_params.format = CUBEB_SAMPLE_FLOAT32NE;
|
||||
|
||||
int target_rate = input_params.rate;
|
||||
|
||||
cubeb_resampler * resampler =
|
||||
cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params,
|
||||
target_rate, cb_passthrough_resampler_duplex, nullptr,
|
||||
CUBEB_RESAMPLER_QUALITY_VOIP);
|
||||
|
||||
const long BUF_BASE_SIZE = 256;
|
||||
float input_buffer_prebuffer[input_channels * BUF_BASE_SIZE * 2];
|
||||
float input_buffer_glitch[input_channels * BUF_BASE_SIZE * 2];
|
||||
float input_buffer_normal[input_channels * BUF_BASE_SIZE];
|
||||
float output_buffer[output_channels * BUF_BASE_SIZE];
|
||||
|
||||
long seq_idx = 0;
|
||||
long output_seq_idx = 0;
|
||||
|
||||
long prebuffer_frames = ARRAY_LENGTH(input_buffer_prebuffer) / input_params.channels;
|
||||
seq_idx = seq(input_buffer_prebuffer, input_channels, seq_idx,
|
||||
prebuffer_frames);
|
||||
|
||||
long got = cubeb_resampler_fill(resampler, input_buffer_prebuffer, &prebuffer_frames,
|
||||
output_buffer, BUF_BASE_SIZE);
|
||||
|
||||
output_seq_idx += BUF_BASE_SIZE;
|
||||
|
||||
ASSERT_EQ(prebuffer_frames, static_cast<long>(ARRAY_LENGTH(input_buffer_prebuffer) / input_params.channels));
|
||||
ASSERT_EQ(got, BUF_BASE_SIZE);
|
||||
|
||||
for (uint32_t i = 0; i < 300; i++) {
|
||||
long int frames = BUF_BASE_SIZE;
|
||||
// Simulate that sometimes, we don't have the input callback on time
|
||||
if (i != 0 && (i % 100) == 0) {
|
||||
long zero = 0;
|
||||
got = cubeb_resampler_fill(resampler, input_buffer_normal /* unused here */,
|
||||
&zero, output_buffer, BUF_BASE_SIZE);
|
||||
is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx);
|
||||
output_seq_idx += BUF_BASE_SIZE;
|
||||
} else if (i != 0 && (i % 100) == 1) {
|
||||
// if this is the case, the on the next iteration, we'll have twice the
|
||||
// amount of input frames
|
||||
seq_idx = seq(input_buffer_glitch, input_channels, seq_idx, BUF_BASE_SIZE * 2);
|
||||
frames = 2 * BUF_BASE_SIZE;
|
||||
got = cubeb_resampler_fill(resampler, input_buffer_glitch, &frames, output_buffer, BUF_BASE_SIZE);
|
||||
is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx);
|
||||
output_seq_idx += BUF_BASE_SIZE;
|
||||
} else {
|
||||
// normal case
|
||||
seq_idx = seq(input_buffer_normal, input_channels, seq_idx, BUF_BASE_SIZE);
|
||||
long normal_input_frame_count = 256;
|
||||
got = cubeb_resampler_fill(resampler, input_buffer_normal, &normal_input_frame_count, output_buffer, BUF_BASE_SIZE);
|
||||
is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx);
|
||||
output_seq_idx += BUF_BASE_SIZE;
|
||||
}
|
||||
ASSERT_EQ(got, BUF_BASE_SIZE);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "cubeb/cubeb.h"
|
||||
#include "cubeb_log.h"
|
||||
#include "cubeb_assert.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
|
@ -28,9 +29,6 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Crash the caller. */
|
||||
void cubeb_crash() CLANG_ANALYZER_NORETURN;
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
@ -85,11 +83,4 @@ struct cubeb_ops {
|
|||
void * user_ptr);
|
||||
};
|
||||
|
||||
#define XASSERT(expr) do { \
|
||||
if (!(expr)) { \
|
||||
fprintf(stderr, "%s:%d - fatal error: %s\n", __FILE__, __LINE__, #expr); \
|
||||
cubeb_crash(); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#endif /* CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5 */
|
||||
|
|
|
@ -108,12 +108,12 @@ int
|
|||
cubeb_init(cubeb ** context, char const * context_name)
|
||||
{
|
||||
int (* init[])(cubeb **, char const *) = {
|
||||
#if defined(USE_JACK)
|
||||
jack_init,
|
||||
#endif
|
||||
#if defined(USE_PULSE)
|
||||
pulse_init,
|
||||
#endif
|
||||
#if defined(USE_JACK)
|
||||
jack_init,
|
||||
#endif
|
||||
#if defined(USE_ALSA)
|
||||
alsa_init,
|
||||
#endif
|
||||
|
@ -570,9 +570,3 @@ int cubeb_set_log_callback(cubeb_log_level log_level,
|
|||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
void
|
||||
cubeb_crash()
|
||||
{
|
||||
abort();
|
||||
*((volatile int *) NULL) = 0;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef CUBEB_ASSERT
|
||||
#define CUBEB_ASSERT
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <mozilla/Assertions.h>
|
||||
|
||||
/* Forward fatal asserts to MOZ_ASSERT when built inside Gecko. */
|
||||
#define XASSERT(expr) MOZ_ASSERT(expr)
|
||||
|
||||
#endif
|
|
@ -1240,7 +1240,7 @@ pulse_get_state_from_sink_port(pa_sink_port_info * info)
|
|||
return CUBEB_DEVICE_STATE_ENABLED;
|
||||
}
|
||||
|
||||
return CUBEB_DEVICE_STATE_DISABLED;
|
||||
return CUBEB_DEVICE_STATE_ENABLED;
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -1270,7 +1270,8 @@ pulse_sink_info_cb(pa_context * context, const pa_sink_info * info,
|
|||
|
||||
devinfo->type = CUBEB_DEVICE_TYPE_OUTPUT;
|
||||
devinfo->state = pulse_get_state_from_sink_port(info->active_port);
|
||||
devinfo->preferred = strcmp(info->name, list_data->default_sink_name) == 0;
|
||||
devinfo->preferred = (strcmp(info->name, list_data->default_sink_name) == 0) ?
|
||||
CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
|
||||
|
||||
devinfo->format = CUBEB_DEVICE_FMT_ALL;
|
||||
devinfo->default_format = pulse_format_to_cubeb_format(info->sample_spec.format);
|
||||
|
@ -1300,7 +1301,7 @@ pulse_get_state_from_source_port(pa_source_port_info * info)
|
|||
return CUBEB_DEVICE_STATE_ENABLED;
|
||||
}
|
||||
|
||||
return CUBEB_DEVICE_STATE_DISABLED;
|
||||
return CUBEB_DEVICE_STATE_ENABLED;
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -1330,7 +1331,8 @@ pulse_source_info_cb(pa_context * context, const pa_source_info * info,
|
|||
|
||||
devinfo->type = CUBEB_DEVICE_TYPE_INPUT;
|
||||
devinfo->state = pulse_get_state_from_source_port(info->active_port);
|
||||
devinfo->preferred = strcmp(info->name, list_data->default_source_name) == 0;
|
||||
devinfo->preferred = (strcmp(info->name, list_data->default_source_name) == 0) ?
|
||||
CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
|
||||
|
||||
devinfo->format = CUBEB_DEVICE_FMT_ALL;
|
||||
devinfo->default_format = pulse_format_to_cubeb_format(info->sample_spec.format);
|
||||
|
|
|
@ -35,29 +35,46 @@ to_speex_quality(cubeb_resampler_quality q)
|
|||
}
|
||||
}
|
||||
|
||||
long noop_resampler::fill(void * input_buffer, long * input_frames_count,
|
||||
void * output_buffer, long output_frames)
|
||||
template<typename T>
|
||||
passthrough_resampler<T>::passthrough_resampler(cubeb_stream * s,
|
||||
cubeb_data_callback cb,
|
||||
void * ptr,
|
||||
uint32_t input_channels)
|
||||
: processor(input_channels)
|
||||
, stream(s)
|
||||
, data_callback(cb)
|
||||
, user_ptr(ptr)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
long passthrough_resampler<T>::fill(void * input_buffer, long * input_frames_count,
|
||||
void * output_buffer, long output_frames)
|
||||
{
|
||||
if (input_buffer) {
|
||||
assert(input_frames_count);
|
||||
}
|
||||
assert((input_buffer && output_buffer &&
|
||||
*input_frames_count >= output_frames) ||
|
||||
(!input_buffer && (!input_frames_count || *input_frames_count == 0)) ||
|
||||
(!output_buffer && output_frames == 0));
|
||||
*input_frames_count + static_cast<int>(samples_to_frames(internal_input_buffer.length())) >= output_frames) ||
|
||||
(output_buffer && !input_buffer && (!input_frames_count || *input_frames_count == 0)) ||
|
||||
(input_buffer && !output_buffer && output_frames == 0));
|
||||
|
||||
if (output_buffer == nullptr) {
|
||||
assert(input_buffer);
|
||||
output_frames = *input_frames_count;
|
||||
if (input_buffer) {
|
||||
if (!output_buffer) {
|
||||
output_frames = *input_frames_count;
|
||||
}
|
||||
internal_input_buffer.push(static_cast<T*>(input_buffer),
|
||||
frames_to_samples(*input_frames_count));
|
||||
}
|
||||
|
||||
if (input_buffer && *input_frames_count != output_frames) {
|
||||
assert(*input_frames_count > output_frames);
|
||||
*input_frames_count = output_frames;
|
||||
long rv = data_callback(stream, user_ptr, internal_input_buffer.data(),
|
||||
output_buffer, output_frames);
|
||||
|
||||
if (input_buffer) {
|
||||
internal_input_buffer.pop(nullptr, frames_to_samples(output_frames));
|
||||
}
|
||||
|
||||
return data_callback(stream, user_ptr,
|
||||
input_buffer, output_buffer, output_frames);
|
||||
return rv;
|
||||
}
|
||||
|
||||
template<typename T, typename InputProcessor, typename OutputProcessor>
|
||||
|
|
|
@ -48,31 +48,6 @@ struct cubeb_resampler {
|
|||
virtual ~cubeb_resampler() {}
|
||||
};
|
||||
|
||||
class noop_resampler : public cubeb_resampler {
|
||||
public:
|
||||
noop_resampler(cubeb_stream * s,
|
||||
cubeb_data_callback cb,
|
||||
void * ptr)
|
||||
: stream(s)
|
||||
, data_callback(cb)
|
||||
, user_ptr(ptr)
|
||||
{
|
||||
}
|
||||
|
||||
virtual long fill(void * input_buffer, long * input_frames_count,
|
||||
void * output_buffer, long output_frames);
|
||||
|
||||
virtual long latency()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
cubeb_stream * const stream;
|
||||
const cubeb_data_callback data_callback;
|
||||
void * const user_ptr;
|
||||
};
|
||||
|
||||
/** Base class for processors. This is just used to share methods for now. */
|
||||
class processor {
|
||||
public:
|
||||
|
@ -93,6 +68,32 @@ protected:
|
|||
const uint32_t channels;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class passthrough_resampler : public cubeb_resampler
|
||||
, public processor {
|
||||
public:
|
||||
passthrough_resampler(cubeb_stream * s,
|
||||
cubeb_data_callback cb,
|
||||
void * ptr,
|
||||
uint32_t input_channels);
|
||||
|
||||
virtual long fill(void * input_buffer, long * input_frames_count,
|
||||
void * output_buffer, long output_frames);
|
||||
|
||||
virtual long latency()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
cubeb_stream * const stream;
|
||||
const cubeb_data_callback data_callback;
|
||||
void * const user_ptr;
|
||||
/* This allows to buffer some input to account for the fact that we buffer
|
||||
* some inputs. */
|
||||
auto_array<T> internal_input_buffer;
|
||||
};
|
||||
|
||||
/** Bidirectional resampler, can resample an input and an output stream, or just
|
||||
* an input stream or output stream. In this case a delay is inserted in the
|
||||
* opposite direction to keep the streams synchronized. */
|
||||
|
@ -480,7 +481,9 @@ cubeb_resampler_create_internal(cubeb_stream * stream,
|
|||
(output_params && output_params->rate == target_rate)) ||
|
||||
(input_params && !output_params && (input_params->rate == target_rate)) ||
|
||||
(output_params && !input_params && (output_params->rate == target_rate))) {
|
||||
return new noop_resampler(stream, callback, user_ptr);
|
||||
return new passthrough_resampler<T>(stream, callback,
|
||||
user_ptr,
|
||||
input_params ? input_params->channels : 0);
|
||||
}
|
||||
|
||||
/* Determine if we need to resampler one or both directions, and create the
|
||||
|
|
|
@ -245,7 +245,7 @@ sndio_stream_init(cubeb * context,
|
|||
s->data_cb = data_callback;
|
||||
s->state_cb = state_callback;
|
||||
s->arg = user_ptr;
|
||||
s->mtx = PTHREAD_MUTEX_INITIALIZER;
|
||||
s->mtx = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
|
||||
s->rdpos = s->wrpos = 0;
|
||||
if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE) {
|
||||
s->conv = 1;
|
||||
|
|
|
@ -747,15 +747,6 @@ refill_callback_duplex(cubeb_stream * stm)
|
|||
return true;
|
||||
}
|
||||
|
||||
// When WASAPI has not filled the input buffer yet, send silence.
|
||||
double output_duration = double(output_frames) / stm->output_mix_params.rate;
|
||||
double input_duration = double(stm->linear_input_buffer.length() / stm->input_stream_params.channels) / stm->input_mix_params.rate;
|
||||
if (input_duration < output_duration) {
|
||||
size_t padding = size_t(round((output_duration - input_duration) * stm->input_mix_params.rate));
|
||||
LOG("padding silence: out=%f in=%f pad=%u", output_duration, input_duration, padding);
|
||||
stm->linear_input_buffer.push_front_silence(padding * stm->input_stream_params.channels);
|
||||
}
|
||||
|
||||
LOGV("Duplex callback: input frames: %Iu, output frames: %Iu",
|
||||
stm->linear_input_buffer.length(), output_frames);
|
||||
|
||||
|
@ -1651,6 +1642,14 @@ int setup_wasapi_stream(cubeb_stream * stm)
|
|||
stm->input_available_event,
|
||||
stm->capture_client,
|
||||
&stm->input_mix_params);
|
||||
|
||||
// We initializing an input stream, buffer ahead two buffers worth of silence.
|
||||
// This delays the input side slightly, but allow to not glitch when no input
|
||||
// is available when calling into the resampler to call the callback: the input
|
||||
// refill event will be set shortly after to compensate for this lack of data.
|
||||
stm->linear_input_buffer.push_silence(stm->input_buffer_frame_count *
|
||||
stm->input_stream_params.channels * 2);
|
||||
|
||||
if (rv != CUBEB_OK) {
|
||||
LOG("Failure to open the input side.");
|
||||
return rv;
|
||||
|
|
Загрузка…
Ссылка в новой задаче