mirror of
https://github.com/ManchildProductions/UXP-Fixed.git
synced 2026-05-26 19:27:22 +00:00
+11
-20
@@ -50,11 +50,11 @@ enum class CubebState {
|
||||
Shutdown
|
||||
} sCubebState = CubebState::Uninitialized;
|
||||
cubeb* sCubebContext;
|
||||
double sVolumeScale = 1.0;
|
||||
uint32_t sCubebPlaybackLatencyInMilliseconds = 100;
|
||||
uint32_t sCubebMSGLatencyInFrames = 512;
|
||||
bool sCubebPlaybackLatencyPrefSet = false;
|
||||
bool sCubebMSGLatencyPrefSet = false;
|
||||
double sVolumeScale;
|
||||
uint32_t sCubebPlaybackLatencyInMilliseconds;
|
||||
uint32_t sCubebMSGLatencyInFrames;
|
||||
bool sCubebPlaybackLatencyPrefSet;
|
||||
bool sCubebMSGLatencyPrefSet;
|
||||
bool sAudioStreamInitEverSucceeded = false;
|
||||
StaticAutoPtr<char> sBrandName;
|
||||
|
||||
@@ -227,7 +227,7 @@ cubeb* GetCubebContextUnlocked()
|
||||
sBrandName, "Did not initialize sbrandName, and not on the main thread?");
|
||||
}
|
||||
|
||||
int rv = cubeb_init(&sCubebContext, sBrandName, nullptr);
|
||||
int rv = cubeb_init(&sCubebContext, sBrandName);
|
||||
NS_WARNING_ASSERTION(rv == CUBEB_OK, "Could not get a cubeb context.");
|
||||
sCubebState = (rv == CUBEB_OK) ? CubebState::Initialized : CubebState::Uninitialized;
|
||||
|
||||
@@ -258,23 +258,14 @@ bool CubebMSGLatencyPrefSet()
|
||||
return sCubebMSGLatencyPrefSet;
|
||||
}
|
||||
|
||||
uint32_t GetCubebMSGLatencyInFrames(cubeb_stream_params * params)
|
||||
Maybe<uint32_t> GetCubebMSGLatencyInFrames()
|
||||
{
|
||||
StaticMutexAutoLock lock(sMutex);
|
||||
if (sCubebMSGLatencyPrefSet) {
|
||||
MOZ_ASSERT(sCubebMSGLatencyInFrames > 0);
|
||||
return sCubebMSGLatencyInFrames;
|
||||
if (!sCubebMSGLatencyPrefSet) {
|
||||
return Maybe<uint32_t>();
|
||||
}
|
||||
cubeb* context = GetCubebContextUnlocked();
|
||||
if (!context) {
|
||||
return sCubebMSGLatencyInFrames; // default 512
|
||||
}
|
||||
uint32_t latency_frames = 0;
|
||||
if (cubeb_get_min_latency(context, params, &latency_frames) != CUBEB_OK) {
|
||||
NS_WARNING("Could not get minimal latency from cubeb.");
|
||||
return sCubebMSGLatencyInFrames; // default 512
|
||||
}
|
||||
return latency_frames;
|
||||
MOZ_ASSERT(sCubebMSGLatencyInFrames > 0);
|
||||
return Some(sCubebMSGLatencyInFrames);
|
||||
}
|
||||
|
||||
void InitLibrary()
|
||||
|
||||
@@ -36,7 +36,7 @@ bool GetFirstStream();
|
||||
cubeb* GetCubebContext();
|
||||
cubeb* GetCubebContextUnlocked();
|
||||
uint32_t GetCubebPlaybackLatencyInMilliseconds();
|
||||
uint32_t GetCubebMSGLatencyInFrames(cubeb_stream_params * params);
|
||||
Maybe<uint32_t> GetCubebMSGLatencyInFrames();
|
||||
bool CubebLatencyPrefSet();
|
||||
void GetCurrentBackend(nsAString& aBackend);
|
||||
|
||||
|
||||
@@ -593,6 +593,7 @@ AudioCallbackDriver::Init()
|
||||
|
||||
cubeb_stream_params output;
|
||||
cubeb_stream_params input;
|
||||
uint32_t latency_frames;
|
||||
|
||||
MOZ_ASSERT(!NS_IsMainThread(),
|
||||
"This is blocking and should never run on the main thread.");
|
||||
@@ -608,7 +609,14 @@ AudioCallbackDriver::Init()
|
||||
output.format = CUBEB_SAMPLE_FLOAT32NE;
|
||||
}
|
||||
|
||||
uint32_t latency_frames = CubebUtils::GetCubebMSGLatencyInFrames(&output);
|
||||
Maybe<uint32_t> latencyPref = CubebUtils::GetCubebMSGLatencyInFrames();
|
||||
if (latencyPref) {
|
||||
latency_frames = latencyPref.value();
|
||||
} else {
|
||||
if (cubeb_get_min_latency(cubebContext, output, &latency_frames) != CUBEB_OK) {
|
||||
NS_WARNING("Could not get minimal latency from cubeb.");
|
||||
}
|
||||
}
|
||||
|
||||
input = output;
|
||||
input.channels = mInputChannels; // change to support optional stereo capture
|
||||
|
||||
@@ -155,6 +155,7 @@ cubeb_stream_start
|
||||
cubeb_stream_stop
|
||||
cubeb_stream_get_latency
|
||||
cubeb_stream_set_volume
|
||||
cubeb_stream_set_panning
|
||||
cubeb_stream_get_current_device
|
||||
cubeb_stream_device_destroy
|
||||
cubeb_stream_register_device_changed_callback
|
||||
|
||||
@@ -13,4 +13,3 @@ Landry Breuil <landry@openbsd.org>
|
||||
Jacek Caban <jacek@codeweavers.com>
|
||||
Paul Hancock <Paul.Hancock.17041993@live.com>
|
||||
Ted Mielczarek <ted@mielczarek.org>
|
||||
Chun-Min Chang <chun.m.chang@gmail.com>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
[](https://github.com/mozilla/cubeb/actions/workflows/build.yml)
|
||||
[](https://travis-ci.org/kinetiknz/cubeb)
|
||||
[](https://ci.appveyor.com/project/kinetiknz/cubeb/branch/master)
|
||||
|
||||
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.
|
||||
|
||||
Licensed under an ISC-style license. See LICENSE for details.
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
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
|
||||
moz.build build files for the Mozilla build system.
|
||||
|
||||
The cubeb git repository is: https://github.com/mozilla/cubeb.git
|
||||
|
||||
The git commit ID used was 6ce95962c8bb1442a725cbacdc77d5d8cbce43ad.
|
||||
@@ -0,0 +1,8 @@
|
||||
The source from this directory was copied from the cubeb
|
||||
git repository using the update.sh script. The only changes
|
||||
made were those applied by update.sh and the addition of
|
||||
Makefile.in build files for the Mozilla build system.
|
||||
|
||||
The cubeb git repository is: git://github.com/kinetiknz/cubeb.git
|
||||
|
||||
The git commit ID used was f8467510a8b36793b1b8b7e85461e2e189eb7015.
|
||||
@@ -0,0 +1,46 @@
|
||||
commit 2c7617f5ca20b764c605e19af490889c761e65e2
|
||||
Author: Matthew Gregan <kinetik@flim.org>
|
||||
Date: Thu Nov 10 19:07:07 2016 +1300
|
||||
|
||||
pulse: Bail early from pulse_defer_event_cb when shutting down.
|
||||
|
||||
When starting a stream, trigger_user_callback may be called from
|
||||
stream_write_callback and immediately enter a drain situation, creating
|
||||
a drain timer and setting shutdown to true. If pulse_defer_event_cb
|
||||
then runs without checking for shutdown, it can overwrite the current
|
||||
timer with a new timer, resulting in a leaked timer and a null pointer
|
||||
assertion.
|
||||
|
||||
diff --git a/src/cubeb_pulse.c b/src/cubeb_pulse.c
|
||||
index 5b61bda..86f2ba3 100644
|
||||
--- a/src/cubeb_pulse.c
|
||||
+++ b/src/cubeb_pulse.c
|
||||
@@ -181,9 +181,9 @@ static void
|
||||
stream_drain_callback(pa_mainloop_api * a, pa_time_event * e, struct timeval const * tv, void * u)
|
||||
{
|
||||
(void)a;
|
||||
- (void)e;
|
||||
(void)tv;
|
||||
cubeb_stream * stm = u;
|
||||
+ assert(stm->drain_timer == e);
|
||||
stream_state_change_callback(stm, CUBEB_STATE_DRAINED);
|
||||
/* there's no pa_rttime_free, so use this instead. */
|
||||
a->time_free(stm->drain_timer);
|
||||
@@ -267,6 +267,7 @@ trigger_user_callback(pa_stream * s, void const * input_data, size_t nbytes, cub
|
||||
assert(r == 0 || r == -PA_ERR_NODATA);
|
||||
/* pa_stream_drain is useless, see PA bug# 866. this is a workaround. */
|
||||
/* arbitrary safety margin: double the current latency. */
|
||||
+ assert(!stm->drain_timer);
|
||||
stm->drain_timer = WRAP(pa_context_rttime_new)(stm->context->context, WRAP(pa_rtclock_now)() + 2 * latency, stream_drain_callback, stm);
|
||||
stm->shutdown = 1;
|
||||
return;
|
||||
@@ -851,6 +852,9 @@ pulse_defer_event_cb(pa_mainloop_api * a, void * userdata)
|
||||
{
|
||||
(void)a;
|
||||
cubeb_stream * stm = userdata;
|
||||
+ if (stm->shutdown) {
|
||||
+ return;
|
||||
+ }
|
||||
size_t writable_size = WRAP(pa_stream_writable_size)(stm->output_stream);
|
||||
trigger_user_callback(stm->output_stream, NULL, writable_size, stm);
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
From 37ce70d4400a2ab6b59ee432b41d4ffcc9d136ff Mon Sep 17 00:00:00 2001
|
||||
From: Paul Adenot <paul@paul.cx>
|
||||
Date: Thu, 10 Nov 2016 21:45:14 +0100
|
||||
Subject: [PATCH] Bail out safely from the rendering loop if we could not join
|
||||
the rendering thread in time (#187)
|
||||
|
||||
Bail out safely from the rendering loop if we could not join the rendering thread in time.
|
||||
---
|
||||
src/cubeb_wasapi.cpp | 41 ++++++++++++++++++++++++++++++++++++-----
|
||||
1 file changed, 36 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/src/cubeb_wasapi.cpp b/src/cubeb_wasapi.cpp
|
||||
index 9e689b9..519d5ca 100644
|
||||
--- a/src/cubeb_wasapi.cpp
|
||||
+++ b/src/cubeb_wasapi.cpp
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
+#include <atomic>
|
||||
|
||||
#include "cubeb/cubeb.h"
|
||||
#include "cubeb-internal.h"
|
||||
@@ -220,9 +221,11 @@ struct cubeb_stream
|
||||
float volume;
|
||||
/* True if the stream is draining. */
|
||||
bool draining;
|
||||
+ /* 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;
|
||||
};
|
||||
|
||||
-
|
||||
class wasapi_endpoint_notification_client : public IMMNotificationClient
|
||||
{
|
||||
public:
|
||||
@@ -781,6 +784,7 @@ 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;
|
||||
|
||||
bool is_playing = true;
|
||||
HANDLE wait_array[4] = {
|
||||
@@ -820,6 +824,10 @@ wasapi_stream_render_loop(LPVOID stream)
|
||||
wait_array,
|
||||
FALSE,
|
||||
1000);
|
||||
+ if (*emergency_bailout) {
|
||||
+ delete emergency_bailout;
|
||||
+ return 0;
|
||||
+ }
|
||||
if (waitResult != WAIT_TIMEOUT) {
|
||||
timeout_count = 0;
|
||||
}
|
||||
@@ -1134,12 +1142,13 @@ int wasapi_init(cubeb ** context, char const * context_name)
|
||||
}
|
||||
|
||||
namespace {
|
||||
-void stop_and_join_render_thread(cubeb_stream * stm)
|
||||
+bool stop_and_join_render_thread(cubeb_stream * stm)
|
||||
{
|
||||
+ bool rv = true;
|
||||
LOG("Stop and join render thread.");
|
||||
if (!stm->thread) {
|
||||
LOG("No thread present.");
|
||||
- return;
|
||||
+ return true;
|
||||
}
|
||||
|
||||
BOOL ok = SetEvent(stm->shutdown_event);
|
||||
@@ -1153,11 +1162,15 @@ void stop_and_join_render_thread(cubeb_stream * stm)
|
||||
if (r == WAIT_TIMEOUT) {
|
||||
/* Something weird happened, leak the thread and continue the shutdown
|
||||
* process. */
|
||||
+ *(stm->emergency_bailout) = true;
|
||||
LOG("Destroy WaitForSingleObject on thread timed out,"
|
||||
" leaking the thread: %d", GetLastError());
|
||||
+ rv = false;
|
||||
}
|
||||
if (r == WAIT_FAILED) {
|
||||
+ *(stm->emergency_bailout) = true;
|
||||
LOG("Destroy WaitForSingleObject on thread failed: %d", GetLastError());
|
||||
+ rv = false;
|
||||
}
|
||||
|
||||
LOG("Closing thread.");
|
||||
@@ -1167,6 +1180,8 @@ void stop_and_join_render_thread(cubeb_stream * stm)
|
||||
|
||||
CloseHandle(stm->shutdown_event);
|
||||
stm->shutdown_event = 0;
|
||||
+
|
||||
+ return rv;
|
||||
}
|
||||
|
||||
void wasapi_destroy(cubeb * context)
|
||||
@@ -1775,7 +1790,16 @@ void wasapi_stream_destroy(cubeb_stream * stm)
|
||||
{
|
||||
XASSERT(stm);
|
||||
|
||||
- stop_and_join_render_thread(stm);
|
||||
+ // Only free stm->emergency_bailout if we could not 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;
|
||||
+ } else {
|
||||
+ // If we're leaking, it must be that this is true.
|
||||
+ assert(*(stm->emergency_bailout));
|
||||
+ }
|
||||
|
||||
unregister_notification_client(stm);
|
||||
|
||||
@@ -1844,6 +1868,8 @@ int wasapi_stream_start(cubeb_stream * stm)
|
||||
|
||||
auto_lock lock(stm->stream_reset_lock);
|
||||
|
||||
+ stm->emergency_bailout = new std::atomic<bool>(false);
|
||||
+
|
||||
if (stm->output_client) {
|
||||
int rv = stream_start_one_side(stm, OUTPUT);
|
||||
if (rv != CUBEB_OK) {
|
||||
@@ -1903,7 +1929,12 @@ int wasapi_stream_stop(cubeb_stream * stm)
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
|
||||
}
|
||||
|
||||
- stop_and_join_render_thread(stm);
|
||||
+ if (stop_and_join_render_thread(stm)) {
|
||||
+ if (stm->emergency_bailout.load()) {
|
||||
+ delete stm->emergency_bailout.load();
|
||||
+ stm->emergency_bailout = nullptr;
|
||||
+ }
|
||||
+ }
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
--
|
||||
2.7.4
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
This patch fixes three different crashes, one crash per chunk in this patch,
|
||||
in the same order.
|
||||
- Bug 1342389
|
||||
- Bug 1345147
|
||||
- Bug 1347453
|
||||
|
||||
diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp
|
||||
--- a/media/libcubeb/src/cubeb_wasapi.cpp
|
||||
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
|
||||
@@ -878,16 +878,23 @@ wasapi_stream_render_loop(LPVOID stream)
|
||||
|
||||
/* WaitForMultipleObjects timeout can trigger in cases where we don't want to
|
||||
treat it as a timeout, such as across a system sleep/wake cycle. Trigger
|
||||
the timeout error handling only when the timeout_limit is reached, which is
|
||||
reset on each successful loop. */
|
||||
unsigned timeout_count = 0;
|
||||
const unsigned timeout_limit = 5;
|
||||
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;
|
||||
+ }
|
||||
DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array),
|
||||
wait_array,
|
||||
FALSE,
|
||||
1000);
|
||||
if (*emergency_bailout) {
|
||||
delete emergency_bailout;
|
||||
return 0;
|
||||
}
|
||||
@@ -1199,16 +1206,22 @@ bool stop_and_join_render_thread(cubeb_s
|
||||
{
|
||||
bool rv = true;
|
||||
LOG("Stop and join render thread.");
|
||||
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;
|
||||
+ }
|
||||
+
|
||||
BOOL ok = SetEvent(stm->shutdown_event);
|
||||
if (!ok) {
|
||||
LOG("Destroy SetEvent failed: %lx", GetLastError());
|
||||
}
|
||||
|
||||
/* 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);
|
||||
diff --git a/media/libcubeb/update.sh b/media/libcubeb/update.sh
|
||||
--- a/media/libcubeb/update.sh
|
||||
+++ b/media/libcubeb/update.sh
|
||||
@@ -66,8 +66,11 @@ fi
|
||||
echo "Applying a patch on top of $version"
|
||||
patch -p1 < ./wasapi-drift-fix-passthrough-resampler.patch
|
||||
|
||||
echo "Applying a patch on top of $version"
|
||||
patch -p1 < ./audiounit-drift-fix.patch
|
||||
|
||||
echo "Applying a patch on top of $version"
|
||||
patch -p1 < ./uplift-wasapi-fixes-aurora.patch
|
||||
+
|
||||
+echo "Applying a patch on top of $version"
|
||||
+patch -p3 < ./fix-crashes.patch
|
||||
@@ -1,145 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2013 Sebastien Alaiwan
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
#if !defined(TEST_COMMON)
|
||||
#define TEST_COMMON
|
||||
|
||||
#if defined( _WIN32)
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <objbase.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <cstdarg>
|
||||
#include "cubeb/cubeb.h"
|
||||
#include "cubeb_mixer.h"
|
||||
|
||||
template<typename T, size_t N>
|
||||
constexpr size_t
|
||||
ARRAY_LENGTH(T(&)[N])
|
||||
{
|
||||
return N;
|
||||
}
|
||||
|
||||
void delay(unsigned int ms)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
Sleep(ms);
|
||||
#else
|
||||
sleep(ms / 1000);
|
||||
usleep(ms % 1000 * 1000);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(M_PI)
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
char const * name;
|
||||
unsigned int const channels;
|
||||
uint32_t const layout;
|
||||
} layout_info;
|
||||
|
||||
int has_available_input_device(cubeb * ctx)
|
||||
{
|
||||
cubeb_device_collection devices;
|
||||
int input_device_available = 0;
|
||||
int r;
|
||||
/* Bail out early if the host does not have input devices. */
|
||||
r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &devices);
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "error enumerating devices.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (devices.count == 0) {
|
||||
fprintf(stderr, "no input device available, skipping test.\n");
|
||||
cubeb_device_collection_destroy(ctx, &devices);
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < devices.count; i++) {
|
||||
input_device_available |= (devices.device[i].state ==
|
||||
CUBEB_DEVICE_STATE_ENABLED);
|
||||
}
|
||||
|
||||
if (!input_device_available) {
|
||||
fprintf(stderr, "there are input devices, but they are not "
|
||||
"available, skipping\n");
|
||||
}
|
||||
|
||||
cubeb_device_collection_destroy(ctx, &devices);
|
||||
return !!input_device_available;
|
||||
}
|
||||
|
||||
void print_log(const char * msg, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, msg);
|
||||
vprintf(msg, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
/** Initialize cubeb with backend override.
|
||||
* Create call cubeb_init passing value for CUBEB_BACKEND env var as
|
||||
* override. */
|
||||
int common_init(cubeb ** ctx, char const * ctx_name)
|
||||
{
|
||||
#ifdef ENABLE_NORMAL_LOG
|
||||
if (cubeb_set_log_callback(CUBEB_LOG_NORMAL, print_log) != CUBEB_OK) {
|
||||
fprintf(stderr, "Set normal log callback failed\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_VERBOSE_LOG
|
||||
if (cubeb_set_log_callback(CUBEB_LOG_VERBOSE, print_log) != CUBEB_OK) {
|
||||
fprintf(stderr, "Set verbose log callback failed\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
int r;
|
||||
char const * backend;
|
||||
char const * ctx_backend;
|
||||
|
||||
backend = getenv("CUBEB_BACKEND");
|
||||
r = cubeb_init(ctx, ctx_name, backend);
|
||||
if (r == CUBEB_OK && backend) {
|
||||
ctx_backend = cubeb_get_backend_id(*ctx);
|
||||
if (strcmp(backend, ctx_backend) != 0) {
|
||||
fprintf(stderr, "Requested backend `%s', got `%s'\n",
|
||||
backend, ctx_backend);
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
#if defined( _WIN32)
|
||||
class TestEnvironment : public ::testing::Environment {
|
||||
public:
|
||||
void SetUp() override {
|
||||
hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
if (SUCCEEDED(hr)) {
|
||||
CoUninitialize();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
HRESULT hr;
|
||||
};
|
||||
|
||||
::testing::Environment* const foo_env = ::testing::AddGlobalTestEnvironment(new TestEnvironment);
|
||||
#endif
|
||||
|
||||
#endif /* TEST_COMMON */
|
||||
@@ -1,244 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2013 Sebastien Alaiwan <sebastien.alaiwan@gmail.com>
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
/* libcubeb api/function exhaustive test. Plays a series of tones in different
|
||||
* conditions. */
|
||||
#include "gtest/gtest.h"
|
||||
#if !defined(_XOPEN_SOURCE)
|
||||
#define _XOPEN_SOURCE 600
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <memory>
|
||||
#include <string.h>
|
||||
#include "cubeb/cubeb.h"
|
||||
#include <string>
|
||||
|
||||
//#define ENABLE_NORMAL_LOG
|
||||
//#define ENABLE_VERBOSE_LOG
|
||||
#include "common.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define MAX_NUM_CHANNELS 32
|
||||
|
||||
#if !defined(M_PI)
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
#define VOLUME 0.2
|
||||
|
||||
float get_frequency(int channel_index)
|
||||
{
|
||||
return 220.0f * (channel_index+1);
|
||||
}
|
||||
|
||||
template<typename T> T ConvertSample(double input);
|
||||
template<> float ConvertSample(double input) { return input; }
|
||||
template<> short ConvertSample(double input) { return short(input * 32767.0f); }
|
||||
|
||||
/* store the phase of the generated waveform */
|
||||
struct synth_state {
|
||||
synth_state(int num_channels_, float sample_rate_)
|
||||
: num_channels(num_channels_),
|
||||
sample_rate(sample_rate_)
|
||||
{
|
||||
for(int i=0;i < MAX_NUM_CHANNELS;++i)
|
||||
phase[i] = 0.0f;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void run(T* audiobuffer, long nframes)
|
||||
{
|
||||
for(int c=0;c < num_channels;++c) {
|
||||
float freq = get_frequency(c);
|
||||
float phase_inc = 2.0 * M_PI * freq / sample_rate;
|
||||
for(long n=0;n < nframes;++n) {
|
||||
audiobuffer[n*num_channels+c] = ConvertSample<T>(sin(phase[c]) * VOLUME);
|
||||
phase[c] += phase_inc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int num_channels;
|
||||
float phase[MAX_NUM_CHANNELS];
|
||||
float sample_rate;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
long data_cb(cubeb_stream * /*stream*/, void * user, const void * /*inputbuffer*/, void * outputbuffer, long nframes)
|
||||
{
|
||||
synth_state *synth = (synth_state *)user;
|
||||
synth->run((T*)outputbuffer, nframes);
|
||||
return nframes;
|
||||
}
|
||||
|
||||
void state_cb_audio(cubeb_stream * /*stream*/, void * /*user*/, cubeb_state /*state*/)
|
||||
{
|
||||
}
|
||||
|
||||
/* Our android backends don't support float, only int16. */
|
||||
int supports_float32(string backend_id)
|
||||
{
|
||||
return backend_id != "opensl"
|
||||
&& backend_id != "audiotrack";
|
||||
}
|
||||
|
||||
/* Some backends don't have code to deal with more than mono or stereo. */
|
||||
int supports_channel_count(string backend_id, int nchannels)
|
||||
{
|
||||
return nchannels <= 2 ||
|
||||
(backend_id != "opensl" && backend_id != "audiotrack");
|
||||
}
|
||||
|
||||
int run_test(int num_channels, int sampling_rate, int is_float)
|
||||
{
|
||||
int r = CUBEB_OK;
|
||||
|
||||
cubeb *ctx = NULL;
|
||||
|
||||
r = common_init(&ctx, "Cubeb audio test: channels");
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Error initializing cubeb library\n");
|
||||
return r;
|
||||
}
|
||||
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
|
||||
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
|
||||
|
||||
const char * backend_id = cubeb_get_backend_id(ctx);
|
||||
|
||||
if ((is_float && !supports_float32(backend_id)) ||
|
||||
!supports_channel_count(backend_id, num_channels)) {
|
||||
/* don't treat this as a test failure. */
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Testing %d channel(s), %d Hz, %s (%s)\n", num_channels, sampling_rate, is_float ? "float" : "short", cubeb_get_backend_id(ctx));
|
||||
|
||||
cubeb_stream_params params;
|
||||
params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
|
||||
params.rate = sampling_rate;
|
||||
params.channels = num_channels;
|
||||
params.layout = CUBEB_LAYOUT_UNDEFINED;
|
||||
params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
synth_state synth(params.channels, params.rate);
|
||||
|
||||
cubeb_stream *stream = NULL;
|
||||
r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, ¶ms,
|
||||
4096, is_float ? &data_cb<float> : &data_cb<short>, state_cb_audio, &synth);
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
|
||||
return r;
|
||||
}
|
||||
|
||||
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
|
||||
cleanup_stream_at_exit(stream, cubeb_stream_destroy);
|
||||
|
||||
cubeb_stream_start(stream);
|
||||
delay(200);
|
||||
cubeb_stream_stop(stream);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int run_volume_test(int is_float)
|
||||
{
|
||||
int r = CUBEB_OK;
|
||||
|
||||
cubeb *ctx = NULL;
|
||||
|
||||
r = common_init(&ctx, "Cubeb audio test");
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Error initializing cubeb library\n");
|
||||
return r;
|
||||
}
|
||||
|
||||
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
|
||||
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
|
||||
|
||||
const char * backend_id = cubeb_get_backend_id(ctx);
|
||||
|
||||
if ((is_float && !supports_float32(backend_id))) {
|
||||
/* don't treat this as a test failure. */
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
cubeb_stream_params params;
|
||||
params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
|
||||
params.rate = 44100;
|
||||
params.channels = 2;
|
||||
params.layout = CUBEB_LAYOUT_STEREO;
|
||||
params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
synth_state synth(params.channels, params.rate);
|
||||
|
||||
cubeb_stream *stream = NULL;
|
||||
r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, ¶ms,
|
||||
4096, is_float ? &data_cb<float> : &data_cb<short>,
|
||||
state_cb_audio, &synth);
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
|
||||
return r;
|
||||
}
|
||||
|
||||
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
|
||||
cleanup_stream_at_exit(stream, cubeb_stream_destroy);
|
||||
|
||||
fprintf(stderr, "Testing: volume\n");
|
||||
for(int i=0;i <= 4; ++i)
|
||||
{
|
||||
fprintf(stderr, "Volume: %d%%\n", i*25);
|
||||
|
||||
cubeb_stream_set_volume(stream, i/4.0f);
|
||||
cubeb_stream_start(stream);
|
||||
delay(400);
|
||||
cubeb_stream_stop(stream);
|
||||
delay(100);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
TEST(cubeb, run_volume_test_short)
|
||||
{
|
||||
ASSERT_EQ(run_volume_test(0), CUBEB_OK);
|
||||
}
|
||||
|
||||
TEST(cubeb, run_volume_test_float)
|
||||
{
|
||||
ASSERT_EQ(run_volume_test(1), CUBEB_OK);
|
||||
}
|
||||
|
||||
TEST(cubeb, run_channel_rate_test)
|
||||
{
|
||||
unsigned int channel_values[] = {
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
6,
|
||||
};
|
||||
|
||||
int freq_values[] = {
|
||||
16000,
|
||||
24000,
|
||||
44100,
|
||||
48000,
|
||||
};
|
||||
|
||||
for(auto channels : channel_values) {
|
||||
for(auto freq : freq_values) {
|
||||
ASSERT_TRUE(channels < MAX_NUM_CHANNELS);
|
||||
fprintf(stderr, "--------------------------\n");
|
||||
ASSERT_EQ(run_test(channels, freq, 0), CUBEB_OK);
|
||||
ASSERT_EQ(run_test(channels, freq, 1), CUBEB_OK);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2015 Haakon Sporsheim <haakon.sporsheim@telenordigital.com>
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
/* libcubeb enumerate device test/example.
|
||||
* Prints out a list of devices enumerated. */
|
||||
#include "gtest/gtest.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <memory>
|
||||
#include "cubeb/cubeb.h"
|
||||
|
||||
//#define ENABLE_NORMAL_LOG
|
||||
//#define ENABLE_VERBOSE_LOG
|
||||
#include "common.h"
|
||||
|
||||
long data_cb_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
|
||||
{
|
||||
// noop, unused
|
||||
return 0;
|
||||
}
|
||||
|
||||
void state_cb_duplex(cubeb_stream * stream, void * /*user*/, cubeb_state state)
|
||||
{
|
||||
// noop, unused
|
||||
}
|
||||
|
||||
static void
|
||||
print_device_info(cubeb_device_info * info, FILE * f)
|
||||
{
|
||||
char devfmts[64] = "";
|
||||
const char * devtype, * devstate, * devdeffmt;
|
||||
|
||||
switch (info->type) {
|
||||
case CUBEB_DEVICE_TYPE_INPUT:
|
||||
devtype = "input";
|
||||
break;
|
||||
case CUBEB_DEVICE_TYPE_OUTPUT:
|
||||
devtype = "output";
|
||||
break;
|
||||
case CUBEB_DEVICE_TYPE_UNKNOWN:
|
||||
default:
|
||||
devtype = "unknown?";
|
||||
break;
|
||||
};
|
||||
|
||||
switch (info->state) {
|
||||
case CUBEB_DEVICE_STATE_DISABLED:
|
||||
devstate = "disabled";
|
||||
break;
|
||||
case CUBEB_DEVICE_STATE_UNPLUGGED:
|
||||
devstate = "unplugged";
|
||||
break;
|
||||
case CUBEB_DEVICE_STATE_ENABLED:
|
||||
devstate = "enabled";
|
||||
break;
|
||||
default:
|
||||
devstate = "unknown?";
|
||||
break;
|
||||
};
|
||||
|
||||
switch (info->default_format) {
|
||||
case CUBEB_DEVICE_FMT_S16LE:
|
||||
devdeffmt = "S16LE";
|
||||
break;
|
||||
case CUBEB_DEVICE_FMT_S16BE:
|
||||
devdeffmt = "S16BE";
|
||||
break;
|
||||
case CUBEB_DEVICE_FMT_F32LE:
|
||||
devdeffmt = "F32LE";
|
||||
break;
|
||||
case CUBEB_DEVICE_FMT_F32BE:
|
||||
devdeffmt = "F32BE";
|
||||
break;
|
||||
default:
|
||||
devdeffmt = "unknown?";
|
||||
break;
|
||||
};
|
||||
|
||||
if (info->format & CUBEB_DEVICE_FMT_S16LE)
|
||||
strcat(devfmts, " S16LE");
|
||||
if (info->format & CUBEB_DEVICE_FMT_S16BE)
|
||||
strcat(devfmts, " S16BE");
|
||||
if (info->format & CUBEB_DEVICE_FMT_F32LE)
|
||||
strcat(devfmts, " F32LE");
|
||||
if (info->format & CUBEB_DEVICE_FMT_F32BE)
|
||||
strcat(devfmts, " F32BE");
|
||||
|
||||
fprintf(f,
|
||||
"dev: \"%s\"%s\n"
|
||||
"\tName: \"%s\"\n"
|
||||
"\tGroup: \"%s\"\n"
|
||||
"\tVendor: \"%s\"\n"
|
||||
"\tType: %s\n"
|
||||
"\tState: %s\n"
|
||||
"\tCh: %u\n"
|
||||
"\tFormat: %s (0x%x) (default: %s)\n"
|
||||
"\tRate: %u - %u (default: %u)\n"
|
||||
"\tLatency: lo %u frames, hi %u frames\n",
|
||||
info->device_id, info->preferred ? " (PREFERRED)" : "",
|
||||
info->friendly_name, info->group_id, info->vendor_name,
|
||||
devtype, devstate, info->max_channels,
|
||||
(devfmts[0] == '\0') ? devfmts : devfmts + 1,
|
||||
(unsigned int)info->format, devdeffmt,
|
||||
info->min_rate, info->max_rate, info->default_rate,
|
||||
info->latency_lo, info->latency_hi);
|
||||
}
|
||||
|
||||
static void
|
||||
print_device_collection(cubeb_device_collection * collection, FILE * f)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0; i < collection->count; i++)
|
||||
print_device_info(&collection->device[i], f);
|
||||
}
|
||||
|
||||
TEST(cubeb, destroy_default_collection)
|
||||
{
|
||||
int r;
|
||||
cubeb * ctx = NULL;
|
||||
cubeb_device_collection collection{ nullptr, 0 };
|
||||
|
||||
r = common_init(&ctx, "Cubeb audio test");
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
|
||||
|
||||
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
|
||||
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
|
||||
|
||||
ASSERT_EQ(collection.device, nullptr);
|
||||
ASSERT_EQ(collection.count, (size_t) 0);
|
||||
|
||||
r = cubeb_device_collection_destroy(ctx, &collection);
|
||||
if (r != CUBEB_ERROR_NOT_SUPPORTED) {
|
||||
ASSERT_EQ(r, CUBEB_OK);
|
||||
ASSERT_EQ(collection.device, nullptr);
|
||||
ASSERT_EQ(collection.count, (size_t) 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(cubeb, enumerate_devices)
|
||||
{
|
||||
int r;
|
||||
cubeb * ctx = NULL;
|
||||
cubeb_device_collection collection;
|
||||
|
||||
r = common_init(&ctx, "Cubeb audio test");
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
|
||||
|
||||
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
|
||||
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
|
||||
|
||||
fprintf(stdout, "Enumerating input devices for backend %s\n",
|
||||
cubeb_get_backend_id(ctx));
|
||||
|
||||
r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &collection);
|
||||
if (r == CUBEB_ERROR_NOT_SUPPORTED) {
|
||||
fprintf(stderr, "Device enumeration not supported"
|
||||
" for this backend, skipping this test.\n");
|
||||
return;
|
||||
}
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
|
||||
|
||||
fprintf(stdout, "Found %zu input devices\n", collection.count);
|
||||
print_device_collection(&collection, stdout);
|
||||
cubeb_device_collection_destroy(ctx, &collection);
|
||||
|
||||
fprintf(stdout, "Enumerating output devices for backend %s\n",
|
||||
cubeb_get_backend_id(ctx));
|
||||
|
||||
r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
|
||||
|
||||
fprintf(stdout, "Found %zu output devices\n", collection.count);
|
||||
print_device_collection(&collection, stdout);
|
||||
cubeb_device_collection_destroy(ctx, &collection);
|
||||
|
||||
uint32_t count_before_creating_duplex_stream;
|
||||
r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
|
||||
count_before_creating_duplex_stream = collection.count;
|
||||
cubeb_device_collection_destroy(ctx, &collection);
|
||||
|
||||
cubeb_stream * stream;
|
||||
cubeb_stream_params input_params;
|
||||
cubeb_stream_params output_params;
|
||||
|
||||
input_params.format = output_params.format = CUBEB_SAMPLE_FLOAT32NE;
|
||||
input_params.rate = output_params.rate = 48000;
|
||||
input_params.channels = output_params.channels = 1;
|
||||
input_params.layout = output_params.layout = CUBEB_LAYOUT_MONO;
|
||||
input_params.prefs = output_params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
|
||||
NULL, &input_params, NULL, &output_params,
|
||||
1024, data_cb_duplex, state_cb_duplex, nullptr);
|
||||
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
|
||||
|
||||
r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
|
||||
ASSERT_EQ(count_before_creating_duplex_stream, collection.count);
|
||||
cubeb_device_collection_destroy(ctx, &collection);
|
||||
|
||||
cubeb_stream_destroy(stream);
|
||||
}
|
||||
|
||||
TEST(cubeb, stream_get_current_device)
|
||||
{
|
||||
cubeb * ctx = NULL;
|
||||
int r = common_init(&ctx, "Cubeb audio test");
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
|
||||
|
||||
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
|
||||
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
|
||||
|
||||
fprintf(stdout, "Getting current devices for backend %s\n",
|
||||
cubeb_get_backend_id(ctx));
|
||||
|
||||
cubeb_stream * stream = NULL;
|
||||
cubeb_stream_params input_params;
|
||||
cubeb_stream_params output_params;
|
||||
|
||||
input_params.format = output_params.format = CUBEB_SAMPLE_FLOAT32NE;
|
||||
input_params.rate = output_params.rate = 48000;
|
||||
input_params.channels = output_params.channels = 1;
|
||||
input_params.layout = output_params.layout = CUBEB_LAYOUT_MONO;
|
||||
input_params.prefs = output_params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
|
||||
NULL, &input_params, NULL, &output_params,
|
||||
1024, data_cb_duplex, state_cb_duplex, nullptr);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
|
||||
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
|
||||
cleanup_stream_at_exit(stream, cubeb_stream_destroy);
|
||||
|
||||
cubeb_device * device;
|
||||
r = cubeb_stream_get_current_device(stream, &device);
|
||||
if (r == CUBEB_ERROR_NOT_SUPPORTED) {
|
||||
fprintf(stderr, "Getting current device is not supported"
|
||||
" for this backend, skipping this test.\n");
|
||||
return;
|
||||
}
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error getting current devices";
|
||||
|
||||
fprintf(stdout, "Current output device: %s\n", device->output_name);
|
||||
fprintf(stdout, "Current input device: %s\n", device->input_name);
|
||||
|
||||
r = cubeb_stream_device_destroy(stream, device);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error destroying current devices";
|
||||
}
|
||||
@@ -1,315 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2016 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
/* libcubeb api/function test. Loops input back to output and check audio
|
||||
* is flowing. */
|
||||
#include "gtest/gtest.h"
|
||||
#if !defined(_XOPEN_SOURCE)
|
||||
#define _XOPEN_SOURCE 600
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <memory>
|
||||
#include "cubeb/cubeb.h"
|
||||
#include <atomic>
|
||||
|
||||
//#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
|
||||
|
||||
struct user_state_duplex
|
||||
{
|
||||
std::atomic<int> invalid_audio_value{ 0 };
|
||||
};
|
||||
|
||||
long data_cb_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
|
||||
{
|
||||
user_state_duplex * u = reinterpret_cast<user_state_duplex*>(user);
|
||||
float *ib = (float *)inputbuffer;
|
||||
float *ob = (float *)outputbuffer;
|
||||
|
||||
if (stream == NULL || inputbuffer == NULL || outputbuffer == NULL) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
// Loop back: upmix the single input channel to the two output channels,
|
||||
// checking if there is noise in the process.
|
||||
long output_index = 0;
|
||||
for (long i = 0; i < nframes; i++) {
|
||||
if (ib[i] <= -1.0 || ib[i] >= 1.0) {
|
||||
u->invalid_audio_value = 1;
|
||||
break;
|
||||
}
|
||||
ob[output_index] = ob[output_index + 1] = ib[i];
|
||||
output_index += 2;
|
||||
}
|
||||
|
||||
return nframes;
|
||||
}
|
||||
|
||||
void state_cb_duplex(cubeb_stream * stream, void * /*user*/, cubeb_state state)
|
||||
{
|
||||
if (stream == NULL)
|
||||
return;
|
||||
|
||||
switch (state) {
|
||||
case CUBEB_STATE_STARTED:
|
||||
fprintf(stderr, "stream started\n"); break;
|
||||
case CUBEB_STATE_STOPPED:
|
||||
fprintf(stderr, "stream stopped\n"); break;
|
||||
case CUBEB_STATE_DRAINED:
|
||||
fprintf(stderr, "stream drained\n"); break;
|
||||
default:
|
||||
fprintf(stderr, "unknown stream state %d\n", state);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
TEST(cubeb, duplex)
|
||||
{
|
||||
cubeb *ctx;
|
||||
cubeb_stream *stream;
|
||||
cubeb_stream_params input_params;
|
||||
cubeb_stream_params output_params;
|
||||
int r;
|
||||
user_state_duplex stream_state;
|
||||
uint32_t latency_frames = 0;
|
||||
|
||||
r = common_init(&ctx, "Cubeb duplex example");
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
|
||||
|
||||
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
|
||||
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
|
||||
|
||||
/* This test needs an available input device, skip it if this host does not
|
||||
* have one. */
|
||||
if (!has_available_input_device(ctx)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* 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_cb_duplex, state_cb_duplex, &stream_state);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
|
||||
|
||||
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
|
||||
cleanup_stream_at_exit(stream, cubeb_stream_destroy);
|
||||
|
||||
cubeb_stream_start(stream);
|
||||
delay(500);
|
||||
cubeb_stream_stop(stream);
|
||||
|
||||
ASSERT_FALSE(stream_state.invalid_audio_value.load());
|
||||
}
|
||||
|
||||
void device_collection_changed_callback(cubeb * context, void * user)
|
||||
{
|
||||
fprintf(stderr, "collection changed callback\n");
|
||||
ASSERT_TRUE(false) << "Error: device collection changed callback"
|
||||
" called when opening a stream";
|
||||
}
|
||||
|
||||
TEST(cubeb, duplex_collection_change)
|
||||
{
|
||||
cubeb *ctx;
|
||||
cubeb_stream *stream;
|
||||
cubeb_stream_params input_params;
|
||||
cubeb_stream_params output_params;
|
||||
int r;
|
||||
uint32_t latency_frames = 0;
|
||||
|
||||
r = common_init(&ctx, "Cubeb duplex example with collection change");
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
|
||||
|
||||
r = cubeb_register_device_collection_changed(ctx,
|
||||
static_cast<cubeb_device_type>(CUBEB_DEVICE_TYPE_INPUT),
|
||||
device_collection_changed_callback,
|
||||
nullptr);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
|
||||
|
||||
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_cb_duplex, state_cb_duplex, nullptr);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
|
||||
cubeb_stream_destroy(stream);
|
||||
}
|
||||
|
||||
long data_cb_input(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
|
||||
{
|
||||
if (stream == NULL || inputbuffer == NULL || outputbuffer != NULL) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
return nframes;
|
||||
}
|
||||
|
||||
void state_cb_input(cubeb_stream * stream, void * /*user*/, cubeb_state state)
|
||||
{
|
||||
if (stream == NULL)
|
||||
return;
|
||||
|
||||
switch (state) {
|
||||
case CUBEB_STATE_STARTED:
|
||||
fprintf(stderr, "stream started\n"); break;
|
||||
case CUBEB_STATE_STOPPED:
|
||||
fprintf(stderr, "stream stopped\n"); break;
|
||||
case CUBEB_STATE_DRAINED:
|
||||
fprintf(stderr, "stream drained\n"); break;
|
||||
case CUBEB_STATE_ERROR:
|
||||
fprintf(stderr, "stream runs into error state\n"); break;
|
||||
default:
|
||||
fprintf(stderr, "unknown stream state %d\n", state);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<cubeb_devid> get_devices(cubeb * ctx, cubeb_device_type type) {
|
||||
std::vector<cubeb_devid> devices;
|
||||
|
||||
cubeb_device_collection collection;
|
||||
int r = cubeb_enumerate_devices(ctx, type, &collection);
|
||||
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Failed to enumerate devices\n");
|
||||
return devices;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < collection.count; i++) {
|
||||
if (collection.device[i].state == CUBEB_DEVICE_STATE_ENABLED) {
|
||||
devices.emplace_back(collection.device[i].devid);
|
||||
}
|
||||
}
|
||||
|
||||
cubeb_device_collection_destroy(ctx, &collection);
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
TEST(cubeb, one_duplex_one_input)
|
||||
{
|
||||
cubeb *ctx;
|
||||
cubeb_stream *duplex_stream;
|
||||
cubeb_stream_params input_params;
|
||||
cubeb_stream_params output_params;
|
||||
int r;
|
||||
user_state_duplex duplex_stream_state;
|
||||
uint32_t latency_frames = 0;
|
||||
|
||||
r = common_init(&ctx, "Cubeb duplex example");
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
|
||||
|
||||
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
|
||||
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
|
||||
|
||||
/* This test needs at least two available input devices. */
|
||||
std::vector<cubeb_devid> input_devices = get_devices(ctx, CUBEB_DEVICE_TYPE_INPUT);
|
||||
if (input_devices.size() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* This test needs at least one available output device. */
|
||||
std::vector<cubeb_devid> output_devices = get_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT);
|
||||
if (output_devices.size() < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
cubeb_devid duplex_input = input_devices.front();
|
||||
cubeb_devid duplex_output = nullptr; // default device
|
||||
cubeb_devid input_only = input_devices.back();
|
||||
|
||||
/* typical use-case: mono voice input, stereo output, low latency. */
|
||||
input_params.format = STREAM_FORMAT;
|
||||
input_params.rate = SAMPLE_FREQUENCY;
|
||||
input_params.channels = INPUT_CHANNELS;
|
||||
input_params.layout = CUBEB_LAYOUT_UNDEFINED;
|
||||
input_params.prefs = CUBEB_STREAM_PREF_VOICE;
|
||||
|
||||
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, &duplex_stream, "Cubeb duplex",
|
||||
duplex_input, &input_params, duplex_output, &output_params,
|
||||
latency_frames, data_cb_duplex, state_cb_duplex, &duplex_stream_state);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing duplex cubeb stream";
|
||||
|
||||
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
|
||||
cleanup_stream_at_exit(duplex_stream, cubeb_stream_destroy);
|
||||
|
||||
r = cubeb_stream_start(duplex_stream);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Could not start duplex stream";
|
||||
delay(500);
|
||||
|
||||
cubeb_stream *input_stream;
|
||||
r = cubeb_stream_init(ctx, &input_stream, "Cubeb input",
|
||||
input_only, &input_params, NULL, NULL,
|
||||
latency_frames, data_cb_input, state_cb_input, nullptr);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing input-only cubeb stream";
|
||||
|
||||
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
|
||||
cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
|
||||
|
||||
r = cubeb_stream_start(input_stream);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Could not start input stream";
|
||||
delay(500);
|
||||
|
||||
r = cubeb_stream_stop(duplex_stream);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Could not stop duplex stream";
|
||||
|
||||
r = cubeb_stream_stop(input_stream);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Could not stop input stream";
|
||||
|
||||
ASSERT_FALSE(duplex_stream_state.invalid_audio_value.load());
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include <stdlib.h>
|
||||
#include <memory>
|
||||
#include "cubeb/cubeb.h"
|
||||
//#define ENABLE_NORMAL_LOG
|
||||
//#define ENABLE_VERBOSE_LOG
|
||||
#include "common.h"
|
||||
|
||||
TEST(cubeb, latency)
|
||||
{
|
||||
cubeb * ctx = NULL;
|
||||
int r;
|
||||
uint32_t max_channels;
|
||||
uint32_t preferred_rate;
|
||||
uint32_t latency_frames;
|
||||
|
||||
r = common_init(&ctx, "Cubeb audio test");
|
||||
ASSERT_EQ(r, CUBEB_OK);
|
||||
|
||||
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
|
||||
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
|
||||
|
||||
r = cubeb_get_max_channel_count(ctx, &max_channels);
|
||||
ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
|
||||
if (r == CUBEB_OK) {
|
||||
ASSERT_GT(max_channels, 0u);
|
||||
}
|
||||
|
||||
r = cubeb_get_preferred_sample_rate(ctx, &preferred_rate);
|
||||
ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
|
||||
if (r == CUBEB_OK) {
|
||||
ASSERT_GT(preferred_rate, 0u);
|
||||
}
|
||||
|
||||
cubeb_stream_params params = {
|
||||
CUBEB_SAMPLE_FLOAT32NE,
|
||||
preferred_rate,
|
||||
max_channels,
|
||||
CUBEB_LAYOUT_UNDEFINED,
|
||||
CUBEB_STREAM_PREF_NONE
|
||||
};
|
||||
r = cubeb_get_min_latency(ctx, ¶ms, &latency_frames);
|
||||
ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
|
||||
if (r == CUBEB_OK) {
|
||||
ASSERT_GT(latency_frames, 0u);
|
||||
}
|
||||
}
|
||||
@@ -1,578 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
/* libcubeb api/function test. Requests a loopback device and checks that
|
||||
output is being looped back to input. NOTE: Usage of output devices while
|
||||
performing this test will cause flakey results! */
|
||||
#include "gtest/gtest.h"
|
||||
#if !defined(_XOPEN_SOURCE)
|
||||
#define _XOPEN_SOURCE 600
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include "cubeb/cubeb.h"
|
||||
//#define ENABLE_NORMAL_LOG
|
||||
//#define ENABLE_VERBOSE_LOG
|
||||
#include "common.h"
|
||||
const uint32_t SAMPLE_FREQUENCY = 48000;
|
||||
const uint32_t TONE_FREQUENCY = 440;
|
||||
const double OUTPUT_AMPLITUDE = 0.25;
|
||||
const int32_t NUM_FRAMES_TO_OUTPUT = SAMPLE_FREQUENCY / 20; /* play ~50ms of samples */
|
||||
|
||||
template<typename T> T ConvertSampleToOutput(double input);
|
||||
template<> float ConvertSampleToOutput(double input) { return float(input); }
|
||||
template<> short ConvertSampleToOutput(double input) { return short(input * 32767.0f); }
|
||||
|
||||
template<typename T> double ConvertSampleFromOutput(T sample);
|
||||
template<> double ConvertSampleFromOutput(float sample) { return double(sample); }
|
||||
template<> double ConvertSampleFromOutput(short sample) { return double(sample / 32767.0); }
|
||||
|
||||
/* Simple cross correlation to help find phase shift. Not a performant impl */
|
||||
std::vector<double> cross_correlate(std::vector<double> & f,
|
||||
std::vector<double> & g,
|
||||
size_t signal_length)
|
||||
{
|
||||
/* the length we sweep our window through to find the cross correlation */
|
||||
size_t sweep_length = f.size() - signal_length + 1;
|
||||
std::vector<double> correlation;
|
||||
correlation.reserve(sweep_length);
|
||||
for (size_t i = 0; i < sweep_length; i++) {
|
||||
double accumulator = 0.0;
|
||||
for (size_t j = 0; j < signal_length; j++) {
|
||||
accumulator += f.at(j) * g.at(i + j);
|
||||
}
|
||||
correlation.push_back(accumulator);
|
||||
}
|
||||
return correlation;
|
||||
}
|
||||
|
||||
/* best effort discovery of phase shift between output and (looped) input*/
|
||||
size_t find_phase(std::vector<double> & output_frames,
|
||||
std::vector<double> & input_frames,
|
||||
size_t signal_length)
|
||||
{
|
||||
std::vector<double> correlation = cross_correlate(output_frames, input_frames, signal_length);
|
||||
size_t phase = 0;
|
||||
double max_correlation = correlation.at(0);
|
||||
for (size_t i = 1; i < correlation.size(); i++) {
|
||||
if (correlation.at(i) > max_correlation) {
|
||||
max_correlation = correlation.at(i);
|
||||
phase = i;
|
||||
}
|
||||
}
|
||||
return phase;
|
||||
}
|
||||
|
||||
std::vector<double> normalize_frames(std::vector<double> & frames) {
|
||||
double max = abs(*std::max_element(frames.begin(), frames.end(),
|
||||
[](double a, double b) { return abs(a) < abs(b); }));
|
||||
std::vector<double> normalized_frames;
|
||||
normalized_frames.reserve(frames.size());
|
||||
for (const double frame : frames) {
|
||||
normalized_frames.push_back(frame / max);
|
||||
}
|
||||
return normalized_frames;
|
||||
}
|
||||
|
||||
/* heuristic comparison of aligned output and input signals, gets flaky if TONE_FREQUENCY is too high */
|
||||
void compare_signals(std::vector<double> & output_frames,
|
||||
std::vector<double> & input_frames)
|
||||
{
|
||||
ASSERT_EQ(output_frames.size(), input_frames.size()) << "#Output frames != #input frames";
|
||||
size_t num_frames = output_frames.size();
|
||||
std::vector<double> normalized_output_frames = normalize_frames(output_frames);
|
||||
std::vector<double> normalized_input_frames = normalize_frames(input_frames);
|
||||
|
||||
/* calculate mean absolute errors */
|
||||
/* mean absolute errors between output and input */
|
||||
double io_mas = 0.0;
|
||||
/* mean absolute errors between output and silence */
|
||||
double output_silence_mas = 0.0;
|
||||
/* mean absolute errors between input and silence */
|
||||
double input_silence_mas = 0.0;
|
||||
for (size_t i = 0; i < num_frames; i++) {
|
||||
io_mas += abs(normalized_output_frames.at(i) - normalized_input_frames.at(i));
|
||||
output_silence_mas += abs(normalized_output_frames.at(i));
|
||||
input_silence_mas += abs(normalized_input_frames.at(i));
|
||||
}
|
||||
io_mas /= num_frames;
|
||||
output_silence_mas /= num_frames;
|
||||
input_silence_mas /= num_frames;
|
||||
|
||||
ASSERT_LT(io_mas, output_silence_mas)
|
||||
<< "Error between output and input should be less than output and silence!";
|
||||
ASSERT_LT(io_mas, input_silence_mas)
|
||||
<< "Error between output and input should be less than output and silence!";
|
||||
|
||||
/* make sure extrema are in (roughly) correct location */
|
||||
/* number of maxima + minama expected in the frames*/
|
||||
const long NUM_EXTREMA = 2 * TONE_FREQUENCY * NUM_FRAMES_TO_OUTPUT / SAMPLE_FREQUENCY;
|
||||
/* expected index of first maxima */
|
||||
const long FIRST_MAXIMUM_INDEX = SAMPLE_FREQUENCY / TONE_FREQUENCY / 4;
|
||||
/* Threshold we expect all maxima and minima to be above or below. Ideally
|
||||
the extrema would be 1 or -1, but particularly at the start of loopback
|
||||
the values seen can be significantly lower. */
|
||||
const double THRESHOLD = 0.5;
|
||||
|
||||
for (size_t i = 0; i < NUM_EXTREMA; i++) {
|
||||
bool is_maximum = i % 2 == 0;
|
||||
/* expected offset to current extreme: i * stide between extrema */
|
||||
size_t offset = i * SAMPLE_FREQUENCY / TONE_FREQUENCY / 2;
|
||||
if (is_maximum) {
|
||||
ASSERT_GT(normalized_output_frames.at(FIRST_MAXIMUM_INDEX + offset), THRESHOLD)
|
||||
<< "Output frames have unexpected missing maximum!";
|
||||
ASSERT_GT(normalized_input_frames.at(FIRST_MAXIMUM_INDEX + offset), THRESHOLD)
|
||||
<< "Input frames have unexpected missing maximum!";
|
||||
} else {
|
||||
ASSERT_LT(normalized_output_frames.at(FIRST_MAXIMUM_INDEX + offset), -THRESHOLD)
|
||||
<< "Output frames have unexpected missing minimum!";
|
||||
ASSERT_LT(normalized_input_frames.at(FIRST_MAXIMUM_INDEX + offset), -THRESHOLD)
|
||||
<< "Input frames have unexpected missing minimum!";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct user_state_loopback {
|
||||
std::mutex user_state_mutex;
|
||||
long position = 0;
|
||||
/* track output */
|
||||
std::vector<double> output_frames;
|
||||
/* track input */
|
||||
std::vector<double> input_frames;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
long data_cb_loop_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
|
||||
{
|
||||
struct user_state_loopback * u = (struct user_state_loopback *) user;
|
||||
T * ib = (T *) inputbuffer;
|
||||
T * ob = (T *) outputbuffer;
|
||||
|
||||
if (stream == NULL || inputbuffer == NULL || outputbuffer == NULL) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(u->user_state_mutex);
|
||||
/* generate our test tone on the fly */
|
||||
for (int i = 0; i < nframes; i++) {
|
||||
double tone = 0.0;
|
||||
if (u->position + i < NUM_FRAMES_TO_OUTPUT) {
|
||||
/* generate sine wave */
|
||||
tone = sin(2 * M_PI*(i + u->position) * TONE_FREQUENCY / SAMPLE_FREQUENCY);
|
||||
tone *= OUTPUT_AMPLITUDE;
|
||||
}
|
||||
ob[i] = ConvertSampleToOutput<T>(tone);
|
||||
u->output_frames.push_back(tone);
|
||||
/* store any looped back output, may be silence */
|
||||
u->input_frames.push_back(ConvertSampleFromOutput(ib[i]));
|
||||
}
|
||||
|
||||
u->position += nframes;
|
||||
|
||||
return nframes;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
long data_cb_loop_input_only(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
|
||||
{
|
||||
struct user_state_loopback * u = (struct user_state_loopback *) user;
|
||||
T * ib = (T *) inputbuffer;
|
||||
|
||||
if (outputbuffer != NULL) {
|
||||
// Can't assert as it needs to return, so expect to fail instead
|
||||
EXPECT_EQ(outputbuffer, (void *) NULL) << "outputbuffer should be null in input only callback";
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
if (stream == NULL || inputbuffer == NULL) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(u->user_state_mutex);
|
||||
for (int i = 0; i < nframes; i++) {
|
||||
u->input_frames.push_back(ConvertSampleFromOutput(ib[i]));
|
||||
}
|
||||
|
||||
return nframes;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
long data_cb_playback(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
|
||||
{
|
||||
struct user_state_loopback * u = (struct user_state_loopback *) user;
|
||||
T * ob = (T *) outputbuffer;
|
||||
|
||||
if (stream == NULL || outputbuffer == NULL) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(u->user_state_mutex);
|
||||
/* generate our test tone on the fly */
|
||||
for (int i = 0; i < nframes; i++) {
|
||||
double tone = 0.0;
|
||||
if (u->position + i < NUM_FRAMES_TO_OUTPUT) {
|
||||
/* generate sine wave */
|
||||
tone = sin(2 * M_PI*(i + u->position) * TONE_FREQUENCY / SAMPLE_FREQUENCY);
|
||||
tone *= OUTPUT_AMPLITUDE;
|
||||
}
|
||||
ob[i] = ConvertSampleToOutput<T>(tone);
|
||||
u->output_frames.push_back(tone);
|
||||
}
|
||||
|
||||
u->position += nframes;
|
||||
|
||||
return nframes;
|
||||
}
|
||||
|
||||
void state_cb_loop(cubeb_stream * stream, void * /*user*/, cubeb_state state)
|
||||
{
|
||||
if (stream == NULL)
|
||||
return;
|
||||
|
||||
switch (state) {
|
||||
case CUBEB_STATE_STARTED:
|
||||
fprintf(stderr, "stream started\n"); break;
|
||||
case CUBEB_STATE_STOPPED:
|
||||
fprintf(stderr, "stream stopped\n"); break;
|
||||
case CUBEB_STATE_DRAINED:
|
||||
fprintf(stderr, "stream drained\n"); break;
|
||||
default:
|
||||
fprintf(stderr, "unknown stream state %d\n", state);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void run_loopback_duplex_test(bool is_float)
|
||||
{
|
||||
cubeb * ctx;
|
||||
cubeb_stream * stream;
|
||||
cubeb_stream_params input_params;
|
||||
cubeb_stream_params output_params;
|
||||
int r;
|
||||
uint32_t latency_frames = 0;
|
||||
|
||||
r = common_init(&ctx, "Cubeb loopback example: duplex stream");
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
|
||||
|
||||
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
|
||||
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
|
||||
|
||||
input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
|
||||
input_params.rate = SAMPLE_FREQUENCY;
|
||||
input_params.channels = 1;
|
||||
input_params.layout = CUBEB_LAYOUT_MONO;
|
||||
input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
|
||||
output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
|
||||
output_params.rate = SAMPLE_FREQUENCY;
|
||||
output_params.channels = 1;
|
||||
output_params.layout = CUBEB_LAYOUT_MONO;
|
||||
output_params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
|
||||
ASSERT_TRUE(!!user_data) << "Error allocating user data";
|
||||
|
||||
r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
|
||||
|
||||
/* setup a duplex stream with loopback */
|
||||
r = cubeb_stream_init(ctx, &stream, "Cubeb loopback",
|
||||
NULL, &input_params, NULL, &output_params, latency_frames,
|
||||
is_float ? data_cb_loop_duplex<float> : data_cb_loop_duplex<short>,
|
||||
state_cb_loop, user_data.get());
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
|
||||
|
||||
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
|
||||
cleanup_stream_at_exit(stream, cubeb_stream_destroy);
|
||||
|
||||
cubeb_stream_start(stream);
|
||||
delay(300);
|
||||
cubeb_stream_stop(stream);
|
||||
|
||||
/* access after stop should not happen, but lock just in case and to appease sanitization tools */
|
||||
std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
|
||||
std::vector<double> & output_frames = user_data->output_frames;
|
||||
std::vector<double> & input_frames = user_data->input_frames;
|
||||
ASSERT_EQ(output_frames.size(), input_frames.size())
|
||||
<< "#Output frames != #input frames";
|
||||
|
||||
size_t phase = find_phase(user_data->output_frames, user_data->input_frames, NUM_FRAMES_TO_OUTPUT);
|
||||
|
||||
/* extract vectors of just the relevant signal from output and input */
|
||||
auto output_frames_signal_start = output_frames.begin();
|
||||
auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT;
|
||||
std::vector<double> trimmed_output_frames(output_frames_signal_start, output_frames_signal_end);
|
||||
auto input_frames_signal_start = input_frames.begin() + phase;
|
||||
auto input_frames_signal_end = input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT;
|
||||
std::vector<double> trimmed_input_frames(input_frames_signal_start, input_frames_signal_end);
|
||||
|
||||
compare_signals(trimmed_output_frames, trimmed_input_frames);
|
||||
}
|
||||
|
||||
TEST(cubeb, loopback_duplex)
|
||||
{
|
||||
run_loopback_duplex_test(true);
|
||||
run_loopback_duplex_test(false);
|
||||
}
|
||||
|
||||
void run_loopback_separate_streams_test(bool is_float)
|
||||
{
|
||||
cubeb * ctx;
|
||||
cubeb_stream * input_stream;
|
||||
cubeb_stream * output_stream;
|
||||
cubeb_stream_params input_params;
|
||||
cubeb_stream_params output_params;
|
||||
int r;
|
||||
uint32_t latency_frames = 0;
|
||||
|
||||
r = common_init(&ctx, "Cubeb loopback example: separate streams");
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
|
||||
|
||||
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
|
||||
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
|
||||
|
||||
input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
|
||||
input_params.rate = SAMPLE_FREQUENCY;
|
||||
input_params.channels = 1;
|
||||
input_params.layout = CUBEB_LAYOUT_MONO;
|
||||
input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
|
||||
output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
|
||||
output_params.rate = SAMPLE_FREQUENCY;
|
||||
output_params.channels = 1;
|
||||
output_params.layout = CUBEB_LAYOUT_MONO;
|
||||
output_params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
|
||||
ASSERT_TRUE(!!user_data) << "Error allocating user data";
|
||||
|
||||
r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
|
||||
|
||||
/* setup an input stream with loopback */
|
||||
r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only",
|
||||
NULL, &input_params, NULL, NULL, latency_frames,
|
||||
is_float ? data_cb_loop_input_only<float> : data_cb_loop_input_only<short>,
|
||||
state_cb_loop, user_data.get());
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
|
||||
|
||||
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
|
||||
cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
|
||||
|
||||
/* setup an output stream */
|
||||
r = cubeb_stream_init(ctx, &output_stream, "Cubeb loopback output only",
|
||||
NULL, NULL, NULL, &output_params, latency_frames,
|
||||
is_float ? data_cb_playback<float> : data_cb_playback<short>,
|
||||
state_cb_loop, user_data.get());
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
|
||||
|
||||
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
|
||||
cleanup_output_stream_at_exit(output_stream, cubeb_stream_destroy);
|
||||
|
||||
cubeb_stream_start(input_stream);
|
||||
cubeb_stream_start(output_stream);
|
||||
delay(300);
|
||||
cubeb_stream_stop(output_stream);
|
||||
cubeb_stream_stop(input_stream);
|
||||
|
||||
/* access after stop should not happen, but lock just in case and to appease sanitization tools */
|
||||
std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
|
||||
std::vector<double> & output_frames = user_data->output_frames;
|
||||
std::vector<double> & input_frames = user_data->input_frames;
|
||||
ASSERT_LE(output_frames.size(), input_frames.size())
|
||||
<< "#Output frames should be less or equal to #input frames";
|
||||
|
||||
size_t phase = find_phase(user_data->output_frames, user_data->input_frames, NUM_FRAMES_TO_OUTPUT);
|
||||
|
||||
/* extract vectors of just the relevant signal from output and input */
|
||||
auto output_frames_signal_start = output_frames.begin();
|
||||
auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT;
|
||||
std::vector<double> trimmed_output_frames(output_frames_signal_start, output_frames_signal_end);
|
||||
auto input_frames_signal_start = input_frames.begin() + phase;
|
||||
auto input_frames_signal_end = input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT;
|
||||
std::vector<double> trimmed_input_frames(input_frames_signal_start, input_frames_signal_end);
|
||||
|
||||
compare_signals(trimmed_output_frames, trimmed_input_frames);
|
||||
}
|
||||
|
||||
TEST(cubeb, loopback_separate_streams)
|
||||
{
|
||||
run_loopback_separate_streams_test(true);
|
||||
run_loopback_separate_streams_test(false);
|
||||
}
|
||||
|
||||
void run_loopback_silence_test(bool is_float)
|
||||
{
|
||||
cubeb * ctx;
|
||||
cubeb_stream * input_stream;
|
||||
cubeb_stream_params input_params;
|
||||
int r;
|
||||
uint32_t latency_frames = 0;
|
||||
|
||||
r = common_init(&ctx, "Cubeb loopback example: record silence");
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
|
||||
|
||||
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
|
||||
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
|
||||
|
||||
input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
|
||||
input_params.rate = SAMPLE_FREQUENCY;
|
||||
input_params.channels = 1;
|
||||
input_params.layout = CUBEB_LAYOUT_MONO;
|
||||
input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
|
||||
|
||||
std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
|
||||
ASSERT_TRUE(!!user_data) << "Error allocating user data";
|
||||
|
||||
r = cubeb_get_min_latency(ctx, &input_params, &latency_frames);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
|
||||
|
||||
/* setup an input stream with loopback */
|
||||
r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only",
|
||||
NULL, &input_params, NULL, NULL, latency_frames,
|
||||
is_float ? data_cb_loop_input_only<float> : data_cb_loop_input_only<short>,
|
||||
state_cb_loop, user_data.get());
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
|
||||
|
||||
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
|
||||
cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
|
||||
|
||||
cubeb_stream_start(input_stream);
|
||||
delay(300);
|
||||
cubeb_stream_stop(input_stream);
|
||||
|
||||
/* access after stop should not happen, but lock just in case and to appease sanitization tools */
|
||||
std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
|
||||
std::vector<double> & input_frames = user_data->input_frames;
|
||||
|
||||
/* expect to have at least ~50ms of frames */
|
||||
ASSERT_GE(input_frames.size(), SAMPLE_FREQUENCY / 20);
|
||||
double EPISILON = 0.0001;
|
||||
/* frames should be 0.0, but use epsilon to avoid possible issues with impls
|
||||
that may use ~0.0 silence values. */
|
||||
for (double frame : input_frames) {
|
||||
ASSERT_LT(abs(frame), EPISILON);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(cubeb, loopback_silence)
|
||||
{
|
||||
run_loopback_silence_test(true);
|
||||
run_loopback_silence_test(false);
|
||||
}
|
||||
|
||||
void run_loopback_device_selection_test(bool is_float)
|
||||
{
|
||||
cubeb * ctx;
|
||||
cubeb_device_collection collection;
|
||||
cubeb_stream * input_stream;
|
||||
cubeb_stream * output_stream;
|
||||
cubeb_stream_params input_params;
|
||||
cubeb_stream_params output_params;
|
||||
int r;
|
||||
uint32_t latency_frames = 0;
|
||||
|
||||
r = common_init(&ctx, "Cubeb loopback example: device selection, separate streams");
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
|
||||
|
||||
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
|
||||
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
|
||||
|
||||
r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
|
||||
if (r == CUBEB_ERROR_NOT_SUPPORTED) {
|
||||
fprintf(stderr, "Device enumeration not supported"
|
||||
" for this backend, skipping this test.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
|
||||
/* get first preferred output device id */
|
||||
std::string device_id;
|
||||
for (size_t i = 0; i < collection.count; i++) {
|
||||
if (collection.device[i].preferred) {
|
||||
device_id = collection.device[i].device_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
cubeb_device_collection_destroy(ctx, &collection);
|
||||
if (device_id.empty()) {
|
||||
fprintf(stderr, "Could not find preferred device, aborting test.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
|
||||
input_params.rate = SAMPLE_FREQUENCY;
|
||||
input_params.channels = 1;
|
||||
input_params.layout = CUBEB_LAYOUT_MONO;
|
||||
input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
|
||||
output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
|
||||
output_params.rate = SAMPLE_FREQUENCY;
|
||||
output_params.channels = 1;
|
||||
output_params.layout = CUBEB_LAYOUT_MONO;
|
||||
output_params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
|
||||
ASSERT_TRUE(!!user_data) << "Error allocating user data";
|
||||
|
||||
r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
|
||||
|
||||
/* setup an input stream with loopback */
|
||||
r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only",
|
||||
device_id.c_str(), &input_params, NULL, NULL, latency_frames,
|
||||
is_float ? data_cb_loop_input_only<float> : data_cb_loop_input_only<short>,
|
||||
state_cb_loop, user_data.get());
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
|
||||
|
||||
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
|
||||
cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
|
||||
|
||||
/* setup an output stream */
|
||||
r = cubeb_stream_init(ctx, &output_stream, "Cubeb loopback output only",
|
||||
NULL, NULL, device_id.c_str(), &output_params, latency_frames,
|
||||
is_float ? data_cb_playback<float> : data_cb_playback<short>,
|
||||
state_cb_loop, user_data.get());
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
|
||||
|
||||
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
|
||||
cleanup_output_stream_at_exit(output_stream, cubeb_stream_destroy);
|
||||
|
||||
cubeb_stream_start(input_stream);
|
||||
cubeb_stream_start(output_stream);
|
||||
delay(300);
|
||||
cubeb_stream_stop(output_stream);
|
||||
cubeb_stream_stop(input_stream);
|
||||
|
||||
/* access after stop should not happen, but lock just in case and to appease sanitization tools */
|
||||
std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
|
||||
std::vector<double> & output_frames = user_data->output_frames;
|
||||
std::vector<double> & input_frames = user_data->input_frames;
|
||||
ASSERT_LE(output_frames.size(), input_frames.size())
|
||||
<< "#Output frames should be less or equal to #input frames";
|
||||
|
||||
size_t phase = find_phase(user_data->output_frames, user_data->input_frames, NUM_FRAMES_TO_OUTPUT);
|
||||
|
||||
/* extract vectors of just the relevant signal from output and input */
|
||||
auto output_frames_signal_start = output_frames.begin();
|
||||
auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT;
|
||||
std::vector<double> trimmed_output_frames(output_frames_signal_start, output_frames_signal_end);
|
||||
auto input_frames_signal_start = input_frames.begin() + phase;
|
||||
auto input_frames_signal_end = input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT;
|
||||
std::vector<double> trimmed_input_frames(input_frames_signal_start, input_frames_signal_end);
|
||||
|
||||
compare_signals(trimmed_output_frames, trimmed_input_frames);
|
||||
}
|
||||
|
||||
TEST(cubeb, loopback_device_selection)
|
||||
{
|
||||
run_loopback_device_selection_test(true);
|
||||
run_loopback_device_selection_test(false);
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#if !defined(_XOPEN_SOURCE)
|
||||
#define _XOPEN_SOURCE 600
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include "cubeb/cubeb.h"
|
||||
//#define ENABLE_NORMAL_LOG
|
||||
//#define ENABLE_VERBOSE_LOG
|
||||
#include "common.h"
|
||||
|
||||
#define SAMPLE_FREQUENCY 48000
|
||||
#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
|
||||
|
||||
std::atomic<bool> load_callback{ false };
|
||||
|
||||
long data_cb(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
|
||||
{
|
||||
if (load_callback) {
|
||||
fprintf(stderr, "Sleeping...\n");
|
||||
delay(100000);
|
||||
fprintf(stderr, "Sleeping done\n");
|
||||
}
|
||||
return nframes;
|
||||
}
|
||||
|
||||
void state_cb(cubeb_stream * stream, void * /*user*/, cubeb_state state)
|
||||
{
|
||||
ASSERT_TRUE(!!stream);
|
||||
|
||||
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:
|
||||
FAIL() << "this test is not supposed to drain"; break;
|
||||
case CUBEB_STATE_ERROR:
|
||||
fprintf(stderr, "stream error\n"); break;
|
||||
default:
|
||||
FAIL() << "this test is not supposed to have a weird state"; break;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(cubeb, overload_callback)
|
||||
{
|
||||
cubeb * ctx;
|
||||
cubeb_stream * stream;
|
||||
cubeb_stream_params output_params;
|
||||
int r;
|
||||
uint32_t latency_frames = 0;
|
||||
|
||||
r = common_init(&ctx, "Cubeb callback overload");
|
||||
ASSERT_EQ(r, CUBEB_OK);
|
||||
|
||||
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
|
||||
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
|
||||
|
||||
output_params.format = STREAM_FORMAT;
|
||||
output_params.rate = 48000;
|
||||
output_params.channels = 2;
|
||||
output_params.layout = CUBEB_LAYOUT_STEREO;
|
||||
output_params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
|
||||
ASSERT_EQ(r, CUBEB_OK);
|
||||
|
||||
r = cubeb_stream_init(ctx, &stream, "Cubeb",
|
||||
NULL, NULL, NULL, &output_params,
|
||||
latency_frames, data_cb, state_cb, NULL);
|
||||
ASSERT_EQ(r, CUBEB_OK);
|
||||
|
||||
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
|
||||
cleanup_stream_at_exit(stream, cubeb_stream_destroy);
|
||||
|
||||
cubeb_stream_start(stream);
|
||||
delay(500);
|
||||
// This causes the callback to sleep for a large number of seconds.
|
||||
load_callback = true;
|
||||
delay(500);
|
||||
cubeb_stream_stop(stream);
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2016 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
/* libcubeb api/function test. Record the mic and check there is sound. */
|
||||
#include "gtest/gtest.h"
|
||||
#if !defined(_XOPEN_SOURCE)
|
||||
#define _XOPEN_SOURCE 600
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <memory>
|
||||
#include "cubeb/cubeb.h"
|
||||
#include <atomic>
|
||||
|
||||
//#define ENABLE_NORMAL_LOG
|
||||
//#define ENABLE_VERBOSE_LOG
|
||||
#include "common.h"
|
||||
|
||||
#define SAMPLE_FREQUENCY 48000
|
||||
#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
|
||||
|
||||
struct user_state_record
|
||||
{
|
||||
std::atomic<int> invalid_audio_value{ 0 };
|
||||
};
|
||||
|
||||
long data_cb_record(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
|
||||
{
|
||||
user_state_record * u = reinterpret_cast<user_state_record*>(user);
|
||||
float *b = (float *)inputbuffer;
|
||||
|
||||
if (stream == NULL || inputbuffer == NULL || outputbuffer != NULL) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
for (long i = 0; i < nframes; i++) {
|
||||
if (b[i] <= -1.0 || b[i] >= 1.0) {
|
||||
u->invalid_audio_value = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nframes;
|
||||
}
|
||||
|
||||
void state_cb_record(cubeb_stream * stream, void * /*user*/, cubeb_state state)
|
||||
{
|
||||
if (stream == NULL)
|
||||
return;
|
||||
|
||||
switch (state) {
|
||||
case CUBEB_STATE_STARTED:
|
||||
fprintf(stderr, "stream started\n"); break;
|
||||
case CUBEB_STATE_STOPPED:
|
||||
fprintf(stderr, "stream stopped\n"); break;
|
||||
case CUBEB_STATE_DRAINED:
|
||||
fprintf(stderr, "stream drained\n"); break;
|
||||
default:
|
||||
fprintf(stderr, "unknown stream state %d\n", state);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
TEST(cubeb, record)
|
||||
{
|
||||
if (cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr /*print_log*/) != CUBEB_OK) {
|
||||
fprintf(stderr, "Set log callback failed\n");
|
||||
}
|
||||
cubeb *ctx;
|
||||
cubeb_stream *stream;
|
||||
cubeb_stream_params params;
|
||||
int r;
|
||||
user_state_record stream_state;
|
||||
|
||||
r = common_init(&ctx, "Cubeb record example");
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
|
||||
|
||||
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
|
||||
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
|
||||
|
||||
/* This test needs an available input device, skip it if this host does not
|
||||
* have one. */
|
||||
if (!has_available_input_device(ctx)) {
|
||||
return;
|
||||
}
|
||||
|
||||
params.format = STREAM_FORMAT;
|
||||
params.rate = SAMPLE_FREQUENCY;
|
||||
params.channels = 1;
|
||||
params.layout = CUBEB_LAYOUT_UNDEFINED;
|
||||
params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
r = cubeb_stream_init(ctx, &stream, "Cubeb record (mono)", NULL, ¶ms, NULL, nullptr,
|
||||
4096, data_cb_record, state_cb_record, &stream_state);
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
|
||||
|
||||
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
|
||||
cleanup_stream_at_exit(stream, cubeb_stream_destroy);
|
||||
|
||||
cubeb_stream_start(stream);
|
||||
delay(500);
|
||||
cubeb_stream_stop(stream);
|
||||
|
||||
#ifdef __linux__
|
||||
// user callback does not arrive in Linux, silence the error
|
||||
fprintf(stderr, "Check is disabled in Linux\n");
|
||||
#else
|
||||
ASSERT_FALSE(stream_state.invalid_audio_value.load());
|
||||
#endif
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,73 +0,0 @@
|
||||
#include "gtest/gtest.h"
|
||||
#ifdef __APPLE__
|
||||
#include <string.h>
|
||||
#include <iostream>
|
||||
#include <CoreAudio/CoreAudioTypes.h>
|
||||
#include "cubeb/cubeb.h"
|
||||
#include "cubeb_ring_array.h"
|
||||
|
||||
TEST(cubeb, ring_array)
|
||||
{
|
||||
ring_array ra;
|
||||
|
||||
ASSERT_EQ(ring_array_init(&ra, 0, 0, 1, 1), CUBEB_ERROR_INVALID_PARAMETER);
|
||||
ASSERT_EQ(ring_array_init(&ra, 1, 0, 0, 1), CUBEB_ERROR_INVALID_PARAMETER);
|
||||
|
||||
unsigned int capacity = 8;
|
||||
ring_array_init(&ra, capacity, sizeof(int), 1, 1);
|
||||
int verify_data[capacity] ;// {1,2,3,4,5,6,7,8};
|
||||
AudioBuffer * p_data = NULL;
|
||||
|
||||
for (unsigned int i = 0; i < capacity; ++i) {
|
||||
verify_data[i] = i; // in case capacity change value
|
||||
*(int*)ra.buffer_array[i].mData = i;
|
||||
ASSERT_EQ(ra.buffer_array[i].mDataByteSize, sizeof(int));
|
||||
ASSERT_EQ(ra.buffer_array[i].mNumberChannels, 1u);
|
||||
}
|
||||
|
||||
/* Get store buffers*/
|
||||
for (unsigned int i = 0; i < capacity; ++i) {
|
||||
p_data = ring_array_get_free_buffer(&ra);
|
||||
ASSERT_NE(p_data, nullptr);
|
||||
ASSERT_EQ(*(int*)p_data->mData, verify_data[i]);
|
||||
}
|
||||
/*Now array is full extra store should give NULL*/
|
||||
ASSERT_EQ(ring_array_get_free_buffer(&ra), nullptr);
|
||||
/* Get fetch buffers*/
|
||||
for (unsigned int i = 0; i < capacity; ++i) {
|
||||
p_data = ring_array_get_data_buffer(&ra);
|
||||
ASSERT_NE(p_data, nullptr);
|
||||
ASSERT_EQ(*(int*)p_data->mData, verify_data[i]);
|
||||
}
|
||||
/*Now array is empty extra fetch should give NULL*/
|
||||
ASSERT_EQ(ring_array_get_data_buffer(&ra), nullptr);
|
||||
|
||||
p_data = NULL;
|
||||
/* Repeated store fetch should can go for ever*/
|
||||
for (unsigned int i = 0; i < 2*capacity; ++i) {
|
||||
p_data = ring_array_get_free_buffer(&ra);
|
||||
ASSERT_NE(p_data, nullptr);
|
||||
ASSERT_EQ(ring_array_get_data_buffer(&ra), p_data);
|
||||
}
|
||||
|
||||
p_data = NULL;
|
||||
/* Verify/modify buffer data*/
|
||||
for (unsigned int i = 0; i < capacity; ++i) {
|
||||
p_data = ring_array_get_free_buffer(&ra);
|
||||
ASSERT_NE(p_data, nullptr);
|
||||
ASSERT_EQ(*((int*)p_data->mData), verify_data[i]);
|
||||
(*((int*)p_data->mData))++; // Modify data
|
||||
}
|
||||
for (unsigned int i = 0; i < capacity; ++i) {
|
||||
p_data = ring_array_get_data_buffer(&ra);
|
||||
ASSERT_NE(p_data, nullptr);
|
||||
ASSERT_EQ(*((int*)p_data->mData), verify_data[i]+1); // Verify modified data
|
||||
}
|
||||
|
||||
ring_array_destroy(&ra);
|
||||
}
|
||||
#else
|
||||
TEST(cubeb, DISABLED_ring_array)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
@@ -1,72 +0,0 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "cubeb_utils.h"
|
||||
|
||||
TEST(cubeb, auto_array)
|
||||
{
|
||||
auto_array<uint32_t> array;
|
||||
auto_array<uint32_t> array2(10);
|
||||
uint32_t a[10];
|
||||
|
||||
ASSERT_EQ(array2.length(), 0u);
|
||||
ASSERT_EQ(array2.capacity(), 10u);
|
||||
|
||||
|
||||
for (uint32_t i = 0; i < 10; i++) {
|
||||
a[i] = i;
|
||||
}
|
||||
|
||||
ASSERT_EQ(array.capacity(), 0u);
|
||||
ASSERT_EQ(array.length(), 0u);
|
||||
|
||||
array.push(a, 10);
|
||||
|
||||
ASSERT_TRUE(!array.reserve(9));
|
||||
|
||||
for (uint32_t i = 0; i < 10; i++) {
|
||||
ASSERT_EQ(array.data()[i], i);
|
||||
}
|
||||
|
||||
ASSERT_EQ(array.capacity(), 10u);
|
||||
ASSERT_EQ(array.length(), 10u);
|
||||
|
||||
uint32_t b[10];
|
||||
|
||||
array.pop(b, 5);
|
||||
|
||||
ASSERT_EQ(array.capacity(), 10u);
|
||||
ASSERT_EQ(array.length(), 5u);
|
||||
for (uint32_t i = 0; i < 5; i++) {
|
||||
ASSERT_EQ(b[i], i);
|
||||
ASSERT_EQ(array.data()[i], 5 + i);
|
||||
}
|
||||
uint32_t* bb = b + 5;
|
||||
array.pop(bb, 5);
|
||||
|
||||
ASSERT_EQ(array.capacity(), 10u);
|
||||
ASSERT_EQ(array.length(), 0u);
|
||||
for (uint32_t i = 0; i < 5; i++) {
|
||||
ASSERT_EQ(bb[i], 5 + i);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(!array.pop(nullptr, 1));
|
||||
|
||||
array.push(a, 10);
|
||||
array.push(a, 10);
|
||||
|
||||
for (uint32_t j = 0; j < 2; j++) {
|
||||
for (uint32_t i = 0; i < 10; i++) {
|
||||
ASSERT_EQ(array.data()[10 * j + i], i);
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(array.length(), 20u);
|
||||
ASSERT_EQ(array.capacity(), 20u);
|
||||
array.pop(nullptr, 5);
|
||||
|
||||
for (uint32_t i = 0; i < 5; i++) {
|
||||
ASSERT_EQ(array.data()[i], 5 + i);
|
||||
}
|
||||
|
||||
ASSERT_EQ(array.length(), 15u);
|
||||
ASSERT_EQ(array.capacity(), 20u);
|
||||
}
|
||||
|
||||
+183
-327
@@ -7,9 +7,8 @@
|
||||
#if !defined(CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382)
|
||||
#define CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382
|
||||
|
||||
#include "cubeb_export.h"
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "cubeb_export.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
@@ -31,13 +30,19 @@ extern "C" {
|
||||
|
||||
@code
|
||||
cubeb * app_ctx;
|
||||
cubeb_init(&app_ctx, "Example Application", NULL);
|
||||
cubeb_init(&app_ctx, "Example Application");
|
||||
int rv;
|
||||
uint32_t rate;
|
||||
uint32_t latency_frames;
|
||||
int rate;
|
||||
int latency_frames;
|
||||
uint64_t ts;
|
||||
|
||||
rv = cubeb_get_preferred_sample_rate(app_ctx, &rate);
|
||||
rv = cubeb_get_min_latency(app_ctx, output_params, &latency_frames);
|
||||
if (rv != CUBEB_OK) {
|
||||
fprintf(stderr, "Could not get minimum latency");
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = cubeb_get_preferred_sample_rate(app_ctx, output_params, &rate);
|
||||
if (rv != CUBEB_OK) {
|
||||
fprintf(stderr, "Could not get preferred sample-rate");
|
||||
return rv;
|
||||
@@ -47,26 +52,16 @@ extern "C" {
|
||||
output_params.format = CUBEB_SAMPLE_FLOAT32NE;
|
||||
output_params.rate = rate;
|
||||
output_params.channels = 2;
|
||||
output_params.layout = CUBEB_LAYOUT_UNDEFINED;
|
||||
output_params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
rv = cubeb_get_min_latency(app_ctx, &output_params, &latency_frames);
|
||||
if (rv != CUBEB_OK) {
|
||||
fprintf(stderr, "Could not get minimum latency");
|
||||
return rv;
|
||||
}
|
||||
|
||||
cubeb_stream_params input_params;
|
||||
input_params.format = CUBEB_SAMPLE_FLOAT32NE;
|
||||
input_params.rate = rate;
|
||||
input_params.channels = 1;
|
||||
input_params.layout = CUBEB_LAYOUT_UNDEFINED;
|
||||
input_params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
output_params.format = CUBEB_SAMPLE_FLOAT32NE;
|
||||
output_params.rate = rate;
|
||||
output_params.channels = 1;
|
||||
|
||||
cubeb_stream * stm;
|
||||
rv = cubeb_stream_init(app_ctx, &stm, "Example Stream 1",
|
||||
NULL, &input_params,
|
||||
NULL, &output_params,
|
||||
NULL, input_params,
|
||||
NULL, output_params,
|
||||
latency_frames,
|
||||
data_cb, state_cb,
|
||||
NULL);
|
||||
@@ -97,14 +92,14 @@ extern "C" {
|
||||
|
||||
@code
|
||||
long data_cb(cubeb_stream * stm, void * user,
|
||||
const void * input_buffer, void * output_buffer, long nframes)
|
||||
void * input_buffer, void * output_buffer, long nframes)
|
||||
{
|
||||
const float * in = input_buffer;
|
||||
float * in = input_buffer;
|
||||
float * out = output_buffer;
|
||||
|
||||
for (int i = 0; i < nframes; ++i) {
|
||||
for (int c = 0; c < 2; ++c) {
|
||||
out[2 * i + c] = in[i];
|
||||
for (i = 0; i < nframes; ++i) {
|
||||
for (c = 0; c < 2; ++c) {
|
||||
buf[i][c] = in[i];
|
||||
}
|
||||
}
|
||||
return nframes;
|
||||
@@ -122,10 +117,8 @@ extern "C" {
|
||||
/** @file
|
||||
The <tt>libcubeb</tt> C API. */
|
||||
|
||||
typedef struct cubeb
|
||||
cubeb; /**< Opaque handle referencing the application state. */
|
||||
typedef struct cubeb_stream
|
||||
cubeb_stream; /**< Opaque handle referencing the stream state. */
|
||||
typedef struct cubeb cubeb; /**< Opaque handle referencing the application state. */
|
||||
typedef struct cubeb_stream cubeb_stream; /**< Opaque handle referencing the stream state. */
|
||||
|
||||
/** Sample format enumeration. */
|
||||
typedef enum {
|
||||
@@ -150,126 +143,54 @@ typedef enum {
|
||||
#endif
|
||||
} cubeb_sample_format;
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
/**
|
||||
* This maps to the underlying stream types on supported platforms, e.g.
|
||||
* Android.
|
||||
*/
|
||||
typedef enum {
|
||||
CUBEB_STREAM_TYPE_VOICE_CALL = 0,
|
||||
CUBEB_STREAM_TYPE_SYSTEM = 1,
|
||||
CUBEB_STREAM_TYPE_RING = 2,
|
||||
CUBEB_STREAM_TYPE_MUSIC = 3,
|
||||
CUBEB_STREAM_TYPE_ALARM = 4,
|
||||
CUBEB_STREAM_TYPE_NOTIFICATION = 5,
|
||||
CUBEB_STREAM_TYPE_BLUETOOTH_SCO = 6,
|
||||
CUBEB_STREAM_TYPE_SYSTEM_ENFORCED = 7,
|
||||
CUBEB_STREAM_TYPE_DTMF = 8,
|
||||
CUBEB_STREAM_TYPE_TTS = 9,
|
||||
CUBEB_STREAM_TYPE_FM = 10,
|
||||
|
||||
CUBEB_STREAM_TYPE_MAX
|
||||
} cubeb_stream_type;
|
||||
#endif
|
||||
|
||||
/** An opaque handle used to refer a particular input or output device
|
||||
* across calls. */
|
||||
typedef void const * cubeb_devid;
|
||||
typedef void * cubeb_devid;
|
||||
|
||||
/** Level (verbosity) of logging for a particular cubeb context. */
|
||||
typedef enum {
|
||||
CUBEB_LOG_DISABLED = 0, /** < Logging disabled */
|
||||
CUBEB_LOG_NORMAL =
|
||||
1, /**< Logging lifetime operation (creation/destruction). */
|
||||
CUBEB_LOG_VERBOSE = 2, /**< Verbose logging of callbacks, can have performance
|
||||
implications. */
|
||||
CUBEB_LOG_NORMAL = 1, /**< Logging lifetime operation (creation/destruction). */
|
||||
CUBEB_LOG_VERBOSE = 2, /**< Verbose logging of callbacks, can have performance implications. */
|
||||
} cubeb_log_level;
|
||||
|
||||
typedef enum {
|
||||
CHANNEL_UNKNOWN = 0,
|
||||
CHANNEL_FRONT_LEFT = 1 << 0,
|
||||
CHANNEL_FRONT_RIGHT = 1 << 1,
|
||||
CHANNEL_FRONT_CENTER = 1 << 2,
|
||||
CHANNEL_LOW_FREQUENCY = 1 << 3,
|
||||
CHANNEL_BACK_LEFT = 1 << 4,
|
||||
CHANNEL_BACK_RIGHT = 1 << 5,
|
||||
CHANNEL_FRONT_LEFT_OF_CENTER = 1 << 6,
|
||||
CHANNEL_FRONT_RIGHT_OF_CENTER = 1 << 7,
|
||||
CHANNEL_BACK_CENTER = 1 << 8,
|
||||
CHANNEL_SIDE_LEFT = 1 << 9,
|
||||
CHANNEL_SIDE_RIGHT = 1 << 10,
|
||||
CHANNEL_TOP_CENTER = 1 << 11,
|
||||
CHANNEL_TOP_FRONT_LEFT = 1 << 12,
|
||||
CHANNEL_TOP_FRONT_CENTER = 1 << 13,
|
||||
CHANNEL_TOP_FRONT_RIGHT = 1 << 14,
|
||||
CHANNEL_TOP_BACK_LEFT = 1 << 15,
|
||||
CHANNEL_TOP_BACK_CENTER = 1 << 16,
|
||||
CHANNEL_TOP_BACK_RIGHT = 1 << 17
|
||||
} cubeb_channel;
|
||||
|
||||
typedef uint32_t cubeb_channel_layout;
|
||||
// Some common layout definitions.
|
||||
enum {
|
||||
CUBEB_LAYOUT_UNDEFINED = 0, // Indicate the speaker's layout is undefined.
|
||||
CUBEB_LAYOUT_MONO = CHANNEL_FRONT_CENTER,
|
||||
CUBEB_LAYOUT_MONO_LFE = CUBEB_LAYOUT_MONO | CHANNEL_LOW_FREQUENCY,
|
||||
CUBEB_LAYOUT_STEREO = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT,
|
||||
CUBEB_LAYOUT_STEREO_LFE = CUBEB_LAYOUT_STEREO | CHANNEL_LOW_FREQUENCY,
|
||||
CUBEB_LAYOUT_3F =
|
||||
CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_FRONT_CENTER,
|
||||
CUBEB_LAYOUT_3F_LFE = CUBEB_LAYOUT_3F | CHANNEL_LOW_FREQUENCY,
|
||||
CUBEB_LAYOUT_2F1 =
|
||||
CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_BACK_CENTER,
|
||||
CUBEB_LAYOUT_2F1_LFE = CUBEB_LAYOUT_2F1 | CHANNEL_LOW_FREQUENCY,
|
||||
CUBEB_LAYOUT_3F1 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
|
||||
CHANNEL_FRONT_CENTER | CHANNEL_BACK_CENTER,
|
||||
CUBEB_LAYOUT_3F1_LFE = CUBEB_LAYOUT_3F1 | CHANNEL_LOW_FREQUENCY,
|
||||
CUBEB_LAYOUT_2F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
|
||||
CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT,
|
||||
CUBEB_LAYOUT_2F2_LFE = CUBEB_LAYOUT_2F2 | CHANNEL_LOW_FREQUENCY,
|
||||
CUBEB_LAYOUT_QUAD = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
|
||||
CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT,
|
||||
CUBEB_LAYOUT_QUAD_LFE = CUBEB_LAYOUT_QUAD | CHANNEL_LOW_FREQUENCY,
|
||||
CUBEB_LAYOUT_3F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
|
||||
CHANNEL_FRONT_CENTER | CHANNEL_SIDE_LEFT |
|
||||
CHANNEL_SIDE_RIGHT,
|
||||
CUBEB_LAYOUT_3F2_LFE = CUBEB_LAYOUT_3F2 | CHANNEL_LOW_FREQUENCY,
|
||||
CUBEB_LAYOUT_3F2_BACK = CUBEB_LAYOUT_QUAD | CHANNEL_FRONT_CENTER,
|
||||
CUBEB_LAYOUT_3F2_LFE_BACK = CUBEB_LAYOUT_3F2_BACK | CHANNEL_LOW_FREQUENCY,
|
||||
CUBEB_LAYOUT_3F3R_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
|
||||
CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY |
|
||||
CHANNEL_BACK_CENTER | CHANNEL_SIDE_LEFT |
|
||||
CHANNEL_SIDE_RIGHT,
|
||||
CUBEB_LAYOUT_3F4_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
|
||||
CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY |
|
||||
CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT |
|
||||
CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT,
|
||||
};
|
||||
|
||||
/** Miscellaneous stream preferences. */
|
||||
typedef enum {
|
||||
CUBEB_STREAM_PREF_NONE = 0x00, /**< No stream preferences are requested. */
|
||||
CUBEB_STREAM_PREF_LOOPBACK =
|
||||
0x01, /**< Request a loopback stream. Should be
|
||||
specified on the input params and an
|
||||
output device to loopback from should
|
||||
be passed in place of an input device. */
|
||||
CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING = 0x02, /**< Disable switching
|
||||
default device on OS
|
||||
changes. */
|
||||
CUBEB_STREAM_PREF_VOICE =
|
||||
0x04, /**< This stream is going to transport voice data.
|
||||
Depending on the backend and platform, this can
|
||||
change the audio input or output devices
|
||||
selected, as well as the quality of the stream,
|
||||
for example to accomodate bluetooth SCO modes on
|
||||
bluetooth devices. */
|
||||
CUBEB_STREAM_PREF_RAW =
|
||||
0x08, /**< Windows only. Bypass all signal processing
|
||||
except for always on APO, driver and hardware. */
|
||||
CUBEB_STREAM_PREF_PERSIST = 0x10, /**< Request that the volume and mute
|
||||
settings should persist across restarts
|
||||
of the stream and/or application. This is
|
||||
obsolete and ignored by all backends. */
|
||||
CUBEB_STREAM_PREF_JACK_NO_AUTO_CONNECT = 0x20 /**< Don't automatically try to
|
||||
connect ports. Only affects
|
||||
the jack backend. */
|
||||
} cubeb_stream_prefs;
|
||||
|
||||
/** Stream format initialization parameters. */
|
||||
typedef struct {
|
||||
cubeb_sample_format format; /**< Requested sample format. One of
|
||||
#cubeb_sample_format. */
|
||||
uint32_t rate; /**< Requested sample rate. Valid range is [1000, 192000]. */
|
||||
uint32_t channels; /**< Requested channel count. Valid range is [1, 8]. */
|
||||
cubeb_channel_layout
|
||||
layout; /**< Requested channel layout. This must be consistent with the
|
||||
provided channels. CUBEB_LAYOUT_UNDEFINED if unknown */
|
||||
cubeb_stream_prefs prefs; /**< Requested preferences. */
|
||||
unsigned int rate; /**< Requested sample rate. Valid range is [1000, 192000]. */
|
||||
unsigned int channels; /**< Requested channel count. Valid range is [1, 8]. */
|
||||
#if defined(__ANDROID__)
|
||||
cubeb_stream_type stream_type; /**< Used to map Android audio stream types */
|
||||
#endif
|
||||
} cubeb_stream_params;
|
||||
|
||||
/** Audio device description */
|
||||
typedef struct {
|
||||
char * output_name; /**< The name of the output device */
|
||||
char * input_name; /**< The name of the input device */
|
||||
char * input_name; /**< The name of the input device */
|
||||
} cubeb_device;
|
||||
|
||||
/** Stream states signaled via state_callback. */
|
||||
@@ -282,15 +203,12 @@ typedef enum {
|
||||
|
||||
/** Result code enumeration. */
|
||||
enum {
|
||||
CUBEB_OK = 0, /**< Success. */
|
||||
CUBEB_ERROR = -1, /**< Unclassified error. */
|
||||
CUBEB_ERROR_INVALID_FORMAT =
|
||||
-2, /**< Unsupported #cubeb_stream_params requested. */
|
||||
CUBEB_OK = 0, /**< Success. */
|
||||
CUBEB_ERROR = -1, /**< Unclassified error. */
|
||||
CUBEB_ERROR_INVALID_FORMAT = -2, /**< Unsupported #cubeb_stream_params requested. */
|
||||
CUBEB_ERROR_INVALID_PARAMETER = -3, /**< Invalid parameter specified. */
|
||||
CUBEB_ERROR_NOT_SUPPORTED =
|
||||
-4, /**< Optional function not implemented in current backend. */
|
||||
CUBEB_ERROR_DEVICE_UNAVAILABLE =
|
||||
-5 /**< Device specified by #cubeb_devid not available. */
|
||||
CUBEB_ERROR_NOT_SUPPORTED = -4, /**< Optional function not implemented in current backend. */
|
||||
CUBEB_ERROR_DEVICE_UNAVAILABLE = -5 /**< Device specified by #cubeb_devid not available. */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -306,96 +224,82 @@ typedef enum {
|
||||
* The state of a device.
|
||||
*/
|
||||
typedef enum {
|
||||
CUBEB_DEVICE_STATE_DISABLED, /**< The device has been disabled at the system
|
||||
level. */
|
||||
CUBEB_DEVICE_STATE_UNPLUGGED, /**< The device is enabled, but nothing is
|
||||
plugged into it. */
|
||||
CUBEB_DEVICE_STATE_ENABLED /**< The device is enabled. */
|
||||
CUBEB_DEVICE_STATE_DISABLED, /**< The device has been disabled at the system level. */
|
||||
CUBEB_DEVICE_STATE_UNPLUGGED, /**< The device is enabled, but nothing is plugged into it. */
|
||||
CUBEB_DEVICE_STATE_ENABLED /**< The device is enabled. */
|
||||
} cubeb_device_state;
|
||||
|
||||
/**
|
||||
* Architecture specific sample type.
|
||||
*/
|
||||
typedef enum {
|
||||
CUBEB_DEVICE_FMT_S16LE = 0x0010, /**< 16-bit integers, Little Endian. */
|
||||
CUBEB_DEVICE_FMT_S16BE = 0x0020, /**< 16-bit integers, Big Endian. */
|
||||
CUBEB_DEVICE_FMT_F32LE = 0x1000, /**< 32-bit floating point, Little Endian. */
|
||||
CUBEB_DEVICE_FMT_F32BE = 0x2000 /**< 32-bit floating point, Big Endian. */
|
||||
CUBEB_DEVICE_FMT_S16LE = 0x0010, /**< 16-bit integers, Little Endian. */
|
||||
CUBEB_DEVICE_FMT_S16BE = 0x0020, /**< 16-bit integers, Big Endian. */
|
||||
CUBEB_DEVICE_FMT_F32LE = 0x1000, /**< 32-bit floating point, Little Endian. */
|
||||
CUBEB_DEVICE_FMT_F32BE = 0x2000 /**< 32-bit floating point, Big Endian. */
|
||||
} cubeb_device_fmt;
|
||||
|
||||
#if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__)
|
||||
/** 16-bit integers, native endianess, when on a Big Endian environment. */
|
||||
#define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16BE
|
||||
/** 32-bit floating points, native endianess, when on a Big Endian environment.
|
||||
*/
|
||||
#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32BE
|
||||
#define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16BE
|
||||
/** 32-bit floating points, native endianess, when on a Big Endian environment. */
|
||||
#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32BE
|
||||
#else
|
||||
/** 16-bit integers, native endianess, when on a Little Endian environment. */
|
||||
#define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16LE
|
||||
#define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16LE
|
||||
/** 32-bit floating points, native endianess, when on a Little Endian
|
||||
* environment. */
|
||||
#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32LE
|
||||
#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32LE
|
||||
#endif
|
||||
/** All the 16-bit integers types. */
|
||||
#define CUBEB_DEVICE_FMT_S16_MASK \
|
||||
(CUBEB_DEVICE_FMT_S16LE | CUBEB_DEVICE_FMT_S16BE)
|
||||
#define CUBEB_DEVICE_FMT_S16_MASK (CUBEB_DEVICE_FMT_S16LE | CUBEB_DEVICE_FMT_S16BE)
|
||||
/** All the 32-bit floating points types. */
|
||||
#define CUBEB_DEVICE_FMT_F32_MASK \
|
||||
(CUBEB_DEVICE_FMT_F32LE | CUBEB_DEVICE_FMT_F32BE)
|
||||
#define CUBEB_DEVICE_FMT_F32_MASK (CUBEB_DEVICE_FMT_F32LE | CUBEB_DEVICE_FMT_F32BE)
|
||||
/** All the device formats types. */
|
||||
#define CUBEB_DEVICE_FMT_ALL \
|
||||
(CUBEB_DEVICE_FMT_S16_MASK | CUBEB_DEVICE_FMT_F32_MASK)
|
||||
#define CUBEB_DEVICE_FMT_ALL (CUBEB_DEVICE_FMT_S16_MASK | CUBEB_DEVICE_FMT_F32_MASK)
|
||||
|
||||
/** Channel type for a `cubeb_stream`. Depending on the backend and platform
|
||||
* used, this can control inter-stream interruption, ducking, and volume
|
||||
* control.
|
||||
*/
|
||||
typedef enum {
|
||||
CUBEB_DEVICE_PREF_NONE = 0x00,
|
||||
CUBEB_DEVICE_PREF_MULTIMEDIA = 0x01,
|
||||
CUBEB_DEVICE_PREF_VOICE = 0x02,
|
||||
CUBEB_DEVICE_PREF_NOTIFICATION = 0x04,
|
||||
CUBEB_DEVICE_PREF_ALL = 0x0F
|
||||
CUBEB_DEVICE_PREF_NONE = 0x00,
|
||||
CUBEB_DEVICE_PREF_MULTIMEDIA = 0x01,
|
||||
CUBEB_DEVICE_PREF_VOICE = 0x02,
|
||||
CUBEB_DEVICE_PREF_NOTIFICATION = 0x04,
|
||||
CUBEB_DEVICE_PREF_ALL = 0x0F
|
||||
} cubeb_device_pref;
|
||||
|
||||
/** This structure holds the characteristics
|
||||
* of an input or output audio device. It is obtained using
|
||||
* `cubeb_enumerate_devices`, which returns these structures via
|
||||
* `cubeb_device_collection` and must be destroyed via
|
||||
* `cubeb_device_collection_destroy`. */
|
||||
* of an input or output audio device. It can be obtained using
|
||||
* `cubeb_enumerate_devices`, and must be destroyed using
|
||||
* `cubeb_device_info_destroy`. */
|
||||
typedef struct {
|
||||
cubeb_devid devid; /**< Device identifier handle. */
|
||||
char const *
|
||||
device_id; /**< Device identifier which might be presented in a UI. */
|
||||
char const * friendly_name; /**< Friendly device name which might be presented
|
||||
in a UI. */
|
||||
char const * group_id; /**< Two devices have the same group identifier if they
|
||||
belong to the same physical device; for example a
|
||||
headset and microphone. */
|
||||
char const * vendor_name; /**< Optional vendor name, may be NULL. */
|
||||
cubeb_devid devid; /**< Device identifier handle. */
|
||||
char * device_id; /**< Device identifier which might be presented in a UI. */
|
||||
char * friendly_name; /**< Friendly device name which might be presented in a UI. */
|
||||
char * group_id; /**< Two devices have the same group identifier if they belong to the same physical device; for example a headset and microphone. */
|
||||
char * vendor_name; /**< Optional vendor name, may be NULL. */
|
||||
|
||||
cubeb_device_type type; /**< Type of device (Input/Output). */
|
||||
cubeb_device_state state; /**< State of device disabled/enabled/unplugged. */
|
||||
cubeb_device_pref preferred; /**< Preferred device. */
|
||||
cubeb_device_type type; /**< Type of device (Input/Output). */
|
||||
cubeb_device_state state; /**< State of device disabled/enabled/unplugged. */
|
||||
cubeb_device_pref preferred;/**< Preferred device. */
|
||||
|
||||
cubeb_device_fmt format; /**< Sample format supported. */
|
||||
cubeb_device_fmt
|
||||
default_format; /**< The default sample format for this device. */
|
||||
uint32_t max_channels; /**< Channels. */
|
||||
uint32_t default_rate; /**< Default/Preferred sample rate. */
|
||||
uint32_t max_rate; /**< Maximum sample rate supported. */
|
||||
uint32_t min_rate; /**< Minimum sample rate supported. */
|
||||
cubeb_device_fmt format; /**< Sample format supported. */
|
||||
cubeb_device_fmt default_format; /**< The default sample format for this device. */
|
||||
unsigned int max_channels; /**< Channels. */
|
||||
unsigned int default_rate; /**< Default/Preferred sample rate. */
|
||||
unsigned int max_rate; /**< Maximum sample rate supported. */
|
||||
unsigned int min_rate; /**< Minimum sample rate supported. */
|
||||
|
||||
uint32_t latency_lo; /**< Lowest possible latency in frames. */
|
||||
uint32_t latency_hi; /**< Higest possible latency in frames. */
|
||||
unsigned int latency_lo; /**< Lowest possible latency in frames. */
|
||||
unsigned int latency_hi; /**< Higest possible latency in frames. */
|
||||
} cubeb_device_info;
|
||||
|
||||
/** Device collection.
|
||||
* Returned by `cubeb_enumerate_devices` and destroyed by
|
||||
* `cubeb_device_collection_destroy`. */
|
||||
/** Device collection. */
|
||||
typedef struct {
|
||||
cubeb_device_info * device; /**< Array of pointers to device info. */
|
||||
size_t count; /**< Device count in collection. */
|
||||
uint32_t count; /**< Device count in collection. */
|
||||
cubeb_device_info * device[1]; /**< Array of pointers to device info. */
|
||||
} cubeb_device_collection;
|
||||
|
||||
/** User supplied data callback.
|
||||
@@ -412,68 +316,54 @@ typedef struct {
|
||||
@param output_buffer A pointer to a buffer to be filled with audio samples,
|
||||
or nullptr if this is an input-only stream.
|
||||
@param nframes The number of frames of the two buffer.
|
||||
@retval If the stream has output, this is the number of frames written to
|
||||
the output buffer. In this case, if this number is less than
|
||||
nframes then the stream will start to drain. If the stream is
|
||||
input only, then returning nframes indicates data has been read.
|
||||
In this case, a value less than nframes will result in the stream
|
||||
being stopped.
|
||||
@retval Number of frames written to the output buffer. If this number is
|
||||
less than nframes, then the stream will start to drain.
|
||||
@retval CUBEB_ERROR on error, in which case the data callback will stop
|
||||
and the stream will enter a shutdown state. */
|
||||
typedef long (*cubeb_data_callback)(cubeb_stream * stream, void * user_ptr,
|
||||
void const * input_buffer,
|
||||
void * output_buffer, long nframes);
|
||||
typedef long (* cubeb_data_callback)(cubeb_stream * stream,
|
||||
void * user_ptr,
|
||||
const void * input_buffer,
|
||||
void * output_buffer,
|
||||
long nframes);
|
||||
|
||||
/** User supplied state callback.
|
||||
@param stream The stream for this this callback fired.
|
||||
@param user_ptr The pointer passed to cubeb_stream_init.
|
||||
@param state The new state of the stream. */
|
||||
typedef void (*cubeb_state_callback)(cubeb_stream * stream, void * user_ptr,
|
||||
cubeb_state state);
|
||||
typedef void (* cubeb_state_callback)(cubeb_stream * stream,
|
||||
void * user_ptr,
|
||||
cubeb_state state);
|
||||
|
||||
/**
|
||||
* User supplied callback called when the underlying device changed.
|
||||
* @param user The pointer passed to cubeb_stream_init. */
|
||||
typedef void (*cubeb_device_changed_callback)(void * user_ptr);
|
||||
typedef void (* cubeb_device_changed_callback)(void * user_ptr);
|
||||
|
||||
/**
|
||||
* User supplied callback called when the underlying device collection changed.
|
||||
* @param context A pointer to the cubeb context.
|
||||
* @param user_ptr The pointer passed to
|
||||
* cubeb_register_device_collection_changed. */
|
||||
typedef void (*cubeb_device_collection_changed_callback)(cubeb * context,
|
||||
void * user_ptr);
|
||||
* @param user_ptr The pointer passed to cubeb_stream_init. */
|
||||
typedef void (* cubeb_device_collection_changed_callback)(cubeb * context,
|
||||
void * user_ptr);
|
||||
|
||||
/** User supplied callback called when a message needs logging. */
|
||||
typedef void (*cubeb_log_callback)(char const * fmt, ...);
|
||||
typedef void (* cubeb_log_callback)(const char * fmt, ...);
|
||||
|
||||
/** Initialize an application context. This will perform any library or
|
||||
application scoped initialization.
|
||||
|
||||
Note: On Windows platforms, COM must be initialized in MTA mode on
|
||||
any thread that will call the cubeb API.
|
||||
|
||||
@param context A out param where an opaque pointer to the application
|
||||
context will be returned.
|
||||
@param context_name A name for the context. Depending on the platform this
|
||||
can appear in different locations.
|
||||
@param backend_name The name of the cubeb backend user desires to select.
|
||||
Accepted values self-documented in cubeb.c: init_oneshot
|
||||
If NULL, a default ordering is used for backend choice.
|
||||
A valid choice overrides all other possible backends,
|
||||
so long as the backend was included at compile time.
|
||||
@retval CUBEB_OK in case of success.
|
||||
@retval CUBEB_ERROR in case of error, for example because the host
|
||||
has no audio hardware. */
|
||||
CUBEB_EXPORT int
|
||||
cubeb_init(cubeb ** context, char const * context_name,
|
||||
char const * backend_name);
|
||||
CUBEB_EXPORT int cubeb_init(cubeb ** context, char const * context_name);
|
||||
|
||||
/** Get a read-only string identifying this context's current backend.
|
||||
@param context A pointer to the cubeb context.
|
||||
@retval Read-only string identifying current backend. */
|
||||
CUBEB_EXPORT char const *
|
||||
cubeb_get_backend_id(cubeb * context);
|
||||
CUBEB_EXPORT char const * cubeb_get_backend_id(cubeb * context);
|
||||
|
||||
/** Get the maximum possible number of channels.
|
||||
@param context A pointer to the cubeb context.
|
||||
@@ -482,12 +372,11 @@ cubeb_get_backend_id(cubeb * context);
|
||||
@retval CUBEB_ERROR_INVALID_PARAMETER
|
||||
@retval CUBEB_ERROR_NOT_SUPPORTED
|
||||
@retval CUBEB_ERROR */
|
||||
CUBEB_EXPORT int
|
||||
cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels);
|
||||
CUBEB_EXPORT int cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels);
|
||||
|
||||
/** Get the minimal latency value, in frames, that is guaranteed to work
|
||||
when creating a stream for the specified sample rate. This is platform,
|
||||
hardware and backend dependent.
|
||||
hardware and backend dependant.
|
||||
@param context A pointer to the cubeb context.
|
||||
@param params On some backends, the minimum achievable latency depends on
|
||||
the characteristics of the stream.
|
||||
@@ -496,25 +385,23 @@ cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels);
|
||||
@retval CUBEB_OK
|
||||
@retval CUBEB_ERROR_INVALID_PARAMETER
|
||||
@retval CUBEB_ERROR_NOT_SUPPORTED */
|
||||
CUBEB_EXPORT int
|
||||
cubeb_get_min_latency(cubeb * context, cubeb_stream_params * params,
|
||||
uint32_t * latency_frames);
|
||||
CUBEB_EXPORT int cubeb_get_min_latency(cubeb * context,
|
||||
cubeb_stream_params params,
|
||||
uint32_t * latency_frames);
|
||||
|
||||
/** Get the preferred sample rate for this backend: this is hardware and
|
||||
platform dependent, and can avoid resampling, and/or trigger fastpaths.
|
||||
platform dependant, and can avoid resampling, and/or trigger fastpaths.
|
||||
@param context A pointer to the cubeb context.
|
||||
@param rate The samplerate (in Hz) the current configuration prefers.
|
||||
@retval CUBEB_OK
|
||||
@retval CUBEB_ERROR_INVALID_PARAMETER
|
||||
@retval CUBEB_ERROR_NOT_SUPPORTED */
|
||||
CUBEB_EXPORT int
|
||||
cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate);
|
||||
CUBEB_EXPORT int cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate);
|
||||
|
||||
/** Destroy an application context. This must be called after all stream have
|
||||
* been destroyed.
|
||||
@param context A pointer to the cubeb context.*/
|
||||
CUBEB_EXPORT void
|
||||
cubeb_destroy(cubeb * context);
|
||||
CUBEB_EXPORT void cubeb_destroy(cubeb * context);
|
||||
|
||||
/** Initialize a stream associated with the supplied application context.
|
||||
@param context A pointer to the cubeb context.
|
||||
@@ -522,21 +409,13 @@ cubeb_destroy(cubeb * context);
|
||||
cubeb stream.
|
||||
@param stream_name A name for this stream.
|
||||
@param input_device Device for the input side of the stream. If NULL the
|
||||
default input device is used. Passing a valid
|
||||
cubeb_devid means the stream only ever uses that device. Passing a NULL
|
||||
cubeb_devid allows the stream to follow that device
|
||||
type's OS default.
|
||||
default input device is used.
|
||||
@param input_stream_params Parameters for the input side of the stream, or
|
||||
NULL if this stream is output only.
|
||||
@param output_device Device for the output side of the stream. If NULL the
|
||||
default output device is used. Passing a valid
|
||||
cubeb_devid means the stream only ever uses that device. Passing a NULL
|
||||
cubeb_devid allows the stream to follow that device
|
||||
type's OS default.
|
||||
default output device is used.
|
||||
@param output_stream_params Parameters for the output side of the stream, or
|
||||
NULL if this stream is input only. When input
|
||||
and output stream parameters are supplied, their
|
||||
rate has to be the same.
|
||||
NULL if this stream is input only.
|
||||
@param latency_frames Stream latency in frames. Valid range
|
||||
is [1, 96000].
|
||||
@param data_callback Will be called to preroll data before playback is
|
||||
@@ -548,42 +427,41 @@ cubeb_destroy(cubeb * context);
|
||||
@retval CUBEB_ERROR
|
||||
@retval CUBEB_ERROR_INVALID_FORMAT
|
||||
@retval CUBEB_ERROR_DEVICE_UNAVAILABLE */
|
||||
CUBEB_EXPORT int
|
||||
cubeb_stream_init(cubeb * context, cubeb_stream ** stream,
|
||||
char const * stream_name, cubeb_devid input_device,
|
||||
cubeb_stream_params * input_stream_params,
|
||||
cubeb_devid output_device,
|
||||
cubeb_stream_params * output_stream_params,
|
||||
uint32_t latency_frames, cubeb_data_callback data_callback,
|
||||
cubeb_state_callback state_callback, void * user_ptr);
|
||||
CUBEB_EXPORT int cubeb_stream_init(cubeb * context,
|
||||
cubeb_stream ** stream,
|
||||
char const * stream_name,
|
||||
cubeb_devid input_device,
|
||||
cubeb_stream_params * input_stream_params,
|
||||
cubeb_devid output_device,
|
||||
cubeb_stream_params * output_stream_params,
|
||||
unsigned int latency_frames,
|
||||
cubeb_data_callback data_callback,
|
||||
cubeb_state_callback state_callback,
|
||||
void * user_ptr);
|
||||
|
||||
/** Destroy a stream. `cubeb_stream_stop` MUST be called before destroying a
|
||||
stream.
|
||||
@param stream The stream to destroy. */
|
||||
CUBEB_EXPORT void
|
||||
cubeb_stream_destroy(cubeb_stream * stream);
|
||||
CUBEB_EXPORT void cubeb_stream_destroy(cubeb_stream * stream);
|
||||
|
||||
/** Start playback.
|
||||
@param stream
|
||||
@retval CUBEB_OK
|
||||
@retval CUBEB_ERROR */
|
||||
CUBEB_EXPORT int
|
||||
cubeb_stream_start(cubeb_stream * stream);
|
||||
CUBEB_EXPORT int cubeb_stream_start(cubeb_stream * stream);
|
||||
|
||||
/** Stop playback.
|
||||
@param stream
|
||||
@retval CUBEB_OK
|
||||
@retval CUBEB_ERROR */
|
||||
CUBEB_EXPORT int
|
||||
cubeb_stream_stop(cubeb_stream * stream);
|
||||
CUBEB_EXPORT int cubeb_stream_stop(cubeb_stream * stream);
|
||||
|
||||
/** Get the current stream playback position.
|
||||
@param stream
|
||||
@param position Playback position in frames.
|
||||
@retval CUBEB_OK
|
||||
@retval CUBEB_ERROR */
|
||||
CUBEB_EXPORT int
|
||||
cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position);
|
||||
CUBEB_EXPORT int cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position);
|
||||
|
||||
/** Get the latency for this stream, in frames. This is the number of frames
|
||||
between the time cubeb acquires the data in the callback and the listener
|
||||
@@ -593,20 +471,8 @@ cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position);
|
||||
@retval CUBEB_OK
|
||||
@retval CUBEB_ERROR_NOT_SUPPORTED
|
||||
@retval CUBEB_ERROR */
|
||||
CUBEB_EXPORT int
|
||||
cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency);
|
||||
CUBEB_EXPORT int cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency);
|
||||
|
||||
/** Get the input latency for this stream, in frames. This is the number of
|
||||
frames between the time the audio input devices records the data, and they
|
||||
are available in the data callback.
|
||||
This returns CUBEB_ERROR when the stream is output-only.
|
||||
@param stream
|
||||
@param latency Current approximate stream latency in frames.
|
||||
@retval CUBEB_OK
|
||||
@retval CUBEB_ERROR_NOT_SUPPORTED
|
||||
@retval CUBEB_ERROR */
|
||||
CUBEB_EXPORT int
|
||||
cubeb_stream_get_input_latency(cubeb_stream * stream, uint32_t * latency);
|
||||
/** Set the volume for a stream.
|
||||
@param stream the stream for which to adjust the volume.
|
||||
@param volume a float between 0.0 (muted) and 1.0 (maximum volume)
|
||||
@@ -614,17 +480,21 @@ cubeb_stream_get_input_latency(cubeb_stream * stream, uint32_t * latency);
|
||||
@retval CUBEB_ERROR_INVALID_PARAMETER volume is outside [0.0, 1.0] or
|
||||
stream is an invalid pointer
|
||||
@retval CUBEB_ERROR_NOT_SUPPORTED */
|
||||
CUBEB_EXPORT int
|
||||
cubeb_stream_set_volume(cubeb_stream * stream, float volume);
|
||||
CUBEB_EXPORT int cubeb_stream_set_volume(cubeb_stream * stream, float volume);
|
||||
|
||||
/** Change a stream's name.
|
||||
@param stream the stream for which to set the name.
|
||||
@param stream_name the new name for the stream
|
||||
/** If the stream is stereo, set the left/right panning. If the stream is mono,
|
||||
this has no effect.
|
||||
@param stream the stream for which to change the panning
|
||||
@param panning a number from -1.0 to 1.0. -1.0 means that the stream is
|
||||
fully mixed in the left channel, 1.0 means the stream is fully
|
||||
mixed in the right channel. 0.0 is equal power in the right and
|
||||
left channel (default).
|
||||
@retval CUBEB_OK
|
||||
@retval CUBEB_ERROR_INVALID_PARAMETER if any pointer is invalid
|
||||
@retval CUBEB_ERROR_NOT_SUPPORTED */
|
||||
CUBEB_EXPORT int
|
||||
cubeb_stream_set_name(cubeb_stream * stream, char const * stream_name);
|
||||
@retval CUBEB_ERROR_INVALID_PARAMETER if stream is null or if panning is
|
||||
outside the [-1.0, 1.0] range.
|
||||
@retval CUBEB_ERROR_NOT_SUPPORTED
|
||||
@retval CUBEB_ERROR stream is not mono nor stereo */
|
||||
CUBEB_EXPORT int cubeb_stream_set_panning(cubeb_stream * stream, float panning);
|
||||
|
||||
/** Get the current output device for this stream.
|
||||
@param stm the stream for which to query the current output device
|
||||
@@ -633,9 +503,8 @@ cubeb_stream_set_name(cubeb_stream * stream, char const * stream_name);
|
||||
@retval CUBEB_ERROR_INVALID_PARAMETER if either stm, device or count are
|
||||
invalid pointers
|
||||
@retval CUBEB_ERROR_NOT_SUPPORTED */
|
||||
CUBEB_EXPORT int
|
||||
cubeb_stream_get_current_device(cubeb_stream * stm,
|
||||
cubeb_device ** const device);
|
||||
CUBEB_EXPORT int cubeb_stream_get_current_device(cubeb_stream * stm,
|
||||
cubeb_device ** const device);
|
||||
|
||||
/** Destroy a cubeb_device structure.
|
||||
@param stream the stream passed in cubeb_stream_get_current_device
|
||||
@@ -643,8 +512,8 @@ cubeb_stream_get_current_device(cubeb_stream * stm,
|
||||
@retval CUBEB_OK in case of success
|
||||
@retval CUBEB_ERROR_INVALID_PARAMETER if devices is an invalid pointer
|
||||
@retval CUBEB_ERROR_NOT_SUPPORTED */
|
||||
CUBEB_EXPORT int
|
||||
cubeb_stream_device_destroy(cubeb_stream * stream, cubeb_device * devices);
|
||||
CUBEB_EXPORT int cubeb_stream_device_destroy(cubeb_stream * stream,
|
||||
cubeb_device * devices);
|
||||
|
||||
/** Set a callback to be notified when the output device changes.
|
||||
@param stream the stream for which to set the callback.
|
||||
@@ -654,57 +523,45 @@ cubeb_stream_device_destroy(cubeb_stream * stream, cubeb_device * devices);
|
||||
@retval CUBEB_ERROR_INVALID_PARAMETER if either stream or
|
||||
device_changed_callback are invalid pointers.
|
||||
@retval CUBEB_ERROR_NOT_SUPPORTED */
|
||||
CUBEB_EXPORT int
|
||||
cubeb_stream_register_device_changed_callback(
|
||||
cubeb_stream * stream,
|
||||
cubeb_device_changed_callback device_changed_callback);
|
||||
|
||||
/** Return the user data pointer registered with the stream with
|
||||
cubeb_stream_init.
|
||||
@param stream the stream for which to retrieve user data pointer.
|
||||
@retval user data pointer */
|
||||
CUBEB_EXPORT void *
|
||||
cubeb_stream_user_ptr(cubeb_stream * stream);
|
||||
CUBEB_EXPORT int cubeb_stream_register_device_changed_callback(cubeb_stream * stream,
|
||||
cubeb_device_changed_callback device_changed_callback);
|
||||
|
||||
/** Returns enumerated devices.
|
||||
@param context
|
||||
@param devtype device type to include
|
||||
@param collection output collection. Must be destroyed with
|
||||
cubeb_device_collection_destroy
|
||||
@param collection output collection. Must be destroyed with cubeb_device_collection_destroy
|
||||
@retval CUBEB_OK in case of success
|
||||
@retval CUBEB_ERROR_INVALID_PARAMETER if collection is an invalid pointer
|
||||
@retval CUBEB_ERROR_NOT_SUPPORTED */
|
||||
CUBEB_EXPORT int
|
||||
cubeb_enumerate_devices(cubeb * context, cubeb_device_type devtype,
|
||||
cubeb_device_collection * collection);
|
||||
CUBEB_EXPORT int cubeb_enumerate_devices(cubeb * context,
|
||||
cubeb_device_type devtype,
|
||||
cubeb_device_collection ** collection);
|
||||
|
||||
/** Destroy a cubeb_device_collection, and its `cubeb_device_info`.
|
||||
@param context
|
||||
@param collection collection to destroy
|
||||
@retval CUBEB_OK
|
||||
@retval CUBEB_ERROR_INVALID_PARAMETER if collection is an invalid pointer */
|
||||
CUBEB_EXPORT int
|
||||
cubeb_device_collection_destroy(cubeb * context,
|
||||
cubeb_device_collection * collection);
|
||||
CUBEB_EXPORT int cubeb_device_collection_destroy(cubeb_device_collection * collection);
|
||||
|
||||
/** Destroy a cubeb_device_info structure.
|
||||
@param info pointer to device info structure
|
||||
@retval CUBEB_OK
|
||||
@retval CUBEB_ERROR_INVALID_PARAMETER if info is an invalid pointer */
|
||||
CUBEB_EXPORT int cubeb_device_info_destroy(cubeb_device_info * info);
|
||||
|
||||
/** Registers a callback which is called when the system detects
|
||||
a new device or a device is removed.
|
||||
@param context
|
||||
@param devtype device type to include. Different callbacks and user pointers
|
||||
can be registered for each devtype. The hybrid devtype
|
||||
`CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT` is also valid
|
||||
and will register the provided callback and user pointer in both
|
||||
sides.
|
||||
@param devtype device type to include
|
||||
@param callback a function called whenever the system device list changes.
|
||||
Passing NULL allow to unregister a function. You have to unregister
|
||||
first before you register a new callback.
|
||||
Passing NULL allow to unregister a function
|
||||
@param user_ptr pointer to user specified data which will be present in
|
||||
subsequent callbacks.
|
||||
@retval CUBEB_ERROR_NOT_SUPPORTED */
|
||||
CUBEB_EXPORT int
|
||||
cubeb_register_device_collection_changed(
|
||||
cubeb * context, cubeb_device_type devtype,
|
||||
cubeb_device_collection_changed_callback callback, void * user_ptr);
|
||||
CUBEB_EXPORT int cubeb_register_device_collection_changed(cubeb * context,
|
||||
cubeb_device_type devtype,
|
||||
cubeb_device_collection_changed_callback callback,
|
||||
void * user_ptr);
|
||||
|
||||
/** Set a callback to be called with a message.
|
||||
@param log_level CUBEB_LOG_VERBOSE, CUBEB_LOG_NORMAL.
|
||||
@@ -714,9 +571,8 @@ cubeb_register_device_collection_changed(
|
||||
@retval CUBEB_ERROR_INVALID_PARAMETER if either context or log_callback are
|
||||
invalid pointers, or if level is not
|
||||
in cubeb_log_level. */
|
||||
CUBEB_EXPORT int
|
||||
cubeb_set_log_callback(cubeb_log_level log_level,
|
||||
cubeb_log_callback log_callback);
|
||||
CUBEB_EXPORT int cubeb_set_log_callback(cubeb_log_level log_level,
|
||||
cubeb_log_callback log_callback);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,968 @@
|
||||
From: Paul Adenot <paul@paul.cx>
|
||||
Subject: Linearize operations on AudioUnits to sidestep a deadlock.
|
||||
|
||||
---
|
||||
|
||||
diff --git a/src/cubeb_audiounit.cpp b/src/cubeb_audiounit.cpp
|
||||
--- a/src/cubeb_audiounit.cpp
|
||||
+++ b/src/cubeb_audiounit.cpp
|
||||
@@ -53,40 +53,45 @@ typedef UInt32 AudioFormatFlags;
|
||||
|
||||
#define AU_OUT_BUS 0
|
||||
#define AU_IN_BUS 1
|
||||
|
||||
#define PRINT_ERROR_CODE(str, r) do { \
|
||||
LOG("System call failed: %s (rv: %d)", str, r); \
|
||||
} while(0)
|
||||
|
||||
+const char * DISPATCH_QUEUE_LABEL = "org.mozilla.cubeb";
|
||||
+
|
||||
/* Testing empirically, some headsets report a minimal latency that is very
|
||||
* low, but this does not work in practice. Lie and say the minimum is 256
|
||||
* frames. */
|
||||
const uint32_t SAFE_MIN_LATENCY_FRAMES = 256;
|
||||
const uint32_t SAFE_MAX_LATENCY_FRAMES = 512;
|
||||
|
||||
void audiounit_stream_stop_internal(cubeb_stream * stm);
|
||||
void audiounit_stream_start_internal(cubeb_stream * stm);
|
||||
-static void close_audiounit_stream(cubeb_stream * stm);
|
||||
-static int setup_audiounit_stream(cubeb_stream * stm);
|
||||
+static void audiounit_close_stream(cubeb_stream *stm);
|
||||
+static int audiounit_setup_stream(cubeb_stream *stm);
|
||||
|
||||
extern cubeb_ops const audiounit_ops;
|
||||
|
||||
struct cubeb {
|
||||
cubeb_ops const * ops;
|
||||
owned_critical_section mutex;
|
||||
std::atomic<int> active_streams;
|
||||
+ uint32_t global_latency_frames = 0;
|
||||
int limit_streams;
|
||||
cubeb_device_collection_changed_callback collection_changed_callback;
|
||||
void * collection_changed_user_ptr;
|
||||
/* Differentiate input from output devices. */
|
||||
cubeb_device_type collection_changed_devtype;
|
||||
uint32_t devtype_device_count;
|
||||
AudioObjectID * devtype_device_array;
|
||||
+ // The queue is asynchronously deallocated once all references to it are released
|
||||
+ dispatch_queue_t serial_queue = dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL);
|
||||
};
|
||||
|
||||
class auto_array_wrapper
|
||||
{
|
||||
public:
|
||||
explicit auto_array_wrapper(auto_array<float> * ar)
|
||||
: float_ar(ar)
|
||||
, short_ar(nullptr)
|
||||
@@ -205,16 +210,17 @@ struct cubeb_stream {
|
||||
cubeb_resampler * resampler;
|
||||
/* This is the number of output callback we got in a row. This is usually one,
|
||||
* but can be two when the input and output rate are different, and more when
|
||||
* a device has been plugged or unplugged, as there can be some time before
|
||||
* the device is ready. */
|
||||
std::atomic<int> output_callback_in_a_row;
|
||||
/* This is true if a device change callback is currently running. */
|
||||
std::atomic<bool> switching_device;
|
||||
+ std::atomic<bool> buffer_size_change_state{ false };
|
||||
};
|
||||
|
||||
bool has_input(cubeb_stream * stm)
|
||||
{
|
||||
return stm->input_stream_params.rate != 0;
|
||||
}
|
||||
|
||||
bool has_output(cubeb_stream * stm)
|
||||
@@ -256,16 +262,24 @@ audiotimestamp_to_latency(AudioTimeStamp
|
||||
|
||||
uint64_t pres = AudioConvertHostTimeToNanos(tstamp->mHostTime);
|
||||
uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
|
||||
|
||||
return ((pres - now) * stream->output_desc.mSampleRate) / 1000000000LL;
|
||||
}
|
||||
|
||||
static void
|
||||
+audiounit_set_global_latency(cubeb_stream * stm, uint32_t latency_frames)
|
||||
+{
|
||||
+ stm->mutex.assert_current_thread_owns();
|
||||
+ assert(stm->context->active_streams == 1);
|
||||
+ stm->context->global_latency_frames = latency_frames;
|
||||
+}
|
||||
+
|
||||
+static void
|
||||
audiounit_make_silent(AudioBuffer * ioData)
|
||||
{
|
||||
assert(ioData);
|
||||
assert(ioData->mData);
|
||||
memset(ioData->mData, 0, ioData->mDataByteSize);
|
||||
}
|
||||
|
||||
static OSStatus
|
||||
@@ -576,29 +590,54 @@ audiounit_get_input_device_id(AudioDevic
|
||||
device_id);
|
||||
if (r != noErr) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
+static int
|
||||
+audiounit_reinit_stream(cubeb_stream * stm, bool is_started)
|
||||
+{
|
||||
+ if (is_started) {
|
||||
+ audiounit_stream_stop_internal(stm);
|
||||
+ }
|
||||
+
|
||||
+ {
|
||||
+ auto_lock lock(stm->mutex);
|
||||
+
|
||||
+ audiounit_close_stream(stm);
|
||||
+
|
||||
+ if (audiounit_setup_stream(stm) != CUBEB_OK) {
|
||||
+ LOG("(%p) Stream reinit failed.", stm);
|
||||
+ return CUBEB_ERROR;
|
||||
+ }
|
||||
+
|
||||
+ // Reset input frames to force new stream pre-buffer
|
||||
+ // silence if needed, check `is_extra_input_needed()`
|
||||
+ stm->frames_read = 0;
|
||||
+
|
||||
+ // If the stream was running, start it again.
|
||||
+ if (is_started) {
|
||||
+ audiounit_stream_start_internal(stm);
|
||||
+ }
|
||||
+ }
|
||||
+ return CUBEB_OK;
|
||||
+}
|
||||
+
|
||||
static OSStatus
|
||||
audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_count,
|
||||
const AudioObjectPropertyAddress * addresses,
|
||||
void * user)
|
||||
{
|
||||
cubeb_stream * stm = (cubeb_stream*) user;
|
||||
- int rv;
|
||||
- bool was_running = false;
|
||||
-
|
||||
stm->switching_device = true;
|
||||
-
|
||||
// Note if the stream was running or not
|
||||
- was_running = !stm->shutdown;
|
||||
+ bool was_running = !stm->shutdown;
|
||||
|
||||
LOG("(%p) Audio device changed, %d events.", stm, address_count);
|
||||
for (UInt32 i = 0; i < address_count; i++) {
|
||||
switch(addresses[i].mSelector) {
|
||||
case kAudioHardwarePropertyDefaultOutputDevice: {
|
||||
LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultOutputDevice", i);
|
||||
// Allow restart to choose the new default
|
||||
stm->output_device = nullptr;
|
||||
@@ -639,38 +678,25 @@ audiounit_property_listener_callback(Aud
|
||||
if (stm->device_changed_callback) {
|
||||
stm->device_changed_callback(stm->user_ptr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- // This means the callback won't be called again.
|
||||
- audiounit_stream_stop_internal(stm);
|
||||
-
|
||||
- {
|
||||
- auto_lock lock(stm->mutex);
|
||||
- close_audiounit_stream(stm);
|
||||
- rv = setup_audiounit_stream(stm);
|
||||
- if (rv != CUBEB_OK) {
|
||||
- LOG("(%p) Could not reopen a stream after switching.", stm);
|
||||
+ // Use a new thread, through the queue, to avoid deadlock when calling
|
||||
+ // Get/SetProperties method from inside notify callback
|
||||
+ dispatch_async(stm->context->serial_queue, ^() {
|
||||
+ if (audiounit_reinit_stream(stm, was_running) != CUBEB_OK) {
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
|
||||
- return noErr;
|
||||
+ LOG("(%p) Could not reopen the stream after switching.", stm);
|
||||
}
|
||||
-
|
||||
- stm->frames_read = 0;
|
||||
-
|
||||
- // If the stream was running, start it again.
|
||||
- if (was_running) {
|
||||
- audiounit_stream_start_internal(stm);
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- stm->switching_device = false;
|
||||
+ stm->switching_device = false;
|
||||
+ });
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
OSStatus
|
||||
audiounit_add_listener(cubeb_stream * stm, AudioDeviceID id, AudioObjectPropertySelector selector,
|
||||
AudioObjectPropertyScope scope, AudioObjectPropertyListenerProc listener)
|
||||
{
|
||||
@@ -1155,18 +1181,17 @@ audiounit_init_input_linear_buffer(cubeb
|
||||
|
||||
static void
|
||||
audiounit_destroy_input_linear_buffer(cubeb_stream * stream)
|
||||
{
|
||||
delete stream->input_linear_buffer;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
-audiounit_clamp_latency(cubeb_stream * stm,
|
||||
- uint32_t latency_frames)
|
||||
+audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames)
|
||||
{
|
||||
// For the 1st stream set anything within safe min-max
|
||||
assert(stm->context->active_streams > 0);
|
||||
if (stm->context->active_streams == 1) {
|
||||
return std::max(std::min<uint32_t>(latency_frames, SAFE_MAX_LATENCY_FRAMES),
|
||||
SAFE_MIN_LATENCY_FRAMES);
|
||||
}
|
||||
|
||||
@@ -1219,26 +1244,374 @@ audiounit_clamp_latency(cubeb_stream * s
|
||||
} else {
|
||||
upper_latency_limit = SAFE_MAX_LATENCY_FRAMES;
|
||||
}
|
||||
|
||||
return std::max(std::min<uint32_t>(latency_frames, upper_latency_limit),
|
||||
SAFE_MIN_LATENCY_FRAMES);
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Change buffer size is prone to deadlock thus we change it
|
||||
+ * following the steps:
|
||||
+ * - register a listener for the buffer size property
|
||||
+ * - change the property
|
||||
+ * - wait until the listener is executed
|
||||
+ * - property has changed, remove the listener
|
||||
+ * */
|
||||
+static void
|
||||
+buffer_size_changed_callback(void * inClientData,
|
||||
+ AudioUnit inUnit,
|
||||
+ AudioUnitPropertyID inPropertyID,
|
||||
+ AudioUnitScope inScope,
|
||||
+ AudioUnitElement inElement)
|
||||
+{
|
||||
+ cubeb_stream * stm = (cubeb_stream *)inClientData;
|
||||
+
|
||||
+ AudioUnit au = inUnit;
|
||||
+ AudioUnitScope au_scope = kAudioUnitScope_Input;
|
||||
+ AudioUnitElement au_element = inElement;
|
||||
+ const char * au_type = "output";
|
||||
+
|
||||
+ if (au == stm->input_unit) {
|
||||
+ au_scope = kAudioUnitScope_Output;
|
||||
+ au_type = "input";
|
||||
+ }
|
||||
+
|
||||
+ switch (inPropertyID) {
|
||||
+
|
||||
+ case kAudioDevicePropertyBufferFrameSize: {
|
||||
+ if (inScope != au_scope) {
|
||||
+ break;
|
||||
+ }
|
||||
+ UInt32 new_buffer_size;
|
||||
+ UInt32 outSize = sizeof(UInt32);
|
||||
+ OSStatus r = AudioUnitGetProperty(au,
|
||||
+ kAudioDevicePropertyBufferFrameSize,
|
||||
+ au_scope,
|
||||
+ au_element,
|
||||
+ &new_buffer_size,
|
||||
+ &outSize);
|
||||
+ if (r != noErr) {
|
||||
+ LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: Cannot get current buffer size", stm);
|
||||
+ } else {
|
||||
+ LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: New %s buffer size = %d for scope %d", stm,
|
||||
+ au_type, new_buffer_size, inScope);
|
||||
+ }
|
||||
+ stm->buffer_size_change_state = true;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+enum set_buffer_size_side {
|
||||
+ INPUT,
|
||||
+ OUTPUT,
|
||||
+};
|
||||
+
|
||||
static int
|
||||
-setup_audiounit_stream(cubeb_stream * stm)
|
||||
+audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, set_buffer_size_side set_side)
|
||||
+{
|
||||
+ AudioUnit au = stm->output_unit;
|
||||
+ AudioUnitScope au_scope = kAudioUnitScope_Input;
|
||||
+ AudioUnitElement au_element = AU_OUT_BUS;
|
||||
+ const char * au_type = "output";
|
||||
+
|
||||
+ if (set_side == INPUT) {
|
||||
+ au = stm->input_unit;
|
||||
+ au_scope = kAudioUnitScope_Output;
|
||||
+ au_element = AU_IN_BUS;
|
||||
+ au_type = "input";
|
||||
+ }
|
||||
+
|
||||
+ uint32_t buffer_frames = 0;
|
||||
+ UInt32 size = sizeof(buffer_frames);
|
||||
+ int r = AudioUnitGetProperty(au,
|
||||
+ kAudioDevicePropertyBufferFrameSize,
|
||||
+ au_scope,
|
||||
+ au_element,
|
||||
+ &buffer_frames,
|
||||
+ &size);
|
||||
+ if (r != noErr) {
|
||||
+ if (set_side == INPUT) {
|
||||
+ PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize", r);
|
||||
+ } else {
|
||||
+ PRINT_ERROR_CODE("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize", r);
|
||||
+ }
|
||||
+ return CUBEB_ERROR;
|
||||
+ }
|
||||
+
|
||||
+ if (new_size_frames == buffer_frames) {
|
||||
+ LOG("(%p) No need to update %s buffer size already %u frames", stm, au_type, buffer_frames);
|
||||
+ return CUBEB_OK;
|
||||
+ }
|
||||
+
|
||||
+ r = AudioUnitAddPropertyListener(au,
|
||||
+ kAudioDevicePropertyBufferFrameSize,
|
||||
+ buffer_size_changed_callback,
|
||||
+ stm);
|
||||
+ if (r != noErr) {
|
||||
+ if (set_side == INPUT) {
|
||||
+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r);
|
||||
+ } else {
|
||||
+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r);
|
||||
+ }
|
||||
+ return CUBEB_ERROR;
|
||||
+ }
|
||||
+
|
||||
+ stm->buffer_size_change_state = false;
|
||||
+
|
||||
+ r = AudioUnitSetProperty(au,
|
||||
+ kAudioDevicePropertyBufferFrameSize,
|
||||
+ au_scope,
|
||||
+ au_element,
|
||||
+ &new_size_frames,
|
||||
+ sizeof(new_size_frames));
|
||||
+ if (r != noErr) {
|
||||
+ if (set_side == INPUT) {
|
||||
+ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioDevicePropertyBufferFrameSize", r);
|
||||
+ } else {
|
||||
+ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioDevicePropertyBufferFrameSize", r);
|
||||
+ }
|
||||
+
|
||||
+ r = AudioUnitRemovePropertyListenerWithUserData(au,
|
||||
+ kAudioDevicePropertyBufferFrameSize,
|
||||
+ buffer_size_changed_callback,
|
||||
+ stm);
|
||||
+ if (r != noErr) {
|
||||
+ if (set_side == INPUT) {
|
||||
+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r);
|
||||
+ } else {
|
||||
+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return CUBEB_ERROR;
|
||||
+ }
|
||||
+
|
||||
+ int count = 0;
|
||||
+ while (!stm->buffer_size_change_state && count++ < 30) {
|
||||
+ struct timespec req, rem;
|
||||
+ req.tv_sec = 0;
|
||||
+ req.tv_nsec = 100000000L; // 0.1 sec
|
||||
+ if (nanosleep(&req , &rem) < 0 ) {
|
||||
+ LOG("(%p) Warning: nanosleep call failed or interrupted. Remaining time %ld nano secs \n", stm, rem.tv_nsec);
|
||||
+ }
|
||||
+ LOG("(%p) audiounit_set_buffer_size : wait count = %d", stm, count);
|
||||
+ }
|
||||
+
|
||||
+ r = AudioUnitRemovePropertyListenerWithUserData(au,
|
||||
+ kAudioDevicePropertyBufferFrameSize,
|
||||
+ buffer_size_changed_callback,
|
||||
+ stm);
|
||||
+ if (r != noErr) {
|
||||
+ return CUBEB_ERROR;
|
||||
+ if (set_side == INPUT) {
|
||||
+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r);
|
||||
+ } else {
|
||||
+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (!stm->buffer_size_change_state && count >= 30) {
|
||||
+ LOG("(%p) Error, did not get buffer size change callback ...", stm);
|
||||
+ return CUBEB_ERROR;
|
||||
+ }
|
||||
+
|
||||
+ LOG("(%p) %s buffer size changed to %u frames.", stm, au_type, new_size_frames);
|
||||
+ return CUBEB_OK;
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
+audiounit_configure_input(cubeb_stream * stm)
|
||||
+{
|
||||
+ int r = 0;
|
||||
+ UInt32 size;
|
||||
+ AURenderCallbackStruct aurcbs_in;
|
||||
+
|
||||
+ LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in frames %u.",
|
||||
+ stm, stm->input_stream_params.rate, stm->input_stream_params.channels,
|
||||
+ stm->input_stream_params.format, stm->latency_frames);
|
||||
+
|
||||
+ /* Get input device sample rate. */
|
||||
+ AudioStreamBasicDescription input_hw_desc;
|
||||
+ size = sizeof(AudioStreamBasicDescription);
|
||||
+ r = AudioUnitGetProperty(stm->input_unit,
|
||||
+ kAudioUnitProperty_StreamFormat,
|
||||
+ kAudioUnitScope_Input,
|
||||
+ AU_IN_BUS,
|
||||
+ &input_hw_desc,
|
||||
+ &size);
|
||||
+ if (r != noErr) {
|
||||
+ PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat", r);
|
||||
+ return CUBEB_ERROR;
|
||||
+ }
|
||||
+ stm->input_hw_rate = input_hw_desc.mSampleRate;
|
||||
+ LOG("(%p) Input device sampling rate: %.2f", stm, stm->input_hw_rate);
|
||||
+
|
||||
+ /* Set format description according to the input params. */
|
||||
+ r = audio_stream_desc_init(&stm->input_desc, &stm->input_stream_params);
|
||||
+ if (r != CUBEB_OK) {
|
||||
+ LOG("(%p) Setting format description for input failed.", stm);
|
||||
+ return r;
|
||||
+ }
|
||||
+
|
||||
+ // Use latency to set buffer size
|
||||
+ stm->input_buffer_frames = stm->latency_frames;
|
||||
+ r = audiounit_set_buffer_size(stm, stm->input_buffer_frames, INPUT);
|
||||
+ if (r != CUBEB_OK) {
|
||||
+ LOG("(%p) Error in change input buffer size.", stm);
|
||||
+ return CUBEB_ERROR;
|
||||
+ }
|
||||
+
|
||||
+ AudioStreamBasicDescription src_desc = stm->input_desc;
|
||||
+ /* Input AudioUnit must be configured with device's sample rate.
|
||||
+ we will resample inside input callback. */
|
||||
+ src_desc.mSampleRate = stm->input_hw_rate;
|
||||
+
|
||||
+ r = AudioUnitSetProperty(stm->input_unit,
|
||||
+ kAudioUnitProperty_StreamFormat,
|
||||
+ kAudioUnitScope_Output,
|
||||
+ AU_IN_BUS,
|
||||
+ &src_desc,
|
||||
+ sizeof(AudioStreamBasicDescription));
|
||||
+ if (r != noErr) {
|
||||
+ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat", r);
|
||||
+ return CUBEB_ERROR;
|
||||
+ }
|
||||
+
|
||||
+ /* Frames per buffer in the input callback. */
|
||||
+ r = AudioUnitSetProperty(stm->input_unit,
|
||||
+ kAudioUnitProperty_MaximumFramesPerSlice,
|
||||
+ kAudioUnitScope_Global,
|
||||
+ AU_IN_BUS,
|
||||
+ &stm->input_buffer_frames,
|
||||
+ sizeof(UInt32));
|
||||
+ if (r != noErr) {
|
||||
+ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice", r);
|
||||
+ return CUBEB_ERROR;
|
||||
+ }
|
||||
+
|
||||
+ // Input only capacity
|
||||
+ unsigned int array_capacity = 1;
|
||||
+ if (has_output(stm)) {
|
||||
+ // Full-duplex increase capacity
|
||||
+ array_capacity = 8;
|
||||
+ }
|
||||
+ if (audiounit_init_input_linear_buffer(stm, array_capacity) != CUBEB_OK) {
|
||||
+ return CUBEB_ERROR;
|
||||
+ }
|
||||
+
|
||||
+ assert(stm->input_unit != NULL);
|
||||
+ aurcbs_in.inputProc = audiounit_input_callback;
|
||||
+ aurcbs_in.inputProcRefCon = stm;
|
||||
+
|
||||
+ r = AudioUnitSetProperty(stm->input_unit,
|
||||
+ kAudioOutputUnitProperty_SetInputCallback,
|
||||
+ kAudioUnitScope_Global,
|
||||
+ AU_OUT_BUS,
|
||||
+ &aurcbs_in,
|
||||
+ sizeof(aurcbs_in));
|
||||
+ if (r != noErr) {
|
||||
+ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback", r);
|
||||
+ return CUBEB_ERROR;
|
||||
+ }
|
||||
+ LOG("(%p) Input audiounit init successfully.", stm);
|
||||
+
|
||||
+ return CUBEB_OK;
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
+audiounit_configure_output(cubeb_stream * stm)
|
||||
+{
|
||||
+ int r;
|
||||
+ AURenderCallbackStruct aurcbs_out;
|
||||
+ UInt32 size;
|
||||
+
|
||||
+
|
||||
+ LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in frames %u.",
|
||||
+ stm, stm->output_stream_params.rate, stm->output_stream_params.channels,
|
||||
+ stm->output_stream_params.format, stm->latency_frames);
|
||||
+
|
||||
+ r = audio_stream_desc_init(&stm->output_desc, &stm->output_stream_params);
|
||||
+ if (r != CUBEB_OK) {
|
||||
+ LOG("(%p) Could not initialize the audio stream description.", stm);
|
||||
+ return r;
|
||||
+ }
|
||||
+
|
||||
+ /* Get output device sample rate. */
|
||||
+ AudioStreamBasicDescription output_hw_desc;
|
||||
+ size = sizeof(AudioStreamBasicDescription);
|
||||
+ memset(&output_hw_desc, 0, size);
|
||||
+ r = AudioUnitGetProperty(stm->output_unit,
|
||||
+ kAudioUnitProperty_StreamFormat,
|
||||
+ kAudioUnitScope_Output,
|
||||
+ AU_OUT_BUS,
|
||||
+ &output_hw_desc,
|
||||
+ &size);
|
||||
+ if (r != noErr) {
|
||||
+ PRINT_ERROR_CODE("AudioUnitGetProperty/output/tkAudioUnitProperty_StreamFormat", r);
|
||||
+ return CUBEB_ERROR;
|
||||
+ }
|
||||
+ stm->output_hw_rate = output_hw_desc.mSampleRate;
|
||||
+ LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate);
|
||||
+
|
||||
+ r = AudioUnitSetProperty(stm->output_unit,
|
||||
+ kAudioUnitProperty_StreamFormat,
|
||||
+ kAudioUnitScope_Input,
|
||||
+ AU_OUT_BUS,
|
||||
+ &stm->output_desc,
|
||||
+ sizeof(AudioStreamBasicDescription));
|
||||
+ if (r != noErr) {
|
||||
+ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat", r);
|
||||
+ return CUBEB_ERROR;
|
||||
+ }
|
||||
+
|
||||
+ r = audiounit_set_buffer_size(stm, stm->latency_frames, OUTPUT);
|
||||
+ if (r != CUBEB_OK) {
|
||||
+ LOG("(%p) Error in change output buffer size.", stm);
|
||||
+ return CUBEB_ERROR;
|
||||
+ }
|
||||
+
|
||||
+ /* Frames per buffer in the input callback. */
|
||||
+ r = AudioUnitSetProperty(stm->output_unit,
|
||||
+ kAudioUnitProperty_MaximumFramesPerSlice,
|
||||
+ kAudioUnitScope_Global,
|
||||
+ AU_OUT_BUS,
|
||||
+ &stm->latency_frames,
|
||||
+ sizeof(UInt32));
|
||||
+ if (r != noErr) {
|
||||
+ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_MaximumFramesPerSlice", r);
|
||||
+ return CUBEB_ERROR;
|
||||
+ }
|
||||
+
|
||||
+ assert(stm->output_unit != NULL);
|
||||
+ aurcbs_out.inputProc = audiounit_output_callback;
|
||||
+ aurcbs_out.inputProcRefCon = stm;
|
||||
+ r = AudioUnitSetProperty(stm->output_unit,
|
||||
+ kAudioUnitProperty_SetRenderCallback,
|
||||
+ kAudioUnitScope_Global,
|
||||
+ AU_OUT_BUS,
|
||||
+ &aurcbs_out,
|
||||
+ sizeof(aurcbs_out));
|
||||
+ if (r != noErr) {
|
||||
+ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback", r);
|
||||
+ return CUBEB_ERROR;
|
||||
+ }
|
||||
+
|
||||
+ LOG("(%p) Output audiounit init successfully.", stm);
|
||||
+ return CUBEB_OK;
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
+audiounit_setup_stream(cubeb_stream * stm)
|
||||
{
|
||||
stm->mutex.assert_current_thread_owns();
|
||||
|
||||
- int r;
|
||||
- AURenderCallbackStruct aurcbs_in;
|
||||
- AURenderCallbackStruct aurcbs_out;
|
||||
- UInt32 size;
|
||||
-
|
||||
+ int r = 0;
|
||||
if (has_input(stm)) {
|
||||
r = audiounit_create_unit(&stm->input_unit, true,
|
||||
&stm->input_stream_params,
|
||||
stm->input_device);
|
||||
if (r != CUBEB_OK) {
|
||||
LOG("(%p) AudioUnit creation for input failed.", stm);
|
||||
return r;
|
||||
}
|
||||
@@ -1249,180 +1622,46 @@ setup_audiounit_stream(cubeb_stream * st
|
||||
&stm->output_stream_params,
|
||||
stm->output_device);
|
||||
if (r != CUBEB_OK) {
|
||||
LOG("(%p) AudioUnit creation for output failed.", stm);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
+ /* Latency cannot change if another stream is operating in parallel. In this case
|
||||
+ * latecy is set to the other stream value. */
|
||||
+ if (stm->context->active_streams > 1) {
|
||||
+ LOG("(%p) More than one active stream, use global latency.", stm);
|
||||
+ stm->latency_frames = stm->context->global_latency_frames;
|
||||
+ } else {
|
||||
+ /* Silently clamp the latency down to the platform default, because we
|
||||
+ * synthetize the clock from the callbacks, and we want the clock to update
|
||||
+ * often. */
|
||||
+ stm->latency_frames = audiounit_clamp_latency(stm, stm->latency_frames);
|
||||
+ assert(stm->latency_frames); // Ungly error check
|
||||
+ audiounit_set_global_latency(stm, stm->latency_frames);
|
||||
+ }
|
||||
+
|
||||
/* Setup Input Stream! */
|
||||
if (has_input(stm)) {
|
||||
- LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in frames %u.",
|
||||
- stm, stm->input_stream_params.rate, stm->input_stream_params.channels,
|
||||
- stm->input_stream_params.format, stm->latency_frames);
|
||||
- /* Get input device sample rate. */
|
||||
- AudioStreamBasicDescription input_hw_desc;
|
||||
- size = sizeof(AudioStreamBasicDescription);
|
||||
- r = AudioUnitGetProperty(stm->input_unit,
|
||||
- kAudioUnitProperty_StreamFormat,
|
||||
- kAudioUnitScope_Input,
|
||||
- AU_IN_BUS,
|
||||
- &input_hw_desc,
|
||||
- &size);
|
||||
- if (r != noErr) {
|
||||
- PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat", r);
|
||||
- return CUBEB_ERROR;
|
||||
- }
|
||||
- stm->input_hw_rate = input_hw_desc.mSampleRate;
|
||||
- LOG("(%p) Input device sampling rate: %.2f", stm, stm->input_hw_rate);
|
||||
-
|
||||
- /* Set format description according to the input params. */
|
||||
- r = audio_stream_desc_init(&stm->input_desc, &stm->input_stream_params);
|
||||
+ r = audiounit_configure_input(stm);
|
||||
if (r != CUBEB_OK) {
|
||||
- LOG("(%p) Setting format description for input failed.", stm);
|
||||
+ LOG("(%p) Configure audiounit input failed.", stm);
|
||||
return r;
|
||||
}
|
||||
-
|
||||
- // Use latency to set buffer size
|
||||
- stm->input_buffer_frames = stm->latency_frames;
|
||||
- LOG("(%p) Input buffer frame count %u.", stm, unsigned(stm->input_buffer_frames));
|
||||
- r = AudioUnitSetProperty(stm->input_unit,
|
||||
- kAudioDevicePropertyBufferFrameSize,
|
||||
- kAudioUnitScope_Output,
|
||||
- AU_IN_BUS,
|
||||
- &stm->input_buffer_frames,
|
||||
- sizeof(UInt32));
|
||||
- if (r != noErr) {
|
||||
- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioDevicePropertyBufferFrameSize", r);
|
||||
- return CUBEB_ERROR;
|
||||
- }
|
||||
-
|
||||
- AudioStreamBasicDescription src_desc = stm->input_desc;
|
||||
- /* Input AudioUnit must be configured with device's sample rate.
|
||||
- we will resample inside input callback. */
|
||||
- src_desc.mSampleRate = stm->input_hw_rate;
|
||||
-
|
||||
- r = AudioUnitSetProperty(stm->input_unit,
|
||||
- kAudioUnitProperty_StreamFormat,
|
||||
- kAudioUnitScope_Output,
|
||||
- AU_IN_BUS,
|
||||
- &src_desc,
|
||||
- sizeof(AudioStreamBasicDescription));
|
||||
- if (r != noErr) {
|
||||
- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat", r);
|
||||
- return CUBEB_ERROR;
|
||||
- }
|
||||
-
|
||||
- /* Frames per buffer in the input callback. */
|
||||
- r = AudioUnitSetProperty(stm->input_unit,
|
||||
- kAudioUnitProperty_MaximumFramesPerSlice,
|
||||
- kAudioUnitScope_Output,
|
||||
- AU_IN_BUS,
|
||||
- &stm->input_buffer_frames,
|
||||
- sizeof(UInt32));
|
||||
- if (r != noErr) {
|
||||
- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice", r);
|
||||
- return CUBEB_ERROR;
|
||||
- }
|
||||
-
|
||||
- // Input only capacity
|
||||
- unsigned int array_capacity = 1;
|
||||
- if (has_output(stm)) {
|
||||
- // Full-duplex increase capacity
|
||||
- array_capacity = 8;
|
||||
- }
|
||||
- if (audiounit_init_input_linear_buffer(stm, array_capacity) != CUBEB_OK) {
|
||||
- return CUBEB_ERROR;
|
||||
- }
|
||||
-
|
||||
- assert(stm->input_unit != NULL);
|
||||
- aurcbs_in.inputProc = audiounit_input_callback;
|
||||
- aurcbs_in.inputProcRefCon = stm;
|
||||
-
|
||||
- r = AudioUnitSetProperty(stm->input_unit,
|
||||
- kAudioOutputUnitProperty_SetInputCallback,
|
||||
- kAudioUnitScope_Global,
|
||||
- AU_OUT_BUS,
|
||||
- &aurcbs_in,
|
||||
- sizeof(aurcbs_in));
|
||||
- if (r != noErr) {
|
||||
- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback", r);
|
||||
- return CUBEB_ERROR;
|
||||
- }
|
||||
- LOG("(%p) Input audiounit init successfully.", stm);
|
||||
}
|
||||
|
||||
/* Setup Output Stream! */
|
||||
if (has_output(stm)) {
|
||||
- LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in frames %u.",
|
||||
- stm, stm->output_stream_params.rate, stm->output_stream_params.channels,
|
||||
- stm->output_stream_params.format, stm->latency_frames);
|
||||
- r = audio_stream_desc_init(&stm->output_desc, &stm->output_stream_params);
|
||||
+ r = audiounit_configure_output(stm);
|
||||
if (r != CUBEB_OK) {
|
||||
- LOG("(%p) Could not initialize the audio stream description.", stm);
|
||||
+ LOG("(%p) Configure audiounit output failed.", stm);
|
||||
return r;
|
||||
}
|
||||
-
|
||||
- /* Get output device sample rate. */
|
||||
- AudioStreamBasicDescription output_hw_desc;
|
||||
- size = sizeof(AudioStreamBasicDescription);
|
||||
- memset(&output_hw_desc, 0, size);
|
||||
- r = AudioUnitGetProperty(stm->output_unit,
|
||||
- kAudioUnitProperty_StreamFormat,
|
||||
- kAudioUnitScope_Output,
|
||||
- AU_OUT_BUS,
|
||||
- &output_hw_desc,
|
||||
- &size);
|
||||
- if (r != noErr) {
|
||||
- PRINT_ERROR_CODE("AudioUnitGetProperty/output/tkAudioUnitProperty_StreamFormat", r);
|
||||
- return CUBEB_ERROR;
|
||||
- }
|
||||
- stm->output_hw_rate = output_hw_desc.mSampleRate;
|
||||
- LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate);
|
||||
-
|
||||
- r = AudioUnitSetProperty(stm->output_unit,
|
||||
- kAudioUnitProperty_StreamFormat,
|
||||
- kAudioUnitScope_Input,
|
||||
- AU_OUT_BUS,
|
||||
- &stm->output_desc,
|
||||
- sizeof(AudioStreamBasicDescription));
|
||||
- if (r != noErr) {
|
||||
- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat", r);
|
||||
- return CUBEB_ERROR;
|
||||
- }
|
||||
-
|
||||
- // Use latency to calculate buffer size
|
||||
- uint32_t output_buffer_frames = stm->latency_frames;
|
||||
- LOG("(%p) Output buffer frame count %u.", stm, output_buffer_frames);
|
||||
- r = AudioUnitSetProperty(stm->output_unit,
|
||||
- kAudioDevicePropertyBufferFrameSize,
|
||||
- kAudioUnitScope_Input,
|
||||
- AU_OUT_BUS,
|
||||
- &output_buffer_frames,
|
||||
- sizeof(output_buffer_frames));
|
||||
- if (r != noErr) {
|
||||
- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioDevicePropertyBufferFrameSize", r);
|
||||
- return CUBEB_ERROR;
|
||||
- }
|
||||
-
|
||||
- assert(stm->output_unit != NULL);
|
||||
- aurcbs_out.inputProc = audiounit_output_callback;
|
||||
- aurcbs_out.inputProcRefCon = stm;
|
||||
- r = AudioUnitSetProperty(stm->output_unit,
|
||||
- kAudioUnitProperty_SetRenderCallback,
|
||||
- kAudioUnitScope_Global,
|
||||
- AU_OUT_BUS,
|
||||
- &aurcbs_out,
|
||||
- sizeof(aurcbs_out));
|
||||
- if (r != noErr) {
|
||||
- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback", r);
|
||||
- return CUBEB_ERROR;
|
||||
- }
|
||||
- LOG("(%p) Output audiounit init successfully.", stm);
|
||||
}
|
||||
|
||||
// Setting the latency doesn't work well for USB headsets (eg. plantronics).
|
||||
// Keep the default latency for now.
|
||||
#if 0
|
||||
buffer_size = latency;
|
||||
|
||||
/* Get the range of latency this particular device can work with, and clamp
|
||||
@@ -1535,63 +1774,60 @@ audiounit_stream_init(cubeb * context,
|
||||
void * user_ptr)
|
||||
{
|
||||
cubeb_stream * stm;
|
||||
int r;
|
||||
|
||||
assert(context);
|
||||
*stream = NULL;
|
||||
|
||||
+ assert(latency_frames > 0);
|
||||
if (context->limit_streams && context->active_streams >= CUBEB_STREAM_MAX) {
|
||||
LOG("Reached the stream limit of %d", CUBEB_STREAM_MAX);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
- context->active_streams += 1;
|
||||
|
||||
stm = (cubeb_stream *) calloc(1, sizeof(cubeb_stream));
|
||||
assert(stm);
|
||||
// Placement new to call the ctors of cubeb_stream members.
|
||||
new (stm) cubeb_stream();
|
||||
|
||||
/* These could be different in the future if we have both
|
||||
* full-duplex stream and different devices for input vs output. */
|
||||
stm->context = context;
|
||||
stm->data_callback = data_callback;
|
||||
stm->state_callback = state_callback;
|
||||
stm->user_ptr = user_ptr;
|
||||
+ stm->latency_frames = latency_frames;
|
||||
stm->device_changed_callback = NULL;
|
||||
if (input_stream_params) {
|
||||
stm->input_stream_params = *input_stream_params;
|
||||
stm->input_device = input_device;
|
||||
stm->is_default_input = input_device == nullptr ||
|
||||
(audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT) ==
|
||||
reinterpret_cast<intptr_t>(input_device));
|
||||
}
|
||||
if (output_stream_params) {
|
||||
stm->output_stream_params = *output_stream_params;
|
||||
stm->output_device = output_device;
|
||||
}
|
||||
|
||||
/* Init data members where necessary */
|
||||
stm->hw_latency_frames = UINT64_MAX;
|
||||
|
||||
- /* Silently clamp the latency down to the platform default, because we
|
||||
- * synthetize the clock from the callbacks, and we want the clock to update
|
||||
- * often. */
|
||||
- stm->latency_frames = audiounit_clamp_latency(stm, latency_frames);
|
||||
- assert(latency_frames > 0);
|
||||
-
|
||||
stm->switching_device = false;
|
||||
|
||||
+ auto_lock context_lock(context->mutex);
|
||||
{
|
||||
// It's not critical to lock here, because no other thread has been started
|
||||
// yet, but it allows to assert that the lock has been taken in
|
||||
- // `setup_audiounit_stream`.
|
||||
+ // `audiounit_setup_stream`.
|
||||
+ context->active_streams += 1;
|
||||
auto_lock lock(stm->mutex);
|
||||
- r = setup_audiounit_stream(stm);
|
||||
+ r = audiounit_setup_stream(stm);
|
||||
}
|
||||
|
||||
if (r != CUBEB_OK) {
|
||||
LOG("(%p) Could not setup the audiounit stream.", stm);
|
||||
audiounit_stream_destroy(stm);
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -1602,17 +1838,17 @@ audiounit_stream_init(cubeb * context,
|
||||
}
|
||||
|
||||
*stream = stm;
|
||||
LOG("Cubeb stream (%p) init successful.", stm);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static void
|
||||
-close_audiounit_stream(cubeb_stream * stm)
|
||||
+audiounit_close_stream(cubeb_stream *stm)
|
||||
{
|
||||
stm->mutex.assert_current_thread_owns();
|
||||
if (stm->input_unit) {
|
||||
AudioUnitUninitialize(stm->input_unit);
|
||||
AudioComponentInstanceDispose(stm->input_unit);
|
||||
}
|
||||
|
||||
audiounit_destroy_input_linear_buffer(stm);
|
||||
@@ -1625,33 +1861,36 @@ close_audiounit_stream(cubeb_stream * st
|
||||
cubeb_resampler_destroy(stm->resampler);
|
||||
}
|
||||
|
||||
static void
|
||||
audiounit_stream_destroy(cubeb_stream * stm)
|
||||
{
|
||||
stm->shutdown = true;
|
||||
|
||||
+ auto_lock context_locl(stm->context->mutex);
|
||||
audiounit_stream_stop_internal(stm);
|
||||
|
||||
{
|
||||
auto_lock lock(stm->mutex);
|
||||
- close_audiounit_stream(stm);
|
||||
+ audiounit_close_stream(stm);
|
||||
}
|
||||
|
||||
#if !TARGET_OS_IPHONE
|
||||
int r = audiounit_uninstall_device_changed_callback(stm);
|
||||
if (r != CUBEB_OK) {
|
||||
LOG("(%p) Could not uninstall the device changed callback", stm);
|
||||
}
|
||||
#endif
|
||||
|
||||
assert(stm->context->active_streams >= 1);
|
||||
stm->context->active_streams -= 1;
|
||||
|
||||
+ LOG("Cubeb stream (%p) destroyed successful.", stm);
|
||||
+
|
||||
stm->~cubeb_stream();
|
||||
free(stm);
|
||||
}
|
||||
|
||||
void
|
||||
audiounit_stream_start_internal(cubeb_stream * stm)
|
||||
{
|
||||
OSStatus r;
|
||||
@@ -1666,16 +1905,17 @@ audiounit_stream_start_internal(cubeb_st
|
||||
}
|
||||
|
||||
static int
|
||||
audiounit_stream_start(cubeb_stream * stm)
|
||||
{
|
||||
stm->shutdown = false;
|
||||
stm->draining = false;
|
||||
|
||||
+ auto_lock context_locl(stm->context->mutex);
|
||||
audiounit_stream_start_internal(stm);
|
||||
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
|
||||
|
||||
LOG("Cubeb stream (%p) started successfully.", stm);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
@@ -1693,16 +1933,17 @@ audiounit_stream_stop_internal(cubeb_str
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
audiounit_stream_stop(cubeb_stream * stm)
|
||||
{
|
||||
stm->shutdown = true;
|
||||
|
||||
+ auto_lock context_locl(stm->context->mutex);
|
||||
audiounit_stream_stop_internal(stm);
|
||||
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
|
||||
|
||||
LOG("Cubeb stream (%p) stopped successfully.", stm);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
From f82f15635e09aac4f07d2ddac3d53c84b593d911 Mon Sep 17 00:00:00 2001
|
||||
From: Paul Adenot <paul@paul.cx>
|
||||
Date: Mon, 16 Jan 2017 04:49:41 -0800
|
||||
Subject: [PATCH 1/1] Prevent double-free when doing an emergency bailout from
|
||||
the rendering thread.
|
||||
|
||||
This caused gecko bug 1326176.
|
||||
|
||||
This was caused by the fact that we would null out `stm->thread` when in
|
||||
fact it was still running, so we would delete `stm->emergency_bailout`
|
||||
twice, because we would return true from `stop_and_join_thread`.
|
||||
---
|
||||
src/cubeb_wasapi.cpp | 15 ++++++++++-----
|
||||
1 file changed, 10 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/src/cubeb_wasapi.cpp b/src/cubeb_wasapi.cpp
|
||||
index 63c12ac..2920b5d 100644
|
||||
--- a/src/cubeb_wasapi.cpp
|
||||
+++ b/src/cubeb_wasapi.cpp
|
||||
@@ -1230,13 +1230,18 @@ bool stop_and_join_render_thread(cubeb_stream * stm)
|
||||
rv = false;
|
||||
}
|
||||
|
||||
- LOG("Closing thread.");
|
||||
|
||||
- CloseHandle(stm->thread);
|
||||
- stm->thread = NULL;
|
||||
+ // 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;
|
||||
|
||||
- CloseHandle(stm->shutdown_event);
|
||||
- stm->shutdown_event = 0;
|
||||
+ CloseHandle(stm->shutdown_event);
|
||||
+ stm->shutdown_event = 0;
|
||||
+ }
|
||||
|
||||
return rv;
|
||||
}
|
||||
--
|
||||
2.7.4
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/*
|
||||
* The following definitions are copied from the android sources. Only the
|
||||
* relevant enum member and values needed are copied.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
struct Buffer {
|
||||
uint32_t flags;
|
||||
int channelCount;
|
||||
int format;
|
||||
size_t frameCount;
|
||||
size_t size;
|
||||
union {
|
||||
void* raw;
|
||||
short* i16;
|
||||
int8_t* i8;
|
||||
};
|
||||
};
|
||||
|
||||
enum event_type {
|
||||
EVENT_MORE_DATA = 0,
|
||||
EVENT_UNDERRUN = 1,
|
||||
EVENT_LOOP_END = 2,
|
||||
EVENT_MARKER = 3,
|
||||
EVENT_NEW_POS = 4,
|
||||
EVENT_BUFFER_END = 5
|
||||
};
|
||||
|
||||
/**
|
||||
* 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_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)
|
||||
} 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)
|
||||
} AudioTrack_ChannelMapping_Legacy;
|
||||
|
||||
typedef enum {
|
||||
AUDIO_FORMAT_PCM = 0x00000000,
|
||||
AUDIO_FORMAT_PCM_SUB_16_BIT = 0x1,
|
||||
AUDIO_FORMAT_PCM_16_BIT = (AUDIO_FORMAT_PCM | AUDIO_FORMAT_PCM_SUB_16_BIT),
|
||||
} AudioTrack_SampleType;
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
#ifndef _CUBEB_OUTPUT_LATENCY_H_
|
||||
#define _CUBEB_OUTPUT_LATENCY_H_
|
||||
|
||||
#include "../cubeb-jni.h"
|
||||
#include "cubeb_media_library.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
struct output_latency_function {
|
||||
media_lib * from_lib;
|
||||
cubeb_jni * from_jni;
|
||||
int version;
|
||||
};
|
||||
|
||||
typedef struct output_latency_function output_latency_function;
|
||||
|
||||
const int ANDROID_JELLY_BEAN_MR1_4_2 = 17;
|
||||
|
||||
output_latency_function *
|
||||
cubeb_output_latency_load_method(int version)
|
||||
{
|
||||
output_latency_function * ol = NULL;
|
||||
ol = calloc(1, sizeof(output_latency_function));
|
||||
|
||||
ol->version = version;
|
||||
|
||||
if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) {
|
||||
ol->from_jni = cubeb_jni_init();
|
||||
return ol;
|
||||
}
|
||||
|
||||
ol->from_lib = cubeb_load_media_library();
|
||||
return ol;
|
||||
}
|
||||
|
||||
bool
|
||||
cubeb_output_latency_method_is_loaded(output_latency_function * ol)
|
||||
{
|
||||
assert(ol);
|
||||
if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) {
|
||||
return !!ol->from_jni;
|
||||
}
|
||||
|
||||
return !!ol->from_lib;
|
||||
}
|
||||
|
||||
void
|
||||
cubeb_output_latency_unload_method(output_latency_function * ol)
|
||||
{
|
||||
if (!ol) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2 && ol->from_jni) {
|
||||
cubeb_jni_destroy(ol->from_jni);
|
||||
}
|
||||
|
||||
if (ol->version <= ANDROID_JELLY_BEAN_MR1_4_2 && ol->from_lib) {
|
||||
cubeb_close_media_library(ol->from_lib);
|
||||
}
|
||||
|
||||
free(ol);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
cubeb_get_output_latency(output_latency_function * ol)
|
||||
{
|
||||
assert(cubeb_output_latency_method_is_loaded(ol));
|
||||
|
||||
if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) {
|
||||
return cubeb_get_output_latency_from_jni(ol->from_jni);
|
||||
}
|
||||
|
||||
return cubeb_get_output_latency_from_media_library(ol->from_lib);
|
||||
}
|
||||
|
||||
#endif // _CUBEB_OUTPUT_LATENCY_H_
|
||||
@@ -1,64 +0,0 @@
|
||||
#ifndef _CUBEB_MEDIA_LIBRARY_H_
|
||||
#define _CUBEB_MEDIA_LIBRARY_H_
|
||||
|
||||
struct media_lib {
|
||||
void * libmedia;
|
||||
int32_t (*get_output_latency)(uint32_t * latency, int stream_type);
|
||||
};
|
||||
|
||||
typedef struct media_lib media_lib;
|
||||
|
||||
media_lib *
|
||||
cubeb_load_media_library()
|
||||
{
|
||||
media_lib ml = {0};
|
||||
ml.libmedia = dlopen("libmedia.so", RTLD_LAZY);
|
||||
if (!ml.libmedia) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Get the latency, in ms, from AudioFlinger. First, try the most recent
|
||||
// signature. status_t AudioSystem::getOutputLatency(uint32_t* latency,
|
||||
// audio_stream_type_t streamType)
|
||||
ml.get_output_latency = dlsym(
|
||||
ml.libmedia,
|
||||
"_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t");
|
||||
if (!ml.get_output_latency) {
|
||||
// In case of failure, try the signature from legacy version.
|
||||
// status_t AudioSystem::getOutputLatency(uint32_t* latency, int streamType)
|
||||
ml.get_output_latency =
|
||||
dlsym(ml.libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji");
|
||||
if (!ml.get_output_latency) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
media_lib * rv = NULL;
|
||||
rv = calloc(1, sizeof(media_lib));
|
||||
assert(rv);
|
||||
*rv = ml;
|
||||
return rv;
|
||||
}
|
||||
|
||||
void
|
||||
cubeb_close_media_library(media_lib * ml)
|
||||
{
|
||||
dlclose(ml->libmedia);
|
||||
ml->libmedia = NULL;
|
||||
ml->get_output_latency = NULL;
|
||||
free(ml);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
cubeb_get_output_latency_from_media_library(media_lib * ml)
|
||||
{
|
||||
uint32_t latency = 0;
|
||||
const int audio_stream_type_music = 3;
|
||||
int32_t r = ml->get_output_latency(&latency, audio_stream_type_music);
|
||||
if (r) {
|
||||
return 0;
|
||||
}
|
||||
return latency;
|
||||
}
|
||||
|
||||
#endif // _CUBEB_MEDIA_LIBRARY_H_
|
||||
@@ -29,23 +29,24 @@
|
||||
|
||||
/** Audio recording preset */
|
||||
/** Audio recording preset key */
|
||||
#define SL_ANDROID_KEY_RECORDING_PRESET \
|
||||
((const SLchar *)"androidRecordingPreset")
|
||||
#define SL_ANDROID_KEY_RECORDING_PRESET ((const SLchar*) "androidRecordingPreset")
|
||||
/** Audio recording preset values */
|
||||
/** preset "none" cannot be set, it is used to indicate the current settings
|
||||
* do not match any of the presets. */
|
||||
#define SL_ANDROID_RECORDING_PRESET_NONE ((SLuint32)0x00000000)
|
||||
#define SL_ANDROID_RECORDING_PRESET_NONE ((SLuint32) 0x00000000)
|
||||
/** generic recording configuration on the platform */
|
||||
#define SL_ANDROID_RECORDING_PRESET_GENERIC ((SLuint32)0x00000001)
|
||||
#define SL_ANDROID_RECORDING_PRESET_GENERIC ((SLuint32) 0x00000001)
|
||||
/** uses the microphone audio source with the same orientation as the camera
|
||||
* if available, the main device microphone otherwise */
|
||||
#define SL_ANDROID_RECORDING_PRESET_CAMCORDER ((SLuint32)0x00000002)
|
||||
#define SL_ANDROID_RECORDING_PRESET_CAMCORDER ((SLuint32) 0x00000002)
|
||||
/** uses the main microphone tuned for voice recognition */
|
||||
#define SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION ((SLuint32)0x00000003)
|
||||
#define SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION ((SLuint32) 0x00000003)
|
||||
/** uses the main microphone tuned for audio communications */
|
||||
#define SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION ((SLuint32)0x00000004)
|
||||
/** uses the main microphone unprocessed */
|
||||
#define SL_ANDROID_RECORDING_PRESET_UNPROCESSED ((SLuint32)0x00000005)
|
||||
#define SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION ((SLuint32) 0x00000004)
|
||||
|
||||
/** Audio recording get session ID (read only) */
|
||||
/** Audio recording get session ID key */
|
||||
#define SL_ANDROID_KEY_RECORDING_SESSION_ID ((const SLchar*) "androidRecordingSessionId")
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Android AudioPlayer configuration */
|
||||
@@ -53,52 +54,24 @@
|
||||
|
||||
/** Audio playback stream type */
|
||||
/** Audio playback stream type key */
|
||||
#define SL_ANDROID_KEY_STREAM_TYPE ((const SLchar *)"androidPlaybackStreamType")
|
||||
#define SL_ANDROID_KEY_STREAM_TYPE ((const SLchar*) "androidPlaybackStreamType")
|
||||
|
||||
/** Audio playback stream type values */
|
||||
/* same as android.media.AudioManager.STREAM_VOICE_CALL */
|
||||
#define SL_ANDROID_STREAM_VOICE ((SLint32)0x00000000)
|
||||
#define SL_ANDROID_STREAM_VOICE ((SLint32) 0x00000000)
|
||||
/* same as android.media.AudioManager.STREAM_SYSTEM */
|
||||
#define SL_ANDROID_STREAM_SYSTEM ((SLint32)0x00000001)
|
||||
#define SL_ANDROID_STREAM_SYSTEM ((SLint32) 0x00000001)
|
||||
/* same as android.media.AudioManager.STREAM_RING */
|
||||
#define SL_ANDROID_STREAM_RING ((SLint32)0x00000002)
|
||||
#define SL_ANDROID_STREAM_RING ((SLint32) 0x00000002)
|
||||
/* same as android.media.AudioManager.STREAM_MUSIC */
|
||||
#define SL_ANDROID_STREAM_MEDIA ((SLint32)0x00000003)
|
||||
#define SL_ANDROID_STREAM_MEDIA ((SLint32) 0x00000003)
|
||||
/* same as android.media.AudioManager.STREAM_ALARM */
|
||||
#define SL_ANDROID_STREAM_ALARM ((SLint32)0x00000004)
|
||||
#define SL_ANDROID_STREAM_ALARM ((SLint32) 0x00000004)
|
||||
/* same as android.media.AudioManager.STREAM_NOTIFICATION */
|
||||
#define SL_ANDROID_STREAM_NOTIFICATION ((SLint32)0x00000005)
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Android AudioPlayer and AudioRecorder configuration */
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
/** Audio Performance mode.
|
||||
* Performance mode tells the framework how to configure the audio path
|
||||
* for a player or recorder according to application performance and
|
||||
* functional requirements.
|
||||
* It affects the output or input latency based on acceptable tradeoffs on
|
||||
* battery drain and use of pre or post processing effects.
|
||||
* Performance mode should be set before realizing the object and should be
|
||||
* read after realizing the object to check if the requested mode could be
|
||||
* granted or not.
|
||||
*/
|
||||
/** Audio Performance mode key */
|
||||
#define SL_ANDROID_KEY_PERFORMANCE_MODE \
|
||||
((const SLchar *)"androidPerformanceMode")
|
||||
|
||||
/** Audio performance values */
|
||||
/* No specific performance requirement. Allows HW and SW pre/post
|
||||
* processing. */
|
||||
#define SL_ANDROID_PERFORMANCE_NONE ((SLuint32)0x00000000)
|
||||
/* Priority given to latency. No HW or software pre/post processing.
|
||||
* This is the default if no performance mode is specified. */
|
||||
#define SL_ANDROID_PERFORMANCE_LATENCY ((SLuint32)0x00000001)
|
||||
/* Priority given to latency while still allowing HW pre and post
|
||||
* processing. */
|
||||
#define SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS ((SLuint32)0x00000002)
|
||||
/* Priority given to power saving if latency is not a concern.
|
||||
* Allows HW and SW pre/post processing. */
|
||||
#define SL_ANDROID_PERFORMANCE_POWER_SAVING ((SLuint32)0x00000003)
|
||||
#define SL_ANDROID_STREAM_NOTIFICATION ((SLint32) 0x00000005)
|
||||
/* same as android.media.AudioManager.STREAM_BLUETOOTH_SCO */
|
||||
#define SL_ANDROID_STREAM_BLUETOOTH_SCO ((SLint32) 0x00000006)
|
||||
/* same as android.media.AudioManager.STREAM_SYSTEM_ENFORCED */
|
||||
#define SL_ANDROID_STREAM_SYSTEM_ENFORCED ((SLint32) 0x00000007)
|
||||
|
||||
#endif /* OPENSL_ES_ANDROIDCONFIGURATION_H_ */
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <cubeb/cubeb-stdint.h>
|
||||
|
||||
/*
|
||||
* The following definitions are copied from the android sources. Only the
|
||||
* relevant enum member and values needed are copied.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
struct Buffer {
|
||||
uint32_t flags;
|
||||
int channelCount;
|
||||
int format;
|
||||
size_t frameCount;
|
||||
size_t size;
|
||||
union {
|
||||
void* raw;
|
||||
short* i16;
|
||||
int8_t* i8;
|
||||
};
|
||||
};
|
||||
|
||||
enum event_type {
|
||||
EVENT_MORE_DATA = 0,
|
||||
EVENT_UNDERRUN = 1,
|
||||
EVENT_LOOP_END = 2,
|
||||
EVENT_MARKER = 3,
|
||||
EVENT_NEW_POS = 4,
|
||||
EVENT_BUFFER_END = 5
|
||||
};
|
||||
|
||||
/**
|
||||
* From 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_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)
|
||||
} AudioTrack_ChannelMapping_ICS;
|
||||
|
||||
typedef enum {
|
||||
AUDIO_FORMAT_PCM = 0x00000000,
|
||||
AUDIO_FORMAT_PCM_SUB_16_BIT = 0x1,
|
||||
AUDIO_FORMAT_PCM_16_BIT = (AUDIO_FORMAT_PCM | AUDIO_FORMAT_PCM_SUB_16_BIT),
|
||||
} AudioTrack_SampleType;
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#define CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5
|
||||
|
||||
#include "cubeb/cubeb.h"
|
||||
#include "cubeb_assert.h"
|
||||
#include "cubeb_log.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
@@ -21,7 +20,7 @@
|
||||
#define CLANG_ANALYZER_NORETURN
|
||||
#endif // ifndef CLANG_ANALYZER_NORETURN
|
||||
#endif // __has_feature(attribute_analyzer_noreturn)
|
||||
#else // __clang__
|
||||
#else // __clang__
|
||||
#define CLANG_ANALYZER_NORETURN
|
||||
#endif
|
||||
|
||||
@@ -29,46 +28,59 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Crash the caller. */
|
||||
void cubeb_crash() CLANG_ANALYZER_NORETURN;
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
struct cubeb_ops {
|
||||
int (*init)(cubeb ** context, char const * context_name);
|
||||
char const * (*get_backend_id)(cubeb * context);
|
||||
int (*get_max_channel_count)(cubeb * context, uint32_t * max_channels);
|
||||
int (*get_min_latency)(cubeb * context, cubeb_stream_params params,
|
||||
uint32_t * latency_ms);
|
||||
int (*get_preferred_sample_rate)(cubeb * context, uint32_t * rate);
|
||||
int (*enumerate_devices)(cubeb * context, cubeb_device_type type,
|
||||
cubeb_device_collection * collection);
|
||||
int (*device_collection_destroy)(cubeb * context,
|
||||
cubeb_device_collection * collection);
|
||||
void (*destroy)(cubeb * context);
|
||||
int (*stream_init)(cubeb * context, cubeb_stream ** stream,
|
||||
char const * stream_name, cubeb_devid input_device,
|
||||
cubeb_stream_params * input_stream_params,
|
||||
cubeb_devid output_device,
|
||||
cubeb_stream_params * output_stream_params,
|
||||
unsigned int latency, cubeb_data_callback data_callback,
|
||||
cubeb_state_callback state_callback, void * user_ptr);
|
||||
void (*stream_destroy)(cubeb_stream * stream);
|
||||
int (*stream_start)(cubeb_stream * stream);
|
||||
int (*stream_stop)(cubeb_stream * stream);
|
||||
int (*stream_get_position)(cubeb_stream * stream, uint64_t * position);
|
||||
int (*stream_get_latency)(cubeb_stream * stream, uint32_t * latency);
|
||||
int (*stream_get_input_latency)(cubeb_stream * stream, uint32_t * latency);
|
||||
int (*stream_set_volume)(cubeb_stream * stream, float volumes);
|
||||
int (*stream_set_name)(cubeb_stream * stream, char const * stream_name);
|
||||
int (*stream_get_current_device)(cubeb_stream * stream,
|
||||
cubeb_device ** const device);
|
||||
int (*stream_device_destroy)(cubeb_stream * stream, cubeb_device * device);
|
||||
int (*stream_register_device_changed_callback)(
|
||||
cubeb_stream * stream,
|
||||
cubeb_device_changed_callback device_changed_callback);
|
||||
int (*register_device_collection_changed)(
|
||||
cubeb * context, cubeb_device_type devtype,
|
||||
cubeb_device_collection_changed_callback callback, void * user_ptr);
|
||||
int (* init)(cubeb ** context, char const * context_name);
|
||||
char const * (* get_backend_id)(cubeb * context);
|
||||
int (* get_max_channel_count)(cubeb * context, uint32_t * max_channels);
|
||||
int (* get_min_latency)(cubeb * context,
|
||||
cubeb_stream_params params,
|
||||
uint32_t * latency_ms);
|
||||
int (* get_preferred_sample_rate)(cubeb * context, uint32_t * rate);
|
||||
int (* enumerate_devices)(cubeb * context, cubeb_device_type type,
|
||||
cubeb_device_collection ** collection);
|
||||
void (* destroy)(cubeb * context);
|
||||
int (* stream_init)(cubeb * context,
|
||||
cubeb_stream ** stream,
|
||||
char const * stream_name,
|
||||
cubeb_devid input_device,
|
||||
cubeb_stream_params * input_stream_params,
|
||||
cubeb_devid output_device,
|
||||
cubeb_stream_params * output_stream_params,
|
||||
unsigned int latency,
|
||||
cubeb_data_callback data_callback,
|
||||
cubeb_state_callback state_callback,
|
||||
void * user_ptr);
|
||||
void (* stream_destroy)(cubeb_stream * stream);
|
||||
int (* stream_start)(cubeb_stream * stream);
|
||||
int (* stream_stop)(cubeb_stream * stream);
|
||||
int (* stream_get_position)(cubeb_stream * stream, uint64_t * position);
|
||||
int (* stream_get_latency)(cubeb_stream * stream, uint32_t * latency);
|
||||
int (* stream_set_volume)(cubeb_stream * stream, float volumes);
|
||||
int (* stream_set_panning)(cubeb_stream * stream, float panning);
|
||||
int (* stream_get_current_device)(cubeb_stream * stream,
|
||||
cubeb_device ** const device);
|
||||
int (* stream_device_destroy)(cubeb_stream * stream,
|
||||
cubeb_device * device);
|
||||
int (* stream_register_device_changed_callback)(cubeb_stream * stream,
|
||||
cubeb_device_changed_callback device_changed_callback);
|
||||
int (* register_device_collection_changed)(cubeb * context,
|
||||
cubeb_device_type devtype,
|
||||
cubeb_device_collection_changed_callback callback,
|
||||
void * user_ptr);
|
||||
};
|
||||
|
||||
#define XASSERT(expr) do { \
|
||||
if (!(expr)) { \
|
||||
fprintf(stderr, "%s:%d - fatal error: %s\n", __FILE__, __LINE__, #expr); \
|
||||
cubeb_crash(); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#endif /* CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5 */
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
#include "cubeb-jni-instances.h"
|
||||
#include "jni.h"
|
||||
#include <assert.h>
|
||||
|
||||
#define AUDIO_STREAM_TYPE_MUSIC 3
|
||||
|
||||
struct cubeb_jni {
|
||||
jobject s_audio_manager_obj = nullptr;
|
||||
jclass s_audio_manager_class = nullptr;
|
||||
jmethodID s_get_output_latency_id = nullptr;
|
||||
};
|
||||
|
||||
extern "C" cubeb_jni *
|
||||
cubeb_jni_init()
|
||||
{
|
||||
jobject ctx_obj = cubeb_jni_get_context_instance();
|
||||
JNIEnv * jni_env = cubeb_get_jni_env_for_thread();
|
||||
if (!jni_env || !ctx_obj) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cubeb_jni * cubeb_jni_ptr = new cubeb_jni;
|
||||
assert(cubeb_jni_ptr);
|
||||
|
||||
// Find the audio manager object and make it global to call it from another
|
||||
// method
|
||||
jclass context_class = jni_env->FindClass("android/content/Context");
|
||||
jfieldID audio_service_field = jni_env->GetStaticFieldID(
|
||||
context_class, "AUDIO_SERVICE", "Ljava/lang/String;");
|
||||
jstring jstr = (jstring)jni_env->GetStaticObjectField(context_class,
|
||||
audio_service_field);
|
||||
jmethodID get_system_service_id =
|
||||
jni_env->GetMethodID(context_class, "getSystemService",
|
||||
"(Ljava/lang/String;)Ljava/lang/Object;");
|
||||
jobject audio_manager_obj =
|
||||
jni_env->CallObjectMethod(ctx_obj, get_system_service_id, jstr);
|
||||
cubeb_jni_ptr->s_audio_manager_obj =
|
||||
reinterpret_cast<jobject>(jni_env->NewGlobalRef(audio_manager_obj));
|
||||
|
||||
// Make the audio manager class a global reference in order to preserve method
|
||||
// id
|
||||
jclass audio_manager_class = jni_env->FindClass("android/media/AudioManager");
|
||||
cubeb_jni_ptr->s_audio_manager_class =
|
||||
reinterpret_cast<jclass>(jni_env->NewGlobalRef(audio_manager_class));
|
||||
cubeb_jni_ptr->s_get_output_latency_id =
|
||||
jni_env->GetMethodID(audio_manager_class, "getOutputLatency", "(I)I");
|
||||
|
||||
jni_env->DeleteLocalRef(ctx_obj);
|
||||
jni_env->DeleteLocalRef(context_class);
|
||||
jni_env->DeleteLocalRef(jstr);
|
||||
jni_env->DeleteLocalRef(audio_manager_obj);
|
||||
jni_env->DeleteLocalRef(audio_manager_class);
|
||||
|
||||
return cubeb_jni_ptr;
|
||||
}
|
||||
|
||||
extern "C" int
|
||||
cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr)
|
||||
{
|
||||
assert(cubeb_jni_ptr);
|
||||
JNIEnv * jni_env = cubeb_get_jni_env_for_thread();
|
||||
return jni_env->CallIntMethod(
|
||||
cubeb_jni_ptr->s_audio_manager_obj,
|
||||
cubeb_jni_ptr->s_get_output_latency_id,
|
||||
AUDIO_STREAM_TYPE_MUSIC); // param: AudioManager.STREAM_MUSIC
|
||||
}
|
||||
|
||||
extern "C" void
|
||||
cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr)
|
||||
{
|
||||
assert(cubeb_jni_ptr);
|
||||
|
||||
JNIEnv * jni_env = cubeb_get_jni_env_for_thread();
|
||||
assert(jni_env);
|
||||
|
||||
jni_env->DeleteGlobalRef(cubeb_jni_ptr->s_audio_manager_obj);
|
||||
jni_env->DeleteGlobalRef(cubeb_jni_ptr->s_audio_manager_class);
|
||||
|
||||
delete cubeb_jni_ptr;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#ifndef _CUBEB_JNI_H_
|
||||
#define _CUBEB_JNI_H_
|
||||
|
||||
typedef struct cubeb_jni cubeb_jni;
|
||||
|
||||
cubeb_jni *
|
||||
cubeb_jni_init();
|
||||
int
|
||||
cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr);
|
||||
void
|
||||
cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr);
|
||||
|
||||
#endif // _CUBEB_JNI_H_
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifndef _CUBEB_SLES_H_
|
||||
#define _CUBEB_SLES_H_
|
||||
#include <OpenSLESProvider.h>
|
||||
#include <SLES/OpenSLES.h>
|
||||
|
||||
static SLresult cubeb_get_sles_engine(
|
||||
SLObjectItf *pEngine,
|
||||
SLuint32 numOptions,
|
||||
const SLEngineOption *pEngineOptions,
|
||||
SLuint32 numInterfaces,
|
||||
const SLInterfaceID *pInterfaceIds,
|
||||
const SLboolean * pInterfaceRequired) {
|
||||
return mozilla_get_sles_engine(pEngine, numOptions, pEngineOptions);
|
||||
}
|
||||
|
||||
static void cubeb_destroy_sles_engine(SLObjectItf *self) {
|
||||
mozilla_destroy_sles_engine(self);
|
||||
}
|
||||
|
||||
/* Only synchronous operation is supported, as if the second
|
||||
parameter was FALSE. */
|
||||
static SLresult cubeb_realize_sles_engine(SLObjectItf self) {
|
||||
return mozilla_realize_sles_engine(self);
|
||||
}
|
||||
|
||||
#endif
|
||||
+168
-315
@@ -5,88 +5,59 @@
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
#undef NDEBUG
|
||||
#include "cubeb/cubeb.h"
|
||||
#include "cubeb-internal.h"
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "cubeb/cubeb.h"
|
||||
#include "cubeb-internal.h"
|
||||
|
||||
#define NELEMS(x) ((int)(sizeof(x) / sizeof(x[0])))
|
||||
#define NELEMS(x) ((int) (sizeof(x) / sizeof(x[0])))
|
||||
|
||||
cubeb_log_level g_log_level;
|
||||
cubeb_log_callback g_log_callback;
|
||||
|
||||
struct cubeb {
|
||||
struct cubeb_ops * ops;
|
||||
};
|
||||
|
||||
struct cubeb_stream {
|
||||
/*
|
||||
* Note: All implementations of cubeb_stream must keep the following
|
||||
* layout.
|
||||
*/
|
||||
struct cubeb * context;
|
||||
void * user_ptr;
|
||||
};
|
||||
|
||||
#if defined(USE_PULSE)
|
||||
int
|
||||
pulse_init(cubeb ** context, char const * context_name);
|
||||
#endif
|
||||
#if defined(USE_PULSE_RUST)
|
||||
int
|
||||
pulse_rust_init(cubeb ** contet, char const * context_name);
|
||||
int pulse_init(cubeb ** context, char const * context_name);
|
||||
#endif
|
||||
#if defined(USE_JACK)
|
||||
int
|
||||
jack_init(cubeb ** context, char const * context_name);
|
||||
int jack_init (cubeb ** context, char const * context_name);
|
||||
#endif
|
||||
#if defined(USE_ALSA)
|
||||
int
|
||||
alsa_init(cubeb ** context, char const * context_name);
|
||||
int alsa_init(cubeb ** context, char const * context_name);
|
||||
#endif
|
||||
#if defined(USE_AUDIOUNIT)
|
||||
int
|
||||
audiounit_init(cubeb ** context, char const * context_name);
|
||||
#endif
|
||||
#if defined(USE_AUDIOUNIT_RUST)
|
||||
int
|
||||
audiounit_rust_init(cubeb ** contet, char const * context_name);
|
||||
int audiounit_init(cubeb ** context, char const * context_name);
|
||||
#endif
|
||||
#if defined(USE_WINMM)
|
||||
int
|
||||
winmm_init(cubeb ** context, char const * context_name);
|
||||
int winmm_init(cubeb ** context, char const * context_name);
|
||||
#endif
|
||||
#if defined(USE_WASAPI)
|
||||
int
|
||||
wasapi_init(cubeb ** context, char const * context_name);
|
||||
int wasapi_init(cubeb ** context, char const * context_name);
|
||||
#endif
|
||||
#if defined(USE_SNDIO)
|
||||
int
|
||||
sndio_init(cubeb ** context, char const * context_name);
|
||||
#endif
|
||||
#if defined(USE_SUN)
|
||||
int
|
||||
sun_init(cubeb ** context, char const * context_name);
|
||||
int sndio_init(cubeb ** context, char const * context_name);
|
||||
#endif
|
||||
#if defined(USE_OPENSL)
|
||||
int
|
||||
opensl_init(cubeb ** context, char const * context_name);
|
||||
#endif
|
||||
#if defined(USE_OSS)
|
||||
int
|
||||
oss_init(cubeb ** context, char const * context_name);
|
||||
#endif
|
||||
#if defined(USE_AAUDIO)
|
||||
int
|
||||
aaudio_init(cubeb ** context, char const * context_name);
|
||||
int opensl_init(cubeb ** context, char const * context_name);
|
||||
#endif
|
||||
#if defined(USE_AUDIOTRACK)
|
||||
int
|
||||
audiotrack_init(cubeb ** context, char const * context_name);
|
||||
int audiotrack_init(cubeb ** context, char const * context_name);
|
||||
#endif
|
||||
#if defined(USE_KAI)
|
||||
int
|
||||
kai_init(cubeb ** context, char const * context_name);
|
||||
int kai_init(cubeb ** context, char const * context_name);
|
||||
#endif
|
||||
#if defined(USE_SUN)
|
||||
int sunaudio_init(cubeb ** context, char const * context_name);
|
||||
#endif
|
||||
|
||||
|
||||
static int
|
||||
validate_stream_params(cubeb_stream_params * input_stream_params,
|
||||
@@ -94,32 +65,28 @@ validate_stream_params(cubeb_stream_params * input_stream_params,
|
||||
{
|
||||
XASSERT(input_stream_params || output_stream_params);
|
||||
if (output_stream_params) {
|
||||
if (output_stream_params->rate < 1000 ||
|
||||
output_stream_params->rate > 192000 ||
|
||||
output_stream_params->channels < 1 ||
|
||||
output_stream_params->channels > UINT8_MAX) {
|
||||
if (output_stream_params->rate < 1000 || output_stream_params->rate > 192000 ||
|
||||
output_stream_params->channels < 1 || output_stream_params->channels > 8) {
|
||||
return CUBEB_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
}
|
||||
if (input_stream_params) {
|
||||
if (input_stream_params->rate < 1000 ||
|
||||
input_stream_params->rate > 192000 ||
|
||||
input_stream_params->channels < 1 ||
|
||||
input_stream_params->channels > UINT8_MAX) {
|
||||
if (input_stream_params->rate < 1000 || input_stream_params->rate > 192000 ||
|
||||
input_stream_params->channels < 1 || input_stream_params->channels > 8) {
|
||||
return CUBEB_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
}
|
||||
// Rate and sample format must be the same for input and output, if using a
|
||||
// duplex stream
|
||||
if (input_stream_params && output_stream_params) {
|
||||
if (input_stream_params->rate != output_stream_params->rate ||
|
||||
if (input_stream_params->rate != output_stream_params->rate ||
|
||||
input_stream_params->format != output_stream_params->format) {
|
||||
return CUBEB_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
}
|
||||
|
||||
cubeb_stream_params * params =
|
||||
input_stream_params ? input_stream_params : output_stream_params;
|
||||
cubeb_stream_params * params = input_stream_params ?
|
||||
input_stream_params : output_stream_params;
|
||||
|
||||
switch (params->format) {
|
||||
case CUBEB_SAMPLE_S16LE:
|
||||
@@ -132,6 +99,8 @@ validate_stream_params(cubeb_stream_params * input_stream_params,
|
||||
return CUBEB_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int
|
||||
validate_latency(int latency)
|
||||
{
|
||||
@@ -142,104 +111,18 @@ validate_latency(int latency)
|
||||
}
|
||||
|
||||
int
|
||||
cubeb_init(cubeb ** context, char const * context_name,
|
||||
char const * backend_name)
|
||||
cubeb_init(cubeb ** context, char const * context_name)
|
||||
{
|
||||
int (*init_oneshot)(cubeb **, char const *) = NULL;
|
||||
|
||||
if (backend_name != NULL) {
|
||||
if (!strcmp(backend_name, "pulse")) {
|
||||
#if defined(USE_PULSE)
|
||||
init_oneshot = pulse_init;
|
||||
#endif
|
||||
} else if (!strcmp(backend_name, "pulse-rust")) {
|
||||
#if defined(USE_PULSE_RUST)
|
||||
init_oneshot = pulse_rust_init;
|
||||
#endif
|
||||
} else if (!strcmp(backend_name, "jack")) {
|
||||
int (* init[])(cubeb **, char const *) = {
|
||||
#if defined(USE_JACK)
|
||||
init_oneshot = jack_init;
|
||||
#endif
|
||||
} else if (!strcmp(backend_name, "alsa")) {
|
||||
#if defined(USE_ALSA)
|
||||
init_oneshot = alsa_init;
|
||||
#endif
|
||||
} else if (!strcmp(backend_name, "audiounit")) {
|
||||
#if defined(USE_AUDIOUNIT)
|
||||
init_oneshot = audiounit_init;
|
||||
#endif
|
||||
} else if (!strcmp(backend_name, "audiounit-rust")) {
|
||||
#if defined(USE_AUDIOUNIT_RUST)
|
||||
init_oneshot = audiounit_rust_init;
|
||||
#endif
|
||||
} else if (!strcmp(backend_name, "wasapi")) {
|
||||
#if defined(USE_WASAPI)
|
||||
init_oneshot = wasapi_init;
|
||||
#endif
|
||||
} else if (!strcmp(backend_name, "winmm")) {
|
||||
#if defined(USE_WINMM)
|
||||
init_oneshot = winmm_init;
|
||||
#endif
|
||||
} else if (!strcmp(backend_name, "sndio")) {
|
||||
#if defined(USE_SNDIO)
|
||||
init_oneshot = sndio_init;
|
||||
#endif
|
||||
} else if (!strcmp(backend_name, "sun")) {
|
||||
#if defined(USE_SUN)
|
||||
init_oneshot = sun_init;
|
||||
#endif
|
||||
} else if (!strcmp(backend_name, "opensl")) {
|
||||
#if defined(USE_OPENSL)
|
||||
init_oneshot = opensl_init;
|
||||
#endif
|
||||
} else if (!strcmp(backend_name, "oss")) {
|
||||
#if defined(USE_OSS)
|
||||
init_oneshot = oss_init;
|
||||
#endif
|
||||
} else if (!strcmp(backend_name, "aaudio")) {
|
||||
#if defined(USE_AAUDIO)
|
||||
init_oneshot = aaudio_init;
|
||||
#endif
|
||||
} else if (!strcmp(backend_name, "audiotrack")) {
|
||||
#if defined(USE_AUDIOTRACK)
|
||||
init_oneshot = audiotrack_init;
|
||||
#endif
|
||||
} else if (!strcmp(backend_name, "kai")) {
|
||||
#if defined(USE_KAI)
|
||||
init_oneshot = kai_init;
|
||||
#endif
|
||||
} else {
|
||||
/* Already set */
|
||||
}
|
||||
}
|
||||
|
||||
int (*default_init[])(cubeb **, char const *) = {
|
||||
/*
|
||||
* init_oneshot must be at the top to allow user
|
||||
* to override all other choices
|
||||
*/
|
||||
init_oneshot,
|
||||
#if defined(USE_PULSE_RUST)
|
||||
pulse_rust_init,
|
||||
jack_init,
|
||||
#endif
|
||||
#if defined(USE_PULSE)
|
||||
pulse_init,
|
||||
#endif
|
||||
#if defined(USE_JACK)
|
||||
jack_init,
|
||||
#endif
|
||||
#if defined(USE_SNDIO)
|
||||
sndio_init,
|
||||
#endif
|
||||
#if defined(USE_ALSA)
|
||||
alsa_init,
|
||||
#endif
|
||||
#if defined(USE_OSS)
|
||||
oss_init,
|
||||
#endif
|
||||
#if defined(USE_AUDIOUNIT_RUST)
|
||||
audiounit_rust_init,
|
||||
#endif
|
||||
#if defined(USE_AUDIOUNIT)
|
||||
audiounit_init,
|
||||
#endif
|
||||
@@ -249,22 +132,20 @@ cubeb_init(cubeb ** context, char const * context_name,
|
||||
#if defined(USE_WINMM)
|
||||
winmm_init,
|
||||
#endif
|
||||
#if defined(USE_SUN)
|
||||
sun_init,
|
||||
#if defined(USE_SNDIO)
|
||||
sndio_init,
|
||||
#endif
|
||||
#if defined(USE_OPENSL)
|
||||
opensl_init,
|
||||
#endif
|
||||
// TODO: should probably be preferred over OpenSLES when available.
|
||||
// Initialization will fail on old android devices.
|
||||
#if defined(USE_AAUDIO)
|
||||
aaudio_init,
|
||||
#endif
|
||||
#if defined(USE_AUDIOTRACK)
|
||||
audiotrack_init,
|
||||
#endif
|
||||
#if defined(USE_KAI)
|
||||
kai_init,
|
||||
#endif
|
||||
#if defined(USE_SUN)
|
||||
sunaudio_init,
|
||||
#endif
|
||||
};
|
||||
int i;
|
||||
@@ -273,10 +154,10 @@ cubeb_init(cubeb ** context, char const * context_name,
|
||||
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
#define OK(fn) assert((*context)->ops->fn)
|
||||
for (i = 0; i < NELEMS(default_init); ++i) {
|
||||
if (default_init[i] && default_init[i](context, context_name) == CUBEB_OK) {
|
||||
for (i = 0; i < NELEMS(init); ++i) {
|
||||
if (init[i](context, context_name) == CUBEB_OK) {
|
||||
/* Assert that the minimal API is implemented. */
|
||||
#define OK(fn) assert((* context)->ops->fn)
|
||||
OK(get_backend_id);
|
||||
OK(destroy);
|
||||
OK(stream_init);
|
||||
@@ -287,6 +168,7 @@ cubeb_init(cubeb ** context, char const * context_name,
|
||||
return CUBEB_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
@@ -315,10 +197,9 @@ cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels)
|
||||
}
|
||||
|
||||
int
|
||||
cubeb_get_min_latency(cubeb * context, cubeb_stream_params * params,
|
||||
uint32_t * latency_ms)
|
||||
cubeb_get_min_latency(cubeb * context, cubeb_stream_params params, uint32_t * latency_ms)
|
||||
{
|
||||
if (!context || !params || !latency_ms) {
|
||||
if (!context || !latency_ms) {
|
||||
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
@@ -326,7 +207,7 @@ cubeb_get_min_latency(cubeb * context, cubeb_stream_params * params,
|
||||
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
return context->ops->get_min_latency(context, *params, latency_ms);
|
||||
return context->ops->get_min_latency(context, params, latency_ms);
|
||||
}
|
||||
|
||||
int
|
||||
@@ -354,39 +235,36 @@ cubeb_destroy(cubeb * context)
|
||||
}
|
||||
|
||||
int
|
||||
cubeb_stream_init(cubeb * context, cubeb_stream ** stream,
|
||||
char const * stream_name, cubeb_devid input_device,
|
||||
cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
|
||||
cubeb_devid input_device,
|
||||
cubeb_stream_params * input_stream_params,
|
||||
cubeb_devid output_device,
|
||||
cubeb_stream_params * output_stream_params,
|
||||
unsigned int latency, cubeb_data_callback data_callback,
|
||||
cubeb_state_callback state_callback, void * user_ptr)
|
||||
unsigned int latency,
|
||||
cubeb_data_callback data_callback,
|
||||
cubeb_state_callback state_callback,
|
||||
void * user_ptr)
|
||||
{
|
||||
int r;
|
||||
|
||||
if (!context || !stream || !data_callback || !state_callback) {
|
||||
if (!context || !stream) {
|
||||
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if ((r = validate_stream_params(input_stream_params, output_stream_params)) !=
|
||||
CUBEB_OK ||
|
||||
if ((r = validate_stream_params(input_stream_params, output_stream_params)) != CUBEB_OK ||
|
||||
(r = validate_latency(latency)) != CUBEB_OK) {
|
||||
return r;
|
||||
}
|
||||
|
||||
r = context->ops->stream_init(context, stream, stream_name, input_device,
|
||||
input_stream_params, output_device,
|
||||
output_stream_params, latency, data_callback,
|
||||
state_callback, user_ptr);
|
||||
|
||||
if (r == CUBEB_ERROR_INVALID_FORMAT) {
|
||||
LOG("Invalid format, %p %p %d %d", output_stream_params,
|
||||
input_stream_params,
|
||||
output_stream_params && output_stream_params->format,
|
||||
input_stream_params && input_stream_params->format);
|
||||
}
|
||||
|
||||
return r;
|
||||
return context->ops->stream_init(context, stream, stream_name,
|
||||
input_device,
|
||||
input_stream_params,
|
||||
output_device,
|
||||
output_stream_params,
|
||||
latency,
|
||||
data_callback,
|
||||
state_callback,
|
||||
user_ptr);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -443,20 +321,6 @@ cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
|
||||
return stream->context->ops->stream_get_latency(stream, latency);
|
||||
}
|
||||
|
||||
int
|
||||
cubeb_stream_get_input_latency(cubeb_stream * stream, uint32_t * latency)
|
||||
{
|
||||
if (!stream || !latency) {
|
||||
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!stream->context->ops->stream_get_input_latency) {
|
||||
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
return stream->context->ops->stream_get_input_latency(stream, latency);
|
||||
}
|
||||
|
||||
int
|
||||
cubeb_stream_set_volume(cubeb_stream * stream, float volume)
|
||||
{
|
||||
@@ -471,23 +335,21 @@ cubeb_stream_set_volume(cubeb_stream * stream, float volume)
|
||||
return stream->context->ops->stream_set_volume(stream, volume);
|
||||
}
|
||||
|
||||
int
|
||||
cubeb_stream_set_name(cubeb_stream * stream, char const * stream_name)
|
||||
int cubeb_stream_set_panning(cubeb_stream * stream, float panning)
|
||||
{
|
||||
if (!stream || !stream_name) {
|
||||
if (!stream || panning < -1.0 || panning > 1.0) {
|
||||
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!stream->context->ops->stream_set_name) {
|
||||
if (!stream->context->ops->stream_set_panning) {
|
||||
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
return stream->context->ops->stream_set_name(stream, stream_name);
|
||||
return stream->context->ops->stream_set_panning(stream, panning);
|
||||
}
|
||||
|
||||
int
|
||||
cubeb_stream_get_current_device(cubeb_stream * stream,
|
||||
cubeb_device ** const device)
|
||||
int cubeb_stream_get_current_device(cubeb_stream * stream,
|
||||
cubeb_device ** const device)
|
||||
{
|
||||
if (!stream || !device) {
|
||||
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||
@@ -500,8 +362,8 @@ cubeb_stream_get_current_device(cubeb_stream * stream,
|
||||
return stream->context->ops->stream_get_current_device(stream, device);
|
||||
}
|
||||
|
||||
int
|
||||
cubeb_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
|
||||
int cubeb_stream_device_destroy(cubeb_stream * stream,
|
||||
cubeb_device * device)
|
||||
{
|
||||
if (!stream || !device) {
|
||||
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||
@@ -514,10 +376,8 @@ cubeb_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
|
||||
return stream->context->ops->stream_device_destroy(stream, device);
|
||||
}
|
||||
|
||||
int
|
||||
cubeb_stream_register_device_changed_callback(
|
||||
cubeb_stream * stream,
|
||||
cubeb_device_changed_callback device_changed_callback)
|
||||
int cubeb_stream_register_device_changed_callback(cubeb_stream * stream,
|
||||
cubeb_device_changed_callback device_changed_callback)
|
||||
{
|
||||
if (!stream) {
|
||||
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||
@@ -527,70 +387,59 @@ cubeb_stream_register_device_changed_callback(
|
||||
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
return stream->context->ops->stream_register_device_changed_callback(
|
||||
stream, device_changed_callback);
|
||||
return stream->context->ops->stream_register_device_changed_callback(stream, device_changed_callback);
|
||||
}
|
||||
|
||||
void *
|
||||
cubeb_stream_user_ptr(cubeb_stream * stream)
|
||||
{
|
||||
if (!stream) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return stream->user_ptr;
|
||||
}
|
||||
|
||||
static void
|
||||
log_device(cubeb_device_info * device_info)
|
||||
static
|
||||
void log_device(cubeb_device_info * device_info)
|
||||
{
|
||||
char devfmts[128] = "";
|
||||
const char *devtype, *devstate, *devdeffmt;
|
||||
const char * devtype, * devstate, * devdeffmt;
|
||||
|
||||
switch (device_info->type) {
|
||||
case CUBEB_DEVICE_TYPE_INPUT:
|
||||
devtype = "input";
|
||||
break;
|
||||
case CUBEB_DEVICE_TYPE_OUTPUT:
|
||||
devtype = "output";
|
||||
break;
|
||||
case CUBEB_DEVICE_TYPE_UNKNOWN:
|
||||
default:
|
||||
devtype = "unknown?";
|
||||
break;
|
||||
case CUBEB_DEVICE_TYPE_INPUT:
|
||||
devtype = "input";
|
||||
break;
|
||||
case CUBEB_DEVICE_TYPE_OUTPUT:
|
||||
devtype = "output";
|
||||
break;
|
||||
case CUBEB_DEVICE_TYPE_UNKNOWN:
|
||||
default:
|
||||
devtype = "unknown?";
|
||||
break;
|
||||
};
|
||||
|
||||
switch (device_info->state) {
|
||||
case CUBEB_DEVICE_STATE_DISABLED:
|
||||
devstate = "disabled";
|
||||
break;
|
||||
case CUBEB_DEVICE_STATE_UNPLUGGED:
|
||||
devstate = "unplugged";
|
||||
break;
|
||||
case CUBEB_DEVICE_STATE_ENABLED:
|
||||
devstate = "enabled";
|
||||
break;
|
||||
default:
|
||||
devstate = "unknown?";
|
||||
break;
|
||||
case CUBEB_DEVICE_STATE_DISABLED:
|
||||
devstate = "disabled";
|
||||
break;
|
||||
case CUBEB_DEVICE_STATE_UNPLUGGED:
|
||||
devstate = "unplugged";
|
||||
break;
|
||||
case CUBEB_DEVICE_STATE_ENABLED:
|
||||
devstate = "enabled";
|
||||
break;
|
||||
default:
|
||||
devstate = "unknown?";
|
||||
break;
|
||||
};
|
||||
|
||||
switch (device_info->default_format) {
|
||||
case CUBEB_DEVICE_FMT_S16LE:
|
||||
devdeffmt = "S16LE";
|
||||
break;
|
||||
case CUBEB_DEVICE_FMT_S16BE:
|
||||
devdeffmt = "S16BE";
|
||||
break;
|
||||
case CUBEB_DEVICE_FMT_F32LE:
|
||||
devdeffmt = "F32LE";
|
||||
break;
|
||||
case CUBEB_DEVICE_FMT_F32BE:
|
||||
devdeffmt = "F32BE";
|
||||
break;
|
||||
default:
|
||||
devdeffmt = "unknown?";
|
||||
break;
|
||||
case CUBEB_DEVICE_FMT_S16LE:
|
||||
devdeffmt = "S16LE";
|
||||
break;
|
||||
case CUBEB_DEVICE_FMT_S16BE:
|
||||
devdeffmt = "S16BE";
|
||||
break;
|
||||
case CUBEB_DEVICE_FMT_F32LE:
|
||||
devdeffmt = "F32LE";
|
||||
break;
|
||||
case CUBEB_DEVICE_FMT_F32BE:
|
||||
devdeffmt = "F32BE";
|
||||
break;
|
||||
default:
|
||||
devdeffmt = "unknown?";
|
||||
break;
|
||||
};
|
||||
|
||||
if (device_info->format & CUBEB_DEVICE_FMT_S16LE) {
|
||||
@@ -617,17 +466,20 @@ log_device(cubeb_device_info * device_info)
|
||||
"\tRate:\t[%u, %u] (default: %u)\n"
|
||||
"\tLatency: lo %u frames, hi %u frames",
|
||||
device_info->device_id, device_info->preferred ? " (PREFERRED)" : "",
|
||||
device_info->friendly_name, device_info->group_id,
|
||||
device_info->vendor_name, devtype, devstate, device_info->max_channels,
|
||||
(devfmts[0] == '\0') ? devfmts : devfmts + 1,
|
||||
(unsigned int)device_info->format, devdeffmt, device_info->min_rate,
|
||||
device_info->max_rate, device_info->default_rate, device_info->latency_lo,
|
||||
device_info->latency_hi);
|
||||
device_info->friendly_name,
|
||||
device_info->group_id,
|
||||
device_info->vendor_name,
|
||||
devtype,
|
||||
devstate,
|
||||
device_info->max_channels,
|
||||
(devfmts[0] == '\0') ? devfmts : devfmts + 1, (unsigned int)device_info->format, devdeffmt,
|
||||
device_info->min_rate, device_info->max_rate, device_info->default_rate,
|
||||
device_info->latency_lo, device_info->latency_hi);
|
||||
}
|
||||
|
||||
int
|
||||
cubeb_enumerate_devices(cubeb * context, cubeb_device_type devtype,
|
||||
cubeb_device_collection * collection)
|
||||
int cubeb_enumerate_devices(cubeb * context,
|
||||
cubeb_device_type devtype,
|
||||
cubeb_device_collection ** collection)
|
||||
{
|
||||
int rv;
|
||||
if ((devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0)
|
||||
@@ -639,59 +491,61 @@ cubeb_enumerate_devices(cubeb * context, cubeb_device_type devtype,
|
||||
|
||||
rv = context->ops->enumerate_devices(context, devtype, collection);
|
||||
|
||||
if (g_cubeb_log_callback) {
|
||||
for (size_t i = 0; i < collection->count; i++) {
|
||||
log_device(&collection->device[i]);
|
||||
if (g_log_callback) {
|
||||
for (uint32_t i = 0; i < (*collection)->count; i++) {
|
||||
log_device((*collection)->device[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
int
|
||||
cubeb_device_collection_destroy(cubeb * context,
|
||||
cubeb_device_collection * collection)
|
||||
int cubeb_device_collection_destroy(cubeb_device_collection * collection)
|
||||
{
|
||||
int r;
|
||||
uint32_t i;
|
||||
|
||||
if (context == NULL || collection == NULL)
|
||||
if (collection == NULL)
|
||||
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||
|
||||
if (!context->ops->device_collection_destroy)
|
||||
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||
for (i = 0; i < collection->count; i++)
|
||||
cubeb_device_info_destroy(collection->device[i]);
|
||||
|
||||
if (!collection->device)
|
||||
return CUBEB_OK;
|
||||
|
||||
r = context->ops->device_collection_destroy(context, collection);
|
||||
if (r == CUBEB_OK) {
|
||||
collection->device = NULL;
|
||||
collection->count = 0;
|
||||
}
|
||||
|
||||
return r;
|
||||
free(collection);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
int
|
||||
cubeb_register_device_collection_changed(
|
||||
cubeb * context, cubeb_device_type devtype,
|
||||
cubeb_device_collection_changed_callback callback, void * user_ptr)
|
||||
int cubeb_device_info_destroy(cubeb_device_info * info)
|
||||
{
|
||||
if (context == NULL ||
|
||||
(devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0)
|
||||
if (info == NULL) {
|
||||
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
free(info->device_id);
|
||||
free(info->friendly_name);
|
||||
free(info->group_id);
|
||||
free(info->vendor_name);
|
||||
|
||||
free(info);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
int cubeb_register_device_collection_changed(cubeb * context,
|
||||
cubeb_device_type devtype,
|
||||
cubeb_device_collection_changed_callback callback,
|
||||
void * user_ptr)
|
||||
{
|
||||
if (context == NULL || (devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0)
|
||||
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||
|
||||
if (!context->ops->register_device_collection_changed) {
|
||||
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
return context->ops->register_device_collection_changed(context, devtype,
|
||||
callback, user_ptr);
|
||||
return context->ops->register_device_collection_changed(context, devtype, callback, user_ptr);
|
||||
}
|
||||
|
||||
int
|
||||
cubeb_set_log_callback(cubeb_log_level log_level,
|
||||
cubeb_log_callback log_callback)
|
||||
int cubeb_set_log_callback(cubeb_log_level log_level,
|
||||
cubeb_log_callback log_callback)
|
||||
{
|
||||
if (log_level < CUBEB_LOG_DISABLED || log_level > CUBEB_LOG_VERBOSE) {
|
||||
return CUBEB_ERROR_INVALID_FORMAT;
|
||||
@@ -701,21 +555,20 @@ cubeb_set_log_callback(cubeb_log_level log_level,
|
||||
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (g_cubeb_log_callback && log_callback) {
|
||||
if (g_log_callback && log_callback) {
|
||||
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
g_cubeb_log_callback = log_callback;
|
||||
g_cubeb_log_level = log_level;
|
||||
|
||||
// Logging a message here allows to initialize the asynchronous logger from a
|
||||
// thread that is not the audio rendering thread, and especially to not
|
||||
// initialize it the first time we find a verbose log, which is often in the
|
||||
// audio rendering callback, that runs from the audio rendering thread, and
|
||||
// that is high priority, and that we don't want to block.
|
||||
if (log_level >= CUBEB_LOG_VERBOSE) {
|
||||
ALOGV("Starting cubeb log");
|
||||
}
|
||||
g_log_callback = log_callback;
|
||||
g_log_level = log_level;
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
void
|
||||
cubeb_crash()
|
||||
{
|
||||
*((volatile int *) NULL) = 0;
|
||||
abort();
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+205
-533
File diff suppressed because it is too large
Load Diff
@@ -1,17 +0,0 @@
|
||||
#ifndef CUBEB_ANDROID_H
|
||||
#define CUBEB_ANDROID_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
// If the latency requested is above this threshold, this stream is considered
|
||||
// intended for playback (vs. real-time). Tell Android it should favor saving
|
||||
// power over performance or latency.
|
||||
// This is around 100ms at 44100 or 48000
|
||||
const uint16_t POWERSAVE_LATENCY_FRAMES_THRESHOLD = 4000;
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // CUBEB_ANDROID_H
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2016 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
#ifndef CUBEB_ARRAY_QUEUE_H
|
||||
#define CUBEB_ARRAY_QUEUE_H
|
||||
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
void ** buf;
|
||||
size_t num;
|
||||
size_t writePos;
|
||||
size_t readPos;
|
||||
pthread_mutex_t mutex;
|
||||
} array_queue;
|
||||
|
||||
array_queue *
|
||||
array_queue_create(size_t num)
|
||||
{
|
||||
assert(num != 0);
|
||||
array_queue * new_queue = (array_queue *)calloc(1, sizeof(array_queue));
|
||||
new_queue->buf = (void **)calloc(1, sizeof(void *) * num);
|
||||
new_queue->readPos = 0;
|
||||
new_queue->writePos = 0;
|
||||
new_queue->num = num;
|
||||
|
||||
pthread_mutex_init(&new_queue->mutex, NULL);
|
||||
|
||||
return new_queue;
|
||||
}
|
||||
|
||||
void
|
||||
array_queue_destroy(array_queue * aq)
|
||||
{
|
||||
assert(aq);
|
||||
|
||||
free(aq->buf);
|
||||
pthread_mutex_destroy(&aq->mutex);
|
||||
free(aq);
|
||||
}
|
||||
|
||||
int
|
||||
array_queue_push(array_queue * aq, void * item)
|
||||
{
|
||||
assert(item);
|
||||
|
||||
pthread_mutex_lock(&aq->mutex);
|
||||
int ret = -1;
|
||||
if (aq->buf[aq->writePos % aq->num] == NULL) {
|
||||
aq->buf[aq->writePos % aq->num] = item;
|
||||
aq->writePos = (aq->writePos + 1) % aq->num;
|
||||
ret = 0;
|
||||
}
|
||||
// else queue is full
|
||||
pthread_mutex_unlock(&aq->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void *
|
||||
array_queue_pop(array_queue * aq)
|
||||
{
|
||||
pthread_mutex_lock(&aq->mutex);
|
||||
void * value = aq->buf[aq->readPos % aq->num];
|
||||
if (value) {
|
||||
aq->buf[aq->readPos % aq->num] = NULL;
|
||||
aq->readPos = (aq->readPos + 1) % aq->num;
|
||||
}
|
||||
pthread_mutex_unlock(&aq->mutex);
|
||||
return value;
|
||||
}
|
||||
|
||||
size_t
|
||||
array_queue_get_size(array_queue * aq)
|
||||
{
|
||||
pthread_mutex_lock(&aq->mutex);
|
||||
ssize_t r = aq->writePos - aq->readPos;
|
||||
if (r < 0) {
|
||||
r = aq->num + r;
|
||||
assert(r >= 0);
|
||||
}
|
||||
pthread_mutex_unlock(&aq->mutex);
|
||||
return (size_t)r;
|
||||
}
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // CUBE_ARRAY_QUEUE_H
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
#ifndef CUBEB_ASSERT
|
||||
#define CUBEB_ASSERT
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* This allow using an external release assert method. This file should only
|
||||
* export a function or macro called XASSERT that aborts the program.
|
||||
*/
|
||||
|
||||
#define XASSERT(expr) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
fprintf(stderr, "%s:%d - fatal error: %s\n", __FILE__, __LINE__, #expr); \
|
||||
abort(); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,438 @@
|
||||
/*
|
||||
* Copyright © 2013 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
#define NDEBUG
|
||||
#endif
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <dlfcn.h>
|
||||
#include "android/log.h"
|
||||
|
||||
#include "cubeb/cubeb.h"
|
||||
#include "cubeb-internal.h"
|
||||
#include "android/audiotrack_definitions.h"
|
||||
|
||||
#ifndef ALOG
|
||||
#if defined(DEBUG) || defined(FORCE_ALOG)
|
||||
#define ALOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gecko - Cubeb" , ## args)
|
||||
#else
|
||||
#define ALOG(args...)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
* A lot of bytes for safety. It should be possible to bring this down a bit. */
|
||||
#define SIZE_AUDIOTRACK_INSTANCE 256
|
||||
|
||||
/**
|
||||
* call dlsym to get the symbol |mangled_name|, handle the error and store the
|
||||
* pointer in |pointer|. Because depending on Android version, we want different
|
||||
* symbols, not finding a symbol is not an error. */
|
||||
#define DLSYM_DLERROR(mangled_name, pointer, lib) \
|
||||
do { \
|
||||
pointer = dlsym(lib, mangled_name); \
|
||||
if (!pointer) { \
|
||||
ALOG("error while loading %stm: %stm\n", mangled_name, dlerror()); \
|
||||
} else { \
|
||||
ALOG("%stm: OK", mangled_name); \
|
||||
} \
|
||||
} while(0);
|
||||
|
||||
static struct cubeb_ops const audiotrack_ops;
|
||||
void audiotrack_destroy(cubeb * context);
|
||||
void audiotrack_stream_destroy(cubeb_stream * stream);
|
||||
|
||||
struct AudioTrack {
|
||||
/* only available on ICS and later. The second int paramter is in fact of type audio_stream_type_t. */
|
||||
/* static */ status_t (*get_min_frame_count)(int* frame_count, int stream_type, uint32_t rate);
|
||||
/* if we have a recent ctor, but can't find the above symbol, we
|
||||
* can get the minimum frame count with this signature, and we are
|
||||
* running gingerbread. */
|
||||
/* static */ status_t (*get_min_frame_count_gingerbread)(int* frame_count, int stream_type, uint32_t rate);
|
||||
void* (*ctor)(void* instance, int, unsigned int, int, int, int, unsigned int, void (*)(int, void*, void*), void*, int, int);
|
||||
void* (*dtor)(void* instance);
|
||||
void (*start)(void* instance);
|
||||
void (*pause)(void* instance);
|
||||
uint32_t (*latency)(void* instance);
|
||||
status_t (*check)(void* instance);
|
||||
status_t (*get_position)(void* instance, uint32_t* position);
|
||||
/* static */ int (*get_output_samplingrate)(int* samplerate, int stream);
|
||||
status_t (*set_marker_position)(void* instance, unsigned int);
|
||||
status_t (*set_volume)(void* instance, float left, float right);
|
||||
};
|
||||
|
||||
struct cubeb {
|
||||
struct cubeb_ops const * ops;
|
||||
void * library;
|
||||
struct AudioTrack klass;
|
||||
};
|
||||
|
||||
struct cubeb_stream {
|
||||
cubeb * context;
|
||||
cubeb_stream_params params;
|
||||
cubeb_data_callback data_callback;
|
||||
cubeb_state_callback state_callback;
|
||||
void * instance;
|
||||
void * user_ptr;
|
||||
/* Number of frames that have been passed to the AudioTrack callback */
|
||||
long unsigned written;
|
||||
int draining;
|
||||
};
|
||||
|
||||
static void
|
||||
audiotrack_refill(int event, void* user, void* info)
|
||||
{
|
||||
cubeb_stream * stream = user;
|
||||
switch (event) {
|
||||
case EVENT_MORE_DATA: {
|
||||
long got = 0;
|
||||
struct Buffer * b = (struct Buffer*)info;
|
||||
|
||||
if (stream->draining) {
|
||||
return;
|
||||
}
|
||||
|
||||
got = stream->data_callback(stream, stream->user_ptr, NULL, b->raw, b->frameCount);
|
||||
|
||||
stream->written += got;
|
||||
|
||||
if (got != (long)b->frameCount) {
|
||||
stream->draining = 1;
|
||||
/* set a marker so we are notified when the are done draining, that is,
|
||||
* when every frame has been played by android. */
|
||||
stream->context->klass.set_marker_position(stream->instance, stream->written);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case EVENT_UNDERRUN:
|
||||
ALOG("underrun in cubeb backend.");
|
||||
break;
|
||||
case EVENT_LOOP_END:
|
||||
assert(0 && "We don't support the loop feature of audiotrack.");
|
||||
break;
|
||||
case EVENT_MARKER:
|
||||
assert(stream->draining);
|
||||
stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED);
|
||||
break;
|
||||
case EVENT_NEW_POS:
|
||||
assert(0 && "We don't support the setPositionUpdatePeriod feature of audiotrack.");
|
||||
break;
|
||||
case EVENT_BUFFER_END:
|
||||
assert(0 && "Should not happen.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* We are running on gingerbread if we found the gingerbread signature for
|
||||
* getMinFrameCount */
|
||||
static int
|
||||
audiotrack_version_is_gingerbread(cubeb * ctx)
|
||||
{
|
||||
return ctx->klass.get_min_frame_count_gingerbread != NULL;
|
||||
}
|
||||
|
||||
int
|
||||
audiotrack_get_min_frame_count(cubeb * ctx, cubeb_stream_params * params, int * min_frame_count)
|
||||
{
|
||||
status_t status;
|
||||
/* Recent Android have a getMinFrameCount method. */
|
||||
if (!audiotrack_version_is_gingerbread(ctx)) {
|
||||
status = ctx->klass.get_min_frame_count(min_frame_count, params->stream_type, params->rate);
|
||||
} else {
|
||||
status = ctx->klass.get_min_frame_count_gingerbread(min_frame_count, params->stream_type, params->rate);
|
||||
}
|
||||
if (status != 0) {
|
||||
ALOG("error getting the min frame count");
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
int
|
||||
audiotrack_init(cubeb ** context, char const * context_name)
|
||||
{
|
||||
cubeb * ctx;
|
||||
struct AudioTrack* c;
|
||||
|
||||
assert(context);
|
||||
*context = NULL;
|
||||
|
||||
ctx = calloc(1, sizeof(*ctx));
|
||||
assert(ctx);
|
||||
|
||||
/* If we use an absolute path here ("/system/lib/libmedia.so"), and on Android
|
||||
* 2.2, the dlopen succeeds, all the dlsym succeed, but a segfault happens on
|
||||
* the first call to a dlsym'ed function. Somehow this does not happen when
|
||||
* using only the name of the library. */
|
||||
ctx->library = dlopen("libmedia.so", RTLD_LAZY);
|
||||
if (!ctx->library) {
|
||||
ALOG("dlopen error: %s.", dlerror());
|
||||
free(ctx);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
/* Recent Android first, then Gingerbread. */
|
||||
DLSYM_DLERROR("_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_ii", ctx->klass.ctor, ctx->library);
|
||||
DLSYM_DLERROR("_ZN7android10AudioTrackD1Ev", ctx->klass.dtor, ctx->library);
|
||||
|
||||
DLSYM_DLERROR("_ZNK7android10AudioTrack7latencyEv", ctx->klass.latency, ctx->library);
|
||||
DLSYM_DLERROR("_ZNK7android10AudioTrack9initCheckEv", ctx->klass.check, ctx->library);
|
||||
|
||||
DLSYM_DLERROR("_ZN7android11AudioSystem21getOutputSamplingRateEPii", ctx->klass.get_output_samplingrate, ctx->library);
|
||||
|
||||
/* |getMinFrameCount| is available on gingerbread and ICS with different signatures. */
|
||||
DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPi19audio_stream_type_tj", ctx->klass.get_min_frame_count, ctx->library);
|
||||
if (!ctx->klass.get_min_frame_count) {
|
||||
DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPiij", ctx->klass.get_min_frame_count_gingerbread, ctx->library);
|
||||
}
|
||||
|
||||
DLSYM_DLERROR("_ZN7android10AudioTrack5startEv", ctx->klass.start, ctx->library);
|
||||
DLSYM_DLERROR("_ZN7android10AudioTrack5pauseEv", ctx->klass.pause, ctx->library);
|
||||
DLSYM_DLERROR("_ZN7android10AudioTrack11getPositionEPj", ctx->klass.get_position, ctx->library);
|
||||
DLSYM_DLERROR("_ZN7android10AudioTrack17setMarkerPositionEj", ctx->klass.set_marker_position, ctx->library);
|
||||
DLSYM_DLERROR("_ZN7android10AudioTrack9setVolumeEff", ctx->klass.set_volume, ctx->library);
|
||||
|
||||
/* check that we have a combination of symbol that makes sense */
|
||||
c = &ctx->klass;
|
||||
if(!(c->ctor &&
|
||||
c->dtor && c->latency && c->check &&
|
||||
/* at least one way to get the minimum frame count to request. */
|
||||
(c->get_min_frame_count ||
|
||||
c->get_min_frame_count_gingerbread) &&
|
||||
c->start && c->pause && c->get_position && c->set_marker_position)) {
|
||||
ALOG("Could not find all the symbols we need.");
|
||||
audiotrack_destroy(ctx);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
ctx->ops = &audiotrack_ops;
|
||||
|
||||
*context = ctx;
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
char const *
|
||||
audiotrack_get_backend_id(cubeb * context)
|
||||
{
|
||||
return "audiotrack";
|
||||
}
|
||||
|
||||
static int
|
||||
audiotrack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
|
||||
{
|
||||
assert(ctx && max_channels);
|
||||
|
||||
/* The android mixer handles up to two channels, see
|
||||
http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67 */
|
||||
*max_channels = 2;
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
audiotrack_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms)
|
||||
{
|
||||
/* We always use the lowest latency possible when using this backend (see
|
||||
* audiotrack_stream_init), so this value is not going to be used. */
|
||||
int r;
|
||||
|
||||
r = audiotrack_get_min_frame_count(ctx, ¶ms, (int *)latency_ms);
|
||||
if (r != CUBEB_OK) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
audiotrack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
|
||||
{
|
||||
status_t r;
|
||||
|
||||
r = ctx->klass.get_output_samplingrate((int32_t *)rate, 3 /* MUSIC */);
|
||||
|
||||
return r == 0 ? CUBEB_OK : CUBEB_ERROR;
|
||||
}
|
||||
|
||||
void
|
||||
audiotrack_destroy(cubeb * context)
|
||||
{
|
||||
assert(context);
|
||||
|
||||
dlclose(context->library);
|
||||
|
||||
free(context);
|
||||
}
|
||||
|
||||
int
|
||||
audiotrack_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
|
||||
cubeb_devid input_device,
|
||||
cubeb_stream_params * input_stream_params,
|
||||
cubeb_devid output_device,
|
||||
cubeb_stream_params * output_stream_params,
|
||||
unsigned int latency,
|
||||
cubeb_data_callback data_callback,
|
||||
cubeb_state_callback state_callback,
|
||||
void * user_ptr)
|
||||
{
|
||||
cubeb_stream * stm;
|
||||
int32_t channels;
|
||||
uint32_t min_frame_count;
|
||||
|
||||
assert(ctx && stream);
|
||||
|
||||
assert(!input_stream_params && "not supported");
|
||||
if (input_device || output_device) {
|
||||
/* Device selection not yet implemented. */
|
||||
return CUBEB_ERROR_DEVICE_UNAVAILABLE;
|
||||
}
|
||||
|
||||
if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE ||
|
||||
output_stream_params->format == CUBEB_SAMPLE_FLOAT32BE) {
|
||||
return CUBEB_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
if (audiotrack_get_min_frame_count(ctx, output_stream_params, (int *)&min_frame_count)) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
stm = calloc(1, sizeof(*stm));
|
||||
assert(stm);
|
||||
|
||||
stm->context = ctx;
|
||||
stm->data_callback = data_callback;
|
||||
stm->state_callback = state_callback;
|
||||
stm->user_ptr = user_ptr;
|
||||
stm->params = *output_stream_params;
|
||||
|
||||
stm->instance = calloc(SIZE_AUDIOTRACK_INSTANCE, 1);
|
||||
(*(uint32_t*)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) = 0xbaadbaad;
|
||||
assert(stm->instance && "cubeb: EOM");
|
||||
|
||||
/* gingerbread uses old channel layout enum */
|
||||
if (audiotrack_version_is_gingerbread(ctx)) {
|
||||
channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_Legacy : AUDIO_CHANNEL_OUT_MONO_Legacy;
|
||||
} else {
|
||||
channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_ICS : AUDIO_CHANNEL_OUT_MONO_ICS;
|
||||
}
|
||||
|
||||
ctx->klass.ctor(stm->instance, stm->params.stream_type, stm->params.rate,
|
||||
AUDIO_FORMAT_PCM_16_BIT, channels, min_frame_count, 0,
|
||||
audiotrack_refill, stm, 0, 0);
|
||||
|
||||
assert((*(uint32_t*)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) == 0xbaadbaad);
|
||||
|
||||
if (ctx->klass.check(stm->instance)) {
|
||||
ALOG("stream not initialized properly.");
|
||||
audiotrack_stream_destroy(stm);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
*stream = stm;
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
void
|
||||
audiotrack_stream_destroy(cubeb_stream * stream)
|
||||
{
|
||||
assert(stream->context);
|
||||
|
||||
stream->context->klass.dtor(stream->instance);
|
||||
|
||||
free(stream->instance);
|
||||
stream->instance = NULL;
|
||||
free(stream);
|
||||
}
|
||||
|
||||
int
|
||||
audiotrack_stream_start(cubeb_stream * stream)
|
||||
{
|
||||
assert(stream->instance);
|
||||
|
||||
stream->context->klass.start(stream->instance);
|
||||
stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STARTED);
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
int
|
||||
audiotrack_stream_stop(cubeb_stream * stream)
|
||||
{
|
||||
assert(stream->instance);
|
||||
|
||||
stream->context->klass.pause(stream->instance);
|
||||
stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STOPPED);
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
int
|
||||
audiotrack_stream_get_position(cubeb_stream * stream, uint64_t * position)
|
||||
{
|
||||
uint32_t p;
|
||||
|
||||
assert(stream->instance && position);
|
||||
stream->context->klass.get_position(stream->instance, &p);
|
||||
*position = p;
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
int
|
||||
audiotrack_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
|
||||
{
|
||||
assert(stream->instance && latency);
|
||||
|
||||
/* Android returns the latency in ms, we want it in frames. */
|
||||
*latency = stream->context->klass.latency(stream->instance);
|
||||
/* with rate <= 96000, we won't overflow until 44.739 seconds of latency */
|
||||
*latency = (*latency * stream->params.rate) / 1000;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
audiotrack_stream_set_volume(cubeb_stream * stream, float volume)
|
||||
{
|
||||
status_t status;
|
||||
|
||||
status = stream->context->klass.set_volume(stream->instance, volume, volume);
|
||||
|
||||
if (status) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static struct cubeb_ops const audiotrack_ops = {
|
||||
.init = audiotrack_init,
|
||||
.get_backend_id = audiotrack_get_backend_id,
|
||||
.get_max_channel_count = audiotrack_get_max_channel_count,
|
||||
.get_min_latency = audiotrack_get_min_latency,
|
||||
.get_preferred_sample_rate = audiotrack_get_preferred_sample_rate,
|
||||
.enumerate_devices = NULL,
|
||||
.destroy = audiotrack_destroy,
|
||||
.stream_init = audiotrack_stream_init,
|
||||
.stream_destroy = audiotrack_stream_destroy,
|
||||
.stream_start = audiotrack_stream_start,
|
||||
.stream_stop = audiotrack_stream_stop,
|
||||
.stream_get_position = audiotrack_stream_get_position,
|
||||
.stream_get_latency = audiotrack_stream_get_latency,
|
||||
.stream_set_volume = audiotrack_stream_set_volume,
|
||||
.stream_set_panning = NULL,
|
||||
.stream_get_current_device = NULL,
|
||||
.stream_device_destroy = NULL,
|
||||
.stream_register_device_changed_callback = NULL,
|
||||
.register_device_collection_changed = NULL
|
||||
};
|
||||
+1386
-2322
File diff suppressed because it is too large
Load Diff
+292
-401
File diff suppressed because it is too large
Load Diff
@@ -1,132 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2016 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
#define NOMINMAX
|
||||
|
||||
#include "cubeb_log.h"
|
||||
#include "cubeb_ringbuffer.h"
|
||||
#include <cstdarg>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <time.h>
|
||||
#endif
|
||||
|
||||
cubeb_log_level g_cubeb_log_level;
|
||||
cubeb_log_callback g_cubeb_log_callback;
|
||||
|
||||
/** The maximum size of a log message, after having been formatted. */
|
||||
const size_t CUBEB_LOG_MESSAGE_MAX_SIZE = 256;
|
||||
/** The maximum number of log messages that can be queued before dropping
|
||||
* messages. */
|
||||
const size_t CUBEB_LOG_MESSAGE_QUEUE_DEPTH = 40;
|
||||
/** Number of milliseconds to wait before dequeuing log messages. */
|
||||
#define CUBEB_LOG_BATCH_PRINT_INTERVAL_MS 10
|
||||
|
||||
/**
|
||||
* This wraps an inline buffer, that represents a log message, that must be
|
||||
* null-terminated.
|
||||
* This class should not use system calls or other potentially blocking code.
|
||||
*/
|
||||
class cubeb_log_message {
|
||||
public:
|
||||
cubeb_log_message() { *storage = '\0'; }
|
||||
cubeb_log_message(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE])
|
||||
{
|
||||
size_t length = strlen(str);
|
||||
/* paranoia against malformed message */
|
||||
assert(length < CUBEB_LOG_MESSAGE_MAX_SIZE);
|
||||
if (length > CUBEB_LOG_MESSAGE_MAX_SIZE - 1) {
|
||||
return;
|
||||
}
|
||||
PodCopy(storage, str, length);
|
||||
storage[length] = '\0';
|
||||
}
|
||||
char const * get() { return storage; }
|
||||
|
||||
private:
|
||||
char storage[CUBEB_LOG_MESSAGE_MAX_SIZE];
|
||||
};
|
||||
|
||||
/** Lock-free asynchronous logger, made so that logging from a
|
||||
* real-time audio callback does not block the audio thread. */
|
||||
class cubeb_async_logger {
|
||||
public:
|
||||
/* This is thread-safe since C++11 */
|
||||
static cubeb_async_logger & get()
|
||||
{
|
||||
static cubeb_async_logger instance;
|
||||
return instance;
|
||||
}
|
||||
void push(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE])
|
||||
{
|
||||
cubeb_log_message msg(str);
|
||||
msg_queue.enqueue(msg);
|
||||
}
|
||||
void run()
|
||||
{
|
||||
std::thread([this]() {
|
||||
while (true) {
|
||||
cubeb_log_message msg;
|
||||
while (msg_queue.dequeue(&msg, 1)) {
|
||||
LOGV("%s", msg.get());
|
||||
}
|
||||
#ifdef _WIN32
|
||||
Sleep(CUBEB_LOG_BATCH_PRINT_INTERVAL_MS);
|
||||
#else
|
||||
timespec sleep_duration = sleep_for;
|
||||
timespec remainder;
|
||||
do {
|
||||
if (nanosleep(&sleep_duration, &remainder) == 0 || errno != EINTR) {
|
||||
break;
|
||||
}
|
||||
sleep_duration = remainder;
|
||||
} while (remainder.tv_sec || remainder.tv_nsec);
|
||||
#endif
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
// Tell the underlying queue the producer thread has changed, so it does not
|
||||
// assert in debug. This should be called with the thread stopped.
|
||||
void reset_producer_thread() { msg_queue.reset_thread_ids(); }
|
||||
|
||||
private:
|
||||
#ifndef _WIN32
|
||||
const struct timespec sleep_for = {
|
||||
CUBEB_LOG_BATCH_PRINT_INTERVAL_MS / 1000,
|
||||
(CUBEB_LOG_BATCH_PRINT_INTERVAL_MS % 1000) * 1000 * 1000};
|
||||
#endif
|
||||
cubeb_async_logger() : msg_queue(CUBEB_LOG_MESSAGE_QUEUE_DEPTH) { run(); }
|
||||
/** This is quite a big data structure, but is only instantiated if the
|
||||
* asynchronous logger is used.*/
|
||||
lock_free_queue<cubeb_log_message> msg_queue;
|
||||
};
|
||||
|
||||
void
|
||||
cubeb_async_log(char const * fmt, ...)
|
||||
{
|
||||
if (!g_cubeb_log_callback) {
|
||||
return;
|
||||
}
|
||||
// This is going to copy a 256 bytes array around, which is fine.
|
||||
// We don't want to allocate memory here, because this is made to
|
||||
// be called from a real-time callback.
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
char msg[CUBEB_LOG_MESSAGE_MAX_SIZE];
|
||||
vsnprintf(msg, CUBEB_LOG_MESSAGE_MAX_SIZE, fmt, args);
|
||||
cubeb_async_logger::get().push(msg);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void
|
||||
cubeb_async_log_reset_threads()
|
||||
{
|
||||
if (!g_cubeb_log_callback) {
|
||||
return;
|
||||
}
|
||||
cubeb_async_logger::get().reset_producer_thread();
|
||||
}
|
||||
@@ -8,34 +8,18 @@
|
||||
#ifndef CUBEB_LOG
|
||||
#define CUBEB_LOG
|
||||
|
||||
#include "cubeb/cubeb.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define PRINTF_FORMAT(fmt, args) __attribute__((format(printf, fmt, args)))
|
||||
#if defined(__FILE_NAME__)
|
||||
#define __FILENAME__ __FILE_NAME__
|
||||
#else
|
||||
#define __FILENAME__ \
|
||||
(__builtin_strrchr(__FILE__, '/') ? __builtin_strrchr(__FILE__, '/') + 1 \
|
||||
: __FILE__)
|
||||
#endif
|
||||
#else
|
||||
#define PRINTF_FORMAT(fmt, args)
|
||||
#include <string.h>
|
||||
#define __FILENAME__ \
|
||||
(strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
|
||||
#endif
|
||||
|
||||
extern cubeb_log_level g_cubeb_log_level;
|
||||
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();
|
||||
extern cubeb_log_level g_log_level;
|
||||
extern cubeb_log_callback g_log_callback PRINTF_FORMAT(1, 2);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
@@ -44,19 +28,10 @@ cubeb_async_log_reset_threads();
|
||||
#define LOGV(msg, ...) LOG_INTERNAL(CUBEB_LOG_VERBOSE, msg, ##__VA_ARGS__)
|
||||
#define LOG(msg, ...) LOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__)
|
||||
|
||||
#define LOG_INTERNAL(level, fmt, ...) \
|
||||
do { \
|
||||
if (g_cubeb_log_callback && level <= g_cubeb_log_level) { \
|
||||
g_cubeb_log_callback("%s:%d: " fmt "\n", __FILENAME__, __LINE__, \
|
||||
##__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* Asynchronous verbose logging, to log in real-time callbacks. */
|
||||
/* Should not be used on android due to the use of global/static variables. */
|
||||
#define ALOGV(fmt, ...) \
|
||||
do { \
|
||||
cubeb_async_log(fmt, ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
#define LOG_INTERNAL(level, fmt, ...) do { \
|
||||
if (g_log_callback && level <= g_log_level) { \
|
||||
g_log_callback("%s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#endif // CUBEB_LOG
|
||||
|
||||
@@ -1,625 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2016 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*
|
||||
* Adapted from code based on libswresample's rematrix.c
|
||||
*/
|
||||
|
||||
#define NOMINMAX
|
||||
|
||||
#include "cubeb_mixer.h"
|
||||
#include "cubeb-internal.h"
|
||||
#include "cubeb_utils.h"
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
#ifndef FF_ARRAY_ELEMS
|
||||
#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0]))
|
||||
#endif
|
||||
|
||||
#define CHANNELS_MAX 32
|
||||
#define FRONT_LEFT 0
|
||||
#define FRONT_RIGHT 1
|
||||
#define FRONT_CENTER 2
|
||||
#define LOW_FREQUENCY 3
|
||||
#define BACK_LEFT 4
|
||||
#define BACK_RIGHT 5
|
||||
#define FRONT_LEFT_OF_CENTER 6
|
||||
#define FRONT_RIGHT_OF_CENTER 7
|
||||
#define BACK_CENTER 8
|
||||
#define SIDE_LEFT 9
|
||||
#define SIDE_RIGHT 10
|
||||
#define TOP_CENTER 11
|
||||
#define TOP_FRONT_LEFT 12
|
||||
#define TOP_FRONT_CENTER 13
|
||||
#define TOP_FRONT_RIGHT 14
|
||||
#define TOP_BACK_LEFT 15
|
||||
#define TOP_BACK_CENTER 16
|
||||
#define TOP_BACK_RIGHT 17
|
||||
#define NUM_NAMED_CHANNELS 18
|
||||
|
||||
#ifndef M_SQRT1_2
|
||||
#define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */
|
||||
#endif
|
||||
#ifndef M_SQRT2
|
||||
#define M_SQRT2 1.41421356237309504880 /* sqrt(2) */
|
||||
#endif
|
||||
#define SQRT3_2 1.22474487139158904909 /* sqrt(3/2) */
|
||||
|
||||
#define C30DB M_SQRT2
|
||||
#define C15DB 1.189207115
|
||||
#define C__0DB 1.0
|
||||
#define C_15DB 0.840896415
|
||||
#define C_30DB M_SQRT1_2
|
||||
#define C_45DB 0.594603558
|
||||
#define C_60DB 0.5
|
||||
|
||||
static cubeb_channel_layout
|
||||
cubeb_channel_layout_check(cubeb_channel_layout l, uint32_t c)
|
||||
{
|
||||
if (l == CUBEB_LAYOUT_UNDEFINED) {
|
||||
switch (c) {
|
||||
case 1:
|
||||
return CUBEB_LAYOUT_MONO;
|
||||
case 2:
|
||||
return CUBEB_LAYOUT_STEREO;
|
||||
}
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
unsigned int
|
||||
cubeb_channel_layout_nb_channels(cubeb_channel_layout x)
|
||||
{
|
||||
#if __GNUC__ || __clang__
|
||||
return __builtin_popcount(x);
|
||||
#else
|
||||
x -= (x >> 1) & 0x55555555;
|
||||
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
|
||||
x = (x + (x >> 4)) & 0x0F0F0F0F;
|
||||
x += x >> 8;
|
||||
return (x + (x >> 16)) & 0x3F;
|
||||
#endif
|
||||
}
|
||||
|
||||
struct MixerContext {
|
||||
MixerContext(cubeb_sample_format f, uint32_t in_channels,
|
||||
cubeb_channel_layout in, uint32_t out_channels,
|
||||
cubeb_channel_layout out)
|
||||
: _format(f), _in_ch_layout(cubeb_channel_layout_check(in, in_channels)),
|
||||
_out_ch_layout(cubeb_channel_layout_check(out, out_channels)),
|
||||
_in_ch_count(in_channels), _out_ch_count(out_channels)
|
||||
{
|
||||
if (in_channels != cubeb_channel_layout_nb_channels(in) ||
|
||||
out_channels != cubeb_channel_layout_nb_channels(out)) {
|
||||
// Mismatch between channels and layout, aborting.
|
||||
return;
|
||||
}
|
||||
_valid = init() >= 0;
|
||||
}
|
||||
|
||||
static bool even(cubeb_channel_layout layout)
|
||||
{
|
||||
if (!layout) {
|
||||
return true;
|
||||
}
|
||||
if (layout & (layout - 1)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure that the layout is sane (that is have symmetrical left/right
|
||||
// channels), if not, layout will be treated as mono.
|
||||
static cubeb_channel_layout clean_layout(cubeb_channel_layout layout)
|
||||
{
|
||||
if (layout && layout != CHANNEL_FRONT_LEFT && !(layout & (layout - 1))) {
|
||||
LOG("Treating layout as mono");
|
||||
return CHANNEL_FRONT_CENTER;
|
||||
}
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
static bool sane_layout(cubeb_channel_layout layout)
|
||||
{
|
||||
if (!(layout & CUBEB_LAYOUT_3F)) { // at least 1 front speaker
|
||||
return false;
|
||||
}
|
||||
if (!even(layout & (CHANNEL_FRONT_LEFT |
|
||||
CHANNEL_FRONT_RIGHT))) { // no asymetric front
|
||||
return false;
|
||||
}
|
||||
if (!even(layout &
|
||||
(CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT))) { // no asymetric side
|
||||
return false;
|
||||
}
|
||||
if (!even(layout & (CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT))) {
|
||||
return false;
|
||||
}
|
||||
if (!even(layout &
|
||||
(CHANNEL_FRONT_LEFT_OF_CENTER | CHANNEL_FRONT_RIGHT_OF_CENTER))) {
|
||||
return false;
|
||||
}
|
||||
if (cubeb_channel_layout_nb_channels(layout) >= CHANNELS_MAX) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int auto_matrix();
|
||||
int init();
|
||||
|
||||
const cubeb_sample_format _format;
|
||||
const cubeb_channel_layout _in_ch_layout; ///< input channel layout
|
||||
const cubeb_channel_layout _out_ch_layout; ///< output channel layout
|
||||
const uint32_t _in_ch_count; ///< input channel count
|
||||
const uint32_t _out_ch_count; ///< output channel count
|
||||
const float _surround_mix_level = C_30DB; ///< surround mixing level
|
||||
const float _center_mix_level = C_30DB; ///< center mixing level
|
||||
const float _lfe_mix_level = 1; ///< LFE mixing level
|
||||
double _matrix[CHANNELS_MAX][CHANNELS_MAX] = {
|
||||
{0}}; ///< floating point rematrixing coefficients
|
||||
float _matrix_flt[CHANNELS_MAX][CHANNELS_MAX] = {
|
||||
{0}}; ///< single precision floating point rematrixing coefficients
|
||||
int32_t _matrix32[CHANNELS_MAX][CHANNELS_MAX] = {
|
||||
{0}}; ///< 17.15 fixed point rematrixing coefficients
|
||||
uint8_t _matrix_ch[CHANNELS_MAX][CHANNELS_MAX + 1] = {
|
||||
{0}}; ///< Lists of input channels per output channel that have non zero
|
||||
///< rematrixing coefficients
|
||||
bool _clipping = false; ///< Set to true if clipping detection is required
|
||||
bool _valid = false; ///< Set to true if context is valid.
|
||||
};
|
||||
|
||||
int
|
||||
MixerContext::auto_matrix()
|
||||
{
|
||||
double matrix[NUM_NAMED_CHANNELS][NUM_NAMED_CHANNELS] = {{0}};
|
||||
double maxcoef = 0;
|
||||
float maxval;
|
||||
|
||||
cubeb_channel_layout in_ch_layout = clean_layout(_in_ch_layout);
|
||||
cubeb_channel_layout out_ch_layout = clean_layout(_out_ch_layout);
|
||||
|
||||
if (!sane_layout(in_ch_layout)) {
|
||||
// Channel Not Supported
|
||||
LOG("Input Layout %x is not supported", _in_ch_layout);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!sane_layout(out_ch_layout)) {
|
||||
LOG("Output Layout %x is not supported", _out_ch_layout);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < FF_ARRAY_ELEMS(matrix); i++) {
|
||||
if (in_ch_layout & out_ch_layout & (1U << i)) {
|
||||
matrix[i][i] = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
cubeb_channel_layout unaccounted = in_ch_layout & ~out_ch_layout;
|
||||
|
||||
// Rematrixing is done via a matrix of coefficient that should be applied to
|
||||
// all channels. Channels are treated as pair and must be symmetrical (if a
|
||||
// left channel exists, the corresponding right should exist too) unless the
|
||||
// output layout has similar layout. Channels are then mixed toward the front
|
||||
// center or back center if they exist with a slight bias toward the front.
|
||||
|
||||
if (unaccounted & CHANNEL_FRONT_CENTER) {
|
||||
if ((out_ch_layout & CUBEB_LAYOUT_STEREO) == CUBEB_LAYOUT_STEREO) {
|
||||
if (in_ch_layout & CUBEB_LAYOUT_STEREO) {
|
||||
matrix[FRONT_LEFT][FRONT_CENTER] += _center_mix_level;
|
||||
matrix[FRONT_RIGHT][FRONT_CENTER] += _center_mix_level;
|
||||
} else {
|
||||
matrix[FRONT_LEFT][FRONT_CENTER] += M_SQRT1_2;
|
||||
matrix[FRONT_RIGHT][FRONT_CENTER] += M_SQRT1_2;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (unaccounted & CUBEB_LAYOUT_STEREO) {
|
||||
if (out_ch_layout & CHANNEL_FRONT_CENTER) {
|
||||
matrix[FRONT_CENTER][FRONT_LEFT] += M_SQRT1_2;
|
||||
matrix[FRONT_CENTER][FRONT_RIGHT] += M_SQRT1_2;
|
||||
if (in_ch_layout & CHANNEL_FRONT_CENTER)
|
||||
matrix[FRONT_CENTER][FRONT_CENTER] = _center_mix_level * M_SQRT2;
|
||||
}
|
||||
}
|
||||
|
||||
if (unaccounted & CHANNEL_BACK_CENTER) {
|
||||
if (out_ch_layout & CHANNEL_BACK_LEFT) {
|
||||
matrix[BACK_LEFT][BACK_CENTER] += M_SQRT1_2;
|
||||
matrix[BACK_RIGHT][BACK_CENTER] += M_SQRT1_2;
|
||||
} else if (out_ch_layout & CHANNEL_SIDE_LEFT) {
|
||||
matrix[SIDE_LEFT][BACK_CENTER] += M_SQRT1_2;
|
||||
matrix[SIDE_RIGHT][BACK_CENTER] += M_SQRT1_2;
|
||||
} else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
|
||||
matrix[FRONT_LEFT][BACK_CENTER] += _surround_mix_level * M_SQRT1_2;
|
||||
matrix[FRONT_RIGHT][BACK_CENTER] += _surround_mix_level * M_SQRT1_2;
|
||||
} else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
|
||||
matrix[FRONT_CENTER][BACK_CENTER] += _surround_mix_level * M_SQRT1_2;
|
||||
}
|
||||
}
|
||||
if (unaccounted & CHANNEL_BACK_LEFT) {
|
||||
if (out_ch_layout & CHANNEL_BACK_CENTER) {
|
||||
matrix[BACK_CENTER][BACK_LEFT] += M_SQRT1_2;
|
||||
matrix[BACK_CENTER][BACK_RIGHT] += M_SQRT1_2;
|
||||
} else if (out_ch_layout & CHANNEL_SIDE_LEFT) {
|
||||
if (in_ch_layout & CHANNEL_SIDE_LEFT) {
|
||||
matrix[SIDE_LEFT][BACK_LEFT] += M_SQRT1_2;
|
||||
matrix[SIDE_RIGHT][BACK_RIGHT] += M_SQRT1_2;
|
||||
} else {
|
||||
matrix[SIDE_LEFT][BACK_LEFT] += 1.0;
|
||||
matrix[SIDE_RIGHT][BACK_RIGHT] += 1.0;
|
||||
}
|
||||
} else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
|
||||
matrix[FRONT_LEFT][BACK_LEFT] += _surround_mix_level;
|
||||
matrix[FRONT_RIGHT][BACK_RIGHT] += _surround_mix_level;
|
||||
} else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
|
||||
matrix[FRONT_CENTER][BACK_LEFT] += _surround_mix_level * M_SQRT1_2;
|
||||
matrix[FRONT_CENTER][BACK_RIGHT] += _surround_mix_level * M_SQRT1_2;
|
||||
}
|
||||
}
|
||||
|
||||
if (unaccounted & CHANNEL_SIDE_LEFT) {
|
||||
if (out_ch_layout & CHANNEL_BACK_LEFT) {
|
||||
/* if back channels do not exist in the input, just copy side
|
||||
channels to back channels, otherwise mix side into back */
|
||||
if (in_ch_layout & CHANNEL_BACK_LEFT) {
|
||||
matrix[BACK_LEFT][SIDE_LEFT] += M_SQRT1_2;
|
||||
matrix[BACK_RIGHT][SIDE_RIGHT] += M_SQRT1_2;
|
||||
} else {
|
||||
matrix[BACK_LEFT][SIDE_LEFT] += 1.0;
|
||||
matrix[BACK_RIGHT][SIDE_RIGHT] += 1.0;
|
||||
}
|
||||
} else if (out_ch_layout & CHANNEL_BACK_CENTER) {
|
||||
matrix[BACK_CENTER][SIDE_LEFT] += M_SQRT1_2;
|
||||
matrix[BACK_CENTER][SIDE_RIGHT] += M_SQRT1_2;
|
||||
} else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
|
||||
matrix[FRONT_LEFT][SIDE_LEFT] += _surround_mix_level;
|
||||
matrix[FRONT_RIGHT][SIDE_RIGHT] += _surround_mix_level;
|
||||
} else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
|
||||
matrix[FRONT_CENTER][SIDE_LEFT] += _surround_mix_level * M_SQRT1_2;
|
||||
matrix[FRONT_CENTER][SIDE_RIGHT] += _surround_mix_level * M_SQRT1_2;
|
||||
}
|
||||
}
|
||||
|
||||
if (unaccounted & CHANNEL_FRONT_LEFT_OF_CENTER) {
|
||||
if (out_ch_layout & CHANNEL_FRONT_LEFT) {
|
||||
matrix[FRONT_LEFT][FRONT_LEFT_OF_CENTER] += 1.0;
|
||||
matrix[FRONT_RIGHT][FRONT_RIGHT_OF_CENTER] += 1.0;
|
||||
} else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
|
||||
matrix[FRONT_CENTER][FRONT_LEFT_OF_CENTER] += M_SQRT1_2;
|
||||
matrix[FRONT_CENTER][FRONT_RIGHT_OF_CENTER] += M_SQRT1_2;
|
||||
}
|
||||
}
|
||||
/* mix LFE into front left/right or center */
|
||||
if (unaccounted & CHANNEL_LOW_FREQUENCY) {
|
||||
if (out_ch_layout & CHANNEL_FRONT_CENTER) {
|
||||
matrix[FRONT_CENTER][LOW_FREQUENCY] += _lfe_mix_level;
|
||||
} else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
|
||||
matrix[FRONT_LEFT][LOW_FREQUENCY] += _lfe_mix_level * M_SQRT1_2;
|
||||
matrix[FRONT_RIGHT][LOW_FREQUENCY] += _lfe_mix_level * M_SQRT1_2;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize the conversion matrix.
|
||||
for (uint32_t out_i = 0, i = 0; i < CHANNELS_MAX; i++) {
|
||||
double sum = 0;
|
||||
int in_i = 0;
|
||||
if ((out_ch_layout & (1U << i)) == 0) {
|
||||
continue;
|
||||
}
|
||||
for (uint32_t j = 0; j < CHANNELS_MAX; j++) {
|
||||
if ((in_ch_layout & (1U << j)) == 0) {
|
||||
continue;
|
||||
}
|
||||
if (i < FF_ARRAY_ELEMS(matrix) && j < FF_ARRAY_ELEMS(matrix[0])) {
|
||||
_matrix[out_i][in_i] = matrix[i][j];
|
||||
} else {
|
||||
_matrix[out_i][in_i] =
|
||||
i == j && (in_ch_layout & out_ch_layout & (1U << i));
|
||||
}
|
||||
sum += fabs(_matrix[out_i][in_i]);
|
||||
in_i++;
|
||||
}
|
||||
maxcoef = std::max(maxcoef, sum);
|
||||
out_i++;
|
||||
}
|
||||
|
||||
if (_format == CUBEB_SAMPLE_S16NE) {
|
||||
maxval = 1.0;
|
||||
} else {
|
||||
maxval = INT_MAX;
|
||||
}
|
||||
|
||||
// Normalize matrix if needed.
|
||||
if (maxcoef > maxval) {
|
||||
maxcoef /= maxval;
|
||||
for (uint32_t i = 0; i < CHANNELS_MAX; i++)
|
||||
for (uint32_t j = 0; j < CHANNELS_MAX; j++) {
|
||||
_matrix[i][j] /= maxcoef;
|
||||
}
|
||||
}
|
||||
|
||||
if (_format == CUBEB_SAMPLE_FLOAT32NE) {
|
||||
for (uint32_t i = 0; i < FF_ARRAY_ELEMS(_matrix); i++) {
|
||||
for (uint32_t j = 0; j < FF_ARRAY_ELEMS(_matrix[0]); j++) {
|
||||
_matrix_flt[i][j] = _matrix[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
MixerContext::init()
|
||||
{
|
||||
int r = auto_matrix();
|
||||
if (r) {
|
||||
return r;
|
||||
}
|
||||
|
||||
// Determine if matrix operation would overflow
|
||||
if (_format == CUBEB_SAMPLE_S16NE) {
|
||||
int maxsum = 0;
|
||||
for (uint32_t i = 0; i < _out_ch_count; i++) {
|
||||
double rem = 0;
|
||||
int sum = 0;
|
||||
|
||||
for (uint32_t j = 0; j < _in_ch_count; j++) {
|
||||
double target = _matrix[i][j] * 32768 + rem;
|
||||
int value = lrintf(target);
|
||||
rem += target - value;
|
||||
sum += std::abs(value);
|
||||
}
|
||||
maxsum = std::max(maxsum, sum);
|
||||
}
|
||||
if (maxsum > 32768) {
|
||||
_clipping = true;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME quantize for integers
|
||||
for (uint32_t i = 0; i < CHANNELS_MAX; i++) {
|
||||
int ch_in = 0;
|
||||
for (uint32_t j = 0; j < CHANNELS_MAX; j++) {
|
||||
_matrix32[i][j] = lrintf(_matrix[i][j] * 32768);
|
||||
if (_matrix[i][j]) {
|
||||
_matrix_ch[i][++ch_in] = j;
|
||||
}
|
||||
}
|
||||
_matrix_ch[i][0] = ch_in;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename TYPE_SAMPLE, typename TYPE_COEFF, typename F>
|
||||
void
|
||||
sum2(TYPE_SAMPLE * out, uint32_t stride_out, const TYPE_SAMPLE * in1,
|
||||
const TYPE_SAMPLE * in2, uint32_t stride_in, TYPE_COEFF coeff1,
|
||||
TYPE_COEFF coeff2, F && operand, uint32_t frames)
|
||||
{
|
||||
static_assert(
|
||||
std::is_same<TYPE_COEFF,
|
||||
typename std::result_of<F(TYPE_COEFF)>::type>::value,
|
||||
"function must return the same type as used by matrix_coeff");
|
||||
for (uint32_t i = 0; i < frames; i++) {
|
||||
*out = operand(coeff1 * *in1 + coeff2 * *in2);
|
||||
out += stride_out;
|
||||
in1 += stride_in;
|
||||
in2 += stride_in;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename TYPE_SAMPLE, typename TYPE_COEFF, typename F>
|
||||
void
|
||||
copy(TYPE_SAMPLE * out, uint32_t stride_out, const TYPE_SAMPLE * in,
|
||||
uint32_t stride_in, TYPE_COEFF coeff, F && operand, uint32_t frames)
|
||||
{
|
||||
static_assert(
|
||||
std::is_same<TYPE_COEFF,
|
||||
typename std::result_of<F(TYPE_COEFF)>::type>::value,
|
||||
"function must return the same type as used by matrix_coeff");
|
||||
for (uint32_t i = 0; i < frames; i++) {
|
||||
*out = operand(coeff * *in);
|
||||
out += stride_out;
|
||||
in += stride_in;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename TYPE, typename TYPE_COEFF, size_t COLS, typename F>
|
||||
static int
|
||||
rematrix(const MixerContext * s, TYPE * aOut, const TYPE * aIn,
|
||||
const TYPE_COEFF (&matrix_coeff)[COLS][COLS], F && aF, uint32_t frames)
|
||||
{
|
||||
static_assert(
|
||||
std::is_same<TYPE_COEFF,
|
||||
typename std::result_of<F(TYPE_COEFF)>::type>::value,
|
||||
"function must return the same type as used by matrix_coeff");
|
||||
|
||||
for (uint32_t out_i = 0; out_i < s->_out_ch_count; out_i++) {
|
||||
TYPE * out = aOut + out_i;
|
||||
switch (s->_matrix_ch[out_i][0]) {
|
||||
case 0:
|
||||
for (uint32_t i = 0; i < frames; i++) {
|
||||
out[i * s->_out_ch_count] = 0;
|
||||
}
|
||||
break;
|
||||
case 1: {
|
||||
int in_i = s->_matrix_ch[out_i][1];
|
||||
copy(out, s->_out_ch_count, aIn + in_i, s->_in_ch_count,
|
||||
matrix_coeff[out_i][in_i], aF, frames);
|
||||
} break;
|
||||
case 2:
|
||||
sum2(out, s->_out_ch_count, aIn + s->_matrix_ch[out_i][1],
|
||||
aIn + s->_matrix_ch[out_i][2], s->_in_ch_count,
|
||||
matrix_coeff[out_i][s->_matrix_ch[out_i][1]],
|
||||
matrix_coeff[out_i][s->_matrix_ch[out_i][2]], aF, frames);
|
||||
break;
|
||||
default:
|
||||
for (uint32_t i = 0; i < frames; i++) {
|
||||
TYPE_COEFF v = 0;
|
||||
for (uint32_t j = 0; j < s->_matrix_ch[out_i][0]; j++) {
|
||||
uint32_t in_i = s->_matrix_ch[out_i][1 + j];
|
||||
v += *(aIn + in_i + i * s->_in_ch_count) * matrix_coeff[out_i][in_i];
|
||||
}
|
||||
out[i * s->_out_ch_count] = aF(v);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct cubeb_mixer {
|
||||
cubeb_mixer(cubeb_sample_format format, uint32_t in_channels,
|
||||
cubeb_channel_layout in_layout, uint32_t out_channels,
|
||||
cubeb_channel_layout out_layout)
|
||||
: _context(format, in_channels, in_layout, out_channels, out_layout)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void copy_and_trunc(size_t frames, const T * input_buffer,
|
||||
T * output_buffer) const
|
||||
{
|
||||
if (_context._in_ch_count <= _context._out_ch_count) {
|
||||
// Not enough channels to copy, fill the gaps with silence.
|
||||
if (_context._in_ch_count == 1 && _context._out_ch_count >= 2) {
|
||||
// Special case for upmixing mono input to stereo and more. We will
|
||||
// duplicate the mono channel to the first two channels. On most system,
|
||||
// the first two channels are for left and right. It is commonly
|
||||
// expected that mono will on both left+right channels
|
||||
for (uint32_t i = 0; i < frames; i++) {
|
||||
output_buffer[0] = output_buffer[1] = *input_buffer;
|
||||
PodZero(output_buffer + 2, _context._out_ch_count - 2);
|
||||
output_buffer += _context._out_ch_count;
|
||||
input_buffer++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (uint32_t i = 0; i < frames; i++) {
|
||||
PodCopy(output_buffer, input_buffer, _context._in_ch_count);
|
||||
output_buffer += _context._in_ch_count;
|
||||
input_buffer += _context._in_ch_count;
|
||||
PodZero(output_buffer, _context._out_ch_count - _context._in_ch_count);
|
||||
output_buffer += _context._out_ch_count - _context._in_ch_count;
|
||||
}
|
||||
} else {
|
||||
for (uint32_t i = 0; i < frames; i++) {
|
||||
PodCopy(output_buffer, input_buffer, _context._out_ch_count);
|
||||
output_buffer += _context._out_ch_count;
|
||||
input_buffer += _context._in_ch_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int mix(size_t frames, const void * input_buffer, size_t input_buffer_size,
|
||||
void * output_buffer, size_t output_buffer_size) const
|
||||
{
|
||||
if (frames <= 0 || _context._out_ch_count == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check if output buffer is of sufficient size.
|
||||
size_t size_read_needed =
|
||||
frames * _context._in_ch_count * cubeb_sample_size(_context._format);
|
||||
if (input_buffer_size < size_read_needed) {
|
||||
// We don't have enough data to read!
|
||||
return -1;
|
||||
}
|
||||
if (output_buffer_size * _context._in_ch_count <
|
||||
size_read_needed * _context._out_ch_count) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!valid()) {
|
||||
// The channel layouts were invalid or unsupported, instead we will simply
|
||||
// either drop the extra channels, or fill with silence the missing ones
|
||||
if (_context._format == CUBEB_SAMPLE_FLOAT32NE) {
|
||||
copy_and_trunc(frames, static_cast<const float *>(input_buffer),
|
||||
static_cast<float *>(output_buffer));
|
||||
} else {
|
||||
assert(_context._format == CUBEB_SAMPLE_S16NE);
|
||||
copy_and_trunc(frames, static_cast<const int16_t *>(input_buffer),
|
||||
reinterpret_cast<int16_t *>(output_buffer));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (_context._format) {
|
||||
case CUBEB_SAMPLE_FLOAT32NE: {
|
||||
auto f = [](float x) { return x; };
|
||||
return rematrix(&_context, static_cast<float *>(output_buffer),
|
||||
static_cast<const float *>(input_buffer),
|
||||
_context._matrix_flt, f, frames);
|
||||
}
|
||||
case CUBEB_SAMPLE_S16NE:
|
||||
if (_context._clipping) {
|
||||
auto f = [](int x) {
|
||||
int y = (x + 16384) >> 15;
|
||||
// clip the signed integer value into the -32768,32767 range.
|
||||
if ((y + 0x8000U) & ~0xFFFF) {
|
||||
return (y >> 31) ^ 0x7FFF;
|
||||
}
|
||||
return y;
|
||||
};
|
||||
return rematrix(&_context, static_cast<int16_t *>(output_buffer),
|
||||
static_cast<const int16_t *>(input_buffer),
|
||||
_context._matrix32, f, frames);
|
||||
} else {
|
||||
auto f = [](int x) { return (x + 16384) >> 15; };
|
||||
return rematrix(&_context, static_cast<int16_t *>(output_buffer),
|
||||
static_cast<const int16_t *>(input_buffer),
|
||||
_context._matrix32, f, frames);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Return false if any of the input or ouput layout were invalid.
|
||||
bool valid() const { return _context._valid; }
|
||||
|
||||
virtual ~cubeb_mixer(){};
|
||||
|
||||
MixerContext _context;
|
||||
};
|
||||
|
||||
cubeb_mixer *
|
||||
cubeb_mixer_create(cubeb_sample_format format, uint32_t in_channels,
|
||||
cubeb_channel_layout in_layout, uint32_t out_channels,
|
||||
cubeb_channel_layout out_layout)
|
||||
{
|
||||
return new cubeb_mixer(format, in_channels, in_layout, out_channels,
|
||||
out_layout);
|
||||
}
|
||||
|
||||
void
|
||||
cubeb_mixer_destroy(cubeb_mixer * mixer)
|
||||
{
|
||||
delete mixer;
|
||||
}
|
||||
|
||||
int
|
||||
cubeb_mixer_mix(cubeb_mixer * mixer, size_t frames, const void * input_buffer,
|
||||
size_t input_buffer_size, void * output_buffer,
|
||||
size_t output_buffer_size)
|
||||
{
|
||||
return mixer->mix(frames, input_buffer, input_buffer_size, output_buffer,
|
||||
output_buffer_size);
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2016 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
#ifndef CUBEB_MIXER
|
||||
#define CUBEB_MIXER
|
||||
|
||||
#include "cubeb/cubeb.h" // for cubeb_channel_layout and cubeb_stream_params.
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct cubeb_mixer cubeb_mixer;
|
||||
cubeb_mixer *
|
||||
cubeb_mixer_create(cubeb_sample_format format, uint32_t in_channels,
|
||||
cubeb_channel_layout in_layout, uint32_t out_channels,
|
||||
cubeb_channel_layout out_layout);
|
||||
void
|
||||
cubeb_mixer_destroy(cubeb_mixer * mixer);
|
||||
int
|
||||
cubeb_mixer_mix(cubeb_mixer * mixer, size_t frames, const void * input_buffer,
|
||||
size_t input_buffer_size, void * output_buffer,
|
||||
size_t output_buffer_size);
|
||||
|
||||
unsigned int
|
||||
cubeb_channel_layout_nb_channels(cubeb_channel_layout channel_layout);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // CUBEB_MIXER
|
||||
+527
-1438
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2014 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
/* On OSX 10.6 and after, the notification callbacks from the audio hardware are
|
||||
* called on the main thread. Setting the kAudioHardwarePropertyRunLoop property
|
||||
* to null tells the OSX to use a separate thread for that.
|
||||
*
|
||||
* This has to be called only once per process, so it is in a separate header
|
||||
* for easy integration in other code bases. */
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void
|
||||
cubeb_set_coreaudio_notification_runloop();
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,21 +8,21 @@
|
||||
#define NOMINMAX
|
||||
#endif // NOMINMAX
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include "cubeb_resampler.h"
|
||||
#include "cubeb-speex-resampler.h"
|
||||
#include "cubeb_resampler_internal.h"
|
||||
#include "cubeb_utils.h"
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
int
|
||||
to_speex_quality(cubeb_resampler_quality q)
|
||||
{
|
||||
switch (q) {
|
||||
switch(q) {
|
||||
case CUBEB_RESAMPLER_QUALITY_VOIP:
|
||||
return SPEEX_RESAMPLER_QUALITY_VOIP;
|
||||
case CUBEB_RESAMPLER_QUALITY_DEFAULT:
|
||||
@@ -35,230 +35,157 @@ to_speex_quality(cubeb_resampler_quality q)
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t
|
||||
min_buffered_audio_frame(uint32_t sample_rate)
|
||||
{
|
||||
return sample_rate / 20;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
passthrough_resampler<T>::passthrough_resampler(cubeb_stream * s,
|
||||
cubeb_data_callback cb,
|
||||
void * ptr,
|
||||
uint32_t input_channels,
|
||||
uint32_t sample_rate)
|
||||
: processor(input_channels), stream(s), data_callback(cb), user_ptr(ptr),
|
||||
sample_rate(sample_rate)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
long
|
||||
passthrough_resampler<T>::fill(void * input_buffer, long * input_frames_count,
|
||||
void * output_buffer, long output_frames)
|
||||
long noop_resampler::fill(void * input_buffer, long * input_frames_count,
|
||||
void * output_buffer, long output_frames)
|
||||
{
|
||||
if (input_buffer) {
|
||||
assert(input_frames_count);
|
||||
}
|
||||
assert((input_buffer && output_buffer) ||
|
||||
(output_buffer && !input_buffer &&
|
||||
(!input_frames_count || *input_frames_count == 0)) ||
|
||||
(input_buffer && !output_buffer && output_frames == 0));
|
||||
assert((input_buffer && output_buffer &&
|
||||
*input_frames_count >= output_frames) ||
|
||||
(!input_buffer && (!input_frames_count || *input_frames_count == 0)) ||
|
||||
(!output_buffer && output_frames == 0));
|
||||
|
||||
// When we have no pending input data and exactly as much input
|
||||
// as output data, we don't need to copy it into the internal buffer
|
||||
// and can directly forward it to the callback.
|
||||
void * in_buf = input_buffer;
|
||||
unsigned long pop_input_count = 0u;
|
||||
if (input_buffer && !output_buffer) {
|
||||
if (output_buffer == nullptr) {
|
||||
assert(input_buffer);
|
||||
output_frames = *input_frames_count;
|
||||
} else if (input_buffer) {
|
||||
if (internal_input_buffer.length() != 0 ||
|
||||
*input_frames_count < output_frames) {
|
||||
// If we have pending input data left and have to first append the input
|
||||
// so we can pass it as one pointer to the callback. Or this is a glitch.
|
||||
// It can happen when system's performance is poor. Audible silence is
|
||||
// being pushed at the end of the short input buffer. An improvement for
|
||||
// the future is to resample to the output number of frames, when that
|
||||
// happens.
|
||||
internal_input_buffer.push(static_cast<T *>(input_buffer),
|
||||
frames_to_samples(*input_frames_count));
|
||||
if (internal_input_buffer.length() < frames_to_samples(output_frames)) {
|
||||
// This is unxpected but it can happen when a glitch occurs. Fill the
|
||||
// buffer with silence. First keep the actual number of input samples
|
||||
// used without the silence.
|
||||
pop_input_count = internal_input_buffer.length();
|
||||
internal_input_buffer.push_silence(frames_to_samples(output_frames) -
|
||||
internal_input_buffer.length());
|
||||
} else {
|
||||
pop_input_count = frames_to_samples(output_frames);
|
||||
}
|
||||
in_buf = internal_input_buffer.data();
|
||||
} else if (*input_frames_count > output_frames) {
|
||||
// In this case we have more input that we need output and
|
||||
// fill the overflowing input into internal_input_buffer
|
||||
// Since we have no other pending data, we can nonetheless
|
||||
// pass the current input data directly to the callback
|
||||
assert(pop_input_count == 0);
|
||||
unsigned long samples_off = frames_to_samples(output_frames);
|
||||
internal_input_buffer.push(
|
||||
static_cast<T *>(input_buffer) + samples_off,
|
||||
frames_to_samples(*input_frames_count - output_frames));
|
||||
}
|
||||
}
|
||||
|
||||
long rv =
|
||||
data_callback(stream, user_ptr, in_buf, output_buffer, output_frames);
|
||||
|
||||
if (input_buffer) {
|
||||
if (pop_input_count) {
|
||||
internal_input_buffer.pop(nullptr, pop_input_count);
|
||||
*input_frames_count = samples_to_frames(pop_input_count);
|
||||
} else {
|
||||
*input_frames_count = output_frames;
|
||||
}
|
||||
drop_audio_if_needed();
|
||||
if (input_buffer && *input_frames_count != output_frames) {
|
||||
assert(*input_frames_count > output_frames);
|
||||
*input_frames_count = output_frames;
|
||||
}
|
||||
|
||||
return rv;
|
||||
return data_callback(stream, user_ptr,
|
||||
input_buffer, output_buffer, output_frames);
|
||||
}
|
||||
|
||||
// Explicit instantiation of template class.
|
||||
template class passthrough_resampler<float>;
|
||||
template class passthrough_resampler<short>;
|
||||
|
||||
template <typename T, typename InputProcessor, typename OutputProcessor>
|
||||
cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::
|
||||
cubeb_resampler_speex(InputProcessor * input_processor,
|
||||
OutputProcessor * output_processor, cubeb_stream * s,
|
||||
cubeb_data_callback cb, void * ptr)
|
||||
: input_processor(input_processor), output_processor(output_processor),
|
||||
stream(s), data_callback(cb), user_ptr(ptr)
|
||||
template<typename T, typename InputProcessor, typename OutputProcessor>
|
||||
cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
|
||||
::cubeb_resampler_speex(InputProcessor * input_processor,
|
||||
OutputProcessor * output_processor,
|
||||
cubeb_stream * s,
|
||||
cubeb_data_callback cb,
|
||||
void * ptr)
|
||||
: input_processor(input_processor)
|
||||
, output_processor(output_processor)
|
||||
, stream(s)
|
||||
, data_callback(cb)
|
||||
, user_ptr(ptr)
|
||||
{
|
||||
if (input_processor && output_processor) {
|
||||
// Add some delay on the processor that has the lowest delay so that the
|
||||
// streams are synchronized.
|
||||
uint32_t in_latency = input_processor->latency();
|
||||
uint32_t out_latency = output_processor->latency();
|
||||
if (in_latency > out_latency) {
|
||||
uint32_t latency_diff = in_latency - out_latency;
|
||||
output_processor->add_latency(latency_diff);
|
||||
} else if (in_latency < out_latency) {
|
||||
uint32_t latency_diff = out_latency - in_latency;
|
||||
input_processor->add_latency(latency_diff);
|
||||
}
|
||||
fill_internal = &cubeb_resampler_speex::fill_internal_duplex;
|
||||
} else if (input_processor) {
|
||||
} else if (input_processor) {
|
||||
fill_internal = &cubeb_resampler_speex::fill_internal_input;
|
||||
} else if (output_processor) {
|
||||
} else if (output_processor) {
|
||||
fill_internal = &cubeb_resampler_speex::fill_internal_output;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename InputProcessor, typename OutputProcessor>
|
||||
cubeb_resampler_speex<T, InputProcessor,
|
||||
OutputProcessor>::~cubeb_resampler_speex()
|
||||
{
|
||||
}
|
||||
template<typename T, typename InputProcessor, typename OutputProcessor>
|
||||
cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
|
||||
::~cubeb_resampler_speex()
|
||||
{ }
|
||||
|
||||
template <typename T, typename InputProcessor, typename OutputProcessor>
|
||||
template<typename T, typename InputProcessor, typename OutputProcessor>
|
||||
long
|
||||
cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::fill(
|
||||
void * input_buffer, long * input_frames_count, void * output_buffer,
|
||||
long output_frames_needed)
|
||||
cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
|
||||
::fill(void * input_buffer, long * input_frames_count,
|
||||
void * output_buffer, long output_frames_needed)
|
||||
{
|
||||
/* Input and output buffers, typed */
|
||||
T * in_buffer = reinterpret_cast<T *>(input_buffer);
|
||||
T * out_buffer = reinterpret_cast<T *>(output_buffer);
|
||||
return (this->*fill_internal)(in_buffer, input_frames_count, out_buffer,
|
||||
output_frames_needed);
|
||||
T * in_buffer = reinterpret_cast<T*>(input_buffer);
|
||||
T * out_buffer = reinterpret_cast<T*>(output_buffer);
|
||||
return (this->*fill_internal)(in_buffer, input_frames_count,
|
||||
out_buffer, output_frames_needed);
|
||||
}
|
||||
|
||||
template <typename T, typename InputProcessor, typename OutputProcessor>
|
||||
template<typename T, typename InputProcessor, typename OutputProcessor>
|
||||
long
|
||||
cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::fill_internal_output(
|
||||
T * input_buffer, long * input_frames_count, T * output_buffer,
|
||||
long output_frames_needed)
|
||||
cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
|
||||
::fill_internal_output(T * input_buffer, long * input_frames_count,
|
||||
T * output_buffer, long output_frames_needed)
|
||||
{
|
||||
assert(!input_buffer && (!input_frames_count || *input_frames_count == 0) &&
|
||||
output_buffer && output_frames_needed);
|
||||
|
||||
if (!draining) {
|
||||
long got = 0;
|
||||
T * out_unprocessed = nullptr;
|
||||
long output_frames_before_processing = 0;
|
||||
long got = 0;
|
||||
T * out_unprocessed = nullptr;
|
||||
long output_frames_before_processing = 0;
|
||||
|
||||
/* fill directly the input buffer of the output processor to save a copy */
|
||||
output_frames_before_processing =
|
||||
output_processor->input_needed_for_output(output_frames_needed);
|
||||
|
||||
out_unprocessed =
|
||||
output_processor->input_buffer(output_frames_before_processing);
|
||||
/* fill directly the input buffer of the output processor to save a copy */
|
||||
output_frames_before_processing =
|
||||
output_processor->input_needed_for_output(output_frames_needed);
|
||||
|
||||
got = data_callback(stream, user_ptr, nullptr, out_unprocessed,
|
||||
output_frames_before_processing);
|
||||
out_unprocessed =
|
||||
output_processor->input_buffer(output_frames_before_processing);
|
||||
|
||||
if (got < output_frames_before_processing) {
|
||||
draining = true;
|
||||
got = data_callback(stream, user_ptr,
|
||||
nullptr, out_unprocessed,
|
||||
output_frames_before_processing);
|
||||
|
||||
if (got < 0) {
|
||||
return got;
|
||||
}
|
||||
}
|
||||
|
||||
output_processor->written(got);
|
||||
if (got < 0) {
|
||||
return got;
|
||||
}
|
||||
|
||||
output_processor->written(got);
|
||||
|
||||
/* Process the output. If not enough frames have been returned from the
|
||||
* callback, drain the processors. */
|
||||
* callback, drain the processors. */
|
||||
return output_processor->output(output_buffer, output_frames_needed);
|
||||
}
|
||||
|
||||
template <typename T, typename InputProcessor, typename OutputProcessor>
|
||||
template<typename T, typename InputProcessor, typename OutputProcessor>
|
||||
long
|
||||
cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::fill_internal_input(
|
||||
T * input_buffer, long * input_frames_count, T * output_buffer,
|
||||
long /*output_frames_needed*/)
|
||||
cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
|
||||
::fill_internal_input(T * input_buffer, long * input_frames_count,
|
||||
T * output_buffer, long /*output_frames_needed*/)
|
||||
{
|
||||
assert(input_buffer && input_frames_count && *input_frames_count &&
|
||||
!output_buffer);
|
||||
|
||||
/* The input data, after eventual resampling. This is passed to the callback.
|
||||
*/
|
||||
/* The input data, after eventual resampling. This is passed to the callback. */
|
||||
T * resampled_input = nullptr;
|
||||
uint32_t resampled_frame_count =
|
||||
input_processor->output_for_input(*input_frames_count);
|
||||
uint32_t resampled_frame_count = input_processor->output_for_input(*input_frames_count);
|
||||
|
||||
/* process the input, and present exactly `output_frames_needed` in the
|
||||
* callback. */
|
||||
* callback. */
|
||||
input_processor->input(input_buffer, *input_frames_count);
|
||||
resampled_input = input_processor->output(resampled_frame_count);
|
||||
|
||||
/* resampled_frame_count == 0 happens if the resampler
|
||||
* doesn't have enough input frames buffered to produce 1 resampled frame. */
|
||||
if (resampled_frame_count == 0) {
|
||||
return *input_frames_count;
|
||||
}
|
||||
|
||||
size_t frames_resampled = 0;
|
||||
resampled_input =
|
||||
input_processor->output(resampled_frame_count, &frames_resampled);
|
||||
*input_frames_count = frames_resampled;
|
||||
|
||||
long got = data_callback(stream, user_ptr, resampled_input, nullptr,
|
||||
resampled_frame_count);
|
||||
long got = data_callback(stream, user_ptr,
|
||||
resampled_input, nullptr, resampled_frame_count);
|
||||
|
||||
/* Return the number of initial input frames or part of it.
|
||||
* Since output_frames_needed == 0 in input scenario, the only
|
||||
* available number outside resampler is the initial number of frames. */
|
||||
* Since output_frames_needed == 0 in input scenario, the only
|
||||
* available number outside resampler is the initial number of frames. */
|
||||
return (*input_frames_count) * (got / resampled_frame_count);
|
||||
}
|
||||
|
||||
template <typename T, typename InputProcessor, typename OutputProcessor>
|
||||
long
|
||||
cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::fill_internal_duplex(
|
||||
T * in_buffer, long * input_frames_count, T * out_buffer,
|
||||
long output_frames_needed)
|
||||
{
|
||||
if (draining) {
|
||||
// discard input and drain any signal remaining in the resampler.
|
||||
return output_processor->output(out_buffer, output_frames_needed);
|
||||
}
|
||||
|
||||
/* The input data, after eventual resampling. This is passed to the callback.
|
||||
*/
|
||||
template<typename T, typename InputProcessor, typename OutputProcessor>
|
||||
long
|
||||
cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
|
||||
::fill_internal_duplex(T * in_buffer, long * input_frames_count,
|
||||
T * out_buffer, long output_frames_needed)
|
||||
{
|
||||
/* The input data, after eventual resampling. This is passed to the callback. */
|
||||
T * resampled_input = nullptr;
|
||||
/* The output buffer passed down in the callback, that might be resampled. */
|
||||
T * out_unprocessed = nullptr;
|
||||
long output_frames_before_processing = 0;
|
||||
size_t output_frames_before_processing = 0;
|
||||
/* The number of frames returned from the callback. */
|
||||
long got = 0;
|
||||
|
||||
@@ -274,46 +201,34 @@ cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::fill_internal_duplex(
|
||||
* caller. */
|
||||
|
||||
output_frames_before_processing =
|
||||
output_processor->input_needed_for_output(output_frames_needed);
|
||||
/* fill directly the input buffer of the output processor to save a copy */
|
||||
output_processor->input_needed_for_output(output_frames_needed);
|
||||
/* fill directly the input buffer of the output processor to save a copy */
|
||||
out_unprocessed =
|
||||
output_processor->input_buffer(output_frames_before_processing);
|
||||
output_processor->input_buffer(output_frames_before_processing);
|
||||
|
||||
if (in_buffer) {
|
||||
/* process the input, and present exactly `output_frames_needed` in the
|
||||
* callback. */
|
||||
* callback. */
|
||||
input_processor->input(in_buffer, *input_frames_count);
|
||||
|
||||
size_t frames_resampled = 0;
|
||||
resampled_input = input_processor->output(output_frames_before_processing,
|
||||
&frames_resampled);
|
||||
*input_frames_count = frames_resampled;
|
||||
resampled_input =
|
||||
input_processor->output(output_frames_before_processing);
|
||||
} else {
|
||||
resampled_input = nullptr;
|
||||
}
|
||||
|
||||
got = data_callback(stream, user_ptr, resampled_input, out_unprocessed,
|
||||
got = data_callback(stream, user_ptr,
|
||||
resampled_input, out_unprocessed,
|
||||
output_frames_before_processing);
|
||||
|
||||
if (got < output_frames_before_processing) {
|
||||
draining = true;
|
||||
|
||||
if (got < 0) {
|
||||
return got;
|
||||
}
|
||||
if (got < 0) {
|
||||
return got;
|
||||
}
|
||||
|
||||
output_processor->written(got);
|
||||
|
||||
input_processor->drop_audio_if_needed();
|
||||
|
||||
/* Process the output. If not enough frames have been returned from the
|
||||
* callback, drain the processors. */
|
||||
got = output_processor->output(out_buffer, output_frames_needed);
|
||||
|
||||
output_processor->drop_audio_if_needed();
|
||||
|
||||
return got;
|
||||
return output_processor->output(out_buffer, output_frames_needed);
|
||||
}
|
||||
|
||||
/* Resampler C API */
|
||||
@@ -322,8 +237,10 @@ cubeb_resampler *
|
||||
cubeb_resampler_create(cubeb_stream * stream,
|
||||
cubeb_stream_params * input_params,
|
||||
cubeb_stream_params * output_params,
|
||||
unsigned int target_rate, cubeb_data_callback callback,
|
||||
void * user_ptr, cubeb_resampler_quality quality)
|
||||
unsigned int target_rate,
|
||||
cubeb_data_callback callback,
|
||||
void * user_ptr,
|
||||
cubeb_resampler_quality quality)
|
||||
{
|
||||
cubeb_sample_format format;
|
||||
|
||||
@@ -335,28 +252,38 @@ cubeb_resampler_create(cubeb_stream * stream,
|
||||
format = output_params->format;
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case CUBEB_SAMPLE_S16NE:
|
||||
return cubeb_resampler_create_internal<short>(stream, input_params,
|
||||
output_params, target_rate,
|
||||
callback, user_ptr, quality);
|
||||
case CUBEB_SAMPLE_FLOAT32NE:
|
||||
return cubeb_resampler_create_internal<float>(stream, input_params,
|
||||
output_params, target_rate,
|
||||
callback, user_ptr, quality);
|
||||
default:
|
||||
assert(false);
|
||||
return nullptr;
|
||||
switch(format) {
|
||||
case CUBEB_SAMPLE_S16NE:
|
||||
return cubeb_resampler_create_internal<short>(stream,
|
||||
input_params,
|
||||
output_params,
|
||||
target_rate,
|
||||
callback,
|
||||
user_ptr,
|
||||
quality);
|
||||
case CUBEB_SAMPLE_FLOAT32NE:
|
||||
return cubeb_resampler_create_internal<float>(stream,
|
||||
input_params,
|
||||
output_params,
|
||||
target_rate,
|
||||
callback,
|
||||
user_ptr,
|
||||
quality);
|
||||
default:
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
long
|
||||
cubeb_resampler_fill(cubeb_resampler * resampler, void * input_buffer,
|
||||
long * input_frames_count, void * output_buffer,
|
||||
cubeb_resampler_fill(cubeb_resampler * resampler,
|
||||
void * input_buffer,
|
||||
long * input_frames_count,
|
||||
void * output_buffer,
|
||||
long output_frames_needed)
|
||||
{
|
||||
return resampler->fill(input_buffer, input_frames_count, output_buffer,
|
||||
output_frames_needed);
|
||||
return resampler->fill(input_buffer, input_frames_count,
|
||||
output_buffer, output_frames_needed);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -25,26 +25,20 @@ typedef enum {
|
||||
* Create a resampler to adapt the requested sample rate into something that
|
||||
* is accepted by the audio backend.
|
||||
* @param stream A cubeb_stream instance supplied to the data callback.
|
||||
* @param input_params Used to calculate bytes per frame and buffer size for
|
||||
* resampling of the input side of the stream. NULL if input should not be
|
||||
* resampled.
|
||||
* @param output_params Used to calculate bytes per frame and buffer size for
|
||||
* resampling of the output side of the stream. NULL if output should not be
|
||||
* resampled.
|
||||
* @param target_rate The sampling rate after resampling for the input side of
|
||||
* the stream, and/or the sampling rate prior to resampling of the output side
|
||||
* of the stream.
|
||||
* @param params Used to calculate bytes per frame and buffer size for resampling.
|
||||
* @param target_rate The sampling rate after resampling.
|
||||
* @param callback A callback to request data for resampling.
|
||||
* @param user_ptr User data supplied to the data callback.
|
||||
* @param quality Quality of the resampler.
|
||||
* @retval A non-null pointer if success.
|
||||
*/
|
||||
cubeb_resampler *
|
||||
cubeb_resampler_create(cubeb_stream * stream,
|
||||
cubeb_stream_params * input_params,
|
||||
cubeb_stream_params * output_params,
|
||||
unsigned int target_rate, cubeb_data_callback callback,
|
||||
void * user_ptr, cubeb_resampler_quality quality);
|
||||
cubeb_resampler * cubeb_resampler_create(cubeb_stream * stream,
|
||||
cubeb_stream_params * input_params,
|
||||
cubeb_stream_params * output_params,
|
||||
unsigned int target_rate,
|
||||
cubeb_data_callback callback,
|
||||
void * user_ptr,
|
||||
cubeb_resampler_quality quality);
|
||||
|
||||
/**
|
||||
* Fill the buffer with frames acquired using the data callback. Resampling will
|
||||
@@ -53,30 +47,29 @@ cubeb_resampler_create(cubeb_stream * stream,
|
||||
* @param input_buffer A buffer of input samples
|
||||
* @param input_frame_count The size of the buffer. Returns the number of frames
|
||||
* consumed.
|
||||
* @param output_buffer The buffer to be filled.
|
||||
* @param output_frames_needed Number of frames that should be produced.
|
||||
* @param buffer The buffer to be filled.
|
||||
* @param frames_needed Number of frames that should be produced.
|
||||
* @retval Number of frames that are actually produced.
|
||||
* @retval CUBEB_ERROR on error.
|
||||
*/
|
||||
long
|
||||
cubeb_resampler_fill(cubeb_resampler * resampler, void * input_buffer,
|
||||
long * input_frame_count, void * output_buffer,
|
||||
long output_frames_needed);
|
||||
long cubeb_resampler_fill(cubeb_resampler * resampler,
|
||||
void * input_buffer,
|
||||
long * input_frame_count,
|
||||
void * output_buffer,
|
||||
long output_frames_needed);
|
||||
|
||||
/**
|
||||
* Destroy a cubeb_resampler.
|
||||
* @param resampler A cubeb_resampler instance.
|
||||
*/
|
||||
void
|
||||
cubeb_resampler_destroy(cubeb_resampler * resampler);
|
||||
void cubeb_resampler_destroy(cubeb_resampler * resampler);
|
||||
|
||||
/**
|
||||
* Returns the latency, in frames, of the resampler.
|
||||
* @param resampler A cubeb resampler instance.
|
||||
* @retval The latency, in frames, induced by the resampler.
|
||||
*/
|
||||
long
|
||||
cubeb_resampler_latency(cubeb_resampler * resampler);
|
||||
long cubeb_resampler_latency(cubeb_resampler * resampler);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
#if !defined(CUBEB_RESAMPLER_INTERNAL)
|
||||
#define CUBEB_RESAMPLER_INTERNAL
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#ifdef CUBEB_GECKO_BUILD
|
||||
#include "mozilla/UniquePtr.h"
|
||||
@@ -25,32 +25,21 @@
|
||||
#define MOZ_END_STD_NAMESPACE }
|
||||
#endif
|
||||
MOZ_BEGIN_STD_NAMESPACE
|
||||
using mozilla::DefaultDelete;
|
||||
using mozilla::UniquePtr;
|
||||
#define default_delete DefaultDelete
|
||||
#define unique_ptr UniquePtr
|
||||
using mozilla::DefaultDelete;
|
||||
using mozilla::UniquePtr;
|
||||
#define default_delete DefaultDelete
|
||||
#define unique_ptr UniquePtr
|
||||
MOZ_END_STD_NAMESPACE
|
||||
#endif
|
||||
#include "cubeb-speex-resampler.h"
|
||||
#include "cubeb/cubeb.h"
|
||||
#include "cubeb_log.h"
|
||||
#include "cubeb_resampler.h"
|
||||
#include "cubeb_utils.h"
|
||||
#include "cubeb-speex-resampler.h"
|
||||
#include "cubeb_resampler.h"
|
||||
#include <stdio.h>
|
||||
|
||||
/* This header file contains the internal C++ API of the resamplers, for
|
||||
* testing. */
|
||||
/* This header file contains the internal C++ API of the resamplers, for testing. */
|
||||
|
||||
// When dropping audio input frames to prevent building
|
||||
// an input delay, this function returns the number of frames
|
||||
// to keep in the buffer.
|
||||
// @parameter sample_rate The sample rate of the stream.
|
||||
// @return A number of frames to keep.
|
||||
uint32_t
|
||||
min_buffered_audio_frame(uint32_t sample_rate);
|
||||
|
||||
int
|
||||
to_speex_quality(cubeb_resampler_quality q);
|
||||
int to_speex_quality(cubeb_resampler_quality q);
|
||||
|
||||
struct cubeb_resampler {
|
||||
virtual long fill(void * input_buffer, long * input_frames_count,
|
||||
@@ -59,14 +48,43 @@ struct cubeb_resampler {
|
||||
virtual ~cubeb_resampler() {}
|
||||
};
|
||||
|
||||
class noop_resampler : public cubeb_resampler {
|
||||
public:
|
||||
noop_resampler(cubeb_stream * s,
|
||||
cubeb_data_callback cb,
|
||||
void * ptr)
|
||||
: stream(s)
|
||||
, data_callback(cb)
|
||||
, user_ptr(ptr)
|
||||
{
|
||||
}
|
||||
|
||||
virtual long fill(void * input_buffer, long * input_frames_count,
|
||||
void * output_buffer, long output_frames);
|
||||
|
||||
virtual long latency()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
cubeb_stream * const stream;
|
||||
const cubeb_data_callback data_callback;
|
||||
void * const user_ptr;
|
||||
};
|
||||
|
||||
/** Base class for processors. This is just used to share methods for now. */
|
||||
class processor {
|
||||
public:
|
||||
explicit processor(uint32_t channels) : channels(channels) {}
|
||||
|
||||
explicit processor(uint32_t channels)
|
||||
: channels(channels)
|
||||
{}
|
||||
protected:
|
||||
size_t frames_to_samples(size_t frames) const { return frames * channels; }
|
||||
size_t samples_to_frames(size_t samples) const
|
||||
size_t frames_to_samples(size_t frames)
|
||||
{
|
||||
return frames * channels;
|
||||
}
|
||||
size_t samples_to_frames(size_t samples)
|
||||
{
|
||||
assert(!(samples % channels));
|
||||
return samples / channels;
|
||||
@@ -75,46 +93,17 @@ protected:
|
||||
const uint32_t channels;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class passthrough_resampler : public cubeb_resampler, public processor {
|
||||
public:
|
||||
passthrough_resampler(cubeb_stream * s, cubeb_data_callback cb, void * ptr,
|
||||
uint32_t input_channels, uint32_t sample_rate);
|
||||
|
||||
virtual long fill(void * input_buffer, long * input_frames_count,
|
||||
void * output_buffer, long output_frames);
|
||||
|
||||
virtual long latency() { return 0; }
|
||||
|
||||
void drop_audio_if_needed()
|
||||
{
|
||||
uint32_t to_keep = min_buffered_audio_frame(sample_rate);
|
||||
uint32_t available = samples_to_frames(internal_input_buffer.length());
|
||||
if (available > to_keep) {
|
||||
internal_input_buffer.pop(nullptr,
|
||||
frames_to_samples(available - to_keep));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
cubeb_stream * const stream;
|
||||
const cubeb_data_callback data_callback;
|
||||
void * const user_ptr;
|
||||
/* This allows to buffer some input to account for the fact that we buffer
|
||||
* some inputs. */
|
||||
auto_array<T> internal_input_buffer;
|
||||
uint32_t sample_rate;
|
||||
};
|
||||
|
||||
/** Bidirectional resampler, can resample an input and an output stream, or just
|
||||
* an input stream or output stream. In this case a delay is inserted in the
|
||||
* opposite direction to keep the streams synchronized. */
|
||||
template <typename T, typename InputProcessing, typename OutputProcessing>
|
||||
template<typename T, typename InputProcessing, typename OutputProcessing>
|
||||
class cubeb_resampler_speex : public cubeb_resampler {
|
||||
public:
|
||||
cubeb_resampler_speex(InputProcessing * input_processor,
|
||||
OutputProcessing * output_processor, cubeb_stream * s,
|
||||
cubeb_data_callback cb, void * ptr);
|
||||
OutputProcessing * output_processor,
|
||||
cubeb_stream * s,
|
||||
cubeb_data_callback cb,
|
||||
void * ptr);
|
||||
|
||||
virtual ~cubeb_resampler_speex();
|
||||
|
||||
@@ -134,9 +123,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
typedef long (cubeb_resampler_speex::*processing_callback)(
|
||||
T * input_buffer, long * input_frames_count, T * output_buffer,
|
||||
long output_frames_needed);
|
||||
typedef long(cubeb_resampler_speex::*processing_callback)(T * input_buffer, long * input_frames_count, T * output_buffer, long output_frames_needed);
|
||||
|
||||
long fill_internal_duplex(T * input_buffer, long * input_frames_count,
|
||||
T * output_buffer, long output_frames_needed);
|
||||
@@ -151,14 +138,14 @@ private:
|
||||
cubeb_stream * const stream;
|
||||
const cubeb_data_callback data_callback;
|
||||
void * const user_ptr;
|
||||
bool draining = false;
|
||||
};
|
||||
|
||||
/** Handles one way of a (possibly) duplex resampler, working on interleaved
|
||||
* audio buffers of type T. This class is designed so that the number of frames
|
||||
* coming out of the resampler can be precisely controled. It manages its own
|
||||
* input buffer, and can use the caller's output buffer, or allocate its own. */
|
||||
template <typename T> class cubeb_resampler_speex_one_way : public processor {
|
||||
template<typename T>
|
||||
class cubeb_resampler_speex_one_way : public processor {
|
||||
public:
|
||||
/** The sample type of this resampler, either 16-bit integers or 32-bit
|
||||
* floats. */
|
||||
@@ -170,26 +157,19 @@ public:
|
||||
* @parameter target_rate The sample-rate of the audio output.
|
||||
* @parameter quality A number between 0 (fast, low quality) and 10 (slow,
|
||||
* high quality). */
|
||||
cubeb_resampler_speex_one_way(uint32_t channels, uint32_t source_rate,
|
||||
uint32_t target_rate, int quality)
|
||||
: processor(channels),
|
||||
resampling_ratio(static_cast<float>(source_rate) / target_rate),
|
||||
source_rate(source_rate), additional_latency(0), leftover_samples(0)
|
||||
cubeb_resampler_speex_one_way(uint32_t channels,
|
||||
uint32_t source_rate,
|
||||
uint32_t target_rate,
|
||||
int quality)
|
||||
: processor(channels)
|
||||
, resampling_ratio(static_cast<float>(source_rate) / target_rate)
|
||||
, additional_latency(0)
|
||||
, leftover_samples(0)
|
||||
{
|
||||
int r;
|
||||
speex_resampler =
|
||||
speex_resampler_init(channels, source_rate, target_rate, quality, &r);
|
||||
speex_resampler = speex_resampler_init(channels, source_rate,
|
||||
target_rate, quality, &r);
|
||||
assert(r == RESAMPLER_ERR_SUCCESS && "resampler allocation failure");
|
||||
|
||||
uint32_t input_latency = speex_resampler_get_input_latency(speex_resampler);
|
||||
const size_t LATENCY_SAMPLES = 8192;
|
||||
T input_buffer[LATENCY_SAMPLES] = {};
|
||||
T output_buffer[LATENCY_SAMPLES] = {};
|
||||
uint32_t input_frame_count = input_latency;
|
||||
uint32_t output_frame_count = LATENCY_SAMPLES;
|
||||
assert(input_latency * channels <= LATENCY_SAMPLES);
|
||||
speex_resample(input_buffer, &input_frame_count, output_buffer,
|
||||
&output_frame_count);
|
||||
}
|
||||
|
||||
/** Destructor, deallocate the resampler */
|
||||
@@ -198,6 +178,17 @@ public:
|
||||
speex_resampler_destroy(speex_resampler);
|
||||
}
|
||||
|
||||
/** Sometimes, it is necessary to add latency on one way of a two-way
|
||||
* resampler so that the stream are synchronized. This must be called only on
|
||||
* a fresh resampler, otherwise, silent samples will be inserted in the
|
||||
* stream.
|
||||
* @param frames the number of frames of latency to add. */
|
||||
void add_latency(size_t frames)
|
||||
{
|
||||
additional_latency += frames;
|
||||
resampling_in_buffer.push_silence(frames_to_samples(frames));
|
||||
}
|
||||
|
||||
/* Fill the resampler with `input_frame_count` frames. */
|
||||
void input(T * input_buffer, size_t input_frame_count)
|
||||
{
|
||||
@@ -206,14 +197,14 @@ public:
|
||||
}
|
||||
|
||||
/** Outputs exactly `output_frame_count` into `output_buffer`.
|
||||
* `output_buffer` has to be at least `output_frame_count` long. */
|
||||
* `output_buffer` has to be at least `output_frame_count` long. */
|
||||
size_t output(T * output_buffer, size_t output_frame_count)
|
||||
{
|
||||
uint32_t in_len = samples_to_frames(resampling_in_buffer.length());
|
||||
uint32_t out_len = output_frame_count;
|
||||
|
||||
speex_resample(resampling_in_buffer.data(), &in_len, output_buffer,
|
||||
&out_len);
|
||||
speex_resample(resampling_in_buffer.data(), &in_len,
|
||||
output_buffer, &out_len);
|
||||
|
||||
/* This shifts back any unresampled samples to the beginning of the input
|
||||
buffer. */
|
||||
@@ -224,17 +215,15 @@ public:
|
||||
|
||||
size_t output_for_input(uint32_t input_frames)
|
||||
{
|
||||
return (size_t)floorf(
|
||||
(input_frames + samples_to_frames(resampling_in_buffer.length())) /
|
||||
resampling_ratio);
|
||||
return (size_t)floorf((input_frames + samples_to_frames(resampling_in_buffer.length()))
|
||||
/ resampling_ratio);
|
||||
}
|
||||
|
||||
/** Returns a buffer containing exactly `output_frame_count` resampled frames.
|
||||
* The consumer should not hold onto the pointer. */
|
||||
T * output(size_t output_frame_count, size_t * input_frames_used)
|
||||
* The consumer should not hold onto the pointer. */
|
||||
T * output(size_t output_frame_count)
|
||||
{
|
||||
if (resampling_out_buffer.capacity() <
|
||||
frames_to_samples(output_frame_count)) {
|
||||
if (resampling_out_buffer.capacity() < frames_to_samples(output_frame_count)) {
|
||||
resampling_out_buffer.reserve(frames_to_samples(output_frame_count));
|
||||
}
|
||||
|
||||
@@ -244,21 +233,11 @@ public:
|
||||
speex_resample(resampling_in_buffer.data(), &in_len,
|
||||
resampling_out_buffer.data(), &out_len);
|
||||
|
||||
if (out_len < output_frame_count) {
|
||||
LOGV("underrun during resampling: got %u frames, expected %zu",
|
||||
(unsigned)out_len, output_frame_count);
|
||||
// silence the rightmost part
|
||||
T * data = resampling_out_buffer.data();
|
||||
for (uint32_t i = frames_to_samples(out_len);
|
||||
i < frames_to_samples(output_frame_count); i++) {
|
||||
data[i] = 0;
|
||||
}
|
||||
}
|
||||
assert(out_len == output_frame_count);
|
||||
|
||||
/* This shifts back any unresampled samples to the beginning of the input
|
||||
buffer. */
|
||||
resampling_in_buffer.pop(nullptr, frames_to_samples(in_len));
|
||||
*input_frames_used = in_len;
|
||||
|
||||
return resampling_out_buffer.data();
|
||||
}
|
||||
@@ -270,8 +249,8 @@ public:
|
||||
* only consider a single channel here so it's the same number of frames. */
|
||||
int latency = 0;
|
||||
|
||||
latency = speex_resampler_get_output_latency(speex_resampler) +
|
||||
additional_latency;
|
||||
latency =
|
||||
speex_resampler_get_output_latency(speex_resampler) + additional_latency;
|
||||
|
||||
assert(latency >= 0);
|
||||
|
||||
@@ -282,16 +261,13 @@ public:
|
||||
* exactly `output_frame_count` resampled frames. This can return a number
|
||||
* slightly bigger than what is strictly necessary, but it guaranteed that the
|
||||
* number of output frames will be exactly equal. */
|
||||
uint32_t input_needed_for_output(int32_t output_frame_count) const
|
||||
uint32_t input_needed_for_output(uint32_t output_frame_count)
|
||||
{
|
||||
assert(output_frame_count >= 0); // Check overflow
|
||||
int32_t unresampled_frames_left =
|
||||
samples_to_frames(resampling_in_buffer.length());
|
||||
int32_t resampled_frames_left =
|
||||
samples_to_frames(resampling_out_buffer.length());
|
||||
int32_t unresampled_frames_left = samples_to_frames(resampling_in_buffer.length());
|
||||
int32_t resampled_frames_left = samples_to_frames(resampling_out_buffer.length());
|
||||
float input_frames_needed =
|
||||
(output_frame_count - unresampled_frames_left) * resampling_ratio -
|
||||
resampled_frames_left;
|
||||
(output_frame_count - unresampled_frames_left) * resampling_ratio
|
||||
- resampled_frames_left;
|
||||
if (input_frames_needed < 0) {
|
||||
return 0;
|
||||
}
|
||||
@@ -318,20 +294,9 @@ public:
|
||||
resampling_in_buffer.set_length(leftover_samples +
|
||||
frames_to_samples(written_frames));
|
||||
}
|
||||
|
||||
void drop_audio_if_needed()
|
||||
{
|
||||
// Keep at most 100ms buffered.
|
||||
uint32_t available = samples_to_frames(resampling_in_buffer.length());
|
||||
uint32_t to_keep = min_buffered_audio_frame(source_rate);
|
||||
if (available > to_keep) {
|
||||
resampling_in_buffer.pop(nullptr, frames_to_samples(available - to_keep));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/** Wrapper for the speex resampling functions to have a typed
|
||||
* interface. */
|
||||
* interface. */
|
||||
void speex_resample(float * input_buffer, uint32_t * input_frame_count,
|
||||
float * output_buffer, uint32_t * output_frame_count)
|
||||
{
|
||||
@@ -339,9 +304,11 @@ private:
|
||||
int rv;
|
||||
rv =
|
||||
#endif
|
||||
speex_resampler_process_interleaved_float(
|
||||
speex_resampler, input_buffer, input_frame_count, output_buffer,
|
||||
output_frame_count);
|
||||
speex_resampler_process_interleaved_float(speex_resampler,
|
||||
input_buffer,
|
||||
input_frame_count,
|
||||
output_buffer,
|
||||
output_frame_count);
|
||||
assert(rv == RESAMPLER_ERR_SUCCESS);
|
||||
}
|
||||
|
||||
@@ -352,16 +319,17 @@ private:
|
||||
int rv;
|
||||
rv =
|
||||
#endif
|
||||
speex_resampler_process_interleaved_int(
|
||||
speex_resampler, input_buffer, input_frame_count, output_buffer,
|
||||
output_frame_count);
|
||||
speex_resampler_process_interleaved_int(speex_resampler,
|
||||
input_buffer,
|
||||
input_frame_count,
|
||||
output_buffer,
|
||||
output_frame_count);
|
||||
assert(rv == RESAMPLER_ERR_SUCCESS);
|
||||
}
|
||||
/** The state for the speex resampler used internaly. */
|
||||
SpeexResamplerState * speex_resampler;
|
||||
/** Source rate / target rate. */
|
||||
const float resampling_ratio;
|
||||
const uint32_t source_rate;
|
||||
/** Storage for the input frames, to be resampled. Also contains
|
||||
* any unresampled frames after resampling. */
|
||||
auto_array<T> resampling_in_buffer;
|
||||
@@ -375,20 +343,27 @@ private:
|
||||
};
|
||||
|
||||
/** This class allows delaying an audio stream by `frames` frames. */
|
||||
template <typename T> class delay_line : public processor {
|
||||
template<typename T>
|
||||
class delay_line : public processor {
|
||||
public:
|
||||
/** Constructor
|
||||
* @parameter frames the number of frames of delay.
|
||||
* @parameter channels the number of channels of this delay line.
|
||||
* @parameter sample_rate sample-rate of the audio going through this delay
|
||||
* line */
|
||||
delay_line(uint32_t frames, uint32_t channels, uint32_t sample_rate)
|
||||
: processor(channels), length(frames), leftover_samples(0),
|
||||
sample_rate(sample_rate)
|
||||
* @parameter channels the number of channels of this delay line. */
|
||||
delay_line(uint32_t frames, uint32_t channels)
|
||||
: processor(channels)
|
||||
, length(frames)
|
||||
, leftover_samples(0)
|
||||
{
|
||||
/* Fill the delay line with some silent frames to add latency. */
|
||||
delay_input_buffer.push_silence(frames * channels);
|
||||
}
|
||||
/* Add some latency to the delay line.
|
||||
* @param frames the number of frames of latency to add. */
|
||||
void add_latency(size_t frames)
|
||||
{
|
||||
length += frames;
|
||||
delay_input_buffer.push_silence(frames_to_samples(frames));
|
||||
}
|
||||
/** Push some frames into the delay line.
|
||||
* @parameter buffer the frames to push.
|
||||
* @parameter frame_count the number of frames in #buffer. */
|
||||
@@ -400,7 +375,7 @@ public:
|
||||
* @parameter frames_needed the number of frames to be returned.
|
||||
* @return a buffer containing the delayed frames. The consumer should not
|
||||
* hold onto the pointer. */
|
||||
T * output(uint32_t frames_needed, size_t * input_frames_used)
|
||||
T * output(uint32_t frames_needed)
|
||||
{
|
||||
if (delay_output_buffer.capacity() < frames_to_samples(frames_needed)) {
|
||||
delay_output_buffer.reserve(frames_to_samples(frames_needed));
|
||||
@@ -410,7 +385,6 @@ public:
|
||||
delay_output_buffer.push(delay_input_buffer.data(),
|
||||
frames_to_samples(frames_needed));
|
||||
delay_input_buffer.pop(nullptr, frames_to_samples(frames_needed));
|
||||
*input_frames_used = frames_needed;
|
||||
|
||||
return delay_output_buffer.data();
|
||||
}
|
||||
@@ -422,8 +396,7 @@ public:
|
||||
T * input_buffer(uint32_t frames_needed)
|
||||
{
|
||||
leftover_samples = delay_input_buffer.length();
|
||||
delay_input_buffer.reserve(leftover_samples +
|
||||
frames_to_samples(frames_needed));
|
||||
delay_input_buffer.reserve(leftover_samples + frames_to_samples(frames_needed));
|
||||
return delay_input_buffer.data() + leftover_samples;
|
||||
}
|
||||
/** This method works with `input_buffer`, and allows to inform the processor
|
||||
@@ -453,27 +426,21 @@ public:
|
||||
* @parameter frames_needed the number of frames one want to write into the
|
||||
* delay_line
|
||||
* @returns the number of frames one will get. */
|
||||
uint32_t input_needed_for_output(int32_t frames_needed) const
|
||||
size_t input_needed_for_output(uint32_t frames_needed)
|
||||
{
|
||||
assert(frames_needed >= 0); // Check overflow
|
||||
return frames_needed;
|
||||
}
|
||||
/** Returns the number of frames produces for `input_frames` frames in input
|
||||
*/
|
||||
size_t output_for_input(uint32_t input_frames) { return input_frames; }
|
||||
/** Returns the number of frames produces for `input_frames` frames in input */
|
||||
size_t output_for_input(uint32_t input_frames)
|
||||
{
|
||||
return input_frames;
|
||||
}
|
||||
/** The number of frames this delay line delays the stream by.
|
||||
* @returns The number of frames of delay. */
|
||||
size_t latency() { return length; }
|
||||
|
||||
void drop_audio_if_needed()
|
||||
size_t latency()
|
||||
{
|
||||
size_t available = samples_to_frames(delay_input_buffer.length());
|
||||
uint32_t to_keep = min_buffered_audio_frame(sample_rate);
|
||||
if (available > to_keep) {
|
||||
delay_input_buffer.pop(nullptr, frames_to_samples(available - to_keep));
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
private:
|
||||
/** The length, in frames, of this delay line */
|
||||
uint32_t length;
|
||||
@@ -485,17 +452,17 @@ private:
|
||||
/** The output buffer. This is only ever used if using the ::output with a
|
||||
* single argument. */
|
||||
auto_array<T> delay_output_buffer;
|
||||
uint32_t sample_rate;
|
||||
};
|
||||
|
||||
/** This sits behind the C API and is more typed. */
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
cubeb_resampler *
|
||||
cubeb_resampler_create_internal(cubeb_stream * stream,
|
||||
cubeb_stream_params * input_params,
|
||||
cubeb_stream_params * output_params,
|
||||
unsigned int target_rate,
|
||||
cubeb_data_callback callback, void * user_ptr,
|
||||
cubeb_data_callback callback,
|
||||
void * user_ptr,
|
||||
cubeb_resampler_quality quality)
|
||||
{
|
||||
std::unique_ptr<cubeb_resampler_speex_one_way<T>> input_resampler = nullptr;
|
||||
@@ -510,31 +477,31 @@ cubeb_resampler_create_internal(cubeb_stream * stream,
|
||||
sample rate, use a no-op resampler, that simply forwards the buffers to the
|
||||
callback. */
|
||||
if (((input_params && input_params->rate == target_rate) &&
|
||||
(output_params && output_params->rate == target_rate)) ||
|
||||
(output_params && output_params->rate == target_rate)) ||
|
||||
(input_params && !output_params && (input_params->rate == target_rate)) ||
|
||||
(output_params && !input_params &&
|
||||
(output_params->rate == target_rate))) {
|
||||
LOG("Input and output sample-rate match, target rate of %dHz", target_rate);
|
||||
return new passthrough_resampler<T>(
|
||||
stream, callback, user_ptr, input_params ? input_params->channels : 0,
|
||||
target_rate);
|
||||
(output_params && !input_params && (output_params->rate == target_rate))) {
|
||||
return new noop_resampler(stream, callback, user_ptr);
|
||||
}
|
||||
|
||||
/* Determine if we need to resampler one or both directions, and create the
|
||||
resamplers. */
|
||||
if (output_params && (output_params->rate != target_rate)) {
|
||||
output_resampler.reset(new cubeb_resampler_speex_one_way<T>(
|
||||
output_params->channels, target_rate, output_params->rate,
|
||||
to_speex_quality(quality)));
|
||||
output_resampler.reset(
|
||||
new cubeb_resampler_speex_one_way<T>(output_params->channels,
|
||||
target_rate,
|
||||
output_params->rate,
|
||||
to_speex_quality(quality)));
|
||||
if (!output_resampler) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (input_params && (input_params->rate != target_rate)) {
|
||||
input_resampler.reset(new cubeb_resampler_speex_one_way<T>(
|
||||
input_params->channels, input_params->rate, target_rate,
|
||||
to_speex_quality(quality)));
|
||||
input_resampler.reset(
|
||||
new cubeb_resampler_speex_one_way<T>(input_params->channels,
|
||||
input_params->rate,
|
||||
target_rate,
|
||||
to_speex_quality(quality)));
|
||||
if (!input_resampler) {
|
||||
return NULL;
|
||||
}
|
||||
@@ -545,42 +512,39 @@ cubeb_resampler_create_internal(cubeb_stream * stream,
|
||||
* other direction so that the streams are synchronized. */
|
||||
if (input_resampler && !output_resampler && input_params && output_params) {
|
||||
output_delay.reset(new delay_line<T>(input_resampler->latency(),
|
||||
output_params->channels,
|
||||
output_params->rate));
|
||||
output_params->channels));
|
||||
if (!output_delay) {
|
||||
return NULL;
|
||||
}
|
||||
} else if (output_resampler && !input_resampler && input_params &&
|
||||
output_params) {
|
||||
} else if (output_resampler && !input_resampler && input_params && output_params) {
|
||||
input_delay.reset(new delay_line<T>(output_resampler->latency(),
|
||||
input_params->channels,
|
||||
output_params->rate));
|
||||
input_params->channels));
|
||||
if (!input_delay) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (input_resampler && output_resampler) {
|
||||
LOG("Resampling input (%d) and output (%d) to target rate of %dHz",
|
||||
input_params->rate, output_params->rate, target_rate);
|
||||
return new cubeb_resampler_speex<T, cubeb_resampler_speex_one_way<T>,
|
||||
cubeb_resampler_speex_one_way<T>>(
|
||||
input_resampler.release(), output_resampler.release(), stream, callback,
|
||||
user_ptr);
|
||||
return new cubeb_resampler_speex<T,
|
||||
cubeb_resampler_speex_one_way<T>,
|
||||
cubeb_resampler_speex_one_way<T>>
|
||||
(input_resampler.release(),
|
||||
output_resampler.release(),
|
||||
stream, callback, user_ptr);
|
||||
} else if (input_resampler) {
|
||||
LOG("Resampling input (%d) to target and output rate of %dHz",
|
||||
input_params->rate, target_rate);
|
||||
return new cubeb_resampler_speex<T, cubeb_resampler_speex_one_way<T>,
|
||||
delay_line<T>>(input_resampler.release(),
|
||||
output_delay.release(),
|
||||
stream, callback, user_ptr);
|
||||
return new cubeb_resampler_speex<T,
|
||||
cubeb_resampler_speex_one_way<T>,
|
||||
delay_line<T>>
|
||||
(input_resampler.release(),
|
||||
output_delay.release(),
|
||||
stream, callback, user_ptr);
|
||||
} else {
|
||||
LOG("Resampling output (%dHz) to target and input rate of %dHz",
|
||||
output_params->rate, target_rate);
|
||||
return new cubeb_resampler_speex<T, delay_line<T>,
|
||||
cubeb_resampler_speex_one_way<T>>(
|
||||
input_delay.release(), output_resampler.release(), stream, callback,
|
||||
user_ptr);
|
||||
return new cubeb_resampler_speex<T,
|
||||
delay_line<T>,
|
||||
cubeb_resampler_speex_one_way<T>>
|
||||
(input_delay.release(),
|
||||
output_resampler.release(),
|
||||
stream, callback, user_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,16 +16,17 @@
|
||||
them in the correct order. */
|
||||
|
||||
typedef struct {
|
||||
AudioBuffer * buffer_array; /**< Array that hold pointers of the allocated
|
||||
space for the buffers. */
|
||||
unsigned int tail; /**< Index of the last element (first to deliver). */
|
||||
unsigned int count; /**< Number of elements in the array. */
|
||||
unsigned int capacity; /**< Total length of the array. */
|
||||
AudioBuffer * buffer_array; /**< Array that hold pointers of the allocated space for the buffers. */
|
||||
unsigned int tail; /**< Index of the last element (first to deliver). */
|
||||
unsigned int count; /**< Number of elements in the array. */
|
||||
unsigned int capacity; /**< Total length of the array. */
|
||||
} ring_array;
|
||||
|
||||
static int
|
||||
single_audiobuffer_init(AudioBuffer * buffer, uint32_t bytesPerFrame,
|
||||
uint32_t channelsPerFrame, uint32_t frames)
|
||||
single_audiobuffer_init(AudioBuffer * buffer,
|
||||
uint32_t bytesPerFrame,
|
||||
uint32_t channelsPerFrame,
|
||||
uint32_t frames)
|
||||
{
|
||||
assert(buffer);
|
||||
assert(bytesPerFrame > 0 && channelsPerFrame && frames > 0);
|
||||
@@ -35,7 +36,7 @@ single_audiobuffer_init(AudioBuffer * buffer, uint32_t bytesPerFrame,
|
||||
if (buffer->mData == NULL) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
PodZero(static_cast<char *>(buffer->mData), size);
|
||||
PodZero(static_cast<char*>(buffer->mData), size);
|
||||
|
||||
buffer->mNumberChannels = channelsPerFrame;
|
||||
buffer->mDataByteSize = size;
|
||||
@@ -47,12 +48,15 @@ single_audiobuffer_init(AudioBuffer * buffer, uint32_t bytesPerFrame,
|
||||
@param ra The ring_array pointer of allocated structure.
|
||||
@retval 0 on success. */
|
||||
int
|
||||
ring_array_init(ring_array * ra, uint32_t capacity, uint32_t bytesPerFrame,
|
||||
uint32_t channelsPerFrame, uint32_t framesPerBuffer)
|
||||
ring_array_init(ring_array * ra,
|
||||
uint32_t capacity,
|
||||
uint32_t bytesPerFrame,
|
||||
uint32_t channelsPerFrame,
|
||||
uint32_t framesPerBuffer)
|
||||
{
|
||||
assert(ra);
|
||||
if (capacity == 0 || bytesPerFrame == 0 || channelsPerFrame == 0 ||
|
||||
framesPerBuffer == 0) {
|
||||
if (capacity == 0 || bytesPerFrame == 0 ||
|
||||
channelsPerFrame == 0 || framesPerBuffer == 0) {
|
||||
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
ra->capacity = capacity;
|
||||
@@ -66,7 +70,8 @@ ring_array_init(ring_array * ra, uint32_t capacity, uint32_t bytesPerFrame,
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < ra->capacity; ++i) {
|
||||
if (single_audiobuffer_init(&ra->buffer_array[i], bytesPerFrame,
|
||||
if (single_audiobuffer_init(&ra->buffer_array[i],
|
||||
bytesPerFrame,
|
||||
channelsPerFrame,
|
||||
framesPerBuffer) != CUBEB_OK) {
|
||||
return CUBEB_ERROR;
|
||||
@@ -82,7 +87,7 @@ void
|
||||
ring_array_destroy(ring_array * ra)
|
||||
{
|
||||
assert(ra);
|
||||
if (ra->buffer_array == NULL) {
|
||||
if (ra->buffer_array == NULL){
|
||||
return;
|
||||
}
|
||||
for (unsigned int i = 0; i < ra->capacity; ++i) {
|
||||
@@ -90,13 +95,12 @@ ring_array_destroy(ring_array * ra)
|
||||
operator delete(ra->buffer_array[i].mData);
|
||||
}
|
||||
}
|
||||
delete[] ra->buffer_array;
|
||||
delete [] ra->buffer_array;
|
||||
}
|
||||
|
||||
/** Get the allocated buffer to be stored with fresh data.
|
||||
@param ra The ring_array pointer.
|
||||
@retval Pointer of the allocated space to be stored with fresh data or NULL
|
||||
if full. */
|
||||
@retval Pointer of the allocated space to be stored with fresh data or NULL if full. */
|
||||
AudioBuffer *
|
||||
ring_array_get_free_buffer(ring_array * ra)
|
||||
{
|
||||
@@ -152,4 +156,4 @@ ring_array_get_dummy_buffer(ring_array * ra)
|
||||
return &ra->buffer_array[0];
|
||||
}
|
||||
|
||||
#endif // CUBEB_RING_ARRAY_H
|
||||
#endif //CUBEB_RING_ARRAY_H
|
||||
|
||||
@@ -1,468 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2016 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
#ifndef CUBEB_RING_BUFFER_H
|
||||
#define CUBEB_RING_BUFFER_H
|
||||
|
||||
#include "cubeb_utils.h"
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
/**
|
||||
* Single producer single consumer lock-free and wait-free ring buffer.
|
||||
*
|
||||
* This data structure allows producing data from one thread, and consuming it
|
||||
* on another thread, safely and without explicit synchronization. If used on
|
||||
* two threads, this data structure uses atomics for thread safety. It is
|
||||
* possible to disable the use of atomics at compile time and only use this data
|
||||
* structure on one thread.
|
||||
*
|
||||
* The role for the producer and the consumer must be constant, i.e., the
|
||||
* producer should always be on one thread and the consumer should always be on
|
||||
* another thread.
|
||||
*
|
||||
* Some words about the inner workings of this class:
|
||||
* - Capacity is fixed. Only one allocation is performed, in the constructor.
|
||||
* When reading and writing, the return value of the method allows checking if
|
||||
* the ring buffer is empty or full.
|
||||
* - We always keep the read index at least one element ahead of the write
|
||||
* index, so we can distinguish between an empty and a full ring buffer: an
|
||||
* empty ring buffer is when the write index is at the same position as the
|
||||
* read index. A full buffer is when the write index is exactly one position
|
||||
* before the read index.
|
||||
* - We synchronize updates to the read index after having read the data, and
|
||||
* the write index after having written the data. This means that the each
|
||||
* thread can only touch a portion of the buffer that is not touched by the
|
||||
* other thread.
|
||||
* - Callers are expected to provide buffers. When writing to the queue,
|
||||
* elements are copied into the internal storage from the buffer passed in.
|
||||
* When reading from the queue, the user is expected to provide a buffer.
|
||||
* Because this is a ring buffer, data might not be contiguous in memory,
|
||||
* providing an external buffer to copy into is an easy way to have linear
|
||||
* data for further processing.
|
||||
*/
|
||||
template <typename T> class ring_buffer_base {
|
||||
public:
|
||||
/**
|
||||
* Constructor for a ring buffer.
|
||||
*
|
||||
* This performs an allocation, but is the only allocation that will happen
|
||||
* for the life time of a `ring_buffer_base`.
|
||||
*
|
||||
* @param capacity The maximum number of element this ring buffer will hold.
|
||||
*/
|
||||
ring_buffer_base(int capacity)
|
||||
/* One more element to distinguish from empty and full buffer. */
|
||||
: capacity_(capacity + 1)
|
||||
{
|
||||
assert(storage_capacity() < std::numeric_limits<int>::max() / 2 &&
|
||||
"buffer too large for the type of index used.");
|
||||
assert(capacity_ > 0);
|
||||
|
||||
data_.reset(new T[storage_capacity()]);
|
||||
/* If this queue is using atomics, initializing those members as the last
|
||||
* action in the constructor acts as a full barrier, and allow capacity() to
|
||||
* be thread-safe. */
|
||||
write_index_ = 0;
|
||||
read_index_ = 0;
|
||||
}
|
||||
/**
|
||||
* Push `count` zero or default constructed elements in the array.
|
||||
*
|
||||
* Only safely called on the producer thread.
|
||||
*
|
||||
* @param count The number of elements to enqueue.
|
||||
* @return The number of element enqueued.
|
||||
*/
|
||||
int enqueue_default(int count) { return enqueue(nullptr, count); }
|
||||
/**
|
||||
* @brief Put an element in the queue
|
||||
*
|
||||
* Only safely called on the producer thread.
|
||||
*
|
||||
* @param element The element to put in the queue.
|
||||
*
|
||||
* @return 1 if the element was inserted, 0 otherwise.
|
||||
*/
|
||||
int enqueue(T & element) { return enqueue(&element, 1); }
|
||||
/**
|
||||
* Push `count` elements in the ring buffer.
|
||||
*
|
||||
* Only safely called on the producer thread.
|
||||
*
|
||||
* @param elements a pointer to a buffer containing at least `count` elements.
|
||||
* If `elements` is nullptr, zero or default constructed elements are
|
||||
* enqueued.
|
||||
* @param count The number of elements to read from `elements`
|
||||
* @return The number of elements successfully coped from `elements` and
|
||||
* inserted into the ring buffer.
|
||||
*/
|
||||
int enqueue(T * elements, int count)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
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);
|
||||
|
||||
if (full_internal(rd_idx, wr_idx)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int to_write = std::min(available_write_internal(rd_idx, wr_idx), count);
|
||||
|
||||
/* First part, from the write index to the end of the array. */
|
||||
int first_part = std::min(storage_capacity() - wr_idx, to_write);
|
||||
/* Second part, from the beginning of the array */
|
||||
int second_part = to_write - first_part;
|
||||
|
||||
if (elements) {
|
||||
Copy(data_.get() + wr_idx, elements, first_part);
|
||||
Copy(data_.get(), elements + first_part, second_part);
|
||||
} else {
|
||||
ConstructDefault(data_.get() + wr_idx, first_part);
|
||||
ConstructDefault(data_.get(), second_part);
|
||||
}
|
||||
|
||||
write_index_.store(increment_index(wr_idx, to_write),
|
||||
std::memory_order_release);
|
||||
|
||||
return to_write;
|
||||
}
|
||||
/**
|
||||
* Retrieve at most `count` elements from the ring buffer, and copy them to
|
||||
* `elements`, if non-null.
|
||||
*
|
||||
* Only safely called on the consumer side.
|
||||
*
|
||||
* @param elements A pointer to a buffer with space for at least `count`
|
||||
* elements. If `elements` is `nullptr`, `count` element will be discarded.
|
||||
* @param count The maximum number of elements to dequeue.
|
||||
* @return The number of elements written to `elements`.
|
||||
*/
|
||||
int dequeue(T * elements, int count)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
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);
|
||||
|
||||
if (empty_internal(rd_idx, wr_idx)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int to_read = std::min(available_read_internal(rd_idx, wr_idx), count);
|
||||
|
||||
int first_part = std::min(storage_capacity() - rd_idx, to_read);
|
||||
int second_part = to_read - first_part;
|
||||
|
||||
if (elements) {
|
||||
Copy(elements, data_.get() + rd_idx, first_part);
|
||||
Copy(elements + first_part, data_.get(), second_part);
|
||||
}
|
||||
|
||||
read_index_.store(increment_index(rd_idx, to_read),
|
||||
std::memory_order_relaxed);
|
||||
|
||||
return to_read;
|
||||
}
|
||||
/**
|
||||
* Get the number of available element for consuming.
|
||||
*
|
||||
* Only safely called on the consumer thread.
|
||||
*
|
||||
* @return The number of available elements for reading.
|
||||
*/
|
||||
int available_read() const
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
assert_correct_thread(consumer_id);
|
||||
#endif
|
||||
return available_read_internal(
|
||||
read_index_.load(std::memory_order_relaxed),
|
||||
write_index_.load(std::memory_order_relaxed));
|
||||
}
|
||||
/**
|
||||
* Get the number of available elements for consuming.
|
||||
*
|
||||
* Only safely called on the producer thread.
|
||||
*
|
||||
* @return The number of empty slots in the buffer, available for writing.
|
||||
*/
|
||||
int available_write() const
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
assert_correct_thread(producer_id);
|
||||
#endif
|
||||
return available_write_internal(
|
||||
read_index_.load(std::memory_order_relaxed),
|
||||
write_index_.load(std::memory_order_relaxed));
|
||||
}
|
||||
/**
|
||||
* Get the total capacity, for this ring buffer.
|
||||
*
|
||||
* Can be called safely on any thread.
|
||||
*
|
||||
* @return The maximum capacity of this ring buffer.
|
||||
*/
|
||||
int capacity() const { return storage_capacity() - 1; }
|
||||
/**
|
||||
* Reset the consumer and producer thread identifier, in case the thread are
|
||||
* being changed. This has to be externally synchronized. This is no-op when
|
||||
* asserts are disabled.
|
||||
*/
|
||||
void reset_thread_ids()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
consumer_id = producer_id = std::thread::id();
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
/** Return true if the ring buffer is empty.
|
||||
*
|
||||
* @param read_index the read index to consider
|
||||
* @param write_index the write index to consider
|
||||
* @return true if the ring buffer is empty, false otherwise.
|
||||
**/
|
||||
bool empty_internal(int read_index, int write_index) const
|
||||
{
|
||||
return write_index == read_index;
|
||||
}
|
||||
/** Return true if the ring buffer is full.
|
||||
*
|
||||
* This happens if the write index is exactly one element behind the read
|
||||
* index.
|
||||
*
|
||||
* @param read_index the read index to consider
|
||||
* @param write_index the write index to consider
|
||||
* @return true if the ring buffer is full, false otherwise.
|
||||
**/
|
||||
bool full_internal(int read_index, int write_index) const
|
||||
{
|
||||
return (write_index + 1) % storage_capacity() == read_index;
|
||||
}
|
||||
/**
|
||||
* Return the size of the storage. It is one more than the number of elements
|
||||
* that can be stored in the buffer.
|
||||
*
|
||||
* @return the number of elements that can be stored in the buffer.
|
||||
*/
|
||||
int storage_capacity() const { return capacity_; }
|
||||
/**
|
||||
* Returns the number of elements available for reading.
|
||||
*
|
||||
* @return the number of available elements for reading.
|
||||
*/
|
||||
int available_read_internal(int read_index, int write_index) const
|
||||
{
|
||||
if (write_index >= read_index) {
|
||||
return write_index - read_index;
|
||||
} else {
|
||||
return write_index + storage_capacity() - read_index;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns the number of empty elements, available for writing.
|
||||
*
|
||||
* @return the number of elements that can be written into the array.
|
||||
*/
|
||||
int available_write_internal(int read_index, int write_index) const
|
||||
{
|
||||
/* We substract one element here to always keep at least one sample
|
||||
* free in the buffer, to distinguish between full and empty array. */
|
||||
int rv = read_index - write_index - 1;
|
||||
if (write_index >= read_index) {
|
||||
rv += storage_capacity();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
/**
|
||||
* Increments an index, wrapping it around the storage.
|
||||
*
|
||||
* @param index a reference to the index to increment.
|
||||
* @param increment the number by which `index` is incremented.
|
||||
* @return the new index.
|
||||
*/
|
||||
int increment_index(int index, int increment) const
|
||||
{
|
||||
assert(increment >= 0);
|
||||
return (index + increment) % storage_capacity();
|
||||
}
|
||||
/**
|
||||
* @brief This allows checking that enqueue (resp. dequeue) are always called
|
||||
* by the right thread.
|
||||
*
|
||||
* @param id the id of the thread that has called the calling method first.
|
||||
*/
|
||||
#ifndef NDEBUG
|
||||
static void assert_correct_thread(std::thread::id & id)
|
||||
{
|
||||
if (id == std::thread::id()) {
|
||||
id = std::this_thread::get_id();
|
||||
return;
|
||||
}
|
||||
assert(id == std::this_thread::get_id());
|
||||
}
|
||||
#endif
|
||||
/** Index at which the oldest element is at, in samples. */
|
||||
std::atomic<int> read_index_;
|
||||
/** Index at which to write new elements. `write_index` is always at
|
||||
* least one element ahead of `read_index_`. */
|
||||
std::atomic<int> write_index_;
|
||||
/** Maximum number of elements that can be stored in the ring buffer. */
|
||||
const int capacity_;
|
||||
/** Data storage */
|
||||
std::unique_ptr<T[]> data_;
|
||||
#ifndef NDEBUG
|
||||
/** The id of the only thread that is allowed to read from the queue. */
|
||||
mutable std::thread::id consumer_id;
|
||||
/** The id of the only thread that is allowed to write from the queue. */
|
||||
mutable std::thread::id producer_id;
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* Adapter for `ring_buffer_base` that exposes an interface in frames.
|
||||
*/
|
||||
template <typename T> class audio_ring_buffer_base {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor.
|
||||
*
|
||||
* @param channel_count Number of channels.
|
||||
* @param capacity_in_frames The capacity in frames.
|
||||
*/
|
||||
audio_ring_buffer_base(int channel_count, int capacity_in_frames)
|
||||
: channel_count(channel_count),
|
||||
ring_buffer(frames_to_samples(capacity_in_frames))
|
||||
{
|
||||
assert(channel_count > 0);
|
||||
}
|
||||
/**
|
||||
* @brief Enqueue silence.
|
||||
*
|
||||
* Only safely called on the producer thread.
|
||||
*
|
||||
* @param frame_count The number of frames of silence to enqueue.
|
||||
* @return The number of frames of silence actually written to the queue.
|
||||
*/
|
||||
int enqueue_default(int frame_count)
|
||||
{
|
||||
return samples_to_frames(
|
||||
ring_buffer.enqueue(nullptr, frames_to_samples(frame_count)));
|
||||
}
|
||||
/**
|
||||
* @brief Enqueue `frames_count` frames of audio.
|
||||
*
|
||||
* Only safely called from the producer thread.
|
||||
*
|
||||
* @param [in] frames If non-null, the frames to enqueue.
|
||||
* Otherwise, silent frames are enqueued.
|
||||
* @param frame_count The number of frames to enqueue.
|
||||
*
|
||||
* @return The number of frames enqueued
|
||||
*/
|
||||
|
||||
int enqueue(T * frames, int frame_count)
|
||||
{
|
||||
return samples_to_frames(
|
||||
ring_buffer.enqueue(frames, frames_to_samples(frame_count)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes `frame_count` frames from the buffer, and
|
||||
* write them to `frames` if it is non-null.
|
||||
*
|
||||
* Only safely called on the consumer thread.
|
||||
*
|
||||
* @param frames If non-null, the frames are copied to `frames`.
|
||||
* Otherwise, they are dropped.
|
||||
* @param frame_count The number of frames to remove.
|
||||
*
|
||||
* @return The number of frames actually dequeud.
|
||||
*/
|
||||
int dequeue(T * frames, int frame_count)
|
||||
{
|
||||
return samples_to_frames(
|
||||
ring_buffer.dequeue(frames, frames_to_samples(frame_count)));
|
||||
}
|
||||
/**
|
||||
* Get the number of available frames of audio for consuming.
|
||||
*
|
||||
* Only safely called on the consumer thread.
|
||||
*
|
||||
* @return The number of available frames of audio for reading.
|
||||
*/
|
||||
int available_read() const
|
||||
{
|
||||
return samples_to_frames(ring_buffer.available_read());
|
||||
}
|
||||
/**
|
||||
* Get the number of available frames of audio for consuming.
|
||||
*
|
||||
* Only safely called on the producer thread.
|
||||
*
|
||||
* @return The number of empty slots in the buffer, available for writing.
|
||||
*/
|
||||
int available_write() const
|
||||
{
|
||||
return samples_to_frames(ring_buffer.available_write());
|
||||
}
|
||||
/**
|
||||
* Get the total capacity, for this ring buffer.
|
||||
*
|
||||
* Can be called safely on any thread.
|
||||
*
|
||||
* @return The maximum capacity of this ring buffer.
|
||||
*/
|
||||
int capacity() const { return samples_to_frames(ring_buffer.capacity()); }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Frames to samples conversion.
|
||||
*
|
||||
* @param frames The number of frames.
|
||||
*
|
||||
* @return A number of samples.
|
||||
*/
|
||||
int frames_to_samples(int frames) const { return frames * channel_count; }
|
||||
/**
|
||||
* @brief Samples to frames conversion.
|
||||
*
|
||||
* @param samples The number of samples.
|
||||
*
|
||||
* @return A number of frames.
|
||||
*/
|
||||
int samples_to_frames(int samples) const { return samples / channel_count; }
|
||||
/** Number of channels of audio that will stream through this ring buffer. */
|
||||
int channel_count;
|
||||
/** The underlying ring buffer that is used to store the data. */
|
||||
ring_buffer_base<T> ring_buffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Lock-free instantiation of the `ring_buffer_base` type. This is safe to use
|
||||
* from two threads, one producer, one consumer (that never change role),
|
||||
* without explicit synchronization.
|
||||
*/
|
||||
template <typename T> using lock_free_queue = ring_buffer_base<T>;
|
||||
/**
|
||||
* Lock-free instantiation of the `audio_ring_buffer` type. This is safe to use
|
||||
* from two threads, one producer, one consumer (that never change role),
|
||||
* without explicit synchronization.
|
||||
*/
|
||||
template <typename T>
|
||||
using lock_free_audio_ring_buffer = audio_ring_buffer_base<T>;
|
||||
|
||||
#endif // CUBEB_RING_BUFFER_H
|
||||
+135
-421
@@ -4,109 +4,56 @@
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
#include "cubeb-internal.h"
|
||||
#include "cubeb/cubeb.h"
|
||||
#include <assert.h>
|
||||
#include <dlfcn.h>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <sndio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include "cubeb/cubeb.h"
|
||||
#include "cubeb-internal.h"
|
||||
|
||||
#if defined(CUBEB_SNDIO_DEBUG)
|
||||
#define DPR(...) fprintf(stderr, __VA_ARGS__);
|
||||
#else
|
||||
#define DPR(...) \
|
||||
do { \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#ifdef DISABLE_LIBSNDIO_DLOPEN
|
||||
#define WRAP(x) x
|
||||
#else
|
||||
#define WRAP(x) cubeb_##x
|
||||
#define LIBSNDIO_API_VISIT(X) \
|
||||
X(sio_close) \
|
||||
X(sio_eof) \
|
||||
X(sio_getpar) \
|
||||
X(sio_initpar) \
|
||||
X(sio_nfds) \
|
||||
X(sio_onmove) \
|
||||
X(sio_open) \
|
||||
X(sio_pollfd) \
|
||||
X(sio_read) \
|
||||
X(sio_revents) \
|
||||
X(sio_setpar) \
|
||||
X(sio_start) \
|
||||
X(sio_stop) \
|
||||
X(sio_write)
|
||||
|
||||
#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
|
||||
LIBSNDIO_API_VISIT(MAKE_TYPEDEF);
|
||||
#undef MAKE_TYPEDEF
|
||||
#define DPR(...) do {} while(0)
|
||||
#endif
|
||||
|
||||
static struct cubeb_ops const sndio_ops;
|
||||
|
||||
struct cubeb {
|
||||
struct cubeb_ops const * ops;
|
||||
void * libsndio;
|
||||
};
|
||||
|
||||
struct cubeb_stream {
|
||||
/* Note: Must match cubeb_stream layout in cubeb.c. */
|
||||
cubeb * context;
|
||||
void * arg; /* user arg to {data,state}_cb */
|
||||
/**/
|
||||
pthread_t th; /* to run real-time audio i/o */
|
||||
pthread_mutex_t mtx; /* protects hdl and pos */
|
||||
struct sio_hdl * hdl; /* link us to sndio */
|
||||
int mode; /* bitmap of SIO_{PLAY,REC} */
|
||||
int active; /* cubec_start() called */
|
||||
int conv; /* need float->s16 conversion */
|
||||
unsigned char * rbuf; /* rec data consumed from here */
|
||||
unsigned char * pbuf; /* play data is prepared here */
|
||||
unsigned int nfr; /* number of frames in ibuf and obuf */
|
||||
unsigned int rbpf; /* rec bytes per frame */
|
||||
unsigned int pbpf; /* play bytes per frame */
|
||||
unsigned int rchan; /* number of rec channels */
|
||||
unsigned int pchan; /* number of play channels */
|
||||
unsigned int nblks; /* number of blocks in the buffer */
|
||||
uint64_t hwpos; /* frame number Joe hears right now */
|
||||
uint64_t swpos; /* number of frames produced/consumed */
|
||||
cubeb_data_callback data_cb; /* cb to preapare data */
|
||||
cubeb_state_callback state_cb; /* cb to notify about state changes */
|
||||
float volume; /* current volume */
|
||||
pthread_t th; /* to run real-time audio i/o */
|
||||
pthread_mutex_t mtx; /* protects hdl and pos */
|
||||
struct sio_hdl *hdl; /* link us to sndio */
|
||||
int active; /* cubec_start() called */
|
||||
int conv; /* need float->s16 conversion */
|
||||
unsigned char *buf; /* data is prepared here */
|
||||
unsigned int nfr; /* number of frames in buf */
|
||||
unsigned int bpf; /* bytes per frame */
|
||||
unsigned int pchan; /* number of play channels */
|
||||
uint64_t rdpos; /* frame number Joe hears right now */
|
||||
uint64_t wrpos; /* number of written frames */
|
||||
cubeb_data_callback data_cb; /* cb to preapare data */
|
||||
cubeb_state_callback state_cb; /* cb to notify about state changes */
|
||||
void *arg; /* user arg to {data,state}_cb */
|
||||
};
|
||||
|
||||
static void
|
||||
s16_setvol(void * ptr, long nsamp, float volume)
|
||||
float_to_s16(void *ptr, long nsamp)
|
||||
{
|
||||
int16_t * dst = ptr;
|
||||
int32_t mult = volume * 32768;
|
||||
int32_t s;
|
||||
|
||||
while (nsamp-- > 0) {
|
||||
s = *dst;
|
||||
s = (s * mult) >> 15;
|
||||
*(dst++) = s;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
float_to_s16(void * ptr, long nsamp, float volume)
|
||||
{
|
||||
int16_t * dst = ptr;
|
||||
float * src = ptr;
|
||||
float mult = volume * 32768;
|
||||
int16_t *dst = ptr;
|
||||
float *src = ptr;
|
||||
int s;
|
||||
|
||||
while (nsamp-- > 0) {
|
||||
s = lrintf(*(src++) * mult);
|
||||
s = lrintf(*(src++) * 32768);
|
||||
if (s < -32768)
|
||||
s = -32768;
|
||||
else if (s > 32767)
|
||||
@@ -116,146 +63,61 @@ float_to_s16(void * ptr, long nsamp, float volume)
|
||||
}
|
||||
|
||||
static void
|
||||
s16_to_float(void * ptr, long nsamp)
|
||||
sndio_onmove(void *arg, int delta)
|
||||
{
|
||||
int16_t * src = ptr;
|
||||
float * dst = ptr;
|
||||
cubeb_stream *s = (cubeb_stream *)arg;
|
||||
|
||||
src += nsamp;
|
||||
dst += nsamp;
|
||||
while (nsamp-- > 0)
|
||||
*(--dst) = (1. / 32768) * *(--src);
|
||||
}
|
||||
|
||||
static const char *
|
||||
sndio_get_device()
|
||||
{
|
||||
#ifdef __linux__
|
||||
/*
|
||||
* On other platforms default to sndio devices,
|
||||
* so cubebs other backends can be used instead.
|
||||
*/
|
||||
const char * dev = getenv("AUDIODEVICE");
|
||||
if (dev == NULL || *dev == '\0')
|
||||
return "snd/0";
|
||||
return dev;
|
||||
#else
|
||||
return SIO_DEVANY;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
sndio_onmove(void * arg, int delta)
|
||||
{
|
||||
cubeb_stream * s = (cubeb_stream *)arg;
|
||||
|
||||
s->hwpos += delta;
|
||||
s->rdpos += delta * s->bpf;
|
||||
}
|
||||
|
||||
static void *
|
||||
sndio_mainloop(void * arg)
|
||||
sndio_mainloop(void *arg)
|
||||
{
|
||||
struct pollfd * pfds;
|
||||
cubeb_stream * s = arg;
|
||||
int n, eof = 0, prime, nfds, events, revents, state = CUBEB_STATE_STARTED;
|
||||
size_t pstart = 0, pend = 0, rstart = 0, rend = 0;
|
||||
#define MAXFDS 8
|
||||
struct pollfd pfds[MAXFDS];
|
||||
cubeb_stream *s = arg;
|
||||
int n, nfds, revents, state = CUBEB_STATE_STARTED;
|
||||
size_t start = 0, end = 0;
|
||||
long nfr;
|
||||
|
||||
nfds = WRAP(sio_nfds)(s->hdl);
|
||||
pfds = calloc(nfds, sizeof(struct pollfd));
|
||||
if (pfds == NULL)
|
||||
return NULL;
|
||||
|
||||
DPR("sndio_mainloop()\n");
|
||||
s->state_cb(s, s->arg, CUBEB_STATE_STARTED);
|
||||
pthread_mutex_lock(&s->mtx);
|
||||
if (!WRAP(sio_start)(s->hdl)) {
|
||||
if (!sio_start(s->hdl)) {
|
||||
pthread_mutex_unlock(&s->mtx);
|
||||
free(pfds);
|
||||
return NULL;
|
||||
}
|
||||
DPR("sndio_mainloop(), started\n");
|
||||
|
||||
if (s->mode & SIO_PLAY) {
|
||||
pstart = pend = s->nfr * s->pbpf;
|
||||
prime = s->nblks;
|
||||
if (s->mode & SIO_REC) {
|
||||
memset(s->rbuf, 0, s->nfr * s->rbpf);
|
||||
rstart = rend = s->nfr * s->rbpf;
|
||||
}
|
||||
} else {
|
||||
prime = 0;
|
||||
rstart = 0;
|
||||
rend = s->nfr * s->rbpf;
|
||||
}
|
||||
|
||||
start = end = s->nfr;
|
||||
for (;;) {
|
||||
if (!s->active) {
|
||||
DPR("sndio_mainloop() stopped\n");
|
||||
state = CUBEB_STATE_STOPPED;
|
||||
break;
|
||||
}
|
||||
|
||||
/* do we have a complete block? */
|
||||
if ((!(s->mode & SIO_PLAY) || pstart == pend) &&
|
||||
(!(s->mode & SIO_REC) || rstart == rend)) {
|
||||
|
||||
if (eof) {
|
||||
if (start == end) {
|
||||
if (end < s->nfr) {
|
||||
DPR("sndio_mainloop() drained\n");
|
||||
state = CUBEB_STATE_DRAINED;
|
||||
break;
|
||||
}
|
||||
|
||||
if ((s->mode & SIO_REC) && s->conv)
|
||||
s16_to_float(s->rbuf, s->nfr * s->rchan);
|
||||
|
||||
/* invoke call-back, it returns less that s->nfr if done */
|
||||
pthread_mutex_unlock(&s->mtx);
|
||||
nfr = s->data_cb(s, s->arg, s->rbuf, s->pbuf, s->nfr);
|
||||
nfr = s->data_cb(s, s->arg, NULL, s->buf, s->nfr);
|
||||
pthread_mutex_lock(&s->mtx);
|
||||
if (nfr < 0) {
|
||||
DPR("sndio_mainloop() cb err\n");
|
||||
state = CUBEB_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
s->swpos += nfr;
|
||||
|
||||
/* was this last call-back invocation (aka end-of-stream) ? */
|
||||
if (nfr < s->nfr) {
|
||||
|
||||
if (!(s->mode & SIO_PLAY) || nfr == 0) {
|
||||
state = CUBEB_STATE_DRAINED;
|
||||
break;
|
||||
}
|
||||
|
||||
/* need to write (aka drain) the partial play block we got */
|
||||
pend = nfr * s->pbpf;
|
||||
eof = 1;
|
||||
}
|
||||
|
||||
if (prime > 0)
|
||||
prime--;
|
||||
|
||||
if (s->mode & SIO_PLAY) {
|
||||
if (s->conv)
|
||||
float_to_s16(s->pbuf, nfr * s->pchan, s->volume);
|
||||
else
|
||||
s16_setvol(s->pbuf, nfr * s->pchan, s->volume);
|
||||
}
|
||||
|
||||
if (s->mode & SIO_REC)
|
||||
rstart = 0;
|
||||
if (s->mode & SIO_PLAY)
|
||||
pstart = 0;
|
||||
if (s->conv)
|
||||
float_to_s16(s->buf, nfr * s->pchan);
|
||||
start = 0;
|
||||
end = nfr * s->bpf;
|
||||
}
|
||||
|
||||
events = 0;
|
||||
if ((s->mode & SIO_REC) && rstart < rend && prime == 0)
|
||||
events |= POLLIN;
|
||||
if ((s->mode & SIO_PLAY) && pstart < pend)
|
||||
events |= POLLOUT;
|
||||
nfds = WRAP(sio_pollfd)(s->hdl, pfds, events);
|
||||
|
||||
if (end == 0)
|
||||
continue;
|
||||
nfds = sio_pollfd(s->hdl, pfds, POLLOUT);
|
||||
if (nfds > 0) {
|
||||
pthread_mutex_unlock(&s->mtx);
|
||||
n = poll(pfds, nfds, -1);
|
||||
@@ -263,165 +125,88 @@ sndio_mainloop(void * arg)
|
||||
if (n < 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
revents = WRAP(sio_revents)(s->hdl, pfds);
|
||||
|
||||
if (revents & POLLHUP) {
|
||||
state = CUBEB_STATE_ERROR;
|
||||
revents = sio_revents(s->hdl, pfds);
|
||||
if (revents & POLLHUP)
|
||||
break;
|
||||
}
|
||||
|
||||
if (revents & POLLOUT) {
|
||||
n = WRAP(sio_write)(s->hdl, s->pbuf + pstart, pend - pstart);
|
||||
if (n == 0 && WRAP(sio_eof)(s->hdl)) {
|
||||
n = sio_write(s->hdl, s->buf + start, end - start);
|
||||
if (n == 0) {
|
||||
DPR("sndio_mainloop() werr\n");
|
||||
state = CUBEB_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
pstart += n;
|
||||
s->wrpos += n;
|
||||
start += n;
|
||||
}
|
||||
|
||||
if (revents & POLLIN) {
|
||||
n = WRAP(sio_read)(s->hdl, s->rbuf + rstart, rend - rstart);
|
||||
if (n == 0 && WRAP(sio_eof)(s->hdl)) {
|
||||
DPR("sndio_mainloop() rerr\n");
|
||||
state = CUBEB_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
rstart += n;
|
||||
}
|
||||
|
||||
/* skip rec block, if not recording (yet) */
|
||||
if (prime > 0 && (s->mode & SIO_REC))
|
||||
rstart = rend;
|
||||
}
|
||||
WRAP(sio_stop)(s->hdl);
|
||||
s->hwpos = s->swpos;
|
||||
sio_stop(s->hdl);
|
||||
s->rdpos = s->wrpos;
|
||||
pthread_mutex_unlock(&s->mtx);
|
||||
s->state_cb(s, s->arg, state);
|
||||
free(pfds);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*static*/ int
|
||||
sndio_init(cubeb ** context, char const * context_name)
|
||||
sndio_init(cubeb **context, char const *context_name)
|
||||
{
|
||||
void * libsndio = NULL;
|
||||
struct sio_hdl * hdl;
|
||||
|
||||
assert(context);
|
||||
|
||||
#ifndef DISABLE_LIBSNDIO_DLOPEN
|
||||
libsndio = dlopen("libsndio.so.7.0", RTLD_LAZY);
|
||||
if (!libsndio) {
|
||||
libsndio = dlopen("libsndio.so", RTLD_LAZY);
|
||||
if (!libsndio) {
|
||||
DPR("sndio_init(%s) failed dlopen(libsndio.so)\n", context_name);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
#define LOAD(x) \
|
||||
{ \
|
||||
cubeb_##x = dlsym(libsndio, #x); \
|
||||
if (!cubeb_##x) { \
|
||||
DPR("sndio_init(%s) failed dlsym(%s)\n", context_name, #x); \
|
||||
dlclose(libsndio); \
|
||||
return CUBEB_ERROR; \
|
||||
} \
|
||||
}
|
||||
|
||||
LIBSNDIO_API_VISIT(LOAD);
|
||||
#undef LOAD
|
||||
#endif
|
||||
|
||||
/* test if sndio works */
|
||||
hdl = WRAP(sio_open)(sndio_get_device(), SIO_PLAY, 1);
|
||||
if (hdl == NULL) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
WRAP(sio_close)(hdl);
|
||||
|
||||
DPR("sndio_init(%s)\n", context_name);
|
||||
*context = malloc(sizeof(**context));
|
||||
if (*context == NULL)
|
||||
return CUBEB_ERROR;
|
||||
(*context)->libsndio = libsndio;
|
||||
*context = malloc(sizeof(*context));
|
||||
(*context)->ops = &sndio_ops;
|
||||
(void)context_name;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static char const *
|
||||
sndio_get_backend_id(cubeb * context)
|
||||
sndio_get_backend_id(cubeb *context)
|
||||
{
|
||||
return "sndio";
|
||||
}
|
||||
|
||||
static void
|
||||
sndio_destroy(cubeb * context)
|
||||
sndio_destroy(cubeb *context)
|
||||
{
|
||||
DPR("sndio_destroy()\n");
|
||||
if (context->libsndio)
|
||||
dlclose(context->libsndio);
|
||||
free(context);
|
||||
}
|
||||
|
||||
static int
|
||||
sndio_stream_init(cubeb * context, cubeb_stream ** stream,
|
||||
char const * stream_name, cubeb_devid input_device,
|
||||
sndio_stream_init(cubeb * context,
|
||||
cubeb_stream ** stream,
|
||||
char const * stream_name,
|
||||
cubeb_devid input_device,
|
||||
cubeb_stream_params * input_stream_params,
|
||||
cubeb_devid output_device,
|
||||
cubeb_stream_params * output_stream_params,
|
||||
unsigned int latency_frames,
|
||||
cubeb_data_callback data_callback,
|
||||
cubeb_state_callback state_callback, void * user_ptr)
|
||||
cubeb_state_callback state_callback,
|
||||
void *user_ptr)
|
||||
{
|
||||
cubeb_stream * s;
|
||||
cubeb_stream *s;
|
||||
struct sio_par wpar, rpar;
|
||||
cubeb_sample_format format;
|
||||
int rate;
|
||||
size_t bps;
|
||||
|
||||
DPR("sndio_stream_init(%s)\n", stream_name);
|
||||
size_t size;
|
||||
|
||||
assert(!input_stream_params && "not supported.");
|
||||
if (input_device || output_device) {
|
||||
/* Device selection not yet implemented. */
|
||||
return CUBEB_ERROR_DEVICE_UNAVAILABLE;
|
||||
}
|
||||
|
||||
s = malloc(sizeof(cubeb_stream));
|
||||
if (s == NULL)
|
||||
return CUBEB_ERROR;
|
||||
memset(s, 0, sizeof(cubeb_stream));
|
||||
s->mode = 0;
|
||||
if (input_stream_params) {
|
||||
if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
|
||||
DPR("sndio_stream_init(), loopback not supported\n");
|
||||
goto err;
|
||||
}
|
||||
s->mode |= SIO_REC;
|
||||
format = input_stream_params->format;
|
||||
rate = input_stream_params->rate;
|
||||
}
|
||||
if (output_stream_params) {
|
||||
if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
|
||||
DPR("sndio_stream_init(), loopback not supported\n");
|
||||
goto err;
|
||||
}
|
||||
s->mode |= SIO_PLAY;
|
||||
format = output_stream_params->format;
|
||||
rate = output_stream_params->rate;
|
||||
}
|
||||
if (s->mode == 0) {
|
||||
DPR("sndio_stream_init(), neither playing nor recording\n");
|
||||
goto err;
|
||||
}
|
||||
s->context = context;
|
||||
s->hdl = WRAP(sio_open)(sndio_get_device(), s->mode, 1);
|
||||
s->hdl = sio_open(NULL, SIO_PLAY, 1);
|
||||
if (s->hdl == NULL) {
|
||||
free(s);
|
||||
DPR("sndio_stream_init(), sio_open() failed\n");
|
||||
goto err;
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
WRAP(sio_initpar)(&wpar);
|
||||
sio_initpar(&wpar);
|
||||
wpar.sig = 1;
|
||||
wpar.bits = 16;
|
||||
switch (format) {
|
||||
switch (output_stream_params->format) {
|
||||
case CUBEB_SAMPLE_S16LE:
|
||||
wpar.le = 1;
|
||||
break;
|
||||
@@ -433,70 +218,53 @@ sndio_stream_init(cubeb * context, cubeb_stream ** stream,
|
||||
break;
|
||||
default:
|
||||
DPR("sndio_stream_init() unsupported format\n");
|
||||
goto err;
|
||||
return CUBEB_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
wpar.rate = rate;
|
||||
if (s->mode & SIO_REC)
|
||||
wpar.rchan = input_stream_params->channels;
|
||||
if (s->mode & SIO_PLAY)
|
||||
wpar.pchan = output_stream_params->channels;
|
||||
wpar.rate = output_stream_params->rate;
|
||||
wpar.pchan = output_stream_params->channels;
|
||||
wpar.appbufsz = latency_frames;
|
||||
if (!WRAP(sio_setpar)(s->hdl, &wpar) || !WRAP(sio_getpar)(s->hdl, &rpar)) {
|
||||
if (!sio_setpar(s->hdl, &wpar) || !sio_getpar(s->hdl, &rpar)) {
|
||||
sio_close(s->hdl);
|
||||
free(s);
|
||||
DPR("sndio_stream_init(), sio_setpar() failed\n");
|
||||
goto err;
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
if (rpar.bits != wpar.bits || rpar.le != wpar.le || rpar.sig != wpar.sig ||
|
||||
rpar.rate != wpar.rate ||
|
||||
((s->mode & SIO_REC) && rpar.rchan != wpar.rchan) ||
|
||||
((s->mode & SIO_PLAY) && rpar.pchan != wpar.pchan)) {
|
||||
if (rpar.bits != wpar.bits || rpar.le != wpar.le ||
|
||||
rpar.sig != wpar.sig || rpar.rate != wpar.rate ||
|
||||
rpar.pchan != wpar.pchan) {
|
||||
sio_close(s->hdl);
|
||||
free(s);
|
||||
DPR("sndio_stream_init() unsupported params\n");
|
||||
goto err;
|
||||
return CUBEB_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
WRAP(sio_onmove)(s->hdl, sndio_onmove, s);
|
||||
sio_onmove(s->hdl, sndio_onmove, s);
|
||||
s->active = 0;
|
||||
s->nfr = rpar.round;
|
||||
s->rbpf = rpar.bps * rpar.rchan;
|
||||
s->pbpf = rpar.bps * rpar.pchan;
|
||||
s->rchan = rpar.rchan;
|
||||
s->bpf = rpar.bps * rpar.pchan;
|
||||
s->pchan = rpar.pchan;
|
||||
s->nblks = rpar.bufsz / rpar.round;
|
||||
s->data_cb = data_callback;
|
||||
s->state_cb = state_callback;
|
||||
s->arg = user_ptr;
|
||||
s->mtx = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
|
||||
s->hwpos = s->swpos = 0;
|
||||
if (format == CUBEB_SAMPLE_FLOAT32LE) {
|
||||
s->rdpos = s->wrpos = 0;
|
||||
if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE) {
|
||||
s->conv = 1;
|
||||
bps = sizeof(float);
|
||||
size = rpar.round * rpar.pchan * sizeof(float);
|
||||
} else {
|
||||
s->conv = 0;
|
||||
bps = rpar.bps;
|
||||
size = rpar.round * rpar.pchan * rpar.bps;
|
||||
}
|
||||
if (s->mode & SIO_PLAY) {
|
||||
s->pbuf = malloc(bps * rpar.pchan * rpar.round);
|
||||
if (s->pbuf == NULL)
|
||||
goto err;
|
||||
s->buf = malloc(size);
|
||||
if (s->buf == NULL) {
|
||||
sio_close(s->hdl);
|
||||
free(s);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
if (s->mode & SIO_REC) {
|
||||
s->rbuf = malloc(bps * rpar.rchan * rpar.round);
|
||||
if (s->rbuf == NULL)
|
||||
goto err;
|
||||
}
|
||||
s->volume = 1.;
|
||||
*stream = s;
|
||||
DPR("sndio_stream_init() end, ok\n");
|
||||
(void)context;
|
||||
(void)stream_name;
|
||||
return CUBEB_OK;
|
||||
err:
|
||||
if (s->hdl)
|
||||
WRAP(sio_close)(s->hdl);
|
||||
if (s->pbuf)
|
||||
free(s->pbuf);
|
||||
if (s->rbuf)
|
||||
free(s->pbuf);
|
||||
free(s);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
static int
|
||||
@@ -512,41 +280,31 @@ sndio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
|
||||
static int
|
||||
sndio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
|
||||
{
|
||||
/*
|
||||
* We've no device-independent prefered rate; any rate will work if
|
||||
* sndiod is running. If it isn't, 48kHz is what is most likely to
|
||||
* work as most (but not all) devices support it.
|
||||
*/
|
||||
*rate = 48000;
|
||||
// XXX Not yet implemented.
|
||||
*rate = 44100;
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params,
|
||||
uint32_t * latency_frames)
|
||||
sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames)
|
||||
{
|
||||
/*
|
||||
* We've no device-independent minimum latency.
|
||||
*/
|
||||
// XXX Not yet implemented.
|
||||
*latency_frames = 2048;
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static void
|
||||
sndio_stream_destroy(cubeb_stream * s)
|
||||
sndio_stream_destroy(cubeb_stream *s)
|
||||
{
|
||||
DPR("sndio_stream_destroy()\n");
|
||||
WRAP(sio_close)(s->hdl);
|
||||
if (s->mode & SIO_PLAY)
|
||||
free(s->pbuf);
|
||||
if (s->mode & SIO_REC)
|
||||
free(s->rbuf);
|
||||
sio_close(s->hdl);
|
||||
free(s);
|
||||
}
|
||||
|
||||
static int
|
||||
sndio_stream_start(cubeb_stream * s)
|
||||
sndio_stream_start(cubeb_stream *s)
|
||||
{
|
||||
int err;
|
||||
|
||||
@@ -561,9 +319,9 @@ sndio_stream_start(cubeb_stream * s)
|
||||
}
|
||||
|
||||
static int
|
||||
sndio_stream_stop(cubeb_stream * s)
|
||||
sndio_stream_stop(cubeb_stream *s)
|
||||
{
|
||||
void * dummy;
|
||||
void *dummy;
|
||||
|
||||
DPR("sndio_stream_stop()\n");
|
||||
if (s->active) {
|
||||
@@ -574,25 +332,21 @@ sndio_stream_stop(cubeb_stream * s)
|
||||
}
|
||||
|
||||
static int
|
||||
sndio_stream_get_position(cubeb_stream * s, uint64_t * p)
|
||||
sndio_stream_get_position(cubeb_stream *s, uint64_t *p)
|
||||
{
|
||||
pthread_mutex_lock(&s->mtx);
|
||||
DPR("sndio_stream_get_position() %" PRId64 "\n", s->hwpos);
|
||||
*p = s->hwpos;
|
||||
DPR("sndio_stream_get_position() %lld\n", s->rdpos);
|
||||
*p = s->rdpos / s->bpf;
|
||||
pthread_mutex_unlock(&s->mtx);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sndio_stream_set_volume(cubeb_stream * s, float volume)
|
||||
sndio_stream_set_volume(cubeb_stream *s, float volume)
|
||||
{
|
||||
DPR("sndio_stream_set_volume(%f)\n", volume);
|
||||
pthread_mutex_lock(&s->mtx);
|
||||
if (volume < 0.)
|
||||
volume = 0.;
|
||||
else if (volume > 1.0)
|
||||
volume = 1.;
|
||||
s->volume = volume;
|
||||
sio_setvol(s->hdl, SIO_MAXVOL * volume);
|
||||
pthread_mutex_unlock(&s->mtx);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
@@ -602,68 +356,28 @@ sndio_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
|
||||
{
|
||||
// http://www.openbsd.org/cgi-bin/man.cgi?query=sio_open
|
||||
// in the "Measuring the latency and buffers usage" paragraph.
|
||||
*latency = stm->swpos - stm->hwpos;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sndio_enumerate_devices(cubeb * context, cubeb_device_type type,
|
||||
cubeb_device_collection * collection)
|
||||
{
|
||||
static char dev[] = SIO_DEVANY;
|
||||
cubeb_device_info * device;
|
||||
|
||||
device = malloc(sizeof(cubeb_device_info));
|
||||
if (device == NULL)
|
||||
return CUBEB_ERROR;
|
||||
|
||||
device->devid = dev; /* passed to stream_init() */
|
||||
device->device_id = dev; /* printable in UI */
|
||||
device->friendly_name = dev; /* same, but friendly */
|
||||
device->group_id = dev; /* actual device if full-duplex */
|
||||
device->vendor_name = NULL; /* may be NULL */
|
||||
device->type = type; /* Input/Output */
|
||||
device->state = CUBEB_DEVICE_STATE_ENABLED;
|
||||
device->preferred = CUBEB_DEVICE_PREF_ALL;
|
||||
device->format = CUBEB_DEVICE_FMT_S16NE;
|
||||
device->default_format = CUBEB_DEVICE_FMT_S16NE;
|
||||
device->max_channels = (type == CUBEB_DEVICE_TYPE_INPUT) ? 2 : 8;
|
||||
device->default_rate = 48000;
|
||||
device->min_rate = 4000;
|
||||
device->max_rate = 192000;
|
||||
device->latency_lo = 480;
|
||||
device->latency_hi = 9600;
|
||||
collection->device = device;
|
||||
collection->count = 1;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sndio_device_collection_destroy(cubeb * context,
|
||||
cubeb_device_collection * collection)
|
||||
{
|
||||
free(collection->device);
|
||||
*latency = (stm->wrpos - stm->rdpos) / stm->bpf;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static struct cubeb_ops const sndio_ops = {
|
||||
.init = sndio_init,
|
||||
.get_backend_id = sndio_get_backend_id,
|
||||
.get_max_channel_count = sndio_get_max_channel_count,
|
||||
.get_min_latency = sndio_get_min_latency,
|
||||
.get_preferred_sample_rate = sndio_get_preferred_sample_rate,
|
||||
.enumerate_devices = sndio_enumerate_devices,
|
||||
.device_collection_destroy = sndio_device_collection_destroy,
|
||||
.destroy = sndio_destroy,
|
||||
.stream_init = sndio_stream_init,
|
||||
.stream_destroy = sndio_stream_destroy,
|
||||
.stream_start = sndio_stream_start,
|
||||
.stream_stop = sndio_stream_stop,
|
||||
.stream_get_position = sndio_stream_get_position,
|
||||
.stream_get_latency = sndio_stream_get_latency,
|
||||
.stream_set_volume = sndio_stream_set_volume,
|
||||
.stream_set_name = NULL,
|
||||
.stream_get_current_device = NULL,
|
||||
.stream_device_destroy = NULL,
|
||||
.stream_register_device_changed_callback = NULL,
|
||||
.register_device_collection_changed = NULL};
|
||||
.init = sndio_init,
|
||||
.get_backend_id = sndio_get_backend_id,
|
||||
.get_max_channel_count = sndio_get_max_channel_count,
|
||||
.get_min_latency = sndio_get_min_latency,
|
||||
.get_preferred_sample_rate = sndio_get_preferred_sample_rate,
|
||||
.enumerate_devices = NULL,
|
||||
.destroy = sndio_destroy,
|
||||
.stream_init = sndio_stream_init,
|
||||
.stream_destroy = sndio_stream_destroy,
|
||||
.stream_start = sndio_stream_start,
|
||||
.stream_stop = sndio_stream_stop,
|
||||
.stream_get_position = sndio_stream_get_position,
|
||||
.stream_get_latency = sndio_stream_get_latency,
|
||||
.stream_set_volume = sndio_stream_set_volume,
|
||||
.stream_set_panning = NULL,
|
||||
.stream_get_current_device = NULL,
|
||||
.stream_device_destroy = NULL,
|
||||
.stream_register_device_changed_callback = NULL,
|
||||
.register_device_collection_changed = NULL
|
||||
};
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2011 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
#include "cubeb_strings.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define CUBEB_STRINGS_INLINE_COUNT 4
|
||||
|
||||
struct cubeb_strings {
|
||||
uint32_t size;
|
||||
uint32_t count;
|
||||
char ** data;
|
||||
char * small_store[CUBEB_STRINGS_INLINE_COUNT];
|
||||
};
|
||||
|
||||
int
|
||||
cubeb_strings_init(cubeb_strings ** strings)
|
||||
{
|
||||
cubeb_strings * strs = NULL;
|
||||
|
||||
if (!strings) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
strs = calloc(1, sizeof(cubeb_strings));
|
||||
assert(strs);
|
||||
|
||||
if (!strs) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
strs->size = sizeof(strs->small_store) / sizeof(strs->small_store[0]);
|
||||
strs->count = 0;
|
||||
strs->data = strs->small_store;
|
||||
|
||||
*strings = strs;
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
void
|
||||
cubeb_strings_destroy(cubeb_strings * strings)
|
||||
{
|
||||
char ** sp = NULL;
|
||||
char ** se = NULL;
|
||||
|
||||
if (!strings) {
|
||||
return;
|
||||
}
|
||||
|
||||
sp = strings->data;
|
||||
se = sp + strings->count;
|
||||
|
||||
for (; sp != se; sp++) {
|
||||
if (*sp) {
|
||||
free(*sp);
|
||||
}
|
||||
}
|
||||
|
||||
if (strings->data != strings->small_store) {
|
||||
free(strings->data);
|
||||
}
|
||||
|
||||
free(strings);
|
||||
}
|
||||
|
||||
/** Look for string in string storage.
|
||||
@param strings Opaque pointer to interned string storage.
|
||||
@param s String to look up.
|
||||
@retval Read-only string or NULL if not found. */
|
||||
static char const *
|
||||
cubeb_strings_lookup(cubeb_strings * strings, char const * s)
|
||||
{
|
||||
char ** sp = NULL;
|
||||
char ** se = NULL;
|
||||
|
||||
if (!strings || !s) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sp = strings->data;
|
||||
se = sp + strings->count;
|
||||
|
||||
for (; sp != se; sp++) {
|
||||
if (*sp && strcmp(*sp, s) == 0) {
|
||||
return *sp;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char const *
|
||||
cubeb_strings_push(cubeb_strings * strings, char const * s)
|
||||
{
|
||||
char * is = NULL;
|
||||
|
||||
if (strings->count == strings->size) {
|
||||
char ** new_data;
|
||||
uint32_t value_size = sizeof(char const *);
|
||||
uint32_t new_size = strings->size * 2;
|
||||
if (!new_size || value_size > (uint32_t)-1 / new_size) {
|
||||
// overflow
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (strings->small_store == strings->data) {
|
||||
// First time heap allocation.
|
||||
new_data = malloc(new_size * value_size);
|
||||
if (new_data) {
|
||||
memcpy(new_data, strings->small_store, sizeof(strings->small_store));
|
||||
}
|
||||
} else {
|
||||
new_data = realloc(strings->data, new_size * value_size);
|
||||
}
|
||||
|
||||
if (!new_data) {
|
||||
// out of memory
|
||||
return NULL;
|
||||
}
|
||||
|
||||
strings->size = new_size;
|
||||
strings->data = new_data;
|
||||
}
|
||||
|
||||
is = strdup(s);
|
||||
strings->data[strings->count++] = is;
|
||||
|
||||
return is;
|
||||
}
|
||||
|
||||
char const *
|
||||
cubeb_strings_intern(cubeb_strings * strings, char const * s)
|
||||
{
|
||||
char const * is = NULL;
|
||||
|
||||
if (!strings || !s) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
is = cubeb_strings_lookup(strings, s);
|
||||
if (is) {
|
||||
return is;
|
||||
}
|
||||
|
||||
return cubeb_strings_push(strings, s);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2011 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
#ifndef CUBEB_STRINGS_H
|
||||
#define CUBEB_STRINGS_H
|
||||
|
||||
#include "cubeb/cubeb.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Opaque handle referencing interned string storage. */
|
||||
typedef struct cubeb_strings cubeb_strings;
|
||||
|
||||
/** Initialize an interned string structure.
|
||||
@param strings An out param where an opaque pointer to the
|
||||
interned string storage will be returned.
|
||||
@retval CUBEB_OK in case of success.
|
||||
@retval CUBEB_ERROR in case of error. */
|
||||
CUBEB_EXPORT int
|
||||
cubeb_strings_init(cubeb_strings ** strings);
|
||||
|
||||
/** Destroy an interned string structure freeing all associated memory.
|
||||
@param strings An opaque pointer to the interned string storage to
|
||||
destroy. */
|
||||
CUBEB_EXPORT void
|
||||
cubeb_strings_destroy(cubeb_strings * strings);
|
||||
|
||||
/** Add string to internal storage.
|
||||
@param strings Opaque pointer to interned string storage.
|
||||
@param s String to add to storage.
|
||||
@retval CUBEB_OK
|
||||
@retval CUBEB_ERROR
|
||||
*/
|
||||
CUBEB_EXPORT char const *
|
||||
cubeb_strings_intern(cubeb_strings * strings, char const * s);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // !CUBEB_STRINGS_H
|
||||
+401
-630
File diff suppressed because it is too large
Load Diff
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
#include "cubeb_utils.h"
|
||||
|
||||
size_t
|
||||
cubeb_sample_size(cubeb_sample_format format)
|
||||
{
|
||||
switch (format) {
|
||||
case CUBEB_SAMPLE_S16LE:
|
||||
case CUBEB_SAMPLE_S16BE:
|
||||
return sizeof(int16_t);
|
||||
case CUBEB_SAMPLE_FLOAT32LE:
|
||||
case CUBEB_SAMPLE_FLOAT32BE:
|
||||
return sizeof(float);
|
||||
default:
|
||||
// should never happen as all cases are handled above.
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -8,154 +8,97 @@
|
||||
#if !defined(CUBEB_UTILS)
|
||||
#define CUBEB_UTILS
|
||||
|
||||
#include "cubeb/cubeb.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include <assert.h>
|
||||
#include <mutex>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <type_traits>
|
||||
#if defined(_WIN32)
|
||||
#if defined(WIN32)
|
||||
#include "cubeb_utils_win.h"
|
||||
#else
|
||||
#include "cubeb_utils_unix.h"
|
||||
#endif
|
||||
|
||||
/** Similar to memcpy, but accounts for the size of an element. */
|
||||
template <typename T>
|
||||
void
|
||||
PodCopy(T * destination, const T * source, size_t count)
|
||||
template<typename T>
|
||||
void PodCopy(T * destination, const T * source, size_t count)
|
||||
{
|
||||
static_assert(std::is_trivial<T>::value, "Requires trivial type");
|
||||
assert(destination && source);
|
||||
memcpy(destination, source, count * sizeof(T));
|
||||
}
|
||||
|
||||
/** Similar to memmove, but accounts for the size of an element. */
|
||||
template <typename T>
|
||||
void
|
||||
PodMove(T * destination, const T * source, size_t count)
|
||||
template<typename T>
|
||||
void PodMove(T * destination, const T * source, size_t count)
|
||||
{
|
||||
static_assert(std::is_trivial<T>::value, "Requires trivial type");
|
||||
assert(destination && source);
|
||||
memmove(destination, source, count * sizeof(T));
|
||||
}
|
||||
|
||||
/** Similar to a memset to zero, but accounts for the size of an element. */
|
||||
template <typename T>
|
||||
void
|
||||
PodZero(T * destination, size_t count)
|
||||
template<typename T>
|
||||
void PodZero(T * destination, size_t count)
|
||||
{
|
||||
static_assert(std::is_trivial<T>::value, "Requires trivial type");
|
||||
assert(destination);
|
||||
memset(destination, 0, count * sizeof(T));
|
||||
memset(destination, 0, count * sizeof(T));
|
||||
}
|
||||
|
||||
namespace {
|
||||
template <typename T, typename Trait>
|
||||
void
|
||||
Copy(T * destination, const T * source, size_t count, Trait)
|
||||
template<typename T>
|
||||
class auto_array
|
||||
{
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
destination[i] = source[i];
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
Copy(T * destination, const T * source, size_t count, std::true_type)
|
||||
{
|
||||
PodCopy(destination, source, count);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* This allows copying a number of elements from a `source` pointer to a
|
||||
* `destination` pointer, using `memcpy` if it is safe to do so, or a loop that
|
||||
* calls the constructors and destructors otherwise.
|
||||
*/
|
||||
template <typename T>
|
||||
void
|
||||
Copy(T * destination, const T * source, size_t count)
|
||||
{
|
||||
assert(destination && source);
|
||||
Copy(destination, source, count, typename std::is_trivial<T>::type());
|
||||
}
|
||||
|
||||
namespace {
|
||||
template <typename T, typename Trait>
|
||||
void
|
||||
ConstructDefault(T * destination, size_t count, Trait)
|
||||
{
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
destination[i] = T();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
ConstructDefault(T * destination, size_t count, std::true_type)
|
||||
{
|
||||
PodZero(destination, count);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* This allows zeroing (using memset) or default-constructing a number of
|
||||
* elements calling the constructors and destructors if necessary.
|
||||
*/
|
||||
template <typename T>
|
||||
void
|
||||
ConstructDefault(T * destination, size_t count)
|
||||
{
|
||||
assert(destination);
|
||||
ConstructDefault(destination, count, typename std::is_arithmetic<T>::type());
|
||||
}
|
||||
|
||||
template <typename T> class auto_array {
|
||||
public:
|
||||
explicit auto_array(uint32_t capacity = 0)
|
||||
: data_(capacity ? new T[capacity] : nullptr), capacity_(capacity),
|
||||
length_(0)
|
||||
: data_(capacity ? new T[capacity] : nullptr)
|
||||
, capacity_(capacity)
|
||||
, length_(0)
|
||||
{}
|
||||
|
||||
~auto_array()
|
||||
{
|
||||
delete [] data_;
|
||||
}
|
||||
|
||||
~auto_array() { delete[] data_; }
|
||||
|
||||
/** Get a constant pointer to the underlying data. */
|
||||
T * data() const { return data_; }
|
||||
T * data() const
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
T * end() const { return data_ + length_; }
|
||||
|
||||
const T & at(size_t index) const
|
||||
const T& at(size_t index) const
|
||||
{
|
||||
assert(index < length_ && "out of range");
|
||||
return data_[index];
|
||||
}
|
||||
|
||||
T & at(size_t index)
|
||||
T& at(size_t index)
|
||||
{
|
||||
assert(index < length_ && "out of range");
|
||||
return data_[index];
|
||||
}
|
||||
|
||||
/** Get how much underlying storage this auto_array has. */
|
||||
size_t capacity() const { return capacity_; }
|
||||
size_t capacity() const
|
||||
{
|
||||
return capacity_;
|
||||
}
|
||||
|
||||
/** Get how much elements this auto_array contains. */
|
||||
size_t length() const { return length_; }
|
||||
size_t length() const
|
||||
{
|
||||
return length_;
|
||||
}
|
||||
|
||||
/** Keeps the storage, but removes all the elements from the array. */
|
||||
void clear() { length_ = 0; }
|
||||
void clear()
|
||||
{
|
||||
length_ = 0;
|
||||
}
|
||||
|
||||
/** Change the storage of this auto array, copying the elements to the new
|
||||
* storage.
|
||||
* @returns true in case of success
|
||||
* @returns false if the new capacity is not big enough to accomodate for the
|
||||
* elements in the array.
|
||||
*/
|
||||
/** Change the storage of this auto array, copying the elements to the new
|
||||
* storage.
|
||||
* @returns true in case of success
|
||||
* @returns false if the new capacity is not big enough to accomodate for the
|
||||
* elements in the array.
|
||||
*/
|
||||
bool reserve(size_t new_capacity)
|
||||
{
|
||||
if (new_capacity < length_) {
|
||||
@@ -166,17 +109,17 @@ public:
|
||||
PodCopy(new_data, data_, length_);
|
||||
}
|
||||
capacity_ = new_capacity;
|
||||
delete[] data_;
|
||||
delete [] data_;
|
||||
data_ = new_data;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Append `length` elements to the end of the array, resizing the array if
|
||||
* needed.
|
||||
* @parameter elements the elements to append to the array.
|
||||
* @parameter length the number of elements to append to the array.
|
||||
*/
|
||||
/** Append `length` elements to the end of the array, resizing the array if
|
||||
* needed.
|
||||
* @parameter elements the elements to append to the array.
|
||||
* @parameter length the number of elements to append to the array.
|
||||
*/
|
||||
void push(const T * elements, size_t length)
|
||||
{
|
||||
if (length_ + length > capacity_) {
|
||||
@@ -214,14 +157,17 @@ public:
|
||||
}
|
||||
|
||||
/** Return the number of free elements in the array. */
|
||||
size_t available() const { return capacity_ - length_; }
|
||||
size_t available() const
|
||||
{
|
||||
return capacity_ - length_;
|
||||
}
|
||||
|
||||
/** Copies `length` elements to `elements` if it is not null, and shift
|
||||
* the remaining elements of the `auto_array` to the beginning.
|
||||
* @parameter elements a buffer to copy the elements to, or nullptr.
|
||||
* @parameter length the number of elements to copy.
|
||||
* @returns true in case of success.
|
||||
* @returns false if the auto_array contains less than `length` elements. */
|
||||
* the remaining elements of the `auto_array` to the beginning.
|
||||
* @parameter elements a buffer to copy the elements to, or nullptr.
|
||||
* @parameter length the number of elements to copy.
|
||||
* @returns true in case of success.
|
||||
* @returns false if the auto_array contains less than `length` elements. */
|
||||
bool pop(T * elements, size_t length)
|
||||
{
|
||||
if (length > length_) {
|
||||
@@ -252,58 +198,18 @@ private:
|
||||
size_t length_;
|
||||
};
|
||||
|
||||
struct auto_array_wrapper {
|
||||
virtual void push(void * elements, size_t length) = 0;
|
||||
virtual size_t length() = 0;
|
||||
virtual void push_silence(size_t length) = 0;
|
||||
virtual bool pop(size_t length) = 0;
|
||||
virtual void * data() = 0;
|
||||
virtual void * end() = 0;
|
||||
virtual void clear() = 0;
|
||||
virtual bool reserve(size_t capacity) = 0;
|
||||
virtual void set_length(size_t length) = 0;
|
||||
virtual ~auto_array_wrapper() {}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct auto_array_wrapper_impl : public auto_array_wrapper {
|
||||
auto_array_wrapper_impl() {}
|
||||
|
||||
explicit auto_array_wrapper_impl(uint32_t size) : ar(size) {}
|
||||
|
||||
void push(void * elements, size_t length) override
|
||||
struct auto_lock {
|
||||
explicit auto_lock(owned_critical_section & lock)
|
||||
: lock(lock)
|
||||
{
|
||||
ar.push(static_cast<T *>(elements), length);
|
||||
lock.enter();
|
||||
}
|
||||
~auto_lock()
|
||||
{
|
||||
lock.leave();
|
||||
}
|
||||
|
||||
size_t length() override { return ar.length(); }
|
||||
|
||||
void push_silence(size_t length) override { ar.push_silence(length); }
|
||||
|
||||
bool pop(size_t length) override { return ar.pop(nullptr, length); }
|
||||
|
||||
void * data() override { return ar.data(); }
|
||||
|
||||
void * end() override { return ar.end(); }
|
||||
|
||||
void clear() override { ar.clear(); }
|
||||
|
||||
bool reserve(size_t capacity) override { return ar.reserve(capacity); }
|
||||
|
||||
void set_length(size_t length) override { ar.set_length(length); }
|
||||
|
||||
~auto_array_wrapper_impl() { ar.clear(); }
|
||||
|
||||
private:
|
||||
auto_array<T> ar;
|
||||
owned_critical_section & lock;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
size_t
|
||||
cubeb_sample_size(cubeb_sample_format format);
|
||||
}
|
||||
|
||||
using auto_lock = std::lock_guard<owned_critical_section>;
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif /* CUBEB_UTILS */
|
||||
|
||||
@@ -8,12 +8,13 @@
|
||||
#if !defined(CUBEB_UTILS_UNIX)
|
||||
#define CUBEB_UTILS_UNIX
|
||||
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* This wraps a critical section to track the owner in debug mode. */
|
||||
class owned_critical_section {
|
||||
class owned_critical_section
|
||||
{
|
||||
public:
|
||||
owned_critical_section()
|
||||
{
|
||||
@@ -28,7 +29,7 @@ public:
|
||||
#ifndef NDEBUG
|
||||
int r =
|
||||
#endif
|
||||
pthread_mutex_init(&mutex, &attr);
|
||||
pthread_mutex_init(&mutex, &attr);
|
||||
#ifndef NDEBUG
|
||||
assert(r == 0);
|
||||
#endif
|
||||
@@ -41,29 +42,29 @@ public:
|
||||
#ifndef NDEBUG
|
||||
int r =
|
||||
#endif
|
||||
pthread_mutex_destroy(&mutex);
|
||||
pthread_mutex_destroy(&mutex);
|
||||
#ifndef NDEBUG
|
||||
assert(r == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void lock()
|
||||
void enter()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
int r =
|
||||
#endif
|
||||
pthread_mutex_lock(&mutex);
|
||||
pthread_mutex_lock(&mutex);
|
||||
#ifndef NDEBUG
|
||||
assert(r == 0 && "Deadlock");
|
||||
#endif
|
||||
}
|
||||
|
||||
void unlock()
|
||||
void leave()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
int r =
|
||||
#endif
|
||||
pthread_mutex_unlock(&mutex);
|
||||
pthread_mutex_unlock(&mutex);
|
||||
#ifndef NDEBUG
|
||||
assert(r == 0 && "Unlocking unlocked mutex");
|
||||
#endif
|
||||
@@ -81,8 +82,8 @@ private:
|
||||
pthread_mutex_t mutex;
|
||||
|
||||
// Disallow copy and assignment because pthread_mutex_t cannot be copied.
|
||||
owned_critical_section(const owned_critical_section &);
|
||||
owned_critical_section & operator=(const owned_critical_section &);
|
||||
owned_critical_section(const owned_critical_section&);
|
||||
owned_critical_section& operator=(const owned_critical_section&);
|
||||
};
|
||||
|
||||
#endif /* CUBEB_UTILS_UNIX */
|
||||
|
||||
@@ -8,25 +8,28 @@
|
||||
#if !defined(CUBEB_UTILS_WIN)
|
||||
#define CUBEB_UTILS_WIN
|
||||
|
||||
#include "cubeb-internal.h"
|
||||
#include <windows.h>
|
||||
#include "cubeb-internal.h"
|
||||
|
||||
/* This wraps a critical section to track the owner in debug mode, adapted from
|
||||
NSPR and http://blogs.msdn.com/b/oldnewthing/archive/2013/07/12/10433554.aspx
|
||||
*/
|
||||
class owned_critical_section {
|
||||
NSPR and http://blogs.msdn.com/b/oldnewthing/archive/2013/07/12/10433554.aspx */
|
||||
class owned_critical_section
|
||||
{
|
||||
public:
|
||||
owned_critical_section()
|
||||
#ifndef NDEBUG
|
||||
: owner(0)
|
||||
: owner(0)
|
||||
#endif
|
||||
{
|
||||
InitializeCriticalSection(&critical_section);
|
||||
}
|
||||
|
||||
~owned_critical_section() { DeleteCriticalSection(&critical_section); }
|
||||
~owned_critical_section()
|
||||
{
|
||||
DeleteCriticalSection(&critical_section);
|
||||
}
|
||||
|
||||
void lock()
|
||||
void enter()
|
||||
{
|
||||
EnterCriticalSection(&critical_section);
|
||||
#ifndef NDEBUG
|
||||
@@ -35,7 +38,7 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
void unlock()
|
||||
void leave()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
/* GetCurrentThreadId cannot return 0: it is not a the valid thread id */
|
||||
@@ -61,8 +64,8 @@ private:
|
||||
#endif
|
||||
|
||||
// Disallow copy and assignment because CRICICAL_SECTION cannot be copied.
|
||||
owned_critical_section(const owned_critical_section &);
|
||||
owned_critical_section & operator=(const owned_critical_section &);
|
||||
owned_critical_section(const owned_critical_section&);
|
||||
owned_critical_section& operator=(const owned_critical_section&);
|
||||
};
|
||||
|
||||
#endif /* CUBEB_UTILS_WIN */
|
||||
|
||||
+1034
-2116
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -9,11 +9,7 @@ Library('cubeb')
|
||||
|
||||
SOURCES += [
|
||||
'cubeb.c',
|
||||
'cubeb_log.cpp',
|
||||
'cubeb_mixer.cpp',
|
||||
'cubeb_panner.cpp',
|
||||
'cubeb_strings.c',
|
||||
'cubeb_utils.cpp'
|
||||
'cubeb_panner.cpp'
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_ALSA']:
|
||||
@@ -27,6 +23,12 @@ if CONFIG['MOZ_PULSEAUDIO'] or CONFIG['MOZ_JACK']:
|
||||
'cubeb_resampler.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_PULSEAUDIO']:
|
||||
SOURCES += [
|
||||
'cubeb_pulse.c',
|
||||
]
|
||||
DEFINES['USE_PULSE'] = True
|
||||
|
||||
if CONFIG['MOZ_JACK']:
|
||||
SOURCES += [
|
||||
'cubeb_jack.cpp',
|
||||
@@ -36,32 +38,51 @@ if CONFIG['MOZ_JACK']:
|
||||
]
|
||||
DEFINES['USE_JACK'] = True
|
||||
|
||||
if CONFIG['OS_ARCH'] in ('DragonFly', 'FreeBSD', 'SunOS'):
|
||||
if CONFIG['MOZ_SNDIO']:
|
||||
SOURCES += [
|
||||
'cubeb_oss.c',
|
||||
'cubeb_sndio.c',
|
||||
]
|
||||
DEFINES['USE_OSS'] = True
|
||||
DEFINES['USE_SNDIO'] = True
|
||||
|
||||
if CONFIG['OS_ARCH'] == 'SunOS':
|
||||
SOURCES += [
|
||||
'cubeb_sun.c',
|
||||
]
|
||||
DEFINES['USE_SUN'] = True
|
||||
|
||||
if CONFIG['OS_TARGET'] == 'Darwin':
|
||||
SOURCES += [
|
||||
'cubeb_audiounit.cpp',
|
||||
'cubeb_resampler.cpp'
|
||||
]
|
||||
DEFINES['USE_AUDIOUNIT'] = True
|
||||
|
||||
if CONFIG['OS_TARGET'] == 'WINNT':
|
||||
SOURCES += [
|
||||
'cubeb_resampler.cpp',
|
||||
'cubeb_wasapi.cpp',
|
||||
'cubeb_winmm.c',
|
||||
]
|
||||
DEFINES['UNICODE'] = True
|
||||
DEFINES['USE_WINMM'] = True
|
||||
DEFINES['USE_WASAPI'] = True
|
||||
OS_LIBS += [
|
||||
"avrt",
|
||||
]
|
||||
if CONFIG['_MSC_VER']:
|
||||
CXXFLAGS += ['-wd4005'] # C4005: '_USE_MATH_DEFINES' : macro redefinition
|
||||
|
||||
if CONFIG['OS_TARGET'] == 'Android':
|
||||
SOURCES += ['cubeb_opensl.c']
|
||||
SOURCES += ['cubeb_resampler.cpp']
|
||||
DEFINES['USE_OPENSL'] = True
|
||||
SOURCES += [
|
||||
'cubeb_audiotrack.c',
|
||||
]
|
||||
DEFINES['USE_AUDIOTRACK'] = True
|
||||
|
||||
if CONFIG['GKMEDIAS_SHARED_LIBRARY']:
|
||||
NO_VISIBILITY_FLAGS = True
|
||||
|
||||
FINAL_LIBRARY = 'gkmedias'
|
||||
|
||||
CFLAGS += CONFIG['MOZ_ALSA_CFLAGS']
|
||||
CFLAGS += CONFIG['MOZ_JACK_CFLAGS']
|
||||
CFLAGS += CONFIG['MOZ_PULSEAUDIO_CFLAGS']
|
||||
|
||||
# We allow warnings for third-party code that can be updated from upstream.
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright © 2013 Sebastien Alaiwan
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
#if defined( _WIN32)
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
void delay(unsigned int ms)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
Sleep(ms);
|
||||
#else
|
||||
sleep(ms / 1000);
|
||||
usleep(ms % 1000 * 1000);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(M_PI)
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
int has_available_input_device(cubeb * ctx)
|
||||
{
|
||||
cubeb_device_collection * devices;
|
||||
int input_device_available = 0;
|
||||
int r;
|
||||
/* Bail out early if the host does not have input devices. */
|
||||
r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &devices);
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "error enumerating devices.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (devices->count == 0) {
|
||||
fprintf(stderr, "no input device available, skipping test.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < devices->count; i++) {
|
||||
input_device_available |= (devices->device[i]->state ==
|
||||
CUBEB_DEVICE_STATE_ENABLED);
|
||||
}
|
||||
|
||||
if (!input_device_available) {
|
||||
fprintf(stderr, "there are input devices, but they are not "
|
||||
"available, skipping\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
* Copyright © 2013 Sebastien Alaiwan <sebastien.alaiwan@gmail.com>
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
/* libcubeb api/function exhaustive test. Plays a series of tones in different
|
||||
* conditions. */
|
||||
#ifdef NDEBUG
|
||||
#undef NDEBUG
|
||||
#endif
|
||||
#define _XOPEN_SOURCE 600
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cubeb/cubeb.h"
|
||||
#include "common.h"
|
||||
#ifdef CUBEB_GECKO_BUILD
|
||||
#include "TestHarness.h"
|
||||
#endif
|
||||
|
||||
#define MAX_NUM_CHANNELS 32
|
||||
|
||||
#if !defined(M_PI)
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
#define NELEMS(x) ((int) (sizeof(x) / sizeof(x[0])))
|
||||
#define VOLUME 0.2
|
||||
|
||||
float get_frequency(int channel_index)
|
||||
{
|
||||
return 220.0f * (channel_index+1);
|
||||
}
|
||||
|
||||
/* store the phase of the generated waveform */
|
||||
typedef struct {
|
||||
int num_channels;
|
||||
float phase[MAX_NUM_CHANNELS];
|
||||
float sample_rate;
|
||||
} synth_state;
|
||||
|
||||
synth_state* synth_create(int num_channels, float sample_rate)
|
||||
{
|
||||
synth_state* synth = (synth_state *) malloc(sizeof(synth_state));
|
||||
if (!synth)
|
||||
return NULL;
|
||||
for(int i=0;i < MAX_NUM_CHANNELS;++i)
|
||||
synth->phase[i] = 0.0f;
|
||||
synth->num_channels = num_channels;
|
||||
synth->sample_rate = sample_rate;
|
||||
return synth;
|
||||
}
|
||||
|
||||
void synth_destroy(synth_state* synth)
|
||||
{
|
||||
free(synth);
|
||||
}
|
||||
|
||||
void synth_run_float(synth_state* synth, float* audiobuffer, long nframes)
|
||||
{
|
||||
for(int c=0;c < synth->num_channels;++c) {
|
||||
float freq = get_frequency(c);
|
||||
float phase_inc = 2.0 * M_PI * freq / synth->sample_rate;
|
||||
for(long n=0;n < nframes;++n) {
|
||||
audiobuffer[n*synth->num_channels+c] = sin(synth->phase[c]) * VOLUME;
|
||||
synth->phase[c] += phase_inc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long data_cb_float(cubeb_stream * /*stream*/, void * user, const void * /*inputbuffer*/, void * outputbuffer, long nframes)
|
||||
{
|
||||
synth_state *synth = (synth_state *)user;
|
||||
synth_run_float(synth, (float*)outputbuffer, nframes);
|
||||
return nframes;
|
||||
}
|
||||
|
||||
void synth_run_16bit(synth_state* synth, short* audiobuffer, long nframes)
|
||||
{
|
||||
for(int c=0;c < synth->num_channels;++c) {
|
||||
float freq = get_frequency(c);
|
||||
float phase_inc = 2.0 * M_PI * freq / synth->sample_rate;
|
||||
for(long n=0;n < nframes;++n) {
|
||||
audiobuffer[n*synth->num_channels+c] = sin(synth->phase[c]) * VOLUME * 32767.0f;
|
||||
synth->phase[c] += phase_inc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long data_cb_short(cubeb_stream * /*stream*/, void * user, const void * /*inputbuffer*/, void * outputbuffer, long nframes)
|
||||
{
|
||||
synth_state *synth = (synth_state *)user;
|
||||
synth_run_16bit(synth, (short*)outputbuffer, nframes);
|
||||
return nframes;
|
||||
}
|
||||
|
||||
void state_cb(cubeb_stream * /*stream*/, void * /*user*/, cubeb_state /*state*/)
|
||||
{
|
||||
}
|
||||
|
||||
/* Our android backends don't support float, only int16. */
|
||||
int supports_float32(const char* backend_id)
|
||||
{
|
||||
return (strcmp(backend_id, "opensl") != 0 &&
|
||||
strcmp(backend_id, "audiotrack") != 0);
|
||||
}
|
||||
|
||||
/* The WASAPI backend only supports float. */
|
||||
int supports_int16(const char* backend_id)
|
||||
{
|
||||
return strcmp(backend_id, "wasapi") != 0;
|
||||
}
|
||||
|
||||
/* Some backends don't have code to deal with more than mono or stereo. */
|
||||
int supports_channel_count(const char* backend_id, int nchannels)
|
||||
{
|
||||
return nchannels <= 2 ||
|
||||
(strcmp(backend_id, "opensl") != 0 && strcmp(backend_id, "audiotrack") != 0);
|
||||
}
|
||||
|
||||
int run_test(int num_channels, int sampling_rate, int is_float)
|
||||
{
|
||||
int r = CUBEB_OK;
|
||||
|
||||
cubeb *ctx = NULL;
|
||||
synth_state* synth = NULL;
|
||||
cubeb_stream *stream = NULL;
|
||||
const char * backend_id = NULL;
|
||||
|
||||
r = cubeb_init(&ctx, "Cubeb audio test: channels");
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Error initializing cubeb library\n");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
backend_id = cubeb_get_backend_id(ctx);
|
||||
|
||||
if ((is_float && !supports_float32(backend_id)) ||
|
||||
(!is_float && !supports_int16(backend_id)) ||
|
||||
!supports_channel_count(backend_id, num_channels)) {
|
||||
/* don't treat this as a test failure. */
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Testing %d channel(s), %d Hz, %s (%s)\n", num_channels, sampling_rate, is_float ? "float" : "short", cubeb_get_backend_id(ctx));
|
||||
|
||||
cubeb_stream_params params;
|
||||
params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
|
||||
params.rate = sampling_rate;
|
||||
params.channels = num_channels;
|
||||
|
||||
synth = synth_create(params.channels, params.rate);
|
||||
if (synth == NULL) {
|
||||
fprintf(stderr, "Out of memory\n");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, ¶ms,
|
||||
4096, is_float ? data_cb_float : data_cb_short, state_cb, synth);
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
cubeb_stream_start(stream);
|
||||
delay(200);
|
||||
cubeb_stream_stop(stream);
|
||||
|
||||
cleanup:
|
||||
cubeb_stream_destroy(stream);
|
||||
cubeb_destroy(ctx);
|
||||
synth_destroy(synth);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int run_panning_volume_test(int is_float)
|
||||
{
|
||||
int r = CUBEB_OK;
|
||||
|
||||
cubeb *ctx = NULL;
|
||||
synth_state* synth = NULL;
|
||||
cubeb_stream *stream = NULL;
|
||||
const char * backend_id = NULL;
|
||||
|
||||
r = cubeb_init(&ctx, "Cubeb audio test");
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Error initializing cubeb library\n");
|
||||
goto cleanup;
|
||||
}
|
||||
backend_id = cubeb_get_backend_id(ctx);
|
||||
|
||||
if ((is_float && !supports_float32(backend_id)) ||
|
||||
(!is_float && !supports_int16(backend_id))) {
|
||||
/* don't treat this as a test failure. */
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
cubeb_stream_params params;
|
||||
params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
|
||||
params.rate = 44100;
|
||||
params.channels = 2;
|
||||
|
||||
synth = synth_create(params.channels, params.rate);
|
||||
if (synth == NULL) {
|
||||
fprintf(stderr, "Out of memory\n");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, ¶ms,
|
||||
4096, is_float ? data_cb_float : data_cb_short,
|
||||
state_cb, synth);
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Testing: volume\n");
|
||||
for(int i=0;i <= 4; ++i)
|
||||
{
|
||||
fprintf(stderr, "Volume: %d%%\n", i*25);
|
||||
|
||||
cubeb_stream_set_volume(stream, i/4.0f);
|
||||
cubeb_stream_start(stream);
|
||||
delay(400);
|
||||
cubeb_stream_stop(stream);
|
||||
delay(100);
|
||||
}
|
||||
|
||||
fprintf(stderr, "Testing: panning\n");
|
||||
for(int i=-4;i <= 4; ++i)
|
||||
{
|
||||
fprintf(stderr, "Panning: %.2f%%\n", i/4.0f);
|
||||
|
||||
cubeb_stream_set_panning(stream, i/4.0f);
|
||||
cubeb_stream_start(stream);
|
||||
delay(400);
|
||||
cubeb_stream_stop(stream);
|
||||
delay(100);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
cubeb_stream_destroy(stream);
|
||||
cubeb_destroy(ctx);
|
||||
synth_destroy(synth);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void run_channel_rate_test()
|
||||
{
|
||||
int channel_values[] = {
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
6,
|
||||
};
|
||||
|
||||
int freq_values[] = {
|
||||
16000,
|
||||
24000,
|
||||
44100,
|
||||
48000,
|
||||
};
|
||||
|
||||
for(int j = 0; j < NELEMS(channel_values); ++j) {
|
||||
for(int i = 0; i < NELEMS(freq_values); ++i) {
|
||||
assert(channel_values[j] < MAX_NUM_CHANNELS);
|
||||
fprintf(stderr, "--------------------------\n");
|
||||
assert(run_test(channel_values[j], freq_values[i], 0) == CUBEB_OK);
|
||||
assert(run_test(channel_values[j], freq_values[i], 1) == CUBEB_OK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main(int /*argc*/, char * /*argv*/[])
|
||||
{
|
||||
#ifdef CUBEB_GECKO_BUILD
|
||||
ScopedXPCOM xpcom("test_audio");
|
||||
#endif
|
||||
|
||||
assert(run_panning_volume_test(0) == CUBEB_OK);
|
||||
assert(run_panning_volume_test(1) == CUBEB_OK);
|
||||
run_channel_rate_test();
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright © 2015 Haakon Sporsheim <haakon.sporsheim@telenordigital.com>
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
/* libcubeb enumerate device test/example.
|
||||
* Prints out a list of devices enumerated. */
|
||||
#ifdef NDEBUG
|
||||
#undef NDEBUG
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cubeb/cubeb.h"
|
||||
|
||||
|
||||
static void
|
||||
print_device_info(cubeb_device_info * info, FILE * f)
|
||||
{
|
||||
char devfmts[64] = "";
|
||||
const char * devtype, * devstate, * devdeffmt;
|
||||
|
||||
switch (info->type) {
|
||||
case CUBEB_DEVICE_TYPE_INPUT:
|
||||
devtype = "input";
|
||||
break;
|
||||
case CUBEB_DEVICE_TYPE_OUTPUT:
|
||||
devtype = "output";
|
||||
break;
|
||||
case CUBEB_DEVICE_TYPE_UNKNOWN:
|
||||
default:
|
||||
devtype = "unknown?";
|
||||
break;
|
||||
};
|
||||
|
||||
switch (info->state) {
|
||||
case CUBEB_DEVICE_STATE_DISABLED:
|
||||
devstate = "disabled";
|
||||
break;
|
||||
case CUBEB_DEVICE_STATE_UNPLUGGED:
|
||||
devstate = "unplugged";
|
||||
break;
|
||||
case CUBEB_DEVICE_STATE_ENABLED:
|
||||
devstate = "enabled";
|
||||
break;
|
||||
default:
|
||||
devstate = "unknown?";
|
||||
break;
|
||||
};
|
||||
|
||||
switch (info->default_format) {
|
||||
case CUBEB_DEVICE_FMT_S16LE:
|
||||
devdeffmt = "S16LE";
|
||||
break;
|
||||
case CUBEB_DEVICE_FMT_S16BE:
|
||||
devdeffmt = "S16BE";
|
||||
break;
|
||||
case CUBEB_DEVICE_FMT_F32LE:
|
||||
devdeffmt = "F32LE";
|
||||
break;
|
||||
case CUBEB_DEVICE_FMT_F32BE:
|
||||
devdeffmt = "F32BE";
|
||||
break;
|
||||
default:
|
||||
devdeffmt = "unknown?";
|
||||
break;
|
||||
};
|
||||
|
||||
if (info->format & CUBEB_DEVICE_FMT_S16LE)
|
||||
strcat(devfmts, " S16LE");
|
||||
if (info->format & CUBEB_DEVICE_FMT_S16BE)
|
||||
strcat(devfmts, " S16BE");
|
||||
if (info->format & CUBEB_DEVICE_FMT_F32LE)
|
||||
strcat(devfmts, " F32LE");
|
||||
if (info->format & CUBEB_DEVICE_FMT_F32BE)
|
||||
strcat(devfmts, " F32BE");
|
||||
|
||||
fprintf(f,
|
||||
"dev: \"%s\"%s\n"
|
||||
"\tName: \"%s\"\n"
|
||||
"\tGroup: \"%s\"\n"
|
||||
"\tVendor: \"%s\"\n"
|
||||
"\tType: %s\n"
|
||||
"\tState: %s\n"
|
||||
"\tCh: %u\n"
|
||||
"\tFormat: %s (0x%x) (default: %s)\n"
|
||||
"\tRate: %u - %u (default: %u)\n"
|
||||
"\tLatency: lo %ums, hi %ums\n",
|
||||
info->device_id, info->preferred ? " (PREFERRED)" : "",
|
||||
info->friendly_name, info->group_id, info->vendor_name,
|
||||
devtype, devstate, info->max_channels,
|
||||
(devfmts[0] == ' ') ? &devfmts[1] : devfmts,
|
||||
(unsigned int)info->format, devdeffmt,
|
||||
info->min_rate, info->max_rate, info->default_rate,
|
||||
info->latency_lo_ms, info->latency_hi_ms);
|
||||
}
|
||||
|
||||
static void
|
||||
print_device_collection(cubeb_device_collection * collection, FILE * f)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0; i < collection->count; i++)
|
||||
print_device_info(collection->device[i], f);
|
||||
}
|
||||
|
||||
static int
|
||||
run_enumerate_devices(void)
|
||||
{
|
||||
int r = CUBEB_OK;
|
||||
cubeb * ctx = NULL;
|
||||
cubeb_device_collection * collection = NULL;
|
||||
|
||||
r = cubeb_init(&ctx, "Cubeb audio test");
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Error initializing cubeb library\n");
|
||||
return r;
|
||||
}
|
||||
|
||||
fprintf(stdout, "Enumerating input devices for backend %s\n",
|
||||
cubeb_get_backend_id(ctx));
|
||||
|
||||
r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &collection);
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Error enumerating devices %d\n", r);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
fprintf(stdout, "Found %u input devices\n", collection->count);
|
||||
print_device_collection(collection, stdout);
|
||||
cubeb_device_collection_destroy(collection);
|
||||
|
||||
fprintf(stdout, "Enumerating output devices for backend %s\n",
|
||||
cubeb_get_backend_id(ctx));
|
||||
|
||||
r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Error enumerating devices %d\n", r);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
fprintf(stdout, "Found %u output devices\n", collection->count);
|
||||
print_device_collection(collection, stdout);
|
||||
cubeb_device_collection_destroy(collection);
|
||||
|
||||
cleanup:
|
||||
cubeb_destroy(ctx);
|
||||
return r;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = run_enumerate_devices();
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright © 2016 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
/* libcubeb api/function test. Loops input back to output and check audio
|
||||
* is flowing. */
|
||||
#ifdef NDEBUG
|
||||
#undef NDEBUG
|
||||
#endif
|
||||
#define _XOPEN_SOURCE 600
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "cubeb/cubeb.h"
|
||||
#include "common.h"
|
||||
#ifdef CUBEB_GECKO_BUILD
|
||||
#include "TestHarness.h"
|
||||
#endif
|
||||
|
||||
#define SAMPLE_FREQUENCY 48000
|
||||
#if (defined(_WIN32) || defined(__WIN32__))
|
||||
#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
|
||||
#define SILENT_SAMPLE 0.0f
|
||||
#else
|
||||
#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
|
||||
#define SILENT_SAMPLE 0
|
||||
#endif
|
||||
|
||||
struct user_state
|
||||
{
|
||||
bool seen_noise;
|
||||
};
|
||||
|
||||
|
||||
|
||||
long data_cb(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
|
||||
{
|
||||
user_state * u = reinterpret_cast<user_state*>(user);
|
||||
#if (defined(_WIN32) || defined(__WIN32__))
|
||||
float *ib = (float *)inputbuffer;
|
||||
float *ob = (float *)outputbuffer;
|
||||
#else
|
||||
short *ib = (short *)inputbuffer;
|
||||
short *ob = (short *)outputbuffer;
|
||||
#endif
|
||||
bool seen_noise = false;
|
||||
|
||||
if (stream == NULL || inputbuffer == NULL || outputbuffer == NULL) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
// Loop back: upmix the single input channel to the two output channels,
|
||||
// checking if there is noise in the process.
|
||||
long output_index = 0;
|
||||
for (long i = 0; i < nframes; i++) {
|
||||
if (ib[i] != SILENT_SAMPLE) {
|
||||
seen_noise = true;
|
||||
}
|
||||
ob[output_index] = ob[output_index + 1] = ib[i];
|
||||
output_index += 2;
|
||||
}
|
||||
|
||||
u->seen_noise |= seen_noise;
|
||||
|
||||
return nframes;
|
||||
}
|
||||
|
||||
void state_cb(cubeb_stream * stream, void * /*user*/, cubeb_state state)
|
||||
{
|
||||
if (stream == NULL)
|
||||
return;
|
||||
|
||||
switch (state) {
|
||||
case CUBEB_STATE_STARTED:
|
||||
printf("stream started\n"); break;
|
||||
case CUBEB_STATE_STOPPED:
|
||||
printf("stream stopped\n"); break;
|
||||
case CUBEB_STATE_DRAINED:
|
||||
printf("stream drained\n"); break;
|
||||
default:
|
||||
printf("unknown stream state %d\n", state);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int main(int /*argc*/, char * /*argv*/[])
|
||||
{
|
||||
#ifdef CUBEB_GECKO_BUILD
|
||||
ScopedXPCOM xpcom("test_duplex");
|
||||
#endif
|
||||
|
||||
cubeb *ctx;
|
||||
cubeb_stream *stream;
|
||||
cubeb_stream_params input_params;
|
||||
cubeb_stream_params output_params;
|
||||
int r;
|
||||
user_state stream_state = { false };
|
||||
uint32_t latency_frames = 0;
|
||||
|
||||
r = cubeb_init(&ctx, "Cubeb duplex example");
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Error initializing cubeb library\n");
|
||||
return r;
|
||||
}
|
||||
|
||||
/* This test needs an available input device, skip it if this host does not
|
||||
* have one. */
|
||||
if (!has_available_input_device(ctx)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* typical user-case: mono input, stereo output, low latency. */
|
||||
input_params.format = STREAM_FORMAT;
|
||||
input_params.rate = 48000;
|
||||
input_params.channels = 1;
|
||||
output_params.format = STREAM_FORMAT;
|
||||
output_params.rate = 48000;
|
||||
output_params.channels = 2;
|
||||
|
||||
r = cubeb_get_min_latency(ctx, output_params, &latency_frames);
|
||||
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Could not get minimal latency\n");
|
||||
return r;
|
||||
}
|
||||
|
||||
r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
|
||||
NULL, &input_params, NULL, &output_params,
|
||||
latency_frames, data_cb, state_cb, &stream_state);
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Error initializing cubeb stream\n");
|
||||
return r;
|
||||
}
|
||||
|
||||
cubeb_stream_start(stream);
|
||||
delay(500);
|
||||
cubeb_stream_stop(stream);
|
||||
|
||||
cubeb_stream_destroy(stream);
|
||||
cubeb_destroy(ctx);
|
||||
|
||||
assert(stream_state.seen_noise);
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
#ifdef NDEBUG
|
||||
#undef NDEBUG
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
#include "cubeb/cubeb.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#ifdef CUBEB_GECKO_BUILD
|
||||
#include "TestHarness.h"
|
||||
#endif
|
||||
|
||||
#define LOG(msg) fprintf(stderr, "%s\n", msg);
|
||||
|
||||
int main(int /*argc*/, char * /*argv*/[])
|
||||
{
|
||||
#ifdef CUBEB_GECKO_BUILD
|
||||
ScopedXPCOM xpcom("test_latency");
|
||||
#endif
|
||||
|
||||
cubeb * ctx = NULL;
|
||||
int r;
|
||||
uint32_t max_channels;
|
||||
uint32_t preferred_rate;
|
||||
uint32_t latency_frames;
|
||||
|
||||
LOG("latency_test start");
|
||||
r = cubeb_init(&ctx, "Cubeb audio test");
|
||||
assert(r == CUBEB_OK && "Cubeb init failed.");
|
||||
LOG("cubeb_init ok");
|
||||
|
||||
r = cubeb_get_max_channel_count(ctx, &max_channels);
|
||||
assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
|
||||
if (r == CUBEB_OK) {
|
||||
assert(max_channels > 0 && "Invalid max channel count.");
|
||||
LOG("cubeb_get_max_channel_count ok");
|
||||
}
|
||||
|
||||
r = cubeb_get_preferred_sample_rate(ctx, &preferred_rate);
|
||||
assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
|
||||
if (r == CUBEB_OK) {
|
||||
assert(preferred_rate > 0 && "Invalid preferred sample rate.");
|
||||
LOG("cubeb_get_preferred_sample_rate ok");
|
||||
}
|
||||
|
||||
cubeb_stream_params params = {
|
||||
CUBEB_SAMPLE_FLOAT32NE,
|
||||
preferred_rate,
|
||||
max_channels
|
||||
};
|
||||
r = cubeb_get_min_latency(ctx, params, &latency_frames);
|
||||
assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
|
||||
if (r == CUBEB_OK) {
|
||||
assert(latency_frames > 0 && "Invalid minimal latency.");
|
||||
LOG("cubeb_get_min_latency ok");
|
||||
}
|
||||
|
||||
cubeb_destroy(ctx);
|
||||
LOG("cubeb_destroy ok");
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright © 2016 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
/* libcubeb api/function test. Record the mic and check there is sound. */
|
||||
#ifdef NDEBUG
|
||||
#undef NDEBUG
|
||||
#endif
|
||||
#define _XOPEN_SOURCE 600
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "cubeb/cubeb.h"
|
||||
#include "common.h"
|
||||
#ifdef CUBEB_GECKO_BUILD
|
||||
#include "TestHarness.h"
|
||||
#endif
|
||||
|
||||
#define SAMPLE_FREQUENCY 48000
|
||||
#if (defined(_WIN32) || defined(__WIN32__))
|
||||
#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
|
||||
#else
|
||||
#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
|
||||
#endif
|
||||
|
||||
struct user_state
|
||||
{
|
||||
bool seen_noise;
|
||||
};
|
||||
|
||||
long data_cb(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
|
||||
{
|
||||
user_state * u = reinterpret_cast<user_state*>(user);
|
||||
#if STREAM_FORMAT != CUBEB_SAMPLE_FLOAT32LE
|
||||
short *b = (short *)inputbuffer;
|
||||
#else
|
||||
float *b = (float *)inputbuffer;
|
||||
#endif
|
||||
|
||||
if (stream == NULL || inputbuffer == NULL || outputbuffer != NULL) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
bool seen_noise = false;
|
||||
for (long i = 0; i < nframes; i++) {
|
||||
if (b[i] != 0.0) {
|
||||
seen_noise = true;
|
||||
}
|
||||
}
|
||||
|
||||
u->seen_noise |= seen_noise;
|
||||
|
||||
return nframes;
|
||||
}
|
||||
|
||||
void state_cb(cubeb_stream * stream, void * /*user*/, cubeb_state state)
|
||||
{
|
||||
if (stream == NULL)
|
||||
return;
|
||||
|
||||
switch (state) {
|
||||
case CUBEB_STATE_STARTED:
|
||||
printf("stream started\n"); break;
|
||||
case CUBEB_STATE_STOPPED:
|
||||
printf("stream stopped\n"); break;
|
||||
case CUBEB_STATE_DRAINED:
|
||||
printf("stream drained\n"); break;
|
||||
default:
|
||||
printf("unknown stream state %d\n", state);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int main(int /*argc*/, char * /*argv*/[])
|
||||
{
|
||||
#ifdef CUBEB_GECKO_BUILD
|
||||
ScopedXPCOM xpcom("test_record");
|
||||
#endif
|
||||
|
||||
cubeb *ctx;
|
||||
cubeb_stream *stream;
|
||||
cubeb_stream_params params;
|
||||
int r;
|
||||
user_state stream_state = { false };
|
||||
|
||||
r = cubeb_init(&ctx, "Cubeb record example");
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Error initializing cubeb library\n");
|
||||
return r;
|
||||
}
|
||||
|
||||
/* This test needs an available input device, skip it if this host does not
|
||||
* have one. */
|
||||
if (!has_available_input_device(ctx)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
params.format = STREAM_FORMAT;
|
||||
params.rate = SAMPLE_FREQUENCY;
|
||||
params.channels = 1;
|
||||
|
||||
r = cubeb_stream_init(ctx, &stream, "Cubeb record (mono)", NULL, ¶ms, NULL, nullptr,
|
||||
4096, data_cb, state_cb, &stream_state);
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Error initializing cubeb stream\n");
|
||||
return r;
|
||||
}
|
||||
|
||||
cubeb_stream_start(stream);
|
||||
delay(500);
|
||||
cubeb_stream_stop(stream);
|
||||
|
||||
cubeb_stream_destroy(stream);
|
||||
cubeb_destroy(ctx);
|
||||
|
||||
assert(stream_state.seen_noise);
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
@@ -0,0 +1,554 @@
|
||||
/*
|
||||
* Copyright © 2016 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif // NOMINMAX
|
||||
|
||||
#ifdef NDEBUG
|
||||
#undef NDEBUG
|
||||
#endif
|
||||
#include "cubeb_resampler_internal.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
/* Windows cmath USE_MATH_DEFINE thing... */
|
||||
const float PI = 3.14159265359f;
|
||||
|
||||
/* Testing all sample rates is very long, so if THOROUGH_TESTING is not defined,
|
||||
* only part of the test suite is ran. */
|
||||
#ifdef THOROUGH_TESTING
|
||||
/* Some standard sample rates we're testing with. */
|
||||
const uint32_t sample_rates[] = {
|
||||
8000,
|
||||
16000,
|
||||
32000,
|
||||
44100,
|
||||
48000,
|
||||
88200,
|
||||
96000,
|
||||
192000
|
||||
};
|
||||
/* The maximum number of channels we're resampling. */
|
||||
const uint32_t max_channels = 2;
|
||||
/* The minimum an maximum number of milliseconds we're resampling for. This is
|
||||
* used to simulate the fact that the audio stream is resampled in chunks,
|
||||
* because audio is delivered using callbacks. */
|
||||
const uint32_t min_chunks = 10; /* ms */
|
||||
const uint32_t max_chunks = 30; /* ms */
|
||||
const uint32_t chunk_increment = 1;
|
||||
|
||||
#else
|
||||
|
||||
const uint32_t sample_rates[] = {
|
||||
8000,
|
||||
44100,
|
||||
48000,
|
||||
};
|
||||
const uint32_t max_channels = 2;
|
||||
const uint32_t min_chunks = 10; /* ms */
|
||||
const uint32_t max_chunks = 30; /* ms */
|
||||
const uint32_t chunk_increment = 10;
|
||||
#endif
|
||||
|
||||
#define DUMP_ARRAYS
|
||||
#ifdef DUMP_ARRAYS
|
||||
/**
|
||||
* Files produced by dump(...) can be converted to .wave files using:
|
||||
*
|
||||
* sox -c <channel_count> -r <rate> -e float -b 32 file.raw file.wav
|
||||
*
|
||||
* for floating-point audio, or:
|
||||
*
|
||||
* sox -c <channel_count> -r <rate> -e unsigned -b 16 file.raw file.wav
|
||||
*
|
||||
* for 16bit integer audio.
|
||||
*/
|
||||
|
||||
/* Use the correct implementation of fopen, depending on the platform. */
|
||||
void fopen_portable(FILE ** f, const char * name, const char * mode)
|
||||
{
|
||||
#ifdef WIN32
|
||||
fopen_s(f, name, mode);
|
||||
#else
|
||||
*f = fopen(name, mode);
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void dump(const char * name, T * frames, size_t count)
|
||||
{
|
||||
FILE * file;
|
||||
fopen_portable(&file, name, "wb");
|
||||
|
||||
if (!file) {
|
||||
fprintf(stderr, "error opening %s\n", name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (count != fwrite(frames, sizeof(T), count, file)) {
|
||||
fprintf(stderr, "error writing to %s\n", name);
|
||||
}
|
||||
fclose(file);
|
||||
}
|
||||
#else
|
||||
template<typename T>
|
||||
void dump(const char * name, T * frames, size_t count)
|
||||
{ }
|
||||
#endif
|
||||
|
||||
// The more the ratio is far from 1, the more we accept a big error.
|
||||
float epsilon_tweak_ratio(float ratio)
|
||||
{
|
||||
return ratio >= 1 ? ratio : 1 / ratio;
|
||||
}
|
||||
|
||||
// Epsilon values for comparing resampled data to expected data.
|
||||
// The bigger the resampling ratio is, the more lax we are about errors.
|
||||
template<typename T>
|
||||
T epsilon(float ratio);
|
||||
|
||||
template<>
|
||||
float epsilon(float ratio) {
|
||||
return 0.08f * epsilon_tweak_ratio(ratio);
|
||||
}
|
||||
|
||||
template<>
|
||||
int16_t epsilon(float ratio) {
|
||||
return static_cast<int16_t>(10 * epsilon_tweak_ratio(ratio));
|
||||
}
|
||||
|
||||
void test_delay_lines(uint32_t delay_frames, uint32_t channels, uint32_t chunk_ms)
|
||||
{
|
||||
const size_t length_s = 2;
|
||||
const size_t rate = 44100;
|
||||
const size_t length_frames = rate * length_s;
|
||||
delay_line<float> delay(delay_frames, channels);
|
||||
auto_array<float> input;
|
||||
auto_array<float> output;
|
||||
uint32_t chunk_length = channels * chunk_ms * rate / 1000;
|
||||
uint32_t output_offset = 0;
|
||||
uint32_t channel = 0;
|
||||
|
||||
/** Generate diracs every 100 frames, and check they are delayed. */
|
||||
input.push_silence(length_frames * channels);
|
||||
for (uint32_t i = 0; i < input.length() - 1; i+=100) {
|
||||
input.data()[i + channel] = 0.5;
|
||||
channel = (channel + 1) % channels;
|
||||
}
|
||||
dump("input.raw", input.data(), input.length());
|
||||
while(input.length()) {
|
||||
uint32_t to_pop = std::min<uint32_t>(input.length(), chunk_length * channels);
|
||||
float * in = delay.input_buffer(to_pop / channels);
|
||||
input.pop(in, to_pop);
|
||||
delay.written(to_pop / channels);
|
||||
output.push_silence(to_pop);
|
||||
delay.output(output.data() + output_offset, to_pop / channels);
|
||||
output_offset += to_pop;
|
||||
}
|
||||
|
||||
// Check the diracs have been shifted by `delay_frames` frames.
|
||||
for (uint32_t i = 0; i < output.length() - delay_frames * channels + 1; i+=100) {
|
||||
assert(output.data()[i + channel + delay_frames * channels] == 0.5);
|
||||
channel = (channel + 1) % channels;
|
||||
}
|
||||
|
||||
dump("output.raw", output.data(), output.length());
|
||||
}
|
||||
/**
|
||||
* This takes sine waves with a certain `channels` count, `source_rate`, and
|
||||
* resample them, by chunk of `chunk_duration` milliseconds, to `target_rate`.
|
||||
* Then a sample-wise comparison is performed against a sine wave generated at
|
||||
* the correct rate.
|
||||
*/
|
||||
template<typename T>
|
||||
void test_resampler_one_way(uint32_t channels, uint32_t source_rate, uint32_t target_rate, float chunk_duration)
|
||||
{
|
||||
size_t chunk_duration_in_source_frames = static_cast<uint32_t>(ceil(chunk_duration * source_rate / 1000.));
|
||||
float resampling_ratio = static_cast<float>(source_rate) / target_rate;
|
||||
cubeb_resampler_speex_one_way<T> resampler(channels, source_rate, target_rate, 3);
|
||||
auto_array<T> source(channels * source_rate * 10);
|
||||
auto_array<T> destination(channels * target_rate * 10);
|
||||
auto_array<T> expected(channels * target_rate * 10);
|
||||
uint32_t phase_index = 0;
|
||||
uint32_t offset = 0;
|
||||
const uint32_t buf_len = 2; /* seconds */
|
||||
|
||||
// generate a sine wave in each channel, at the source sample rate
|
||||
source.push_silence(channels * source_rate * buf_len);
|
||||
while(offset != source.length()) {
|
||||
float p = phase_index++ / static_cast<float>(source_rate);
|
||||
for (uint32_t j = 0; j < channels; j++) {
|
||||
source.data()[offset++] = 0.5 * sin(440. * 2 * PI * p);
|
||||
}
|
||||
}
|
||||
|
||||
dump("input.raw", source.data(), source.length());
|
||||
|
||||
expected.push_silence(channels * target_rate * buf_len);
|
||||
// generate a sine wave in each channel, at the target sample rate.
|
||||
// Insert silent samples at the beginning to account for the resampler latency.
|
||||
offset = resampler.latency() * channels;
|
||||
for (uint32_t i = 0; i < offset; i++) {
|
||||
expected.data()[i] = 0.0f;
|
||||
}
|
||||
phase_index = 0;
|
||||
while (offset != expected.length()) {
|
||||
float p = phase_index++ / static_cast<float>(target_rate);
|
||||
for (uint32_t j = 0; j < channels; j++) {
|
||||
expected.data()[offset++] = 0.5 * sin(440. * 2 * PI * p);
|
||||
}
|
||||
}
|
||||
|
||||
dump("expected.raw", expected.data(), expected.length());
|
||||
|
||||
// resample by chunk
|
||||
uint32_t write_offset = 0;
|
||||
destination.push_silence(channels * target_rate * buf_len);
|
||||
while (write_offset < destination.length())
|
||||
{
|
||||
size_t output_frames = static_cast<uint32_t>(floor(chunk_duration_in_source_frames / resampling_ratio));
|
||||
uint32_t input_frames = resampler.input_needed_for_output(output_frames);
|
||||
resampler.input(source.data(), input_frames);
|
||||
source.pop(nullptr, input_frames * channels);
|
||||
resampler.output(destination.data() + write_offset,
|
||||
std::min(output_frames, (destination.length() - write_offset) / channels));
|
||||
write_offset += output_frames * channels;
|
||||
}
|
||||
|
||||
dump("output.raw", destination.data(), expected.length());
|
||||
|
||||
// compare, taking the latency into account
|
||||
bool fuzzy_equal = true;
|
||||
for (uint32_t i = resampler.latency() + 1; i < expected.length(); i++) {
|
||||
float diff = fabs(expected.data()[i] - destination.data()[i]);
|
||||
if (diff > epsilon<T>(resampling_ratio)) {
|
||||
fprintf(stderr, "divergence at %d: %f %f (delta %f)\n", i, expected.data()[i], destination.data()[i], diff);
|
||||
fuzzy_equal = false;
|
||||
}
|
||||
}
|
||||
assert(fuzzy_equal);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
cubeb_sample_format cubeb_format();
|
||||
|
||||
template<>
|
||||
cubeb_sample_format cubeb_format<float>()
|
||||
{
|
||||
return CUBEB_SAMPLE_FLOAT32NE;
|
||||
}
|
||||
|
||||
template<>
|
||||
cubeb_sample_format cubeb_format<short>()
|
||||
{
|
||||
return CUBEB_SAMPLE_S16NE;
|
||||
}
|
||||
|
||||
struct osc_state {
|
||||
osc_state()
|
||||
: input_phase_index(0)
|
||||
, output_phase_index(0)
|
||||
, output_offset(0)
|
||||
, input_channels(0)
|
||||
, output_channels(0)
|
||||
{}
|
||||
uint32_t input_phase_index;
|
||||
uint32_t max_output_phase_index;
|
||||
uint32_t output_phase_index;
|
||||
uint32_t output_offset;
|
||||
uint32_t input_channels;
|
||||
uint32_t output_channels;
|
||||
uint32_t output_rate;
|
||||
uint32_t target_rate;
|
||||
auto_array<float> input;
|
||||
auto_array<float> output;
|
||||
};
|
||||
|
||||
uint32_t fill_with_sine(float * buf, uint32_t rate, uint32_t channels,
|
||||
uint32_t frames, uint32_t initial_phase)
|
||||
{
|
||||
uint32_t offset = 0;
|
||||
for (uint32_t i = 0; i < frames; i++) {
|
||||
float p = initial_phase++ / static_cast<float>(rate);
|
||||
for (uint32_t j = 0; j < channels; j++) {
|
||||
buf[offset++] = 0.5 * sin(440. * 2 * PI * p);
|
||||
}
|
||||
}
|
||||
return initial_phase;
|
||||
}
|
||||
|
||||
long data_cb(cubeb_stream * /*stm*/, void * user_ptr,
|
||||
const void * input_buffer, void * output_buffer, long frame_count)
|
||||
{
|
||||
osc_state * state = reinterpret_cast<osc_state*>(user_ptr);
|
||||
const float * in = reinterpret_cast<const float*>(input_buffer);
|
||||
float * out = reinterpret_cast<float*>(output_buffer);
|
||||
|
||||
|
||||
state->input.push(in, frame_count * state->input_channels);
|
||||
|
||||
/* Check how much output frames we need to write */
|
||||
uint32_t remaining = state->max_output_phase_index - state->output_phase_index;
|
||||
uint32_t to_write = std::min<uint32_t>(remaining, frame_count);
|
||||
state->output_phase_index = fill_with_sine(out,
|
||||
state->target_rate,
|
||||
state->output_channels,
|
||||
to_write,
|
||||
state->output_phase_index);
|
||||
|
||||
return to_write;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool array_fuzzy_equal(const auto_array<T>& lhs, const auto_array<T>& rhs, T epsi)
|
||||
{
|
||||
uint32_t len = std::min(lhs.length(), rhs.length());
|
||||
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
if (fabs(lhs.at(i) - rhs.at(i)) > epsi) {
|
||||
std::cout << "not fuzzy equal at index: " << i
|
||||
<< " lhs: " << lhs.at(i) << " rhs: " << rhs.at(i)
|
||||
<< " delta: " << fabs(lhs.at(i) - rhs.at(i))
|
||||
<< " epsilon: "<< epsi << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void test_resampler_duplex(uint32_t input_channels, uint32_t output_channels,
|
||||
uint32_t input_rate, uint32_t output_rate,
|
||||
uint32_t target_rate, float chunk_duration)
|
||||
{
|
||||
cubeb_stream_params input_params;
|
||||
cubeb_stream_params output_params;
|
||||
osc_state state;
|
||||
|
||||
input_params.format = output_params.format = cubeb_format<T>();
|
||||
state.input_channels = input_params.channels = input_channels;
|
||||
state.output_channels = output_params.channels = output_channels;
|
||||
input_params.rate = input_rate;
|
||||
state.output_rate = output_params.rate = output_rate;
|
||||
state.target_rate = target_rate;
|
||||
long got;
|
||||
|
||||
cubeb_resampler * resampler =
|
||||
cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params, target_rate,
|
||||
data_cb, (void*)&state, CUBEB_RESAMPLER_QUALITY_VOIP);
|
||||
|
||||
long latency = cubeb_resampler_latency(resampler);
|
||||
|
||||
const uint32_t duration_s = 2;
|
||||
int32_t duration_frames = duration_s * target_rate;
|
||||
uint32_t input_array_frame_count = ceil(chunk_duration * input_rate / 1000) + ceilf(static_cast<float>(input_rate) / target_rate) * 2;
|
||||
uint32_t output_array_frame_count = chunk_duration * output_rate / 1000;
|
||||
auto_array<float> input_buffer(input_channels * input_array_frame_count);
|
||||
auto_array<float> output_buffer(output_channels * output_array_frame_count);
|
||||
auto_array<float> expected_resampled_input(input_channels * duration_frames);
|
||||
auto_array<float> expected_resampled_output(output_channels * output_rate * duration_s);
|
||||
|
||||
state.max_output_phase_index = duration_s * target_rate;
|
||||
|
||||
expected_resampled_input.push_silence(input_channels * duration_frames);
|
||||
expected_resampled_output.push_silence(output_channels * output_rate * duration_s);
|
||||
|
||||
/* expected output is a 440Hz sine wave at 16kHz */
|
||||
fill_with_sine(expected_resampled_input.data() + latency,
|
||||
target_rate, input_channels, duration_frames - latency, 0);
|
||||
/* expected output is a 440Hz sine wave at 32kHz */
|
||||
fill_with_sine(expected_resampled_output.data() + latency,
|
||||
output_rate, output_channels, output_rate * duration_s - latency, 0);
|
||||
|
||||
|
||||
while (state.output_phase_index != state.max_output_phase_index) {
|
||||
uint32_t leftover_samples = input_buffer.length() * input_channels;
|
||||
input_buffer.reserve(input_array_frame_count);
|
||||
state.input_phase_index = fill_with_sine(input_buffer.data() + leftover_samples,
|
||||
input_rate,
|
||||
input_channels,
|
||||
input_array_frame_count - leftover_samples,
|
||||
state.input_phase_index);
|
||||
long input_consumed = input_array_frame_count;
|
||||
input_buffer.set_length(input_array_frame_count);
|
||||
|
||||
got = cubeb_resampler_fill(resampler,
|
||||
input_buffer.data(), &input_consumed,
|
||||
output_buffer.data(), output_array_frame_count);
|
||||
|
||||
/* handle leftover input */
|
||||
if (input_array_frame_count != static_cast<uint32_t>(input_consumed)) {
|
||||
input_buffer.pop(nullptr, input_consumed * input_channels);
|
||||
} else {
|
||||
input_buffer.clear();
|
||||
}
|
||||
|
||||
state.output.push(output_buffer.data(), got * state.output_channels);
|
||||
}
|
||||
|
||||
dump("input_expected.raw", expected_resampled_input.data(), expected_resampled_input.length());
|
||||
dump("output_expected.raw", expected_resampled_output.data(), expected_resampled_output.length());
|
||||
dump("input.raw", state.input.data(), state.input.length());
|
||||
dump("output.raw", state.output.data(), state.output.length());
|
||||
|
||||
assert(array_fuzzy_equal(state.input, expected_resampled_input, epsilon<T>(input_rate/target_rate)));
|
||||
assert(array_fuzzy_equal(state.output, expected_resampled_output, epsilon<T>(output_rate/target_rate)));
|
||||
|
||||
cubeb_resampler_destroy(resampler);
|
||||
}
|
||||
|
||||
#define array_size(x) (sizeof(x) / sizeof(x[0]))
|
||||
|
||||
void test_resamplers_one_way()
|
||||
{
|
||||
/* Test one way resamplers */
|
||||
for (uint32_t channels = 1; channels <= max_channels; channels++) {
|
||||
for (uint32_t source_rate = 0; source_rate < array_size(sample_rates); source_rate++) {
|
||||
for (uint32_t dest_rate = 0; dest_rate < array_size(sample_rates); dest_rate++) {
|
||||
for (uint32_t chunk_duration = min_chunks; chunk_duration < max_chunks; chunk_duration+=chunk_increment) {
|
||||
printf("one_way: channels: %d, source_rate: %d, dest_rate: %d, chunk_duration: %d\n",
|
||||
channels, sample_rates[source_rate], sample_rates[dest_rate], chunk_duration);
|
||||
test_resampler_one_way<float>(channels, sample_rates[source_rate],
|
||||
sample_rates[dest_rate], chunk_duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test_resamplers_duplex()
|
||||
{
|
||||
/* Test duplex resamplers */
|
||||
for (uint32_t input_channels = 1; input_channels <= max_channels; input_channels++) {
|
||||
for (uint32_t output_channels = 1; output_channels <= max_channels; output_channels++) {
|
||||
for (uint32_t source_rate_input = 0; source_rate_input < array_size(sample_rates); source_rate_input++) {
|
||||
for (uint32_t source_rate_output = 0; source_rate_output < array_size(sample_rates); source_rate_output++) {
|
||||
for (uint32_t dest_rate = 0; dest_rate < array_size(sample_rates); dest_rate++) {
|
||||
for (uint32_t chunk_duration = min_chunks; chunk_duration < max_chunks; chunk_duration+=chunk_increment) {
|
||||
printf("input channels:%d output_channels:%d input_rate:%d "
|
||||
"output_rate:%d target_rate:%d chunk_ms:%d\n",
|
||||
input_channels, output_channels,
|
||||
sample_rates[source_rate_input],
|
||||
sample_rates[source_rate_output],
|
||||
sample_rates[dest_rate],
|
||||
chunk_duration);
|
||||
test_resampler_duplex<float>(input_channels, output_channels,
|
||||
sample_rates[source_rate_input],
|
||||
sample_rates[source_rate_output],
|
||||
sample_rates[dest_rate],
|
||||
chunk_duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test_delay_line()
|
||||
{
|
||||
for (uint32_t channel = 1; channel <= 2; channel++) {
|
||||
for (uint32_t delay_frames = 4; delay_frames <= 40; delay_frames+=chunk_increment) {
|
||||
for (uint32_t chunk_size = 10; chunk_size <= 30; chunk_size++) {
|
||||
printf("channel: %d, delay_frames: %d, chunk_size: %d\n",
|
||||
channel, delay_frames, chunk_size);
|
||||
test_delay_lines(delay_frames, channel, chunk_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long test_output_only_noop_data_cb(cubeb_stream * /*stm*/, void * /*user_ptr*/,
|
||||
const void * input_buffer,
|
||||
void * output_buffer, long frame_count)
|
||||
{
|
||||
assert(output_buffer);
|
||||
assert(!input_buffer);
|
||||
return frame_count;
|
||||
}
|
||||
|
||||
void test_output_only_noop()
|
||||
{
|
||||
cubeb_stream_params output_params;
|
||||
int target_rate;
|
||||
|
||||
output_params.rate = 44100;
|
||||
output_params.channels = 1;
|
||||
output_params.format = CUBEB_SAMPLE_FLOAT32NE;
|
||||
target_rate = output_params.rate;
|
||||
|
||||
cubeb_resampler * resampler =
|
||||
cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params, target_rate,
|
||||
test_output_only_noop_data_cb, nullptr,
|
||||
CUBEB_RESAMPLER_QUALITY_VOIP);
|
||||
|
||||
const long out_frames = 128;
|
||||
float out_buffer[out_frames];
|
||||
long got;
|
||||
|
||||
got = cubeb_resampler_fill(resampler, nullptr, nullptr,
|
||||
out_buffer, out_frames);
|
||||
|
||||
assert(got == out_frames);
|
||||
|
||||
cubeb_resampler_destroy(resampler);
|
||||
}
|
||||
|
||||
long test_drain_data_cb(cubeb_stream * /*stm*/, void * /*user_ptr*/,
|
||||
const void * input_buffer,
|
||||
void * output_buffer, long frame_count)
|
||||
{
|
||||
assert(output_buffer);
|
||||
assert(!input_buffer);
|
||||
return frame_count - 10;
|
||||
}
|
||||
|
||||
void test_resampler_drain()
|
||||
{
|
||||
cubeb_stream_params output_params;
|
||||
int target_rate;
|
||||
|
||||
output_params.rate = 44100;
|
||||
output_params.channels = 1;
|
||||
output_params.format = CUBEB_SAMPLE_FLOAT32NE;
|
||||
target_rate = 48000;
|
||||
|
||||
cubeb_resampler * resampler =
|
||||
cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params, target_rate,
|
||||
test_drain_data_cb, nullptr,
|
||||
CUBEB_RESAMPLER_QUALITY_VOIP);
|
||||
|
||||
const long out_frames = 128;
|
||||
float out_buffer[out_frames];
|
||||
long got;
|
||||
|
||||
do {
|
||||
got = cubeb_resampler_fill(resampler, nullptr, nullptr,
|
||||
out_buffer, out_frames);
|
||||
} while (got == out_frames);
|
||||
|
||||
/* If the above is not an infinite loop, the drain was a success, just mark
|
||||
* this test as such. */
|
||||
assert(true);
|
||||
|
||||
cubeb_resampler_destroy(resampler);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
test_resamplers_one_way();
|
||||
test_delay_line();
|
||||
// This is disabled because the latency estimation in the resampler code is
|
||||
// slightly off so we can generate expected vectors.
|
||||
// test_resamplers_duplex();
|
||||
test_output_only_noop();
|
||||
test_resampler_drain();
|
||||
|
||||
return 0;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,35 +6,42 @@
|
||||
*/
|
||||
|
||||
/* libcubeb api/function test. Plays a simple tone. */
|
||||
#include "gtest/gtest.h"
|
||||
#if !defined(_XOPEN_SOURCE)
|
||||
#define _XOPEN_SOURCE 600
|
||||
#ifdef NDEBUG
|
||||
#undef NDEBUG
|
||||
#endif
|
||||
#define _XOPEN_SOURCE 600
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <memory>
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "cubeb/cubeb.h"
|
||||
#include <atomic>
|
||||
|
||||
//#define ENABLE_NORMAL_LOG
|
||||
//#define ENABLE_VERBOSE_LOG
|
||||
#include "common.h"
|
||||
|
||||
#ifdef CUBEB_GECKO_BUILD
|
||||
#include "TestHarness.h"
|
||||
#endif
|
||||
|
||||
#define SAMPLE_FREQUENCY 48000
|
||||
#if (defined(_WIN32) || defined(__WIN32__))
|
||||
#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
|
||||
#else
|
||||
#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
|
||||
#endif
|
||||
|
||||
/* store the phase of the generated waveform */
|
||||
struct cb_user_data {
|
||||
std::atomic<long> position;
|
||||
long position;
|
||||
};
|
||||
|
||||
long data_cb_tone(cubeb_stream *stream, void *user, const void* /*inputbuffer*/, void *outputbuffer, long nframes)
|
||||
long data_cb(cubeb_stream *stream, void *user, const void* /*inputbuffer*/, void *outputbuffer, long nframes)
|
||||
{
|
||||
struct cb_user_data *u = (struct cb_user_data *)user;
|
||||
#if (defined(_WIN32) || defined(__WIN32__))
|
||||
float *b = (float *)outputbuffer;
|
||||
#else
|
||||
short *b = (short *)outputbuffer;
|
||||
#endif
|
||||
float t1, t2;
|
||||
int i;
|
||||
|
||||
@@ -46,12 +53,21 @@ long data_cb_tone(cubeb_stream *stream, void *user, const void* /*inputbuffer*/,
|
||||
/* North American dial tone */
|
||||
t1 = sin(2*M_PI*(i + u->position)*350/SAMPLE_FREQUENCY);
|
||||
t2 = sin(2*M_PI*(i + u->position)*440/SAMPLE_FREQUENCY);
|
||||
#if (defined(_WIN32) || defined(__WIN32__))
|
||||
b[i] = 0.5 * t1;
|
||||
b[i] += 0.5 * t2;
|
||||
#else
|
||||
b[i] = (SHRT_MAX / 2) * t1;
|
||||
b[i] += (SHRT_MAX / 2) * t2;
|
||||
#endif
|
||||
/* European dial tone */
|
||||
/*
|
||||
t1 = sin(2*M_PI*(i + u->position)*425/SAMPLE_FREQUENCY);
|
||||
#if (defined(_WIN32) || defined(__WIN32__))
|
||||
b[i] = t1;
|
||||
#else
|
||||
b[i] = SHRT_MAX * t1;
|
||||
#endif
|
||||
*/
|
||||
}
|
||||
/* remember our phase to avoid clicking on buffer transitions */
|
||||
@@ -61,7 +77,7 @@ long data_cb_tone(cubeb_stream *stream, void *user, const void* /*inputbuffer*/,
|
||||
return nframes;
|
||||
}
|
||||
|
||||
void state_cb_tone(cubeb_stream *stream, void *user, cubeb_state state)
|
||||
void state_cb(cubeb_stream *stream, void *user, cubeb_state state)
|
||||
{
|
||||
struct cb_user_data *u = (struct cb_user_data *)user;
|
||||
|
||||
@@ -70,52 +86,64 @@ void state_cb_tone(cubeb_stream *stream, void *user, cubeb_state state)
|
||||
|
||||
switch (state) {
|
||||
case CUBEB_STATE_STARTED:
|
||||
fprintf(stderr, "stream started\n"); break;
|
||||
printf("stream started\n"); break;
|
||||
case CUBEB_STATE_STOPPED:
|
||||
fprintf(stderr, "stream stopped\n"); break;
|
||||
printf("stream stopped\n"); break;
|
||||
case CUBEB_STATE_DRAINED:
|
||||
fprintf(stderr, "stream drained\n"); break;
|
||||
printf("stream drained\n"); break;
|
||||
default:
|
||||
fprintf(stderr, "unknown stream state %d\n", state);
|
||||
printf("unknown stream state %d\n", state);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
TEST(cubeb, tone)
|
||||
int main(int /*argc*/, char * /*argv*/[])
|
||||
{
|
||||
#ifdef CUBEB_GECKO_BUILD
|
||||
ScopedXPCOM xpcom("test_tone");
|
||||
#endif
|
||||
|
||||
cubeb *ctx;
|
||||
cubeb_stream *stream;
|
||||
cubeb_stream_params params;
|
||||
struct cb_user_data *user_data;
|
||||
int r;
|
||||
|
||||
r = common_init(&ctx, "Cubeb tone example");
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
|
||||
|
||||
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
|
||||
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
|
||||
r = cubeb_init(&ctx, "Cubeb tone example");
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Error initializing cubeb library\n");
|
||||
return r;
|
||||
}
|
||||
|
||||
params.format = STREAM_FORMAT;
|
||||
params.rate = SAMPLE_FREQUENCY;
|
||||
params.channels = 1;
|
||||
params.layout = CUBEB_LAYOUT_MONO;
|
||||
params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
std::unique_ptr<cb_user_data> user_data(new cb_user_data());
|
||||
ASSERT_TRUE(!!user_data) << "Error allocating user data";
|
||||
|
||||
user_data = (struct cb_user_data *) malloc(sizeof(*user_data));
|
||||
if (user_data == NULL) {
|
||||
fprintf(stderr, "Error allocating user data\n");
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
user_data->position = 0;
|
||||
|
||||
r = cubeb_stream_init(ctx, &stream, "Cubeb tone (mono)", NULL, NULL, NULL, ¶ms,
|
||||
4096, data_cb_tone, state_cb_tone, user_data.get());
|
||||
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
|
||||
|
||||
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
|
||||
cleanup_stream_at_exit(stream, cubeb_stream_destroy);
|
||||
4096, data_cb, state_cb, user_data);
|
||||
if (r != CUBEB_OK) {
|
||||
fprintf(stderr, "Error initializing cubeb stream\n");
|
||||
return r;
|
||||
}
|
||||
|
||||
cubeb_stream_start(stream);
|
||||
delay(5000);
|
||||
delay(500);
|
||||
cubeb_stream_stop(stream);
|
||||
|
||||
ASSERT_TRUE(user_data->position.load());
|
||||
cubeb_stream_destroy(stream);
|
||||
cubeb_destroy(ctx);
|
||||
|
||||
assert(user_data->position);
|
||||
|
||||
free(user_data);
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#include <cassert>
|
||||
#include "cubeb_utils.h"
|
||||
|
||||
int test_auto_array()
|
||||
{
|
||||
auto_array<uint32_t> array;
|
||||
auto_array<uint32_t> array2(10);
|
||||
uint32_t a[10];
|
||||
|
||||
assert(array2.length() == 0);
|
||||
assert(array2.capacity() == 10);
|
||||
|
||||
|
||||
for (uint32_t i = 0; i < 10; i++) {
|
||||
a[i] = i;
|
||||
}
|
||||
|
||||
assert(array.capacity() == 0);
|
||||
assert(array.length() == 0);
|
||||
|
||||
array.push(a, 10);
|
||||
|
||||
assert(!array.reserve(9));
|
||||
|
||||
for (uint32_t i = 0; i < 10; i++) {
|
||||
assert(array.data()[i] == i);
|
||||
}
|
||||
|
||||
assert(array.capacity() == 10);
|
||||
assert(array.length() == 10);
|
||||
|
||||
uint32_t b[10];
|
||||
|
||||
array.pop(b, 5);
|
||||
|
||||
assert(array.capacity() == 10);
|
||||
assert(array.length() == 5);
|
||||
for (uint32_t i = 0; i < 5; i++) {
|
||||
assert(b[i] == i);
|
||||
assert(array.data()[i] == 5 + i);
|
||||
}
|
||||
uint32_t* bb = b + 5;
|
||||
array.pop(bb, 5);
|
||||
|
||||
assert(array.capacity() == 10);
|
||||
assert(array.length() == 0);
|
||||
for (uint32_t i = 0; i < 5; i++) {
|
||||
assert(bb[i] == 5 + i);
|
||||
}
|
||||
|
||||
assert(!array.pop(nullptr, 1));
|
||||
|
||||
array.push(a, 10);
|
||||
array.push(a, 10);
|
||||
|
||||
for (uint32_t j = 0; j < 2; j++) {
|
||||
for (uint32_t i = 0; i < 10; i++) {
|
||||
assert(array.data()[10 * j + i] == i);
|
||||
}
|
||||
}
|
||||
assert(array.length() == 20);
|
||||
assert(array.capacity() == 20);
|
||||
array.pop(nullptr, 5);
|
||||
|
||||
for (uint32_t i = 0; i < 5; i++) {
|
||||
assert(array.data()[i] == 5 + i);
|
||||
}
|
||||
|
||||
assert(array.length() == 15);
|
||||
assert(array.capacity() == 20);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
test_auto_array();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
From 46d12e9ae6fa9c233bc32812b13185ee7df8d3fd Mon Sep 17 00:00:00 2001
|
||||
From: Paul Adenot <paul@paul.cx>
|
||||
Date: Thu, 10 Nov 2016 06:20:16 +0100
|
||||
Subject: [PATCH] Prevent underflowing the number of input frames needed in
|
||||
input when resampling. (#188)
|
||||
|
||||
---
|
||||
src/cubeb_resampler_internal.h | 12 +++++++++---
|
||||
1 file changed, 9 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/cubeb_resampler_internal.h b/src/cubeb_resampler_internal.h
|
||||
index e165cc2..3c37a04 100644
|
||||
--- a/src/cubeb_resampler_internal.h
|
||||
+++ b/src/cubeb_resampler_internal.h
|
||||
@@ -263,9 +263,15 @@ public:
|
||||
* number of output frames will be exactly equal. */
|
||||
uint32_t input_needed_for_output(uint32_t output_frame_count)
|
||||
{
|
||||
- return (uint32_t)ceilf((output_frame_count - samples_to_frames(resampling_out_buffer.length()))
|
||||
- * resampling_ratio);
|
||||
-
|
||||
+ int32_t unresampled_frames_left = samples_to_frames(resampling_in_buffer.length());
|
||||
+ int32_t resampled_frames_left = samples_to_frames(resampling_out_buffer.length());
|
||||
+ float input_frames_needed =
|
||||
+ (output_frame_count - unresampled_frames_left) * resampling_ratio
|
||||
+ - resampled_frames_left;
|
||||
+ if (input_frames_needed < 0) {
|
||||
+ return 0;
|
||||
+ }
|
||||
+ return (uint32_t)ceilf(input_frames_needed);
|
||||
}
|
||||
|
||||
/** Returns a pointer to the input buffer, that contains empty space for at
|
||||
--
|
||||
2.7.4
|
||||
|
||||
+49
-33
@@ -5,55 +5,41 @@ cp $1/AUTHORS .
|
||||
cp $1/LICENSE .
|
||||
cp $1/README.md .
|
||||
cp $1/include/cubeb/cubeb.h include
|
||||
cp $1/src/android/audiotrack_definitions.h src/android
|
||||
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_assert.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_audiotrack.c src
|
||||
cp $1/src/cubeb_audiounit.cpp src
|
||||
cp $1/src/cubeb_osx_run_loop.h src
|
||||
cp $1/src/cubeb_jack.cpp src
|
||||
cp $1/src/cubeb_opensl.c src
|
||||
cp $1/src/cubeb_panner.cpp src
|
||||
cp $1/src/cubeb_panner.h src
|
||||
cp $1/src/cubeb_pulse.c 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/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
|
||||
cp $1/src/cubeb_winmm.c src
|
||||
cp $1/test/common.h tests/common.h
|
||||
cp $1/test/test_audio.cpp tests/test_audio.cpp
|
||||
#cp $1/test/test_devices.c tests/test_devices.cpp
|
||||
cp $1/test/test_duplex.cpp tests/test_duplex.cpp
|
||||
cp $1/test/test_latency.cpp tests/test_latency.cpp
|
||||
cp $1/test/test_record.cpp tests/test_record.cpp
|
||||
cp $1/test/test_resampler.cpp tests/test_resampler.cpp
|
||||
cp $1/test/test_sanity.cpp tests/test_sanity.cpp
|
||||
cp $1/test/test_tone.cpp tests/test_tone.cpp
|
||||
cp $1/test/test_utils.cpp tests/test_utils.cpp
|
||||
|
||||
if [ -d $1/.git ]; then
|
||||
rev=$(cd $1 && git rev-parse --verify HEAD)
|
||||
@@ -71,3 +57,33 @@ if [ -n "$rev" ]; then
|
||||
else
|
||||
echo "Remember to update README_MOZILLA with the version details."
|
||||
fi
|
||||
|
||||
echo "Applying a patch on top of $version"
|
||||
patch -p1 < ./unresampled-frames.patch
|
||||
|
||||
echo "Applying a patch on top of $version"
|
||||
patch -p1 < ./bug1302231_emergency_bailout.patch
|
||||
|
||||
echo "Applying a patch on top of $version"
|
||||
patch -p1 < ./osx-linearize-operations.patch
|
||||
|
||||
echo "Applying a patch on top of $version"
|
||||
patch -p1 < ./prevent-double-free.patch
|
||||
|
||||
echo "Applying a patch on top of $version"
|
||||
patch -p1 < ./bug1292803_pulse_assert.patch
|
||||
|
||||
echo "Applying a patch on top of $version"
|
||||
patch -p1 < ./uplift-wasapi-part-to-beta.patch
|
||||
|
||||
echo "Applying a patch on top of $version"
|
||||
patch -p3 < ./fix-crashes.patch
|
||||
|
||||
echo "Applying a patch on top of $version"
|
||||
patch -p3 < ./uplift-part-of-f07ee6d-esr52.patch
|
||||
|
||||
echo "Applying a patch on top of $version"
|
||||
patch -p3 < ./uplift-system-listener-patch.patch
|
||||
|
||||
echo "Applying a patch on top of $version"
|
||||
patch -p1 < ./uplift-patch-7a4c711.patch
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
# HG changeset patch
|
||||
# User Alex Chronopoulos <achronop@gmail.com>
|
||||
# Parent 00c051cd38c7a6cb3178fd0890d52056f83abfdc
|
||||
Bug 1345049 - Uplift part of cubeb upstream f07ee6d to esr52. r=padenot. a=xxxxxx
|
||||
|
||||
diff --git a/media/libcubeb/src/cubeb_audiounit.cpp b/media/libcubeb/src/cubeb_audiounit.cpp
|
||||
--- a/media/libcubeb/src/cubeb_audiounit.cpp
|
||||
+++ b/media/libcubeb/src/cubeb_audiounit.cpp
|
||||
@@ -590,33 +590,43 @@ audiounit_get_input_device_id(AudioDevic
|
||||
device_id);
|
||||
if (r != noErr) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
+static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume);
|
||||
+static int audiounit_stream_set_volume(cubeb_stream * stm, float volume);
|
||||
+
|
||||
static int
|
||||
audiounit_reinit_stream(cubeb_stream * stm, bool is_started)
|
||||
{
|
||||
+ auto_lock context_lock(stm->context->mutex);
|
||||
if (is_started) {
|
||||
audiounit_stream_stop_internal(stm);
|
||||
}
|
||||
|
||||
{
|
||||
auto_lock lock(stm->mutex);
|
||||
+ float volume = 0.0;
|
||||
+ int vol_rv = audiounit_stream_get_volume(stm, &volume);
|
||||
|
||||
audiounit_close_stream(stm);
|
||||
|
||||
if (audiounit_setup_stream(stm) != CUBEB_OK) {
|
||||
LOG("(%p) Stream reinit failed.", stm);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
+ if (vol_rv == CUBEB_OK) {
|
||||
+ audiounit_stream_set_volume(stm, volume);
|
||||
+ }
|
||||
+
|
||||
// Reset input frames to force new stream pre-buffer
|
||||
// silence if needed, check `is_extra_input_needed()`
|
||||
stm->frames_read = 0;
|
||||
|
||||
// If the stream was running, start it again.
|
||||
if (is_started) {
|
||||
audiounit_stream_start_internal(stm);
|
||||
}
|
||||
@@ -1007,20 +1017,22 @@ audiounit_get_preferred_sample_rate(cube
|
||||
static OSStatus audiounit_remove_device_listener(cubeb * context);
|
||||
|
||||
static void
|
||||
audiounit_destroy(cubeb * ctx)
|
||||
{
|
||||
// Disabling this assert for bug 1083664 -- we seem to leak a stream
|
||||
// assert(ctx->active_streams == 0);
|
||||
|
||||
- /* Unregister the callback if necessary. */
|
||||
- if(ctx->collection_changed_callback) {
|
||||
+ {
|
||||
auto_lock lock(ctx->mutex);
|
||||
- audiounit_remove_device_listener(ctx);
|
||||
+ /* Unregister the callback if necessary. */
|
||||
+ if(ctx->collection_changed_callback) {
|
||||
+ audiounit_remove_device_listener(ctx);
|
||||
+ }
|
||||
}
|
||||
|
||||
ctx->~cubeb();
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
static void audiounit_stream_destroy(cubeb_stream * stm);
|
||||
|
||||
@@ -1861,17 +1873,17 @@ audiounit_close_stream(cubeb_stream *stm
|
||||
cubeb_resampler_destroy(stm->resampler);
|
||||
}
|
||||
|
||||
static void
|
||||
audiounit_stream_destroy(cubeb_stream * stm)
|
||||
{
|
||||
stm->shutdown = true;
|
||||
|
||||
- auto_lock context_locl(stm->context->mutex);
|
||||
+ auto_lock context_lock(stm->context->mutex);
|
||||
audiounit_stream_stop_internal(stm);
|
||||
|
||||
{
|
||||
auto_lock lock(stm->mutex);
|
||||
audiounit_close_stream(stm);
|
||||
}
|
||||
|
||||
#if !TARGET_OS_IPHONE
|
||||
@@ -1905,17 +1917,17 @@ audiounit_stream_start_internal(cubeb_st
|
||||
}
|
||||
|
||||
static int
|
||||
audiounit_stream_start(cubeb_stream * stm)
|
||||
{
|
||||
stm->shutdown = false;
|
||||
stm->draining = false;
|
||||
|
||||
- auto_lock context_locl(stm->context->mutex);
|
||||
+ auto_lock context_lock(stm->context->mutex);
|
||||
audiounit_stream_start_internal(stm);
|
||||
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
|
||||
|
||||
LOG("Cubeb stream (%p) started successfully.", stm);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
@@ -1933,17 +1945,17 @@ audiounit_stream_stop_internal(cubeb_str
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
audiounit_stream_stop(cubeb_stream * stm)
|
||||
{
|
||||
stm->shutdown = true;
|
||||
|
||||
- auto_lock context_locl(stm->context->mutex);
|
||||
+ auto_lock context_lock(stm->context->mutex);
|
||||
audiounit_stream_stop_internal(stm);
|
||||
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
|
||||
|
||||
LOG("Cubeb stream (%p) stopped successfully.", stm);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
@@ -2030,16 +2042,31 @@ audiounit_stream_get_latency(cubeb_strea
|
||||
}
|
||||
|
||||
*latency = stm->hw_latency_frames + stm->current_latency_frames;
|
||||
|
||||
return CUBEB_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
+static int
|
||||
+audiounit_stream_get_volume(cubeb_stream * stm, float * volume)
|
||||
+{
|
||||
+ assert(stm->output_unit);
|
||||
+ OSStatus r = AudioUnitGetParameter(stm->output_unit,
|
||||
+ kHALOutputParam_Volume,
|
||||
+ kAudioUnitScope_Global,
|
||||
+ 0, volume);
|
||||
+ if (r != noErr) {
|
||||
+ LOG("AudioUnitGetParameter/kHALOutputParam_Volume rv=%d", r);
|
||||
+ return CUBEB_ERROR;
|
||||
+ }
|
||||
+ return CUBEB_OK;
|
||||
+}
|
||||
+
|
||||
int audiounit_stream_set_volume(cubeb_stream * stm, float volume)
|
||||
{
|
||||
OSStatus r;
|
||||
|
||||
r = AudioUnitSetParameter(stm->output_unit,
|
||||
kHALOutputParam_Volume,
|
||||
kAudioUnitScope_Global,
|
||||
0, volume, 0);
|
||||
@@ -0,0 +1,69 @@
|
||||
From 7a4c711d6e998b451326a0a87dd2e9dab5a257ef Mon Sep 17 00:00:00 2001
|
||||
From: Alex Chronopoulos <achronop@gmail.com>
|
||||
Date: Mon, 15 May 2017 16:47:26 +0300
|
||||
Subject: [PATCH] audiounit: synchronize destroy stream and reinit (Bug
|
||||
1361657)
|
||||
|
||||
---
|
||||
src/cubeb_audiounit.cpp | 22 +++++++++++++++-------
|
||||
1 file changed, 15 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/src/cubeb_audiounit.cpp b/src/cubeb_audiounit.cpp
|
||||
index 8aa40d54..331bc735 100644
|
||||
--- a/src/cubeb_audiounit.cpp
|
||||
+++ b/src/cubeb_audiounit.cpp
|
||||
@@ -603,6 +603,7 @@ audiounit_get_input_device_id(AudioDeviceID * device_id)
|
||||
|
||||
static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume);
|
||||
static int audiounit_stream_set_volume(cubeb_stream * stm, float volume);
|
||||
+static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm);
|
||||
|
||||
static int
|
||||
audiounit_reinit_stream(cubeb_stream * stm)
|
||||
@@ -612,6 +613,11 @@ audiounit_reinit_stream(cubeb_stream * stm)
|
||||
audiounit_stream_stop_internal(stm);
|
||||
}
|
||||
|
||||
+ int r = audiounit_uninstall_device_changed_callback(stm);
|
||||
+ if (r != CUBEB_OK) {
|
||||
+ LOG("(%p) Could not uninstall the device changed callback", stm);
|
||||
+ }
|
||||
+
|
||||
{
|
||||
auto_lock lock(stm->mutex);
|
||||
float volume = 0.0;
|
||||
@@ -2516,11 +2522,6 @@ audiounit_close_stream(cubeb_stream *stm)
|
||||
{
|
||||
stm->mutex.assert_current_thread_owns();
|
||||
|
||||
- int r = audiounit_uninstall_device_changed_callback(stm);
|
||||
- if (r != CUBEB_OK) {
|
||||
- LOG("(%p) Could not uninstall the device changed callback", stm);
|
||||
- }
|
||||
-
|
||||
if (stm->input_unit) {
|
||||
AudioUnitUninitialize(stm->input_unit);
|
||||
AudioComponentInstanceDispose(stm->input_unit);
|
||||
@@ -2554,13 +2555,20 @@ audiounit_stream_destroy(cubeb_stream * stm)
|
||||
LOG("(%p) Could not uninstall the device changed callback", stm);
|
||||
}
|
||||
|
||||
+ r = audiounit_uninstall_device_changed_callback(stm);
|
||||
+ if (r != CUBEB_OK) {
|
||||
+ LOG("(%p) Could not uninstall the device changed callback", stm);
|
||||
+ }
|
||||
+
|
||||
auto_lock context_lock(stm->context->mutex);
|
||||
audiounit_stream_stop_internal(stm);
|
||||
|
||||
- {
|
||||
+ // Execute close in serial queue to avoid collision
|
||||
+ // with reinit when un/plug devices
|
||||
+ dispatch_sync(stm->context->serial_queue, ^() {
|
||||
auto_lock lock(stm->mutex);
|
||||
audiounit_close_stream(stm);
|
||||
- }
|
||||
+ });
|
||||
|
||||
assert(stm->context->active_streams >= 1);
|
||||
stm->context->active_streams -= 1;
|
||||
@@ -0,0 +1,402 @@
|
||||
diff --git a/media/libcubeb/src/cubeb_audiounit.cpp b/media/libcubeb/src/cubeb_audiounit.cpp
|
||||
--- a/media/libcubeb/src/cubeb_audiounit.cpp
|
||||
+++ b/media/libcubeb/src/cubeb_audiounit.cpp
|
||||
@@ -594,20 +594,20 @@ audiounit_get_input_device_id(AudioDevic
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume);
|
||||
static int audiounit_stream_set_volume(cubeb_stream * stm, float volume);
|
||||
|
||||
static int
|
||||
-audiounit_reinit_stream(cubeb_stream * stm, bool is_started)
|
||||
+audiounit_reinit_stream(cubeb_stream * stm)
|
||||
{
|
||||
auto_lock context_lock(stm->context->mutex);
|
||||
- if (is_started) {
|
||||
+ if (!stm->shutdown) {
|
||||
audiounit_stream_stop_internal(stm);
|
||||
}
|
||||
|
||||
{
|
||||
auto_lock lock(stm->mutex);
|
||||
float volume = 0.0;
|
||||
int vol_rv = audiounit_stream_get_volume(stm, &volume);
|
||||
|
||||
@@ -622,32 +622,30 @@ audiounit_reinit_stream(cubeb_stream * s
|
||||
audiounit_stream_set_volume(stm, volume);
|
||||
}
|
||||
|
||||
// Reset input frames to force new stream pre-buffer
|
||||
// silence if needed, check `is_extra_input_needed()`
|
||||
stm->frames_read = 0;
|
||||
|
||||
// If the stream was running, start it again.
|
||||
- if (is_started) {
|
||||
+ if (!stm->shutdown) {
|
||||
audiounit_stream_start_internal(stm);
|
||||
}
|
||||
}
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static OSStatus
|
||||
audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_count,
|
||||
const AudioObjectPropertyAddress * addresses,
|
||||
void * user)
|
||||
{
|
||||
cubeb_stream * stm = (cubeb_stream*) user;
|
||||
stm->switching_device = true;
|
||||
- // Note if the stream was running or not
|
||||
- bool was_running = !stm->shutdown;
|
||||
|
||||
LOG("(%p) Audio device changed, %d events.", stm, address_count);
|
||||
for (UInt32 i = 0; i < address_count; i++) {
|
||||
switch(addresses[i].mSelector) {
|
||||
case kAudioHardwarePropertyDefaultOutputDevice: {
|
||||
LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultOutputDevice", i);
|
||||
// Allow restart to choose the new default
|
||||
stm->output_device = nullptr;
|
||||
@@ -666,19 +664,20 @@ audiounit_property_listener_callback(Aud
|
||||
if (stm->is_default_input) {
|
||||
LOG("It's the default input device, ignore the event");
|
||||
return noErr;
|
||||
}
|
||||
// Allow restart to choose the new default. Event register only for input.
|
||||
stm->input_device = nullptr;
|
||||
}
|
||||
break;
|
||||
- case kAudioDevicePropertyDataSource:
|
||||
- LOG("Event[%d] - mSelector == kAudioHardwarePropertyDataSource", i);
|
||||
- break;
|
||||
+ case kAudioDevicePropertyDataSource: {
|
||||
+ LOG("Event[%d] - mSelector == kAudioHardwarePropertyDataSource", i);
|
||||
+ return noErr;
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
for (UInt32 i = 0; i < address_count; i++) {
|
||||
switch(addresses[i].mSelector) {
|
||||
case kAudioHardwarePropertyDefaultOutputDevice:
|
||||
case kAudioHardwarePropertyDefaultInputDevice:
|
||||
case kAudioDevicePropertyDeviceIsAlive:
|
||||
@@ -691,17 +690,17 @@ audiounit_property_listener_callback(Aud
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use a new thread, through the queue, to avoid deadlock when calling
|
||||
// Get/SetProperties method from inside notify callback
|
||||
dispatch_async(stm->context->serial_queue, ^() {
|
||||
- if (audiounit_reinit_stream(stm, was_running) != CUBEB_OK) {
|
||||
+ if (audiounit_reinit_stream(stm) != CUBEB_OK) {
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
|
||||
LOG("(%p) Could not reopen the stream after switching.", stm);
|
||||
}
|
||||
stm->switching_device = false;
|
||||
});
|
||||
|
||||
return noErr;
|
||||
}
|
||||
@@ -752,27 +751,16 @@ audiounit_install_device_changed_callbac
|
||||
}
|
||||
|
||||
r = audiounit_add_listener(stm, output_dev_id, kAudioDevicePropertyDataSource,
|
||||
kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback);
|
||||
if (r != noErr) {
|
||||
PRINT_ERROR_CODE("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource", r);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
-
|
||||
- /* This event will notify us when the default audio device changes,
|
||||
- * for example when the user plugs in a USB headset and the system chooses it
|
||||
- * automatically as the default, or when another device is chosen in the
|
||||
- * dropdown list. */
|
||||
- r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice,
|
||||
- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
|
||||
- if (r != noErr) {
|
||||
- PRINT_ERROR_CODE("AudioObjectAddPropertyListener/output/kAudioHardwarePropertyDefaultOutputDevice", r);
|
||||
- return CUBEB_ERROR;
|
||||
- }
|
||||
}
|
||||
|
||||
if (stm->input_unit) {
|
||||
/* This event will notify us when the data source on the input device changes. */
|
||||
AudioDeviceID input_dev_id;
|
||||
r = audiounit_get_input_device_id(&input_dev_id);
|
||||
if (r != noErr) {
|
||||
return CUBEB_ERROR;
|
||||
@@ -780,78 +768,112 @@ audiounit_install_device_changed_callbac
|
||||
|
||||
r = audiounit_add_listener(stm, input_dev_id, kAudioDevicePropertyDataSource,
|
||||
kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback);
|
||||
if (r != noErr) {
|
||||
PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource", r);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
- /* This event will notify us when the default input device changes. */
|
||||
- r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice,
|
||||
- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
|
||||
- if (r != noErr) {
|
||||
- PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioHardwarePropertyDefaultInputDevice", r);
|
||||
- return CUBEB_ERROR;
|
||||
- }
|
||||
-
|
||||
/* Event to notify when the input is going away. */
|
||||
AudioDeviceID dev = stm->input_device ? reinterpret_cast<intptr_t>(stm->input_device) :
|
||||
audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT);
|
||||
r = audiounit_add_listener(stm, dev, kAudioDevicePropertyDeviceIsAlive,
|
||||
kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
|
||||
if (r != noErr) {
|
||||
PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive", r);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
+audiounit_install_system_changed_callback(cubeb_stream * stm)
|
||||
+{
|
||||
+ OSStatus r;
|
||||
+
|
||||
+ if (stm->output_unit) {
|
||||
+ /* This event will notify us when the default audio device changes,
|
||||
+ * for example when the user plugs in a USB headset and the system chooses it
|
||||
+ * automatically as the default, or when another device is chosen in the
|
||||
+ * dropdown list. */
|
||||
+ r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice,
|
||||
+ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
|
||||
+ if (r != noErr) {
|
||||
+ LOG("AudioObjectAddPropertyListener/output/kAudioHardwarePropertyDefaultOutputDevice rv=%d", r);
|
||||
+ return CUBEB_ERROR;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (stm->input_unit) {
|
||||
+ /* This event will notify us when the default input device changes. */
|
||||
+ r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice,
|
||||
+ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
|
||||
+ if (r != noErr) {
|
||||
+ LOG("AudioObjectAddPropertyListener/input/kAudioHardwarePropertyDefaultInputDevice rv=%d", r);
|
||||
+ return CUBEB_ERROR;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return CUBEB_OK;
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
audiounit_uninstall_device_changed_callback(cubeb_stream * stm)
|
||||
{
|
||||
OSStatus r;
|
||||
|
||||
if (stm->output_unit) {
|
||||
AudioDeviceID output_dev_id;
|
||||
r = audiounit_get_output_device_id(&output_dev_id);
|
||||
if (r != noErr) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
r = audiounit_remove_listener(stm, output_dev_id, kAudioDevicePropertyDataSource,
|
||||
kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback);
|
||||
if (r != noErr) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
-
|
||||
- r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice,
|
||||
- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
|
||||
- if (r != noErr) {
|
||||
- return CUBEB_ERROR;
|
||||
- }
|
||||
}
|
||||
|
||||
if (stm->input_unit) {
|
||||
AudioDeviceID input_dev_id;
|
||||
r = audiounit_get_input_device_id(&input_dev_id);
|
||||
if (r != noErr) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
r = audiounit_remove_listener(stm, input_dev_id, kAudioDevicePropertyDataSource,
|
||||
kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback);
|
||||
if (r != noErr) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
-
|
||||
+ }
|
||||
+ return CUBEB_OK;
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
+audiounit_uninstall_system_changed_callback(cubeb_stream * stm)
|
||||
+{
|
||||
+ OSStatus r;
|
||||
+
|
||||
+ if (stm->output_unit) {
|
||||
+ r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice,
|
||||
+ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
|
||||
+ if (r != noErr) {
|
||||
+ return CUBEB_ERROR;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (stm->input_unit) {
|
||||
r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice,
|
||||
- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
|
||||
+ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
|
||||
if (r != noErr) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
}
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
/* Get the acceptable buffer size (in frames) that this device can work with. */
|
||||
@@ -1764,16 +1786,22 @@ audiounit_setup_stream(cubeb_stream * st
|
||||
|
||||
if (stm->input_unit && stm->output_unit) {
|
||||
// According to the I/O hardware rate it is expected a specific pattern of callbacks
|
||||
// for example is input is 44100 and output is 48000 we expected no more than 2
|
||||
// out callback in a row.
|
||||
stm->expected_output_callbacks_in_a_row = ceilf(stm->output_hw_rate / stm->input_hw_rate);
|
||||
}
|
||||
|
||||
+ r = audiounit_install_device_changed_callback(stm);
|
||||
+ if (r != CUBEB_OK) {
|
||||
+ LOG("(%p) Could not install the device change callback.", stm);
|
||||
+ return r;
|
||||
+ }
|
||||
+
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
audiounit_stream_init(cubeb * context,
|
||||
cubeb_stream ** stream,
|
||||
char const * /* stream_name */,
|
||||
cubeb_devid input_device,
|
||||
@@ -1838,31 +1866,37 @@ audiounit_stream_init(cubeb * context,
|
||||
}
|
||||
|
||||
if (r != CUBEB_OK) {
|
||||
LOG("(%p) Could not setup the audiounit stream.", stm);
|
||||
audiounit_stream_destroy(stm);
|
||||
return r;
|
||||
}
|
||||
|
||||
- r = audiounit_install_device_changed_callback(stm);
|
||||
+ r = audiounit_install_system_changed_callback(stm);
|
||||
if (r != CUBEB_OK) {
|
||||
LOG("(%p) Could not install the device change callback.", stm);
|
||||
return r;
|
||||
}
|
||||
|
||||
*stream = stm;
|
||||
LOG("Cubeb stream (%p) init successful.", stm);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static void
|
||||
audiounit_close_stream(cubeb_stream *stm)
|
||||
{
|
||||
stm->mutex.assert_current_thread_owns();
|
||||
+
|
||||
+ int r = audiounit_uninstall_device_changed_callback(stm);
|
||||
+ if (r != CUBEB_OK) {
|
||||
+ LOG("(%p) Could not uninstall the device changed callback", stm);
|
||||
+ }
|
||||
+
|
||||
if (stm->input_unit) {
|
||||
AudioUnitUninitialize(stm->input_unit);
|
||||
AudioComponentInstanceDispose(stm->input_unit);
|
||||
}
|
||||
|
||||
audiounit_destroy_input_linear_buffer(stm);
|
||||
|
||||
if (stm->output_unit) {
|
||||
@@ -1873,31 +1907,29 @@ audiounit_close_stream(cubeb_stream *stm
|
||||
cubeb_resampler_destroy(stm->resampler);
|
||||
}
|
||||
|
||||
static void
|
||||
audiounit_stream_destroy(cubeb_stream * stm)
|
||||
{
|
||||
stm->shutdown = true;
|
||||
|
||||
+ int r = audiounit_uninstall_system_changed_callback(stm);
|
||||
+ if (r != CUBEB_OK) {
|
||||
+ LOG("(%p) Could not uninstall the device changed callback", stm);
|
||||
+ }
|
||||
+
|
||||
auto_lock context_lock(stm->context->mutex);
|
||||
audiounit_stream_stop_internal(stm);
|
||||
|
||||
{
|
||||
auto_lock lock(stm->mutex);
|
||||
audiounit_close_stream(stm);
|
||||
}
|
||||
|
||||
-#if !TARGET_OS_IPHONE
|
||||
- int r = audiounit_uninstall_device_changed_callback(stm);
|
||||
- if (r != CUBEB_OK) {
|
||||
- LOG("(%p) Could not uninstall the device changed callback", stm);
|
||||
- }
|
||||
-#endif
|
||||
-
|
||||
assert(stm->context->active_streams >= 1);
|
||||
stm->context->active_streams -= 1;
|
||||
|
||||
LOG("Cubeb stream (%p) destroyed successful.", stm);
|
||||
|
||||
stm->~cubeb_stream();
|
||||
free(stm);
|
||||
}
|
||||
@@ -1914,20 +1946,20 @@ audiounit_stream_start_internal(cubeb_st
|
||||
r = AudioOutputUnitStart(stm->output_unit);
|
||||
assert(r == 0);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
audiounit_stream_start(cubeb_stream * stm)
|
||||
{
|
||||
+ auto_lock context_lock(stm->context->mutex);
|
||||
stm->shutdown = false;
|
||||
stm->draining = false;
|
||||
|
||||
- auto_lock context_lock(stm->context->mutex);
|
||||
audiounit_stream_start_internal(stm);
|
||||
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
|
||||
|
||||
LOG("Cubeb stream (%p) started successfully.", stm);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
@@ -1943,19 +1975,19 @@ audiounit_stream_stop_internal(cubeb_str
|
||||
r = AudioOutputUnitStop(stm->output_unit);
|
||||
assert(r == 0);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
audiounit_stream_stop(cubeb_stream * stm)
|
||||
{
|
||||
+ auto_lock context_lock(stm->context->mutex);
|
||||
stm->shutdown = true;
|
||||
|
||||
- auto_lock context_lock(stm->context->mutex);
|
||||
audiounit_stream_stop_internal(stm);
|
||||
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
|
||||
|
||||
LOG("Cubeb stream (%p) stopped successfully.", stm);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
# HG changeset patch
|
||||
# User Alex Chronopoulos <achronop@gmail.com>
|
||||
# Parent b7bb31e5a851d6f8e142c39dc077e3774719eced
|
||||
Bug 1342363 - Uplift wasapi fixes in Beta. r?kinetik
|
||||
|
||||
diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp
|
||||
--- a/media/libcubeb/src/cubeb_wasapi.cpp
|
||||
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
|
||||
@@ -807,16 +807,20 @@ wasapi_stream_render_loop(LPVOID stream)
|
||||
maybe WebRTC. */
|
||||
mmcss_handle =
|
||||
stm->context->set_mm_thread_characteristics("Audio", &mmcss_task_index);
|
||||
if (!mmcss_handle) {
|
||||
/* This is not fatal, but we might glitch under heavy load. */
|
||||
LOG("Unable to use mmcss to bump the render thread priority: %x", GetLastError());
|
||||
}
|
||||
|
||||
+ // This has already been nulled out, simply exit.
|
||||
+ if (!emergency_bailout) {
|
||||
+ is_playing = false;
|
||||
+ }
|
||||
|
||||
/* WaitForMultipleObjects timeout can trigger in cases where we don't want to
|
||||
treat it as a timeout, such as across a system sleep/wake cycle. Trigger
|
||||
the timeout error handling only when the timeout_limit is reached, which is
|
||||
reset on each successful loop. */
|
||||
unsigned timeout_count = 0;
|
||||
const unsigned timeout_limit = 5;
|
||||
while (is_playing) {
|
||||
@@ -1158,22 +1162,26 @@ bool stop_and_join_render_thread(cubeb_s
|
||||
|
||||
/* 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);
|
||||
if (r == WAIT_TIMEOUT) {
|
||||
/* 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 timed out,"
|
||||
" leaking the thread: %d", GetLastError());
|
||||
rv = false;
|
||||
}
|
||||
if (r == WAIT_FAILED) {
|
||||
*(stm->emergency_bailout) = true;
|
||||
+ // We give the ownership to the rendering thread.
|
||||
+ stm->emergency_bailout = nullptr;
|
||||
LOG("Destroy WaitForSingleObject on thread failed: %d", GetLastError());
|
||||
rv = 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.
|
||||
@@ -1798,19 +1806,16 @@ void wasapi_stream_destroy(cubeb_stream
|
||||
XASSERT(stm);
|
||||
|
||||
// Only free stm->emergency_bailout if we could not 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;
|
||||
- } else {
|
||||
- // If we're leaking, it must be that this is true.
|
||||
- assert(*(stm->emergency_bailout));
|
||||
}
|
||||
|
||||
unregister_notification_client(stm);
|
||||
|
||||
SafeRelease(stm->reconfigure_event);
|
||||
SafeRelease(stm->refill_event);
|
||||
SafeRelease(stm->input_available_event);
|
||||
|
||||
@@ -1865,21 +1870,21 @@ int stream_start_one_side(cubeb_stream *
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
int wasapi_stream_start(cubeb_stream * stm)
|
||||
{
|
||||
+ auto_lock lock(stm->stream_reset_lock);
|
||||
+
|
||||
XASSERT(stm && !stm->thread && !stm->shutdown_event);
|
||||
XASSERT(stm->output_client || stm->input_client);
|
||||
|
||||
- auto_lock lock(stm->stream_reset_lock);
|
||||
-
|
||||
stm->emergency_bailout = new std::atomic<bool>(false);
|
||||
|
||||
if (stm->output_client) {
|
||||
int rv = stream_start_one_side(stm, OUTPUT);
|
||||
if (rv != CUBEB_OK) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
@@ -1932,16 +1937,17 @@ int wasapi_stream_stop(cubeb_stream * st
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
|
||||
}
|
||||
|
||||
if (stop_and_join_render_thread(stm)) {
|
||||
+ // This is null if we've given the pointer to the other thread
|
||||
if (stm->emergency_bailout.load()) {
|
||||
delete stm->emergency_bailout.load();
|
||||
stm->emergency_bailout = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
Reference in New Issue
Block a user