зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1762104 - Switch media/libcubeb to mach vendor model. r=cubeb-reviewers,padenot
Differential Revision: https://phabricator.services.mozilla.com/D142521
This commit is contained in:
Родитель
720131f0e9
Коммит
bef5976c4e
|
@ -5,23 +5,23 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
# 'test_duplex.cpp', # DISABLED: See bug 1314514.
|
||||
# 'test_record.cpp', # DISABLED: See bug 1314514.
|
||||
# 'test_overload_callback.cpp', # DISABLED: Times out in automation.
|
||||
'test_tone.cpp',
|
||||
'test_utils.cpp'
|
||||
# '../test/test_duplex.cpp', # DISABLED: See bug 1314514.
|
||||
# '../test/test_record.cpp', # DISABLED: See bug 1314514.
|
||||
# '../test/test_overload_callback.cpp', # DISABLED: Times out in automation.
|
||||
'../test/test_tone.cpp',
|
||||
'../test/test_utils.cpp'
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_PULSEAUDIO'] or CONFIG['OS_TARGET'] in ('Darwin', 'WINNT'):
|
||||
UNIFIED_SOURCES += [
|
||||
'test_resampler.cpp',
|
||||
'../test/test_resampler.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['OS_TARGET'] != 'Android':
|
||||
UNIFIED_SOURCES += [
|
||||
'test_audio.cpp',
|
||||
'test_latency.cpp',
|
||||
'test_sanity.cpp'
|
||||
'../test/test_audio.cpp',
|
||||
'../test/test_latency.cpp',
|
||||
'../test/test_sanity.cpp'
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
EXPORTS.cubeb += [
|
||||
'cubeb.h',
|
||||
'cubeb/cubeb.h',
|
||||
'cubeb_export.h'
|
||||
]
|
||||
|
||||
|
|
|
@ -1,23 +1,50 @@
|
|||
# Version of this schema
|
||||
schema: 1
|
||||
|
||||
bugzilla:
|
||||
# Bugzilla product and component for this directory and subdirectories
|
||||
product: "Core"
|
||||
product: Core
|
||||
component: "Audio/Video: cubeb"
|
||||
|
||||
# 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 and the addition of
|
||||
# Makefile.in build files for the Mozilla build system.S
|
||||
|
||||
origin:
|
||||
name: "cubeb"
|
||||
name: cubeb
|
||||
description: "Cross platform audio library"
|
||||
url: https://github.com/mozilla/cubeb
|
||||
license: ISC
|
||||
release: commit b62d61bc661b49c7a7f5d97f4657189c630ac7a5 (2022-03-30T05:12:01Z).
|
||||
revision: b62d61bc661b49c7a7f5d97f4657189c630ac7a5
|
||||
|
||||
url: "https://github.com/kinetiknz/cubeb"
|
||||
license: "ISC"
|
||||
|
||||
# update.sh will update this value
|
||||
release: "d97fea4c9015b5d79e11ad68e9cdac610c27ca07 (2022-03-24 17:53:27 -0700)"
|
||||
vendoring:
|
||||
url: https://github.com/mozilla/cubeb
|
||||
source-hosting: github
|
||||
vendor-directory: media/libcubeb
|
||||
skip-vendoring-steps:
|
||||
- update-moz-build
|
||||
exclude:
|
||||
- .clang-format
|
||||
- .github
|
||||
- CMakeLists.txt
|
||||
- Config.cmake.in
|
||||
- INSTALL.md
|
||||
- cmake
|
||||
- cubeb.supp
|
||||
- docs
|
||||
- scan-build-install.sh
|
||||
- src/cubeb-jni-instances.h
|
||||
- src/cubeb-sles.h
|
||||
- src/cubeb_assert.h
|
||||
- src/cubeb_audiotrack.c
|
||||
- src/cubeb_kai.c
|
||||
- src/cubeb_osx_run_loop.cpp
|
||||
- src/cubeb_pulse.c
|
||||
- subprojects
|
||||
- tools
|
||||
keep:
|
||||
- gtest/moz.build
|
||||
- include/cubeb-stdint.h
|
||||
- include/cubeb_export.h
|
||||
- include/moz.build
|
||||
- src/cubeb-jni-instances.h
|
||||
- src/cubeb-sles.h
|
||||
- src/cubeb_assert.h
|
||||
- src/cubeb_osx_run_loop.c
|
||||
- src/moz.build
|
||||
|
||||
|
|
|
@ -22,23 +22,25 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
* From https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/utils/Errors.h
|
||||
* From
|
||||
* https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/utils/Errors.h
|
||||
*/
|
||||
typedef int32_t status_t;
|
||||
|
||||
/*
|
||||
* From https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/media/AudioTrack.h
|
||||
* From
|
||||
* https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/media/AudioTrack.h
|
||||
*/
|
||||
struct Buffer {
|
||||
uint32_t flags;
|
||||
int channelCount;
|
||||
int format;
|
||||
size_t frameCount;
|
||||
size_t size;
|
||||
uint32_t flags;
|
||||
int channelCount;
|
||||
int format;
|
||||
size_t frameCount;
|
||||
size_t size;
|
||||
union {
|
||||
void* raw;
|
||||
short* i16;
|
||||
int8_t* i8;
|
||||
void * raw;
|
||||
short * i16;
|
||||
int8_t * i8;
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -52,25 +54,28 @@ enum event_type {
|
|||
};
|
||||
|
||||
/**
|
||||
* From https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/media/AudioSystem.h
|
||||
* and
|
||||
* From
|
||||
* https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/media/AudioSystem.h
|
||||
* and
|
||||
* https://android.googlesource.com/platform/system/core/+/android-4.2.2_r1/include/system/audio.h
|
||||
*/
|
||||
|
||||
#define AUDIO_STREAM_TYPE_MUSIC 3
|
||||
|
||||
enum {
|
||||
AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS = 0x1,
|
||||
AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS = 0x1,
|
||||
AUDIO_CHANNEL_OUT_FRONT_RIGHT_ICS = 0x2,
|
||||
AUDIO_CHANNEL_OUT_MONO_ICS = AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS,
|
||||
AUDIO_CHANNEL_OUT_STEREO_ICS = (AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS | AUDIO_CHANNEL_OUT_FRONT_RIGHT_ICS)
|
||||
AUDIO_CHANNEL_OUT_MONO_ICS = AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS,
|
||||
AUDIO_CHANNEL_OUT_STEREO_ICS =
|
||||
(AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS | AUDIO_CHANNEL_OUT_FRONT_RIGHT_ICS)
|
||||
} AudioTrack_ChannelMapping_ICS;
|
||||
|
||||
enum {
|
||||
AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy = 0x4,
|
||||
AUDIO_CHANNEL_OUT_FRONT_RIGHT_Legacy = 0x8,
|
||||
AUDIO_CHANNEL_OUT_MONO_Legacy = AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy,
|
||||
AUDIO_CHANNEL_OUT_STEREO_Legacy = (AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy | AUDIO_CHANNEL_OUT_FRONT_RIGHT_Legacy)
|
||||
AUDIO_CHANNEL_OUT_STEREO_Legacy = (AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy |
|
||||
AUDIO_CHANNEL_OUT_FRONT_RIGHT_Legacy)
|
||||
} AudioTrack_ChannelMapping_Legacy;
|
||||
|
||||
typedef enum {
|
||||
|
@ -78,4 +83,3 @@ typedef enum {
|
|||
AUDIO_FORMAT_PCM_SUB_16_BIT = 0x1,
|
||||
AUDIO_FORMAT_PCM_16_BIT = (AUDIO_FORMAT_PCM | AUDIO_FORMAT_PCM_SUB_16_BIT),
|
||||
} AudioTrack_SampleType;
|
||||
|
||||
|
|
|
@ -541,6 +541,13 @@ audiounit_input_callback(void * user_ptr, AudioUnitRenderActionFlags * flags,
|
|||
long outframes = cubeb_resampler_fill(stm->resampler.get(),
|
||||
stm->input_linear_buffer->data(),
|
||||
&total_input_frames, NULL, 0);
|
||||
if (outframes < 0) {
|
||||
stm->shutdown = true;
|
||||
OSStatus r = AudioOutputUnitStop(stm->input_unit);
|
||||
assert(r == 0);
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
return noErr;
|
||||
}
|
||||
stm->draining = outframes < total_input_frames;
|
||||
|
||||
// Reset input buffer
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
/*
|
||||
* Copyright © 2014 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "cubeb_panner.h"
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159263
|
||||
#endif
|
||||
|
||||
/**
|
||||
* We use a cos/sin law.
|
||||
*/
|
||||
|
||||
namespace {
|
||||
template<typename T>
|
||||
void cubeb_pan_stereo_buffer(T * buf, uint32_t frames, float pan)
|
||||
{
|
||||
if (pan == 0.0) {
|
||||
return;
|
||||
}
|
||||
/* rescale in [0; 1] */
|
||||
pan += 1;
|
||||
pan /= 2;
|
||||
float left_gain = float(cos(pan * M_PI * 0.5));
|
||||
float right_gain = float(sin(pan * M_PI * 0.5));
|
||||
|
||||
/* In we are panning on the left, pan the right channel into the left one and
|
||||
* vice-versa. */
|
||||
if (pan < 0.5) {
|
||||
for (uint32_t i = 0; i < frames * 2; i+=2) {
|
||||
buf[i] = T(buf[i] + buf[i + 1] * left_gain);
|
||||
buf[i + 1] = T(buf[i + 1] * right_gain);
|
||||
}
|
||||
} else {
|
||||
for (uint32_t i = 0; i < frames * 2; i+=2) {
|
||||
buf[i] = T(buf[i] * left_gain);
|
||||
buf[i + 1] = T(buf[i + 1] + buf[i] * right_gain);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cubeb_pan_stereo_buffer_float(float * buf, uint32_t frames, float pan)
|
||||
{
|
||||
cubeb_pan_stereo_buffer(buf, frames, pan);
|
||||
}
|
||||
|
||||
void cubeb_pan_stereo_buffer_int(short * buf, uint32_t frames, float pan)
|
||||
{
|
||||
cubeb_pan_stereo_buffer(buf, frames, pan);
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* Copyright © 2014 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
#if !defined(CUBEB_PANNER)
|
||||
#define CUBEB_PANNER
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Pan an integer or an float stereo buffer according to a cos/sin pan law
|
||||
* @param buf the buffer to pan
|
||||
* @param frames the number of frames in `buf`
|
||||
* @param pan a float in [-1.0; 1.0]
|
||||
*/
|
||||
void cubeb_pan_stereo_buffer_float(float * buf, uint32_t frames, float pan);
|
||||
void cubeb_pan_stereo_buffer_int(short* buf, uint32_t frames, float pan);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -873,8 +873,11 @@ refill(cubeb_stream * stm, void * input_buffer, long input_frames_count,
|
|||
long out_frames =
|
||||
cubeb_resampler_fill(stm->resampler.get(), input_buffer,
|
||||
&input_frames_count, dest, output_frames_needed);
|
||||
/* TODO: Report out_frames < 0 as an error via the API. */
|
||||
XASSERT(out_frames >= 0);
|
||||
if (out_frames < 0) {
|
||||
ALOGV("Callback refill error: %d", out_frames);
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
return out_frames;
|
||||
}
|
||||
|
||||
float volume = 1.0;
|
||||
{
|
||||
|
@ -1190,6 +1193,7 @@ refill_callback_duplex(cubeb_stream * stm)
|
|||
static_cast<long>(stm->total_output_frames) - stm->total_input_frames,
|
||||
static_cast<float>(stm->total_output_frames) / stm->total_input_frames);
|
||||
|
||||
long got;
|
||||
if (stm->has_dummy_output) {
|
||||
ALOGV(
|
||||
"Duplex callback (dummy output): input frames: %Iu, output frames: %Iu",
|
||||
|
@ -1197,13 +1201,15 @@ refill_callback_duplex(cubeb_stream * stm)
|
|||
|
||||
// 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);
|
||||
got =
|
||||
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);
|
||||
got = refill(stm, stm->linear_input_buffer->data(), input_frames,
|
||||
output_buffer, output_frames);
|
||||
}
|
||||
|
||||
stm->linear_input_buffer->clear();
|
||||
|
@ -1219,6 +1225,9 @@ refill_callback_duplex(cubeb_stream * stm)
|
|||
LOG("failed to release buffer: %lx", hr);
|
||||
return false;
|
||||
}
|
||||
if (got < 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1245,8 +1254,9 @@ refill_callback_input(cubeb_stream * stm)
|
|||
|
||||
long read =
|
||||
refill(stm, stm->linear_input_buffer->data(), input_frames, nullptr, 0);
|
||||
|
||||
XASSERT(read >= 0);
|
||||
if (read < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
stm->linear_input_buffer->clear();
|
||||
|
||||
|
@ -1276,8 +1286,9 @@ refill_callback_output(cubeb_stream * stm)
|
|||
|
||||
ALOGV("Output callback: output frames requested: %Iu, got %ld", output_frames,
|
||||
got);
|
||||
|
||||
XASSERT(got >= 0);
|
||||
if (got < 0) {
|
||||
return false;
|
||||
}
|
||||
XASSERT(size_t(got) == output_frames || stm->draining);
|
||||
|
||||
hr = stm->render_client->ReleaseBuffer(got, 0);
|
||||
|
|
|
@ -12,7 +12,6 @@ SOURCES += [
|
|||
'cubeb.c',
|
||||
'cubeb_log.cpp',
|
||||
'cubeb_mixer.cpp',
|
||||
'cubeb_panner.cpp',
|
||||
'cubeb_strings.c',
|
||||
'cubeb_utils.cpp'
|
||||
]
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
Notes on writing tests.
|
||||
|
||||
The googletest submodule is currently at 1.6 rather than the latest, and should
|
||||
only be updated to track the version used in Gecko to make test compatibility
|
||||
easier.
|
||||
|
||||
Always #include "gtest/gtest.h" before *anything* else.
|
||||
|
||||
All tests should be part of the "cubeb" test case, e.g. TEST(cubeb, my_test).
|
||||
|
||||
Tests are built stand-alone in cubeb, but built as a single unit in Gecko, so
|
||||
you must use unique names for globally visible items in each test, e.g. rather
|
||||
than state_cb use state_cb_my_test.
|
|
@ -0,0 +1,239 @@
|
|||
/*
|
||||
* Copyright <EFBFBD> 2017 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
/* libcubeb api/function test. Test that different return values from user
|
||||
specified callbacks are handled correctly. */
|
||||
#include "gtest/gtest.h"
|
||||
#if !defined(_XOPEN_SOURCE)
|
||||
#define _XOPEN_SOURCE 600
|
||||
#endif
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include "cubeb/cubeb.h"
|
||||
|
||||
//#define ENABLE_NORMAL_LOG
|
||||
//#define ENABLE_VERBOSE_LOG
|
||||
#include "common.h"
|
||||
|
||||
const uint32_t SAMPLE_FREQUENCY = 48000;
|
||||
const cubeb_sample_format SAMPLE_FORMAT = CUBEB_SAMPLE_S16NE;
|
||||
|
||||
enum test_direction {
|
||||
INPUT_ONLY,
|
||||
OUTPUT_ONLY,
|
||||
DUPLEX
|
||||
};
|
||||
|
||||
// Structure which is used by data callbacks to track the total callbacks
|
||||
// executed vs the number of callbacks expected.
|
||||
struct user_state_callback_ret {
|
||||
std::atomic<int> cb_count{ 0 };
|
||||
std::atomic<int> expected_cb_count{ 0 };
|
||||
std::atomic<int> error_state{ 0 };
|
||||
};
|
||||
|
||||
// Data callback that always returns 0
|
||||
long data_cb_ret_zero(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
|
||||
{
|
||||
user_state_callback_ret * u = (user_state_callback_ret *) user;
|
||||
// If this is the first time the callback has been called set our expected
|
||||
// callback count
|
||||
if (u->cb_count == 0) {
|
||||
u->expected_cb_count = 1;
|
||||
}
|
||||
u->cb_count++;
|
||||
if (nframes < 1) {
|
||||
// This shouldn't happen
|
||||
EXPECT_TRUE(false) << "nframes should not be 0 in data callback!";
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Data callback that always returns nframes - 1
|
||||
long data_cb_ret_nframes_minus_one(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
|
||||
{
|
||||
user_state_callback_ret * u = (user_state_callback_ret *)user;
|
||||
// If this is the first time the callback has been called set our expected
|
||||
// callback count
|
||||
if (u->cb_count == 0) {
|
||||
u->expected_cb_count = 1;
|
||||
}
|
||||
u->cb_count++;
|
||||
if (nframes < 1) {
|
||||
// This shouldn't happen
|
||||
EXPECT_TRUE(false) << "nframes should not be 0 in data callback!";
|
||||
}
|
||||
if (outputbuffer != NULL) {
|
||||
// If we have an output buffer insert silence
|
||||
short * ob = (short *) outputbuffer;
|
||||
for (long i = 0; i < nframes - 1; i++) {
|
||||
ob[i] = 0;
|
||||
}
|
||||
}
|
||||
return nframes - 1;
|
||||
}
|
||||
|
||||
// Data callback that always returns nframes
|
||||
long data_cb_ret_nframes(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
|
||||
{
|
||||
user_state_callback_ret * u = (user_state_callback_ret *)user;
|
||||
u->cb_count++;
|
||||
// Every callback returns nframes, so every callback is expected
|
||||
u->expected_cb_count++;
|
||||
if (nframes < 1) {
|
||||
// This shouldn't happen
|
||||
EXPECT_TRUE(false) << "nframes should not be 0 in data callback!";
|
||||
}
|
||||
if (outputbuffer != NULL) {
|
||||
// If we have an output buffer insert silence
|
||||
short * ob = (short *) outputbuffer;
|
||||
for (long i = 0; i < nframes; i++) {
|
||||
ob[i] = 0;
|
||||
}
|
||||
}
|
||||
return nframes;
|
||||
}
|
||||
|
||||
// Data callback that always returns CUBEB_ERROR
|
||||
long
|
||||
data_cb_ret_error(cubeb_stream * stream, void * user, const void * inputbuffer,
|
||||
void * outputbuffer, long nframes)
|
||||
{
|
||||
user_state_callback_ret * u = (user_state_callback_ret *)user;
|
||||
// If this is the first time the callback has been called set our expected
|
||||
// callback count
|
||||
if (u->cb_count == 0) {
|
||||
u->expected_cb_count = 1;
|
||||
}
|
||||
u->cb_count++;
|
||||
if (nframes < 1) {
|
||||
// This shouldn't happen
|
||||
EXPECT_TRUE(false) << "nframes should not be 0 in data callback!";
|
||||
}
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
void state_cb_ret(cubeb_stream * stream, void * user, cubeb_state state)
|
||||
{
|
||||
if (stream == NULL)
|
||||
return;
|
||||
user_state_callback_ret * u = (user_state_callback_ret *)user;
|
||||
|
||||
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;
|
||||
case CUBEB_STATE_ERROR:
|
||||
fprintf(stderr, "stream error\n");
|
||||
u->error_state.fetch_add(1);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "unknown stream state %d\n", state);
|
||||
}
|
||||
}
|
||||
|
||||
void run_test_callback(test_direction direction,
|
||||
cubeb_data_callback data_cb,
|
||||
const std::string & test_desc) {
|
||||
cubeb * ctx;
|
||||
cubeb_stream * stream;
|
||||
cubeb_stream_params input_params;
|
||||
cubeb_stream_params output_params;
|
||||
int r;
|
||||
user_state_callback_ret user_state;
|
||||
uint32_t latency_frames = 0;
|
||||
|
||||
r = common_init(&ctx, "Cubeb callback return value example");
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
|
||||
|
||||
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
|
||||
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
|
||||
|
||||
if ((direction == INPUT_ONLY || direction == DUPLEX) &&
|
||||
!has_available_input_device(ctx)) {
|
||||
/* This test needs an available input device, skip it if this host does not
|
||||
* have one. */
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup all params, but only pass them later as required by direction
|
||||
input_params.format = SAMPLE_FORMAT;
|
||||
input_params.rate = SAMPLE_FREQUENCY;
|
||||
input_params.channels = 1;
|
||||
input_params.layout = CUBEB_LAYOUT_MONO;
|
||||
input_params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
output_params = input_params;
|
||||
|
||||
r = cubeb_get_min_latency(ctx, &input_params, &latency_frames);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case INPUT_ONLY:
|
||||
r = cubeb_stream_init(ctx, &stream, "Cubeb callback ret input",
|
||||
NULL, &input_params, NULL, NULL,
|
||||
latency_frames, data_cb, state_cb_ret, &user_state);
|
||||
break;
|
||||
case OUTPUT_ONLY:
|
||||
r = cubeb_stream_init(ctx, &stream, "Cubeb callback ret output",
|
||||
NULL, NULL, NULL, &output_params,
|
||||
latency_frames, data_cb, state_cb_ret, &user_state);
|
||||
break;
|
||||
case DUPLEX:
|
||||
r = cubeb_stream_init(ctx, &stream, "Cubeb callback ret duplex",
|
||||
NULL, &input_params, NULL, &output_params,
|
||||
latency_frames, data_cb, state_cb_ret, &user_state);
|
||||
break;
|
||||
default:
|
||||
ASSERT_TRUE(false) << "Unrecognized test direction!";
|
||||
}
|
||||
EXPECT_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(100);
|
||||
cubeb_stream_stop(stream);
|
||||
|
||||
ASSERT_EQ(user_state.expected_cb_count, user_state.cb_count) <<
|
||||
"Callback called unexpected number of times for " << test_desc << "!";
|
||||
// TODO: On some test configurations, the data_callback is never called.
|
||||
if (data_cb == data_cb_ret_error && user_state.cb_count != 0) {
|
||||
ASSERT_EQ(user_state.error_state, 1) << "Callback expected error state";
|
||||
}
|
||||
}
|
||||
|
||||
TEST(cubeb, test_input_callback)
|
||||
{
|
||||
run_test_callback(INPUT_ONLY, data_cb_ret_zero, "input only, return 0");
|
||||
run_test_callback(INPUT_ONLY, data_cb_ret_nframes_minus_one, "input only, return nframes - 1");
|
||||
run_test_callback(INPUT_ONLY, data_cb_ret_nframes, "input only, return nframes");
|
||||
run_test_callback(INPUT_ONLY, data_cb_ret_error,
|
||||
"input only, return CUBEB_ERROR");
|
||||
}
|
||||
|
||||
TEST(cubeb, test_output_callback)
|
||||
{
|
||||
run_test_callback(OUTPUT_ONLY, data_cb_ret_zero, "output only, return 0");
|
||||
run_test_callback(OUTPUT_ONLY, data_cb_ret_nframes_minus_one, "output only, return nframes - 1");
|
||||
run_test_callback(OUTPUT_ONLY, data_cb_ret_nframes, "output only, return nframes");
|
||||
run_test_callback(OUTPUT_ONLY, data_cb_ret_error,
|
||||
"output only, return CUBEB_ERROR");
|
||||
}
|
||||
|
||||
TEST(cubeb, test_duplex_callback)
|
||||
{
|
||||
run_test_callback(DUPLEX, data_cb_ret_zero, "duplex, return 0");
|
||||
run_test_callback(DUPLEX, data_cb_ret_nframes_minus_one, "duplex, return nframes - 1");
|
||||
run_test_callback(DUPLEX, data_cb_ret_nframes, "duplex, return nframes");
|
||||
run_test_callback(DUPLEX, data_cb_ret_error, "duplex, return CUBEB_ERROR");
|
||||
}
|
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
* Copyright © 2017 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*
|
||||
*
|
||||
* Purpose
|
||||
* =============================================================================
|
||||
* In CoreAudio, the data callback will holds a mutex shared with AudioUnit
|
||||
* (mutex_AU). Thus, if the callback request another mutex M held by the another
|
||||
* function, without releasing mutex_AU, then it will cause a deadlock when the
|
||||
* another function, which holds the mutex M, request to use AudioUnit.
|
||||
*
|
||||
* The following figure illustrates the deadlock in bug 1337805:
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=1337805
|
||||
* (The detail analysis can be found on bug 1350511:
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=1350511)
|
||||
*
|
||||
* holds
|
||||
* data_callback <---------- mutext_AudioUnit(mutex_AU)
|
||||
* | ^
|
||||
* | |
|
||||
* | request | request
|
||||
* | |
|
||||
* v holds |
|
||||
* mutex_cubeb ------------> get_channel_layout
|
||||
*
|
||||
* In this example, the "audiounit_get_channel_layout" in f4edfb8:
|
||||
* https://github.com/kinetiknz/cubeb/blob/f4edfb8eea920887713325e44773f3a2d959860c/src/cubeb_audiounit.cpp#L2725
|
||||
* requests the mutex_AU to create an AudioUnit, when it holds a mutex for cubeb
|
||||
* context. Meanwhile, the data callback who holds the mutex_AU requests the
|
||||
* mutex for cubeb context. As a result, it causes a deadlock.
|
||||
*
|
||||
* The problem is solve by pull 236: https://github.com/kinetiknz/cubeb/pull/236
|
||||
* We store the latest channel layout and return it when there is an active
|
||||
* AudioUnit, otherwise, we will create an AudioUnit to get it.
|
||||
*
|
||||
* Although the problem is solved, to prevent it happens again, we add the test
|
||||
* here in case someone without such knowledge misuses the AudioUnit in
|
||||
* get_channel_layout. Moreover, it's a good way to record the known issues
|
||||
* to warn other developers.
|
||||
*/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
//#define ENABLE_NORMAL_LOG
|
||||
//#define ENABLE_VERBOSE_LOG
|
||||
#include "common.h" // for layout_infos
|
||||
#include "cubeb/cubeb.h" // for cubeb utils
|
||||
#include "cubeb_utils.h" // for owned_critical_section, auto_lock
|
||||
#include <iostream> // for fprintf
|
||||
#include <pthread.h> // for pthread
|
||||
#include <signal.h> // for signal
|
||||
#include <stdexcept> // for std::logic_error
|
||||
#include <string> // for std::string
|
||||
#include <unistd.h> // for sleep, usleep
|
||||
#include <atomic> // for std::atomic
|
||||
|
||||
// The signal alias for calling our thread killer.
|
||||
#define CALL_THREAD_KILLER SIGUSR1
|
||||
|
||||
// This indicator will become true when our pending task thread is killed by
|
||||
// ourselves.
|
||||
bool killed = false;
|
||||
|
||||
// This indicator will become true when the assigned task is done.
|
||||
std::atomic<bool> task_done{ false };
|
||||
|
||||
// Indicating the data callback is fired or not.
|
||||
bool called = false;
|
||||
|
||||
// Toggle to true when running data callback. Before data callback gets
|
||||
// the mutex for cubeb context, it toggles back to false.
|
||||
// The task to get channel layout should be executed when this is true.
|
||||
std::atomic<bool> callbacking_before_getting_context{ false };
|
||||
|
||||
owned_critical_section context_mutex;
|
||||
cubeb * context = nullptr;
|
||||
|
||||
cubeb * get_cubeb_context_unlocked()
|
||||
{
|
||||
if (context) {
|
||||
return context;
|
||||
}
|
||||
|
||||
int r = CUBEB_OK;
|
||||
r = common_init(&context, "Cubeb deadlock test");
|
||||
if (r != CUBEB_OK) {
|
||||
context = nullptr;
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
cubeb * get_cubeb_context()
|
||||
{
|
||||
auto_lock lock(context_mutex);
|
||||
return get_cubeb_context_unlocked();
|
||||
}
|
||||
|
||||
void state_cb_audio(cubeb_stream * /*stream*/, void * /*user*/, cubeb_state /*state*/)
|
||||
{
|
||||
}
|
||||
|
||||
// Fired by coreaudio's rendering mechanism. It holds a mutex shared with the
|
||||
// current used AudioUnit.
|
||||
template<typename T>
|
||||
long data_cb(cubeb_stream * /*stream*/, void * /*user*/,
|
||||
const void * /*inputbuffer*/, void * outputbuffer, long nframes)
|
||||
{
|
||||
called = true;
|
||||
|
||||
uint64_t tid; // Current thread id.
|
||||
pthread_threadid_np(NULL, &tid);
|
||||
fprintf(stderr, "Audio output is on thread %llu\n", tid);
|
||||
|
||||
if (!task_done) {
|
||||
callbacking_before_getting_context = true;
|
||||
fprintf(stderr, "[%llu] time to switch thread\n", tid);
|
||||
// Force to switch threads by sleeping 10 ms. Notice that anything over
|
||||
// 10ms would create a glitch. It's intended here for test, so the delay
|
||||
// is ok.
|
||||
usleep(10000);
|
||||
callbacking_before_getting_context = false;
|
||||
}
|
||||
|
||||
fprintf(stderr, "[%llu] try getting backend id ...\n", tid);
|
||||
|
||||
// Try requesting mutex for context by get_cubeb_context()
|
||||
// when holding a mutex for AudioUnit.
|
||||
char const * backend_id = cubeb_get_backend_id(get_cubeb_context());
|
||||
fprintf(stderr, "[%llu] callback on %s\n", tid, backend_id);
|
||||
|
||||
// Mute the output (or get deaf)
|
||||
memset(outputbuffer, 0, nframes * 2 * sizeof(float));
|
||||
return nframes;
|
||||
}
|
||||
|
||||
// Called by wait_to_get_layout, which is run out of main thread.
|
||||
void get_preferred_channel_layout()
|
||||
{
|
||||
auto_lock lock(context_mutex);
|
||||
cubeb * context = get_cubeb_context_unlocked();
|
||||
ASSERT_TRUE(!!context);
|
||||
|
||||
// We will cause a deadlock if cubeb_get_preferred_channel_layout requests
|
||||
// mutex for AudioUnit when it holds mutex for context.
|
||||
cubeb_channel_layout layout;
|
||||
int r = cubeb_get_preferred_channel_layout(context, &layout);
|
||||
ASSERT_EQ(r == CUBEB_OK, layout != CUBEB_LAYOUT_UNDEFINED);
|
||||
fprintf(stderr, "layout is %s\n", layout_infos[layout].name);
|
||||
}
|
||||
|
||||
void * wait_to_get_layout(void *)
|
||||
{
|
||||
uint64_t tid; // Current thread id.
|
||||
pthread_threadid_np(NULL, &tid);
|
||||
|
||||
while(!callbacking_before_getting_context) {
|
||||
fprintf(stderr, "[%llu] waiting for data callback ...\n", tid);
|
||||
usleep(1000); // Force to switch threads by sleeping 1 ms.
|
||||
}
|
||||
|
||||
fprintf(stderr, "[%llu] try getting channel layout ...\n", tid);
|
||||
get_preferred_channel_layout(); // Deadlock checkpoint.
|
||||
task_done = true;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void * watchdog(void * s)
|
||||
{
|
||||
uint64_t tid; // Current thread id.
|
||||
pthread_threadid_np(NULL, &tid);
|
||||
|
||||
pthread_t subject = *((pthread_t *) s);
|
||||
uint64_t stid; // task thread id.
|
||||
pthread_threadid_np(subject, &stid);
|
||||
|
||||
unsigned int sec = 2;
|
||||
fprintf(stderr, "[%llu] sleep %d seconds before checking task for thread %llu\n", tid, sec, stid);
|
||||
sleep(sec); // Force to switch threads.
|
||||
|
||||
fprintf(stderr, "[%llu] check task for thread %llu now\n", tid, stid);
|
||||
if (!task_done) {
|
||||
fprintf(stderr, "[%llu] kill the task thread %llu\n", tid, stid);
|
||||
pthread_kill(subject, CALL_THREAD_KILLER);
|
||||
pthread_detach(subject);
|
||||
// pthread_kill doesn't release the mutex held by the killed thread,
|
||||
// so we need to unlock it manually.
|
||||
context_mutex.unlock();
|
||||
}
|
||||
fprintf(stderr, "[%llu] the assigned task for thread %llu is %sdone\n", tid, stid, (task_done) ? "" : "not ");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void thread_killer(int signal)
|
||||
{
|
||||
ASSERT_EQ(signal, CALL_THREAD_KILLER);
|
||||
fprintf(stderr, "task thread is killed!\n");
|
||||
killed = true;
|
||||
}
|
||||
|
||||
TEST(cubeb, run_deadlock_test)
|
||||
{
|
||||
#if !defined(__APPLE__)
|
||||
FAIL() << "Deadlock test is only for OSX now";
|
||||
#endif
|
||||
|
||||
cubeb * ctx = get_cubeb_context();
|
||||
ASSERT_TRUE(!!ctx);
|
||||
|
||||
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
|
||||
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
|
||||
|
||||
cubeb_stream_params params;
|
||||
params.format = CUBEB_SAMPLE_FLOAT32NE;
|
||||
params.rate = 44100;
|
||||
params.channels = 2;
|
||||
params.layout = CUBEB_LAYOUT_STEREO;
|
||||
params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
cubeb_stream * stream = NULL;
|
||||
int r = cubeb_stream_init(ctx, &stream, "test deadlock", NULL, NULL, NULL,
|
||||
¶ms, 512, &data_cb<float>, state_cb_audio, NULL);
|
||||
ASSERT_EQ(r, CUBEB_OK);
|
||||
|
||||
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
|
||||
cleanup_stream_at_exit(stream, cubeb_stream_destroy);
|
||||
|
||||
// Install signal handler.
|
||||
signal(CALL_THREAD_KILLER, thread_killer);
|
||||
|
||||
pthread_t subject, detector;
|
||||
pthread_create(&subject, NULL, wait_to_get_layout, NULL);
|
||||
pthread_create(&detector, NULL, watchdog, (void *) &subject);
|
||||
|
||||
uint64_t stid, dtid;
|
||||
pthread_threadid_np(subject, &stid);
|
||||
pthread_threadid_np(detector, &dtid);
|
||||
fprintf(stderr, "task thread %llu, monitor thread %llu are created\n", stid, dtid);
|
||||
|
||||
cubeb_stream_start(stream);
|
||||
|
||||
pthread_join(subject, NULL);
|
||||
pthread_join(detector, NULL);
|
||||
|
||||
ASSERT_TRUE(called);
|
||||
|
||||
fprintf(stderr, "\n%sDeadlock detected!\n", (called && !task_done.load()) ? "" : "No ");
|
||||
|
||||
// Check the task is killed by ourselves if deadlock happends.
|
||||
// Otherwise, thread_killer should not be triggered.
|
||||
ASSERT_NE(task_done.load(), killed);
|
||||
|
||||
ASSERT_TRUE(task_done.load());
|
||||
|
||||
cubeb_stream_stop(stream);
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright © 2018 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
/* libcubeb api/function test. Check behaviors of registering device changed
|
||||
* callbacks for the streams. */
|
||||
#include "gtest/gtest.h"
|
||||
#if !defined(_XOPEN_SOURCE)
|
||||
#define _XOPEN_SOURCE 600
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <memory>
|
||||
#include "cubeb/cubeb.h"
|
||||
|
||||
//#define ENABLE_NORMAL_LOG
|
||||
//#define ENABLE_VERBOSE_LOG
|
||||
#include "common.h"
|
||||
|
||||
#define SAMPLE_FREQUENCY 48000
|
||||
#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
|
||||
#define INPUT_CHANNELS 1
|
||||
#define INPUT_LAYOUT CUBEB_LAYOUT_MONO
|
||||
#define OUTPUT_CHANNELS 2
|
||||
#define OUTPUT_LAYOUT CUBEB_LAYOUT_STEREO
|
||||
|
||||
long data_callback(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void state_callback(cubeb_stream * stream, void * user, cubeb_state state)
|
||||
{
|
||||
}
|
||||
|
||||
void device_changed_callback(void * user)
|
||||
{
|
||||
fprintf(stderr, "device changed callback\n");
|
||||
ASSERT_TRUE(false) << "Error: device changed callback"
|
||||
" called without changing devices";
|
||||
}
|
||||
|
||||
void test_registering_null_callback_twice(cubeb_stream * stream)
|
||||
{
|
||||
int r = cubeb_stream_register_device_changed_callback(stream, nullptr);
|
||||
if (r == CUBEB_ERROR_NOT_SUPPORTED) {
|
||||
return;
|
||||
}
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error registering null device changed callback";
|
||||
|
||||
r = cubeb_stream_register_device_changed_callback(stream, nullptr);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error registering null device changed callback again";
|
||||
}
|
||||
|
||||
void test_registering_and_unregistering_callback(cubeb_stream * stream)
|
||||
{
|
||||
int r = cubeb_stream_register_device_changed_callback(stream, device_changed_callback);
|
||||
if (r == CUBEB_ERROR_NOT_SUPPORTED) {
|
||||
return;
|
||||
}
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error registering device changed callback";
|
||||
|
||||
r = cubeb_stream_register_device_changed_callback(stream, nullptr);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error unregistering device changed callback";
|
||||
}
|
||||
|
||||
TEST(cubeb, device_changed_callbacks)
|
||||
{
|
||||
cubeb * ctx;
|
||||
cubeb_stream * stream;
|
||||
cubeb_stream_params input_params;
|
||||
cubeb_stream_params output_params;
|
||||
int r = CUBEB_OK;
|
||||
uint32_t latency_frames = 0;
|
||||
|
||||
r = common_init(&ctx, "Cubeb duplex example with device change");
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
|
||||
|
||||
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
|
||||
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
|
||||
|
||||
/* typical user-case: mono input, stereo output, low latency. */
|
||||
input_params.format = STREAM_FORMAT;
|
||||
input_params.rate = SAMPLE_FREQUENCY;
|
||||
input_params.channels = INPUT_CHANNELS;
|
||||
input_params.layout = INPUT_LAYOUT;
|
||||
input_params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
output_params.format = STREAM_FORMAT;
|
||||
output_params.rate = SAMPLE_FREQUENCY;
|
||||
output_params.channels = OUTPUT_CHANNELS;
|
||||
output_params.layout = OUTPUT_LAYOUT;
|
||||
output_params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
|
||||
|
||||
r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
|
||||
NULL, &input_params, NULL, &output_params,
|
||||
latency_frames, data_callback, state_callback, nullptr);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
|
||||
|
||||
test_registering_null_callback_twice(stream);
|
||||
|
||||
test_registering_and_unregistering_callback(stream);
|
||||
|
||||
cubeb_stream_destroy(stream);
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
* Copyright © 2016 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
#define NOMINMAX
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "cubeb_ringbuffer.h"
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
/* Generate a monotonically increasing sequence of numbers. */
|
||||
template<typename T>
|
||||
class sequence_generator
|
||||
{
|
||||
public:
|
||||
sequence_generator(size_t channels)
|
||||
: channels(channels)
|
||||
{ }
|
||||
void get(T * elements, size_t frames)
|
||||
{
|
||||
for (size_t i = 0; i < frames; i++) {
|
||||
for (size_t c = 0; c < channels; c++) {
|
||||
elements[i * channels + c] = static_cast<T>(index_);
|
||||
}
|
||||
index_++;
|
||||
}
|
||||
}
|
||||
void rewind(size_t frames)
|
||||
{
|
||||
index_ -= frames;
|
||||
}
|
||||
private:
|
||||
size_t index_ = 0;
|
||||
size_t channels = 0;
|
||||
};
|
||||
|
||||
/* Checks that a sequence is monotonically increasing. */
|
||||
template<typename T>
|
||||
class sequence_verifier
|
||||
{
|
||||
public:
|
||||
sequence_verifier(size_t channels)
|
||||
: channels(channels)
|
||||
{ }
|
||||
void check(T * elements, size_t frames)
|
||||
{
|
||||
for (size_t i = 0; i < frames; i++) {
|
||||
for (size_t c = 0; c < channels; c++) {
|
||||
if (elements[i * channels + c] != static_cast<T>(index_)) {
|
||||
std::cerr << "Element " << i << " is different. Expected "
|
||||
<< static_cast<T>(index_) << ", got " << elements[i]
|
||||
<< ". (channel count: " << channels << ")." << std::endl;
|
||||
ASSERT_TRUE(false);
|
||||
}
|
||||
}
|
||||
index_++;
|
||||
}
|
||||
}
|
||||
private:
|
||||
size_t index_ = 0;
|
||||
size_t channels = 0;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
void test_ring(lock_free_audio_ring_buffer<T>& buf, int channels, int capacity_frames)
|
||||
{
|
||||
std::unique_ptr<T[]> seq(new T[capacity_frames * channels]);
|
||||
sequence_generator<T> gen(channels);
|
||||
sequence_verifier<T> checker(channels);
|
||||
|
||||
int iterations = 1002;
|
||||
|
||||
const int block_size = 128;
|
||||
|
||||
while(iterations--) {
|
||||
gen.get(seq.get(), block_size);
|
||||
int rv = buf.enqueue(seq.get(), block_size);
|
||||
ASSERT_EQ(rv, block_size);
|
||||
PodZero(seq.get(), block_size);
|
||||
rv = buf.dequeue(seq.get(), block_size);
|
||||
ASSERT_EQ(rv, block_size);
|
||||
checker.check(seq.get(), block_size);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void test_ring_multi(lock_free_audio_ring_buffer<T>& buf, int channels, int capacity_frames)
|
||||
{
|
||||
sequence_verifier<T> checker(channels);
|
||||
std::unique_ptr<T[]> out_buffer(new T[capacity_frames * channels]);
|
||||
|
||||
const int block_size = 128;
|
||||
|
||||
std::thread t([=, &buf] {
|
||||
int iterations = 1002;
|
||||
std::unique_ptr<T[]> in_buffer(new T[capacity_frames * channels]);
|
||||
sequence_generator<T> gen(channels);
|
||||
|
||||
while(iterations--) {
|
||||
std::this_thread::yield();
|
||||
gen.get(in_buffer.get(), block_size);
|
||||
int rv = buf.enqueue(in_buffer.get(), block_size);
|
||||
ASSERT_TRUE(rv <= block_size);
|
||||
if (rv != block_size) {
|
||||
gen.rewind(block_size - rv);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
int remaining = 1002;
|
||||
|
||||
while(remaining--) {
|
||||
std::this_thread::yield();
|
||||
int rv = buf.dequeue(out_buffer.get(), block_size);
|
||||
ASSERT_TRUE(rv <= block_size);
|
||||
checker.check(out_buffer.get(), rv);
|
||||
}
|
||||
|
||||
t.join();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void basic_api_test(T& ring)
|
||||
{
|
||||
ASSERT_EQ(ring.capacity(), 128);
|
||||
|
||||
ASSERT_EQ(ring.available_read(), 0);
|
||||
ASSERT_EQ(ring.available_write(), 128);
|
||||
|
||||
int rv = ring.enqueue_default(63);
|
||||
|
||||
ASSERT_TRUE(rv == 63);
|
||||
ASSERT_EQ(ring.available_read(), 63);
|
||||
ASSERT_EQ(ring.available_write(), 65);
|
||||
|
||||
rv = ring.enqueue_default(65);
|
||||
|
||||
ASSERT_EQ(rv, 65);
|
||||
ASSERT_EQ(ring.available_read(), 128);
|
||||
ASSERT_EQ(ring.available_write(), 0);
|
||||
|
||||
rv = ring.dequeue(nullptr, 63);
|
||||
|
||||
ASSERT_EQ(ring.available_read(), 65);
|
||||
ASSERT_EQ(ring.available_write(), 63);
|
||||
|
||||
rv = ring.dequeue(nullptr, 65);
|
||||
|
||||
ASSERT_EQ(ring.available_read(), 0);
|
||||
ASSERT_EQ(ring.available_write(), 128);
|
||||
}
|
||||
|
||||
void test_reset_api() {
|
||||
const size_t ring_buffer_size = 128;
|
||||
const size_t enqueue_size = ring_buffer_size / 2;
|
||||
|
||||
lock_free_queue<float> ring(ring_buffer_size);
|
||||
std::thread t([=, &ring] {
|
||||
std::unique_ptr<float[]> in_buffer(new float[enqueue_size]);
|
||||
ring.enqueue(in_buffer.get(), enqueue_size);
|
||||
});
|
||||
|
||||
t.join();
|
||||
|
||||
ring.reset_thread_ids();
|
||||
|
||||
// Enqueue with a different thread. We have reset the thread ID
|
||||
// in the ring buffer, this should work.
|
||||
std::thread t2([=, &ring] {
|
||||
std::unique_ptr<float[]> in_buffer(new float[enqueue_size]);
|
||||
ring.enqueue(in_buffer.get(), enqueue_size);
|
||||
});
|
||||
|
||||
t2.join();
|
||||
|
||||
ASSERT_TRUE(true);
|
||||
}
|
||||
|
||||
TEST(cubeb, ring_buffer)
|
||||
{
|
||||
/* Basic API test. */
|
||||
const int min_channels = 1;
|
||||
const int max_channels = 10;
|
||||
const int min_capacity = 199;
|
||||
const int max_capacity = 1277;
|
||||
const int capacity_increment = 27;
|
||||
|
||||
lock_free_queue<float> q1(128);
|
||||
basic_api_test(q1);
|
||||
lock_free_queue<short> q2(128);
|
||||
basic_api_test(q2);
|
||||
|
||||
for (size_t channels = min_channels; channels < max_channels; channels++) {
|
||||
lock_free_audio_ring_buffer<float> q3(channels, 128);
|
||||
basic_api_test(q3);
|
||||
lock_free_audio_ring_buffer<short> q4(channels, 128);
|
||||
basic_api_test(q4);
|
||||
}
|
||||
|
||||
/* Single thread testing. */
|
||||
/* Test mono to 9.1 */
|
||||
for (size_t channels = min_channels; channels < max_channels; channels++) {
|
||||
/* Use non power-of-two numbers to catch edge-cases. */
|
||||
for (size_t capacity_frames = min_capacity;
|
||||
capacity_frames < max_capacity; capacity_frames+=capacity_increment) {
|
||||
lock_free_audio_ring_buffer<float> ring(channels, capacity_frames);
|
||||
test_ring(ring, channels, capacity_frames);
|
||||
}
|
||||
}
|
||||
|
||||
/* Multi thread testing */
|
||||
for (size_t channels = min_channels; channels < max_channels; channels++) {
|
||||
/* Use non power-of-two numbers to catch edge-cases. */
|
||||
for (size_t capacity_frames = min_capacity;
|
||||
capacity_frames < max_capacity; capacity_frames+=capacity_increment) {
|
||||
lock_free_audio_ring_buffer<short> ring(channels, capacity_frames);
|
||||
test_ring_multi(ring, channels, capacity_frames);
|
||||
}
|
||||
}
|
||||
|
||||
test_reset_api();
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Usage: sh update.sh <upstream_src_directory>
|
||||
|
||||
set -e
|
||||
|
||||
[[ -n "$1" ]] || ( echo "syntax: $0 update_src_directory"; exit 1 )
|
||||
[[ -e "$1/src/cubeb.c" ]] || ( echo "$1: cubeb not found"; exit 1 )
|
||||
|
||||
cp $1/AUTHORS .
|
||||
cp $1/LICENSE .
|
||||
cp $1/README.md .
|
||||
cp $1/include/cubeb/cubeb.h include
|
||||
cp $1/src/android/sles_definitions.h src/android
|
||||
cp $1/src/cubeb-internal.h src
|
||||
cp $1/src/cubeb-speex-resampler.h src
|
||||
cp $1/src/cubeb.c src
|
||||
cp $1/src/cubeb_aaudio.cpp src
|
||||
cp $1/src/cubeb_alsa.c src
|
||||
cp $1/src/cubeb_array_queue.h src
|
||||
cp $1/src/cubeb_audiounit.cpp src
|
||||
cp $1/src/cubeb_jack.cpp src
|
||||
cp $1/src/cubeb_log.cpp src
|
||||
cp $1/src/cubeb_log.h src
|
||||
cp $1/src/cubeb_mixer.cpp src
|
||||
cp $1/src/cubeb_mixer.h src
|
||||
cp $1/src/cubeb_opensl.c src
|
||||
cp $1/src/cubeb_android.h src
|
||||
cp $1/src/cubeb-jni.cpp src
|
||||
cp $1/src/cubeb-jni.h src
|
||||
cp $1/src/android/cubeb-output-latency.h src/android
|
||||
cp $1/src/android/cubeb_media_library.h src/android
|
||||
cp $1/src/cubeb_oss.c src
|
||||
cp $1/src/cubeb_osx_run_loop.h src
|
||||
cp $1/src/cubeb_resampler.cpp src
|
||||
cp $1/src/cubeb_resampler.h src
|
||||
cp $1/src/cubeb_resampler_internal.h src
|
||||
cp $1/src/cubeb_ring_array.h src
|
||||
cp $1/src/cubeb_ringbuffer.h src
|
||||
cp $1/src/cubeb_sndio.c src
|
||||
cp $1/src/cubeb_strings.c src
|
||||
cp $1/src/cubeb_strings.h src
|
||||
cp $1/src/cubeb_sun.c src
|
||||
cp $1/src/cubeb_utils.h src
|
||||
cp $1/src/cubeb_utils.cpp src
|
||||
cp $1/src/cubeb_utils_unix.h src
|
||||
cp $1/src/cubeb_utils_win.h src
|
||||
cp $1/src/cubeb_wasapi.cpp src
|
||||
cp $1/src/cubeb_winmm.c src
|
||||
cp $1/test/common.h gtest
|
||||
cp $1/test/test_audio.cpp gtest
|
||||
cp $1/test/test_devices.cpp gtest
|
||||
cp $1/test/test_duplex.cpp gtest
|
||||
cp $1/test/test_latency.cpp gtest
|
||||
cp $1/test/test_loopback.cpp gtest
|
||||
cp $1/test/test_overload_callback.cpp gtest
|
||||
cp $1/test/test_record.cpp gtest
|
||||
cp $1/test/test_resampler.cpp gtest
|
||||
cp $1/test/test_ring_array.cpp gtest
|
||||
cp $1/test/test_sanity.cpp gtest
|
||||
cp $1/test/test_tone.cpp gtest
|
||||
cp $1/test/test_utils.cpp gtest
|
||||
|
||||
if [ -d $1/.git ]; then
|
||||
rev=$(cd $1 && git rev-parse --verify HEAD)
|
||||
date=$(cd $1 && git show -s --format=%ci HEAD)
|
||||
dirty=$(cd $1 && git diff-index --name-only HEAD)
|
||||
set +e
|
||||
pre_rev=$(grep -o '[[:xdigit:]]\{40\}' moz.yaml)
|
||||
commits=$(cd $1 && git log --pretty=format:'%h - %s' $pre_rev..$rev)
|
||||
set -e
|
||||
fi
|
||||
|
||||
if [ -n "$rev" ]; then
|
||||
version=$rev
|
||||
if [ -n "$dirty" ]; then
|
||||
version=$version-dirty
|
||||
echo "WARNING: updating from a dirty git repository."
|
||||
fi
|
||||
sed -i.bak -e "s/^ *release:.*/ release: \"$version ($date)\"/" moz.yaml
|
||||
if [[ ! "$( grep "$version" moz.yaml )" ]]; then
|
||||
echo "Updating moz.yaml failed."
|
||||
exit 1
|
||||
fi
|
||||
rm -f moz.yaml.bak
|
||||
[[ -n "$commits" ]] && echo -e "Pick commits:\n$commits"
|
||||
else
|
||||
echo "Remember to update moz.yaml with the version details."
|
||||
fi
|
Загрузка…
Ссылка в новой задаче