early-access version 2824
This commit is contained in:
10
externals/cubeb/.github/workflows/build.yml
vendored
10
externals/cubeb/.github/workflows/build.yml
vendored
@@ -9,11 +9,11 @@ jobs:
|
||||
BUILD_TYPE: ${{ matrix.type }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-20.04, windows-2019, macos-10.15]
|
||||
os: [ubuntu-20.04, windows-2019, macos-11]
|
||||
type: [Release, Debug]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
@@ -46,13 +46,13 @@ jobs:
|
||||
matrix:
|
||||
type: [Release, Debug]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-26
|
||||
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-28
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
check_format:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
|
10
externals/cubeb/INSTALL.md
vendored
10
externals/cubeb/INSTALL.md
vendored
@@ -5,9 +5,9 @@ You must have CMake v3.1 or later installed.
|
||||
1. `git clone --recursive https://github.com/mozilla/cubeb.git`
|
||||
2. `mkdir cubeb-build`
|
||||
3. `cd cubeb-build`
|
||||
3. `cmake ../cubeb`
|
||||
4. `cmake --build .`
|
||||
5. `ctest`
|
||||
4. `cmake ../cubeb`
|
||||
5. `cmake --build .`
|
||||
6. `ctest`
|
||||
|
||||
# Windows build notes
|
||||
|
||||
@@ -41,6 +41,6 @@ To build with MinGW-w64, install the following items:
|
||||
- Download and install MinGW-w64 with Win32 threads.
|
||||
- Download and install CMake.
|
||||
- Run MinGW-w64 Terminal from the Start Menu.
|
||||
- Follow the build steps at the top of this file, but at step 3 run:
|
||||
`cmake -G "MinGW Makefiles" ..`
|
||||
- Follow the build steps at the top of this file, but at step 4 run:
|
||||
`cmake -G "MinGW Makefiles" ../cubeb`
|
||||
- Continue the build steps at the top of this file.
|
||||
|
2
externals/cubeb/README.md
vendored
2
externals/cubeb/README.md
vendored
@@ -2,6 +2,6 @@
|
||||
|
||||
See INSTALL.md for build instructions.
|
||||
|
||||
See [Backend Support](https://github.com/kinetiknz/cubeb/wiki/Backend-Support) in the wiki for the support level of each backend.
|
||||
See [Backend Support](https://github.com/mozilla/cubeb/wiki/Backend-Support) in the wiki for the support level of each backend.
|
||||
|
||||
Licensed under an ISC-style license. See LICENSE for details.
|
||||
|
7
externals/cubeb/src/cubeb_audiounit.cpp
vendored
7
externals/cubeb/src/cubeb_audiounit.cpp
vendored
@@ -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
|
||||
|
2
externals/cubeb/src/cubeb_log.cpp
vendored
2
externals/cubeb/src/cubeb_log.cpp
vendored
@@ -123,7 +123,7 @@ cubeb_async_log(char const * fmt, ...)
|
||||
}
|
||||
|
||||
void
|
||||
cubeb_async_log_reset_threads()
|
||||
cubeb_async_log_reset_threads(void)
|
||||
{
|
||||
if (!g_cubeb_log_callback) {
|
||||
return;
|
||||
|
2
externals/cubeb/src/cubeb_log.h
vendored
2
externals/cubeb/src/cubeb_log.h
vendored
@@ -35,7 +35,7 @@ extern cubeb_log_callback g_cubeb_log_callback PRINTF_FORMAT(1, 2);
|
||||
void
|
||||
cubeb_async_log(const char * fmt, ...);
|
||||
void
|
||||
cubeb_async_log_reset_threads();
|
||||
cubeb_async_log_reset_threads(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
4
externals/cubeb/src/cubeb_pulse.c
vendored
4
externals/cubeb/src/cubeb_pulse.c
vendored
@@ -280,6 +280,7 @@ trigger_user_callback(pa_stream * s, void const * input_data, size_t nbytes,
|
||||
if (got < 0) {
|
||||
WRAP(pa_stream_cancel_write)(s);
|
||||
stm->shutdown = 1;
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
return;
|
||||
}
|
||||
// If more iterations move offset of read buffer
|
||||
@@ -392,6 +393,9 @@ stream_read_callback(pa_stream * s, size_t nbytes, void * u)
|
||||
if (got < 0 || (size_t)got != read_frames) {
|
||||
WRAP(pa_stream_cancel_write)(s);
|
||||
stm->shutdown = 1;
|
||||
if (got < 0) {
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
10
externals/cubeb/src/cubeb_ringbuffer.h
vendored
10
externals/cubeb/src/cubeb_ringbuffer.h
vendored
@@ -110,8 +110,8 @@ public:
|
||||
assert_correct_thread(producer_id);
|
||||
#endif
|
||||
|
||||
int rd_idx = read_index_.load(std::memory_order_relaxed);
|
||||
int wr_idx = write_index_.load(std::memory_order_relaxed);
|
||||
int rd_idx = read_index_.load(std::memory_order_acquire);
|
||||
|
||||
if (full_internal(rd_idx, wr_idx)) {
|
||||
return 0;
|
||||
@@ -154,8 +154,8 @@ public:
|
||||
assert_correct_thread(consumer_id);
|
||||
#endif
|
||||
|
||||
int wr_idx = write_index_.load(std::memory_order_acquire);
|
||||
int rd_idx = read_index_.load(std::memory_order_relaxed);
|
||||
int wr_idx = write_index_.load(std::memory_order_acquire);
|
||||
|
||||
if (empty_internal(rd_idx, wr_idx)) {
|
||||
return 0;
|
||||
@@ -172,7 +172,7 @@ public:
|
||||
}
|
||||
|
||||
read_index_.store(increment_index(rd_idx, to_read),
|
||||
std::memory_order_relaxed);
|
||||
std::memory_order_release);
|
||||
|
||||
return to_read;
|
||||
}
|
||||
@@ -190,7 +190,7 @@ public:
|
||||
#endif
|
||||
return available_read_internal(
|
||||
read_index_.load(std::memory_order_relaxed),
|
||||
write_index_.load(std::memory_order_relaxed));
|
||||
write_index_.load(std::memory_order_acquire));
|
||||
}
|
||||
/**
|
||||
* Get the number of available elements for consuming.
|
||||
@@ -205,7 +205,7 @@ public:
|
||||
assert_correct_thread(producer_id);
|
||||
#endif
|
||||
return available_write_internal(
|
||||
read_index_.load(std::memory_order_relaxed),
|
||||
read_index_.load(std::memory_order_acquire),
|
||||
write_index_.load(std::memory_order_relaxed));
|
||||
}
|
||||
/**
|
||||
|
281
externals/cubeb/src/cubeb_wasapi.cpp
vendored
281
externals/cubeb/src/cubeb_wasapi.cpp
vendored
@@ -240,8 +240,9 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info & ret,
|
||||
void
|
||||
wasapi_destroy_device(cubeb_device_info * device_info);
|
||||
static int
|
||||
wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
|
||||
cubeb_device_collection * out);
|
||||
wasapi_enumerate_devices_internal(cubeb * context, cubeb_device_type type,
|
||||
cubeb_device_collection * out,
|
||||
DWORD state_mask);
|
||||
static int
|
||||
wasapi_device_collection_destroy(cubeb * ctx,
|
||||
cubeb_device_collection * collection);
|
||||
@@ -409,12 +410,9 @@ struct cubeb_stream {
|
||||
float volume = 1.0;
|
||||
/* True if the stream is draining. */
|
||||
bool draining = false;
|
||||
/* True when we've destroyed the stream. This pointer is leaked on stream
|
||||
* destruction if we could not join the thread. */
|
||||
std::atomic<std::atomic<bool> *> emergency_bailout{nullptr};
|
||||
/* Synchronizes render thread start to ensure safe access to
|
||||
* emergency_bailout. */
|
||||
HANDLE thread_ready_event = 0;
|
||||
/* If the render thread fails to stop, this is set to true and ownership of
|
||||
* the stm is "leaked" to the render thread for later cleanup. */
|
||||
std::atomic<bool> emergency_bailout{false};
|
||||
/* This needs an active audio input stream to be known, and is updated in the
|
||||
* first audio input callback. */
|
||||
std::atomic<int64_t> input_latency_hns{LATENCY_NOT_AVAILABLE_YET};
|
||||
@@ -753,6 +751,27 @@ private:
|
||||
|
||||
namespace {
|
||||
|
||||
long
|
||||
wasapi_data_callback(cubeb_stream * stm, void * user_ptr,
|
||||
void const * input_buffer, void * output_buffer,
|
||||
long nframes)
|
||||
{
|
||||
if (stm->emergency_bailout) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
return stm->data_callback(stm, user_ptr, input_buffer, output_buffer,
|
||||
nframes);
|
||||
}
|
||||
|
||||
void
|
||||
wasapi_state_callback(cubeb_stream * stm, void * user_ptr, cubeb_state state)
|
||||
{
|
||||
if (stm->emergency_bailout) {
|
||||
return;
|
||||
}
|
||||
return stm->state_callback(stm, user_ptr, state);
|
||||
}
|
||||
|
||||
char const *
|
||||
intern_device_id(cubeb * ctx, wchar_t const * id)
|
||||
{
|
||||
@@ -873,8 +892,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);
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
return out_frames;
|
||||
}
|
||||
|
||||
float volume = 1.0;
|
||||
{
|
||||
@@ -991,7 +1013,7 @@ get_input_buffer(cubeb_stream * stm)
|
||||
(stm->input_stream_params.prefs &
|
||||
CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING) ||
|
||||
!trigger_async_reconfigure(stm)) {
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -1105,7 +1127,7 @@ get_output_buffer(cubeb_stream * stm, void *& buffer, size_t & frame_count)
|
||||
(stm->output_stream_params.prefs &
|
||||
CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING) ||
|
||||
!trigger_async_reconfigure(stm)) {
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -1121,7 +1143,7 @@ get_output_buffer(cubeb_stream * stm, void *& buffer, size_t & frame_count)
|
||||
if (stm->draining) {
|
||||
if (padding_out == 0) {
|
||||
LOG("Draining finished.");
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
|
||||
return false;
|
||||
}
|
||||
LOG("Draining.");
|
||||
@@ -1190,6 +1212,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 +1220,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 +1244,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 +1273,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 +1305,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);
|
||||
@@ -1289,17 +1319,25 @@ refill_callback_output(cubeb_stream * stm)
|
||||
return size_t(got) == output_frames || stm->draining;
|
||||
}
|
||||
|
||||
void
|
||||
wasapi_stream_destroy(cubeb_stream * stm);
|
||||
|
||||
static void
|
||||
handle_emergency_bailout(cubeb_stream * stm)
|
||||
{
|
||||
if (stm->emergency_bailout) {
|
||||
CloseHandle(stm->thread);
|
||||
stm->thread = NULL;
|
||||
CloseHandle(stm->shutdown_event);
|
||||
stm->shutdown_event = 0;
|
||||
wasapi_stream_destroy(stm);
|
||||
_endthreadex(0);
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
|
||||
{
|
||||
cubeb_stream * stm = static_cast<cubeb_stream *>(stream);
|
||||
std::atomic<bool> * emergency_bailout = stm->emergency_bailout;
|
||||
|
||||
// Signal wasapi_stream_start that we've copied emergency_bailout.
|
||||
BOOL ok = SetEvent(stm->thread_ready_event);
|
||||
if (!ok) {
|
||||
LOG("thread_ready SetEvent failed: %lx", GetLastError());
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool is_playing = true;
|
||||
HANDLE wait_array[4] = {stm->shutdown_event, stm->reconfigure_event,
|
||||
@@ -1332,20 +1370,10 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
|
||||
unsigned timeout_count = 0;
|
||||
const unsigned timeout_limit = 3;
|
||||
while (is_playing) {
|
||||
// We want to check the emergency bailout variable before a
|
||||
// and after the WaitForMultipleObject, because the handles
|
||||
// WaitForMultipleObjects is going to wait on might have been closed
|
||||
// already.
|
||||
if (*emergency_bailout) {
|
||||
delete emergency_bailout;
|
||||
return 0;
|
||||
}
|
||||
handle_emergency_bailout(stm);
|
||||
DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array),
|
||||
wait_array, FALSE, 1000);
|
||||
if (*emergency_bailout) {
|
||||
delete emergency_bailout;
|
||||
return 0;
|
||||
}
|
||||
handle_emergency_bailout(stm);
|
||||
if (waitResult != WAIT_TIMEOUT) {
|
||||
timeout_count = 0;
|
||||
}
|
||||
@@ -1355,7 +1383,7 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
|
||||
/* We don't check if the drain is actually finished here, we just want to
|
||||
shutdown. */
|
||||
if (stm->draining) {
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -1417,7 +1445,8 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
|
||||
case WAIT_OBJECT_0 + 3: { /* input available */
|
||||
HRESULT rv = get_input_buffer(stm);
|
||||
if (FAILED(rv)) {
|
||||
return rv;
|
||||
is_playing = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!has_output(stm)) {
|
||||
@@ -1436,18 +1465,29 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
|
||||
break;
|
||||
default:
|
||||
LOG("case %lu not handled in render loop.", waitResult);
|
||||
abort();
|
||||
XASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (FAILED(hr)) {
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
// Stop audio clients since this thread will no longer service
|
||||
// the events.
|
||||
if (stm->output_client) {
|
||||
stm->output_client->Stop();
|
||||
}
|
||||
if (stm->input_client) {
|
||||
stm->input_client->Stop();
|
||||
}
|
||||
|
||||
if (mmcss_handle) {
|
||||
AvRevertMmThreadCharacteristics(mmcss_handle);
|
||||
}
|
||||
|
||||
handle_emergency_bailout(stm);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1696,54 +1736,58 @@ wasapi_init(cubeb ** context, char const * context_name)
|
||||
}
|
||||
|
||||
namespace {
|
||||
enum ShutdownPhase { OnStop, OnDestroy };
|
||||
|
||||
bool
|
||||
stop_and_join_render_thread(cubeb_stream * stm)
|
||||
stop_and_join_render_thread(cubeb_stream * stm, ShutdownPhase phase)
|
||||
{
|
||||
bool rv = true;
|
||||
LOG("Stop and join render thread.");
|
||||
// Only safe to transfer `stm` ownership to the render thread when
|
||||
// the stream is being destroyed by the caller.
|
||||
bool bailout = phase == OnDestroy;
|
||||
|
||||
LOG("%p: Stop and join render thread: %p (%d), phase=%d", stm, stm->thread,
|
||||
stm->emergency_bailout.load(), static_cast<int>(phase));
|
||||
if (!stm->thread) {
|
||||
LOG("No thread present.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we've already leaked the thread, just return,
|
||||
// there is not much we can do.
|
||||
if (!stm->emergency_bailout.load()) {
|
||||
return false;
|
||||
}
|
||||
XASSERT(!stm->emergency_bailout);
|
||||
|
||||
BOOL ok = SetEvent(stm->shutdown_event);
|
||||
if (!ok) {
|
||||
LOG("Destroy SetEvent failed: %lx", GetLastError());
|
||||
LOG("stop_and_join_render_thread: SetEvent failed: %lx", GetLastError());
|
||||
stm->emergency_bailout = bailout;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Wait five seconds for the rendering thread to return. It's supposed to
|
||||
* check its event loop very often, five seconds is rather conservative. */
|
||||
DWORD r = WaitForSingleObject(stm->thread, 5000);
|
||||
* check its event loop very often, five seconds is rather conservative.
|
||||
* Note: 5*1s loop to work around timer sleep issues on pre-Windows 8. */
|
||||
DWORD r;
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
r = WaitForSingleObject(stm->thread, 1000);
|
||||
if (r == WAIT_OBJECT_0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r != WAIT_OBJECT_0) {
|
||||
/* Something weird happened, leak the thread and continue the shutdown
|
||||
* process. */
|
||||
*(stm->emergency_bailout) = true;
|
||||
// We give the ownership to the rendering thread.
|
||||
stm->emergency_bailout = nullptr;
|
||||
LOG("Destroy WaitForSingleObject on thread failed: %lx, %lx", r,
|
||||
GetLastError());
|
||||
rv = false;
|
||||
LOG("stop_and_join_render_thread: WaitForSingleObject on thread failed: "
|
||||
"%lx, %lx",
|
||||
r, GetLastError());
|
||||
stm->emergency_bailout = bailout;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only attempts to close and null out the thread and event if the
|
||||
// WaitForSingleObject above succeeded, so that calling this function again
|
||||
// attemps to clean up the thread and event each time.
|
||||
if (rv) {
|
||||
LOG("Closing thread.");
|
||||
CloseHandle(stm->thread);
|
||||
stm->thread = NULL;
|
||||
// Only attempt to close and null out the thread and event if the
|
||||
// WaitForSingleObject above succeeded.
|
||||
LOG("stop_and_join_render_thread: Closing thread.");
|
||||
CloseHandle(stm->thread);
|
||||
stm->thread = NULL;
|
||||
|
||||
CloseHandle(stm->shutdown_event);
|
||||
stm->shutdown_event = 0;
|
||||
}
|
||||
CloseHandle(stm->shutdown_event);
|
||||
stm->shutdown_event = 0;
|
||||
|
||||
return rv;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1881,9 +1925,6 @@ wasapi_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
void
|
||||
wasapi_stream_destroy(cubeb_stream * stm);
|
||||
|
||||
static void
|
||||
waveformatex_update_derived_properties(WAVEFORMATEX * format)
|
||||
{
|
||||
@@ -2259,8 +2300,12 @@ setup_wasapi_stream_one_side(cubeb_stream * stm,
|
||||
if (wasapi_create_device(stm->context, device_info,
|
||||
stm->device_enumerator.get(), device.get(),
|
||||
&default_devices) == CUBEB_OK) {
|
||||
if (device_info.latency_hi == 0) {
|
||||
LOG("Input: could not query latency_hi to guess safe latency");
|
||||
wasapi_destroy_device(&device_info);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
// This multiplicator has been found empirically.
|
||||
XASSERT(device_info.latency_hi > 0);
|
||||
uint32_t latency_frames = device_info.latency_hi * 8;
|
||||
LOG("Input: latency increased to %u frames from a default of %u",
|
||||
latency_frames, device_info.latency_hi);
|
||||
@@ -2362,10 +2407,10 @@ wasapi_find_bt_handsfree_output_device(cubeb_stream * stm)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int rv = wasapi_enumerate_devices(
|
||||
int rv = wasapi_enumerate_devices_internal(
|
||||
stm->context,
|
||||
(cubeb_device_type)(CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT),
|
||||
&collection);
|
||||
&collection, DEVICE_STATE_ACTIVE);
|
||||
if (rv != CUBEB_OK) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -2561,7 +2606,7 @@ setup_wasapi_stream(cubeb_stream * stm)
|
||||
stm->resampler.reset(cubeb_resampler_create(
|
||||
stm, has_input(stm) ? &input_params : nullptr,
|
||||
has_output(stm) && !stm->has_dummy_output ? &output_params : nullptr,
|
||||
target_sample_rate, stm->data_callback, stm->user_ptr,
|
||||
target_sample_rate, wasapi_data_callback, stm->user_ptr,
|
||||
stm->voice ? CUBEB_RESAMPLER_QUALITY_VOIP
|
||||
: CUBEB_RESAMPLER_QUALITY_DESKTOP,
|
||||
CUBEB_RESAMPLER_RECLOCK_NONE));
|
||||
@@ -2794,33 +2839,25 @@ wasapi_stream_destroy(cubeb_stream * stm)
|
||||
XASSERT(stm);
|
||||
LOG("Stream destroy (%p)", stm);
|
||||
|
||||
// Only free stm->emergency_bailout if we could join the thread.
|
||||
// If we could not join the thread, stm->emergency_bailout is true
|
||||
// and is still alive until the thread wakes up and exits cleanly.
|
||||
if (stop_and_join_render_thread(stm)) {
|
||||
delete stm->emergency_bailout.load();
|
||||
stm->emergency_bailout = nullptr;
|
||||
if (!stop_and_join_render_thread(stm, OnDestroy)) {
|
||||
// Emergency bailout: render thread becomes responsible for calling
|
||||
// wasapi_stream_destroy.
|
||||
return;
|
||||
}
|
||||
|
||||
if (stm->notification_client) {
|
||||
unregister_notification_client(stm);
|
||||
}
|
||||
|
||||
CloseHandle(stm->reconfigure_event);
|
||||
CloseHandle(stm->refill_event);
|
||||
CloseHandle(stm->input_available_event);
|
||||
|
||||
// The variables intialized in wasapi_stream_init,
|
||||
// must be destroyed in wasapi_stream_destroy.
|
||||
stm->linear_input_buffer.reset();
|
||||
|
||||
stm->device_enumerator = nullptr;
|
||||
|
||||
{
|
||||
auto_lock lock(stm->stream_reset_lock);
|
||||
close_wasapi_stream(stm);
|
||||
}
|
||||
|
||||
CloseHandle(stm->reconfigure_event);
|
||||
CloseHandle(stm->refill_event);
|
||||
CloseHandle(stm->input_available_event);
|
||||
|
||||
delete stm;
|
||||
}
|
||||
|
||||
@@ -2875,8 +2912,6 @@ wasapi_stream_start(cubeb_stream * stm)
|
||||
XASSERT(stm && !stm->thread && !stm->shutdown_event);
|
||||
XASSERT(stm->output_client || stm->input_client);
|
||||
|
||||
stm->emergency_bailout = new std::atomic<bool>(false);
|
||||
|
||||
if (stm->output_client) {
|
||||
int rv = stream_start_one_side(stm, OUTPUT);
|
||||
if (rv != CUBEB_OK) {
|
||||
@@ -2897,30 +2932,18 @@ wasapi_stream_start(cubeb_stream * stm)
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
stm->thread_ready_event = CreateEvent(NULL, 0, 0, NULL);
|
||||
if (!stm->thread_ready_event) {
|
||||
LOG("Can't create the thread_ready event, error: %lx", GetLastError());
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
cubeb_async_log_reset_threads();
|
||||
stm->thread =
|
||||
(HANDLE)_beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm,
|
||||
STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
|
||||
if (stm->thread == NULL) {
|
||||
LOG("could not create WASAPI render thread.");
|
||||
CloseHandle(stm->shutdown_event);
|
||||
stm->shutdown_event = 0;
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
// Wait for wasapi_stream_render_loop to signal that emergency_bailout has
|
||||
// been read, avoiding a bailout situation where we could free `stm`
|
||||
// before wasapi_stream_render_loop had a chance to run.
|
||||
HRESULT hr = WaitForSingleObject(stm->thread_ready_event, INFINITE);
|
||||
XASSERT(hr == WAIT_OBJECT_0);
|
||||
CloseHandle(stm->thread_ready_event);
|
||||
stm->thread_ready_event = 0;
|
||||
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
@@ -2950,15 +2973,12 @@ wasapi_stream_stop(cubeb_stream * stm)
|
||||
}
|
||||
}
|
||||
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
|
||||
}
|
||||
|
||||
if (stop_and_join_render_thread(stm)) {
|
||||
delete stm->emergency_bailout.load();
|
||||
stm->emergency_bailout = nullptr;
|
||||
} else {
|
||||
if (!stop_and_join_render_thread(stm, OnStop)) {
|
||||
// If we could not join the thread, put the stream in error.
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
@@ -3324,8 +3344,9 @@ wasapi_destroy_device(cubeb_device_info * device)
|
||||
}
|
||||
|
||||
static int
|
||||
wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
|
||||
cubeb_device_collection * out)
|
||||
wasapi_enumerate_devices_internal(cubeb * context, cubeb_device_type type,
|
||||
cubeb_device_collection * out,
|
||||
DWORD state_mask)
|
||||
{
|
||||
com_ptr<IMMDeviceEnumerator> enumerator;
|
||||
com_ptr<IMMDeviceCollection> collection;
|
||||
@@ -3353,8 +3374,7 @@ wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
hr = enumerator->EnumAudioEndpoints(flow, DEVICE_STATEMASK_ALL,
|
||||
collection.receive());
|
||||
hr = enumerator->EnumAudioEndpoints(flow, state_mask, collection.receive());
|
||||
if (FAILED(hr)) {
|
||||
LOG("Could not enumerate audio endpoints: %lx", hr);
|
||||
return CUBEB_ERROR;
|
||||
@@ -3388,6 +3408,15 @@ wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
|
||||
cubeb_device_collection * out)
|
||||
{
|
||||
return wasapi_enumerate_devices_internal(
|
||||
context, type, out,
|
||||
DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED | DEVICE_STATE_UNPLUGGED);
|
||||
}
|
||||
|
||||
static int
|
||||
wasapi_device_collection_destroy(cubeb * /*ctx*/,
|
||||
cubeb_device_collection * collection)
|
||||
|
38
externals/cubeb/test/test_callback_ret.cpp
vendored
38
externals/cubeb/test/test_callback_ret.cpp
vendored
@@ -34,6 +34,7 @@ enum test_direction {
|
||||
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
|
||||
@@ -98,10 +99,30 @@ long data_cb_ret_nframes(cubeb_stream * stream, void * user, const void * inputb
|
||||
return nframes;
|
||||
}
|
||||
|
||||
void state_cb_ret(cubeb_stream * stream, void * /*user*/, cubeb_state state)
|
||||
// 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:
|
||||
@@ -110,11 +131,13 @@ void state_cb_ret(cubeb_stream * stream, void * /*user*/, cubeb_state state)
|
||||
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);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void run_test_callback(test_direction direction,
|
||||
@@ -183,6 +206,10 @@ void run_test_callback(test_direction direction,
|
||||
|
||||
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)
|
||||
@@ -190,6 +217,8 @@ 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)
|
||||
@@ -197,6 +226,8 @@ 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)
|
||||
@@ -204,4 +235,5 @@ 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");
|
||||
}
|
||||
|
Reference in New Issue
Block a user