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:
Matthew Gregan 2022-03-31 10:13:59 +00:00
Родитель 720131f0e9
Коммит bef5976c4e
29 изменённых файлов: 947 добавлений и 227 удалений

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

@ -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,
&params, 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