Revert "Issue #1806 - Update libcubeb"

This reverts commit 741fd8b61c.
This commit is contained in:
Moonchild
2021-08-22 17:39:55 +00:00
parent a725c0fe3d
commit ee49838173
83 changed files with 12241 additions and 17726 deletions
+11 -20
View File
@@ -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()
+1 -1
View File
@@ -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);
+9 -1
View File
@@ -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
+1
View File
@@ -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
-1
View File
@@ -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>
+2 -3
View File
@@ -1,7 +1,6 @@
[![Build Status](https://github.com/mozilla/cubeb/actions/workflows/build.yml/badge.svg)](https://github.com/mozilla/cubeb/actions/workflows/build.yml)
[![Build Status](https://travis-ci.org/kinetiknz/cubeb.svg?branch=master)](https://travis-ci.org/kinetiknz/cubeb)
[![Build status](https://ci.appveyor.com/api/projects/status/osv2r0m1j1nt9csr/branch/master?svg=true)](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.
-8
View File
@@ -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.
+8
View File
@@ -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
+71
View File
@@ -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
-145
View File
@@ -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 */
-244
View File
@@ -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, &params,
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, &params,
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);
}
}
}
-255
View File
@@ -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";
}
-315
View File
@@ -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());
}
-47
View File
@@ -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, &params, &latency_frames);
ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
if (r == CUBEB_OK) {
ASSERT_GT(latency_frames, 0u);
}
}
-578
View File
@@ -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);
}
-116
View File
@@ -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, &params, 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
-73
View File
@@ -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
-72
View File
@@ -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
View File
@@ -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;
}
+46
View File
@@ -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_
+21 -48
View File
@@ -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;
+49 -37
View File
@@ -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 */
-80
View File
@@ -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;
}
-13
View File
@@ -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_
+26
View File
@@ -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
View File
@@ -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
File diff suppressed because it is too large Load Diff
-17
View File
@@ -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
-99
View File
@@ -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
-27
View File
@@ -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
+438
View File
@@ -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, &params, (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
};
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-132
View File
@@ -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();
}
+7 -32
View File
@@ -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
-625
View File
@@ -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);
}
-36
View File
@@ -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
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-23
View File
@@ -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
+138 -211
View File
@@ -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
+18 -25
View File
@@ -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)
}
+160 -196
View File
@@ -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);
}
}
+22 -18
View File
@@ -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
-468
View File
@@ -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
View File
@@ -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
};
-154
View File
@@ -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);
}
-47
View File
@@ -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
File diff suppressed because it is too large Load Diff
-25
View File
@@ -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;
}
}
+66 -160
View File
@@ -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 */
+11 -10
View File
@@ -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 */
+13 -10
View File
@@ -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 */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+34 -13
View File
@@ -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.
+61
View File
@@ -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;
}
+294
View File
@@ -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, &params,
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, &params,
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;
}
+162
View File
@@ -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;
}
+151
View File
@@ -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;
}
+60
View File
@@ -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;
}
+125
View File
@@ -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, &params, 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;
}
+554
View File
@@ -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, &params,
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;
}
+80
View File
@@ -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;
}
+36
View File
@@ -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
View File
@@ -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);
+69
View File
@@ -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;
}