aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Makefile.am6
-rw-r--r--include/cubeb/cubeb.h2
-rw-r--r--src/cubeb.c1
-rw-r--r--src/cubeb_audiounit.c803
-rw-r--r--src/cubeb_pulse.c4
-rw-r--r--src/cubeb_resampler.cpp21
-rw-r--r--src/cubeb_resampler_internal.h21
-rw-r--r--src/cubeb_ring_array.h133
-rw-r--r--test/test_resampler.cpp14
-rw-r--r--test/test_ring_array.c70
11 files changed, 863 insertions, 214 deletions
diff --git a/.gitignore b/.gitignore
index c8d9152..2f9c85a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -63,3 +63,5 @@ include/cubeb/cubeb-stdint.h
test-suite.log
test/test_sanity.log
test/test_sanity.trs
+test/test_ring_array
+test/test_ring_array.exe
diff --git a/Makefile.am b/Makefile.am
index 7707c31..880bffa 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -83,6 +83,7 @@ check_PROGRAMS = test/test_sanity \
test/test_resampler \
test/test_record \
test/test_utils \
+ test/test_ring_array\
$(NULL)
test_test_sanity_SOURCES = test/test_sanity.cpp
@@ -101,7 +102,7 @@ test_test_devices_SOURCES = test/test_devices.cpp
test_test_devices_LDADD = -lm src/libcubeb.la $(platform_lib)
test_test_resampler_SOURCES = test/test_resampler.cpp
-test_test_resampler_LDADD = -lm src/libcubeb.la $(platform_lib) src/cubeb_resampler.o
+test_test_resampler_LDADD = -lm src/libcubeb.la $(platform_lib) src/cubeb_resampler.o src/speex/resample.lo
test_test_duplex_SOURCES = test/test_duplex.cpp
test_test_duplex_LDADD = -lm src/libcubeb.la $(platform_lib)
@@ -111,6 +112,9 @@ test_test_record_LDADD = -lm src/libcubeb.la $(platform_lib)
test_test_utils_SOURCES = test/test_utils.cpp
+test_test_ring_array_SOURCES = test/test_ring_array.c
+test_test_ring_array_LDADD = -lpthread
+
TESTS = $(check_PROGRAMS)
dist-hook:
diff --git a/include/cubeb/cubeb.h b/include/cubeb/cubeb.h
index d1db0c9..1662c3d 100644
--- a/include/cubeb/cubeb.h
+++ b/include/cubeb/cubeb.h
@@ -221,7 +221,7 @@ typedef enum {
* `cubeb_enumerate_devices`, and must be destroyed using
* `cubeb_device_info_destroy`. */
typedef struct {
- cubeb_devid devid; /**< Device identifier handle. */
+ cubeb_devid devid; /**< Device identifier handle. Always fresh allocate and copy it's value. */
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. */
diff --git a/src/cubeb.c b/src/cubeb.c
index 5f5e92b..8c5dfc0 100644
--- a/src/cubeb.c
+++ b/src/cubeb.c
@@ -425,6 +425,7 @@ int cubeb_device_collection_destroy(cubeb_device_collection * collection)
int cubeb_device_info_destroy(cubeb_device_info * info)
{
+ free(info->devid);
free(info->device_id);
free(info->friendly_name);
free(info->group_id);
diff --git a/src/cubeb_audiounit.c b/src/cubeb_audiounit.c
index ed7c2ed..db38628 100644
--- a/src/cubeb_audiounit.c
+++ b/src/cubeb_audiounit.c
@@ -16,16 +16,17 @@
#include <CoreAudio/AudioHardware.h>
#include <CoreAudio/HostTime.h>
#include <CoreFoundation/CoreFoundation.h>
-#else
+#endif
#include <CoreAudio/CoreAudioTypes.h>
#include <AudioToolbox/AudioToolbox.h>
-#endif
#include "cubeb/cubeb.h"
#include "cubeb-internal.h"
#include "cubeb_panner.h"
#if !TARGET_OS_IPHONE
#include "cubeb_osx_run_loop.h"
#endif
+#include "cubeb_resampler.h"
+#include "cubeb_ring_array.h"
#if !defined(kCFCoreFoundationVersionNumber10_7)
/* From CoreFoundation CFBase.h */
@@ -33,11 +34,42 @@
#endif
#if !TARGET_OS_IPHONE && MAC_OS_X_VERSION_MIN_REQUIRED < 1060
-#define MACOSX_LESS_THAN_106
+#define AudioComponent Component
+#define AudioComponentDescription ComponentDescription
+#define AudioComponentFindNext FindNextComponent
+#define AudioComponentInstanceNew OpenAComponent
+#define AudioComponentInstanceDispose CloseComponent
#endif
#define CUBEB_STREAM_MAX 8
-#define NBUFS 4
+
+#define AU_OUT_BUS 0
+#define AU_IN_BUS 1
+
+#if TARGET_OS_IPHONE
+#define CUBEB_AUDIOUNIT_SUBTYPE kAudioUnitSubType_RemoteIO
+#else
+#define CUBEB_AUDIOUNIT_SUBTYPE kAudioUnitSubType_HALOutput
+#endif
+
+//#define LOGGING_ENABLED
+#ifdef LOGGING_ENABLED
+#define LOG(...) do { \
+ fprintf(stderr, __VA_ARGS__); \
+ } while(0)
+#else
+#define LOG(...)
+#endif
+
+#ifdef DEBUG
+#define ASSERT_LOCKED(mutex) \
+do { \
+ int rv = pthread_mutex_lock(&mutex); \
+ assert(rv == EDEADLK); \
+} while (0);
+#else
+#define ASSERT_LOCKED(mutex)
+#endif
static struct cubeb_ops const audiounit_ops;
@@ -50,20 +82,36 @@ struct cubeb {
struct cubeb_stream {
cubeb * context;
- AudioUnit unit;
cubeb_data_callback data_callback;
cubeb_state_callback state_callback;
cubeb_device_changed_callback device_changed_callback;
+ /* User pointer of data_callback */
void * user_ptr;
- AudioStreamBasicDescription sample_spec;
+ /* Format descriptions */
+ AudioStreamBasicDescription input_desc;
+ AudioStreamBasicDescription output_desc;
+ /* I/O AudioUnits */
+ AudioUnit input_unit;
+ AudioUnit output_unit;
+ /* Sample rate of input device*/
+ Float64 input_hw_rate;
pthread_mutex_t mutex;
+ /* Hold the input samples in every
+ * input callback iteration */
+ AudioBufferList input_buffer_list[RING_ARRAY_CAPACITY];
+ ring_array input_buffer_list_array;
+ /* Frames on input buffer */
+ uint32_t input_buffer_frames;
+ /* Frame counters */
uint64_t frames_played;
uint64_t frames_queued;
+ uint64_t frames_read;
int shutdown;
int draining;
uint64_t current_latency_frames;
uint64_t hw_latency_frames;
float panning;
+ cubeb_resampler * resampler;
};
#if TARGET_OS_IPHONE
@@ -101,70 +149,209 @@ audiotimestamp_to_latency(AudioTimeStamp const * tstamp, cubeb_stream * stream)
uint64_t pres = AudioConvertHostTimeToNanos(tstamp->mHostTime);
uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
- return ((pres - now) * stream->sample_spec.mSampleRate) / 1000000000LL;
+ return ((pres - now) * stream->output_desc.mSampleRate) / 1000000000LL;
}
static OSStatus
-audiounit_output_callback(void * user_ptr, AudioUnitRenderActionFlags * flags,
- AudioTimeStamp const * tstamp, UInt32 bus, UInt32 nframes,
- AudioBufferList * bufs)
+audiounit_input_callback(void * user_ptr,
+ AudioUnitRenderActionFlags * flags,
+ AudioTimeStamp const * tstamp,
+ UInt32 bus,
+ UInt32 input_frames,
+ AudioBufferList * bufs)
{
- cubeb_stream * stm;
- unsigned char * buf;
- long got;
- OSStatus r;
- float panning;
+ cubeb_stream * stream = user_ptr;
+ long outframes, frames;
+ void * input_buffer = NULL;
- assert(bufs->mNumberBuffers == 1);
- buf = bufs->mBuffers[0].mData;
+ pthread_mutex_lock(&stream->mutex);
+ ASSERT_LOCKED(stream->mutex);
- stm = user_ptr;
+ assert(stream->input_unit != NULL);
+ assert(AU_IN_BUS == bus);
- pthread_mutex_lock(&stm->mutex);
+ if (stream->shutdown) {
+ pthread_mutex_unlock(&stream->mutex);
+ return noErr;
+ }
- stm->current_latency_frames = audiotimestamp_to_latency(tstamp, stm);
- panning = stm->panning;
+ /* Get next store buffer from ring array */
+ AudioBufferList * store_input_buffer_list = ring_array_store_buffer(&stream->input_buffer_list_array);
+ if (store_input_buffer_list == NULL) {
+ LOG("input: Ring array is full drop one buffer\n");
+ ring_array_fetch_buffer(&stream->input_buffer_list_array);
- if (stm->draining || stm->shutdown) {
- pthread_mutex_unlock(&stm->mutex);
- if (stm->draining) {
- r = AudioOutputUnitStop(stm->unit);
- assert(r == 0);
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
- }
+ store_input_buffer_list = ring_array_store_buffer(&stream->input_buffer_list_array);
+ assert(store_input_buffer_list);
+ }
+ /* Render input samples */
+ OSStatus r = AudioUnitRender(stream->input_unit,
+ flags,
+ tstamp,
+ bus,
+ input_frames,
+ store_input_buffer_list);
+ assert(r == noErr);
+ LOG("- input: buffers %d, size %d, channels %d, frames %d\n", store_input_buffer_list->mNumberBuffers,
+ store_input_buffer_list->mBuffers[0].mDataByteSize, store_input_buffer_list->mBuffers[0].mNumberChannels, input_frames);
+
+ assert(input_frames > 0);
+ stream->frames_read += input_frames;
+
+ // Full Duplex. We'll call data_callback in the AudioUnit output callback.
+ if (stream->output_unit != NULL) {
+ // User callback will be called by output callback
+ pthread_mutex_unlock(&stream->mutex);
return noErr;
}
- pthread_mutex_unlock(&stm->mutex);
- got = stm->data_callback(stm, stm->user_ptr, NULL, buf, nframes);
- pthread_mutex_lock(&stm->mutex);
- if (got < 0) {
- /* XXX handle this case. */
- assert(false);
- pthread_mutex_unlock(&stm->mutex);
+ /* Input only. Call the user callback through resampler.
+ Resampler will deliver input buffer in the correct rate. */
+ frames = input_frames;
+ AudioBufferList * fetch_input_buffer_list = ring_array_fetch_buffer(&stream->input_buffer_list_array);
+ assert(fetch_input_buffer_list && "fetch buffer is null in the input");
+ input_buffer = fetch_input_buffer_list->mBuffers[0].mData;
+ outframes = cubeb_resampler_fill(stream->resampler,
+ input_buffer,
+ &frames,
+ NULL,
+ 0);
+
+ if (outframes < 0 || outframes != input_frames) {
+ stream->shutdown = 1;
+ pthread_mutex_unlock(&stream->mutex);
return noErr;
}
- if ((UInt32) got < nframes) {
- size_t got_bytes = got * stm->sample_spec.mBytesPerFrame;
- size_t rem_bytes = (nframes - got) * stm->sample_spec.mBytesPerFrame;
+ pthread_mutex_unlock(&stream->mutex);
+ return noErr;
+}
- stm->draining = 1;
+static void
+audiounit_make_silent(AudioBufferList * ioData)
+{
+ for(UInt32 i = 0; i<ioData->mNumberBuffers;i++) {
+ memset(ioData->mBuffers[i].mData, 0, ioData->mBuffers[i].mDataByteSize);
+ }
+}
- memset(buf + got_bytes, 0, rem_bytes);
+static OSStatus
+audiounit_output_callback(void * user_ptr,
+ AudioUnitRenderActionFlags * flags,
+ AudioTimeStamp const * tstamp,
+ UInt32 bus,
+ UInt32 output_frames,
+ AudioBufferList * outBufferList)
+{
+ assert(AU_OUT_BUS == bus);
+ assert(outBufferList->mNumberBuffers == 1);
+
+ LOG("- output: buffers %d, size %d, channels %d, frames %d\n",
+ outBufferList->mNumberBuffers, outBufferList->mBuffers[0].mDataByteSize,
+ outBufferList->mBuffers[0].mNumberChannels, output_frames);
+
+ cubeb_stream * stream = user_ptr;
+ long outframes = 0, input_frames = 0;
+ void * output_buffer = NULL, * input_buffer = NULL;
+
+ pthread_mutex_lock(&stream->mutex);
+ ASSERT_LOCKED(stream->mutex);
+
+ if (stream->shutdown) {
+ audiounit_make_silent(outBufferList);
+ pthread_mutex_unlock(&stream->mutex);
+ return noErr;
}
- stm->frames_played = stm->frames_queued;
- stm->frames_queued += got;
- pthread_mutex_unlock(&stm->mutex);
+ stream->current_latency_frames = audiotimestamp_to_latency(tstamp, stream);
+ if (stream->draining) {
+ OSStatus r = AudioOutputUnitStop(stream->output_unit);
+ assert(r == 0);
+ if (stream->input_unit) {
+ r = AudioOutputUnitStop(stream->input_unit);
+ assert(r==0);
+ }
+ stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED);
+ pthread_mutex_unlock(&stream->mutex);
+ audiounit_make_silent(outBufferList);
+ return noErr;
+ }
+ /* Get output buffer. */
+ output_buffer = outBufferList->mBuffers[0].mData;
+ /* If Full duplex get also input buffer */
+ AudioBufferList * fetch_input_buffer_list = NULL;
+ if (stream->input_unit != NULL) {
+ /* Output callback came first */
+ if (stream->frames_read == 0) {
+ audiounit_make_silent(outBufferList);
+ pthread_mutex_unlock(&stream->mutex);
+ return noErr;
+ }
+ /* Input samples stored previously in input callback. */
+ fetch_input_buffer_list = ring_array_fetch_buffer(&stream->input_buffer_list_array);
+ if (fetch_input_buffer_list == NULL) {
+ LOG("Requested more output than input. "
+ "This is either a hole or we are after a stream stop and input thread stopped before output\n");
+ /* Provide silent input. Other than that we could provide silent output and exit without
+ * calling user callback. I do not prefer it because the user loose the control of
+ * the output. Also resampler loose frame counting and produce less frame than
+ * expected at some point in the future breaking an assert. */
+
+ /* Avoid here to allocate new memory since we are inside callback. Use the existing
+ * allocated buffers since the ring array is empty and the buffer is not used. */
+ fetch_input_buffer_list = &stream->input_buffer_list[0];
+ audiounit_make_silent(fetch_input_buffer_list);
+ }
+ input_buffer = fetch_input_buffer_list->mBuffers[0].mData;
+ input_frames = stream->input_buffer_frames;
+ assert(stream->frames_read > 0);
+ }
- if (stm->sample_spec.mChannelsPerFrame == 2) {
- if (stm->sample_spec.mFormatFlags & kAudioFormatFlagIsFloat)
- cubeb_pan_stereo_buffer_float((float*)buf, got, panning);
- else if (stm->sample_spec.mFormatFlags & kAudioFormatFlagIsSignedInteger)
- cubeb_pan_stereo_buffer_int((short*)buf, got, panning);
+ /* Call user callback through resampler. */
+ outframes = cubeb_resampler_fill(stream->resampler,
+ input_buffer,
+ &input_frames,
+ output_buffer,
+ output_frames);
+
+ /* Cleanup the input buffer to make sure that we have fresh data. */
+ if (input_buffer) {
+ audiounit_make_silent(fetch_input_buffer_list);
}
+
+ if (outframes < 0) {
+ stream->shutdown = 1;
+ pthread_mutex_unlock(&stream->mutex);
+ return noErr;
+ }
+
+ AudioFormatFlags outaff;
+ float panning;
+ size_t outbpf;
+
+ outbpf = stream->output_desc.mBytesPerFrame;
+ stream->draining = (outframes < output_frames);
+ stream->frames_played = stream->frames_queued;
+ stream->frames_queued += outframes;
+
+ outaff = stream->output_desc.mFormatFlags;
+ panning = (stream->output_desc.mChannelsPerFrame == 2) ? stream->panning : 0.0f;
+ pthread_mutex_unlock(&stream->mutex);
+
+ /* Post process output samples. */
+ if (stream->draining) {
+ /* Clear missing frames (silence) */
+ memset(output_buffer + outframes * outbpf, 0, (output_frames - outframes) * outbpf);
+ }
+ /* Pan stereo. */
+ if (panning != 0.0f) {
+ if (outaff & kAudioFormatFlagIsFloat) {
+ cubeb_pan_stereo_buffer_float((float*)output_buffer, outframes, panning);
+ } else if (outaff & kAudioFormatFlagIsSignedInteger) {
+ cubeb_pan_stereo_buffer_int((short*)output_buffer, outframes, panning);
+ }
+ }
return noErr;
}
@@ -538,73 +725,186 @@ audiounit_destroy(cubeb * ctx)
static void audiounit_stream_destroy(cubeb_stream * stm);
static int
-audiounit_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)
+audio_stream_desc_init (AudioStreamBasicDescription * ss,
+ const cubeb_stream_params * stream_params)
{
- AudioStreamBasicDescription ss;
-#if MACOSX_LESS_THAN_106
- ComponentDescription desc;
- Component comp;
-#else
- AudioComponentDescription desc;
- AudioComponent comp;
-#endif
- cubeb_stream * stm;
- AURenderCallbackStruct input;
- unsigned int buffer_size, default_buffer_size;
- OSStatus r;
- UInt32 size;
- AudioValueRange latency_range;
-
- assert(!input_stream_params && "not supported");
- if (input_device || output_device) {
- /* Device selection not yet implemented. */
- return CUBEB_ERROR_DEVICE_UNAVAILABLE;
- }
-
- assert(context);
- *stream = NULL;
+ memset(ss, 0, sizeof(AudioStreamBasicDescription));
- memset(&ss, 0, sizeof(ss));
- ss.mFormatFlags = 0;
-
- switch (output_stream_params->format) {
+ switch (stream_params->format) {
case CUBEB_SAMPLE_S16LE:
- ss.mBitsPerChannel = 16;
- ss.mFormatFlags |= kAudioFormatFlagIsSignedInteger;
+ ss->mBitsPerChannel = 16;
+ ss->mFormatFlags = kAudioFormatFlagIsSignedInteger;
break;
case CUBEB_SAMPLE_S16BE:
- ss.mBitsPerChannel = 16;
- ss.mFormatFlags |= kAudioFormatFlagIsSignedInteger |
+ ss->mBitsPerChannel = 16;
+ ss->mFormatFlags = kAudioFormatFlagIsSignedInteger |
kAudioFormatFlagIsBigEndian;
break;
case CUBEB_SAMPLE_FLOAT32LE:
- ss.mBitsPerChannel = 32;
- ss.mFormatFlags |= kAudioFormatFlagIsFloat;
+ ss->mBitsPerChannel = 32;
+ ss->mFormatFlags = kAudioFormatFlagIsFloat;
break;
case CUBEB_SAMPLE_FLOAT32BE:
- ss.mBitsPerChannel = 32;
- ss.mFormatFlags |= kAudioFormatFlagIsFloat |
+ ss->mBitsPerChannel = 32;
+ ss->mFormatFlags = kAudioFormatFlagIsFloat |
kAudioFormatFlagIsBigEndian;
break;
default:
return CUBEB_ERROR_INVALID_FORMAT;
}
- ss.mFormatID = kAudioFormatLinearPCM;
- ss.mFormatFlags |= kLinearPCMFormatFlagIsPacked;
- ss.mSampleRate = output_stream_params->rate;
- ss.mChannelsPerFrame = output_stream_params->channels;
+ ss->mFormatID = kAudioFormatLinearPCM;
+ ss->mFormatFlags |= kLinearPCMFormatFlagIsPacked;
+ ss->mSampleRate = stream_params->rate;
+ ss->mChannelsPerFrame = stream_params->channels;
+
+ ss->mBytesPerFrame = (ss->mBitsPerChannel / 8) * ss->mChannelsPerFrame;
+ ss->mFramesPerPacket = 1;
+ ss->mBytesPerPacket = ss->mBytesPerFrame * ss->mFramesPerPacket;
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_create_unit(AudioUnit * unit,
+ bool is_input,
+ const cubeb_stream_params * stream_params,
+ cubeb_devid device)
+{
+ AudioComponentDescription desc;
+ AudioComponent comp;
+ UInt32 enable;
+ AudioDeviceID devid;
+
+ desc.componentType = kAudioUnitType_Output;
+ desc.componentSubType = CUBEB_AUDIOUNIT_SUBTYPE;
+ desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+ desc.componentFlags = 0;
+ desc.componentFlagsMask = 0;
+ comp = AudioComponentFindNext(NULL, &desc);
+ if (comp == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ if (AudioComponentInstanceNew(comp, unit) != 0) {
+ return CUBEB_ERROR;
+ }
+
+ enable = 1;
+ if (AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_EnableIO,
+ is_input ? kAudioUnitScope_Input : kAudioUnitScope_Output,
+ is_input ? AU_IN_BUS : AU_OUT_BUS, &enable, sizeof(UInt32)) != noErr) {
+ return CUBEB_ERROR;
+ }
+
+ enable = 0;
+ if (AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_EnableIO,
+ is_input ? kAudioUnitScope_Output : kAudioUnitScope_Input,
+ is_input ? AU_OUT_BUS : AU_IN_BUS, &enable, sizeof(UInt32)) != noErr) {
+ return CUBEB_ERROR;
+ }
+
+ if (device == NULL) {
+ devid = audiounit_get_default_device_id (is_input ? CUBEB_DEVICE_TYPE_INPUT
+ : CUBEB_DEVICE_TYPE_OUTPUT);
+ } else {
+ devid = *(AudioDeviceID *)device;
+ }
+ int err = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global,
+ is_input ? AU_IN_BUS : AU_OUT_BUS,
+ &devid, sizeof(AudioDeviceID));
+ if (err != noErr) {
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_buflst_init_single_buffer (AudioBufferList * buflst,
+ const AudioStreamBasicDescription * desc,
+ uint32_t frames)
+{
+ size_t size = desc->mBytesPerFrame * frames;
+
+ if ((buflst->mBuffers[0].mData = malloc(size)) == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ buflst->mBuffers[0].mNumberChannels = desc->mChannelsPerFrame;
+ buflst->mBuffers[0].mDataByteSize = size;
+ buflst->mNumberBuffers = 1;
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_init_input_buffer_list_array(cubeb_stream * stream)
+{
+ /* Init ring array. */
+ ring_array_init(&stream->input_buffer_list_array);
+ /* Initialize buffer list. */
+ memset(&stream->input_buffer_list, 0, sizeof(stream->input_buffer_list));
+ /* Create input descriptor. */
+ AudioStreamBasicDescription src_desc = stream->input_desc;
+ src_desc.mSampleRate = stream->input_hw_rate;
+ /* Set data to ring array. */
+ for (int i = 0; i < RING_ARRAY_CAPACITY; ++i) {
+ /* Allocate the data (AudioBufferList here). */
+ if (audiounit_buflst_init_single_buffer(&stream->input_buffer_list[i],
+ &src_desc,
+ stream->input_buffer_frames) != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+ /* Set the data in the array. */
+ if (ring_array_set_data(&stream->input_buffer_list_array,
+ &stream->input_buffer_list[i], i) == NULL) {
+ return CUBEB_ERROR;
+ }
+ }
+ return CUBEB_OK;
+}
+
+static void
+audiounit_destroy_input_buffer_list_array(cubeb_stream * stream)
+{
+ /* Destroy the data in the array first*/
+ for (int i = 0; i < RING_ARRAY_CAPACITY; ++i) {
+ if (stream->input_buffer_list[i].mBuffers[0].mData) {
+ free(stream->input_buffer_list[i].mBuffers[0].mData);
+ }
+ }
+ ring_array_destroy(&stream->input_buffer_list_array);
+}
+
+static int
+audiounit_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)
+{
+ cubeb_stream * stm;
+ AudioUnit input_unit;
+ AudioUnit output_unit;
+ int ret;
+ AURenderCallbackStruct aurcbs_in;
+ AURenderCallbackStruct aurcbs_out;
+ UInt32 size;
+#if 0
+ unsigned int buffer_size, default_buffer_size;
+ AudioValueRange latency_range;
+#endif
- ss.mBytesPerFrame = (ss.mBitsPerChannel / 8) * ss.mChannelsPerFrame;
- ss.mFramesPerPacket = 1;
- ss.mBytesPerPacket = ss.mBytesPerFrame * ss.mFramesPerPacket;
+ assert(context);
+ *stream = NULL;
pthread_mutex_lock(&context->mutex);
if (context->limit_streams && context->active_streams >= CUBEB_STREAM_MAX) {
@@ -614,65 +914,176 @@ audiounit_stream_init(cubeb * context, cubeb_stream ** stream, char const * stre
context->active_streams += 1;
pthread_mutex_unlock(&context->mutex);
- desc.componentType = kAudioUnitType_Output;
- desc.componentSubType =
-#if TARGET_OS_IPHONE
- kAudioUnitSubType_RemoteIO;
-#else
- kAudioUnitSubType_DefaultOutput;
-#endif
- desc.componentManufacturer = kAudioUnitManufacturer_Apple;
- desc.componentFlags = 0;
- desc.componentFlagsMask = 0;
-#if MACOSX_LESS_THAN_106
- comp = FindNextComponent(NULL, &desc);
-#else
- comp = AudioComponentFindNext(NULL, &desc);
-#endif
- assert(comp);
+ if (input_stream_params != NULL) {
+ if ((ret = audiounit_create_unit (&input_unit, true,
+ input_stream_params, input_device)) != CUBEB_OK)
+ return ret;
+ }
- stm = calloc(1, sizeof(*stm));
+ if (output_stream_params != NULL) {
+ if ((ret = audiounit_create_unit (&output_unit, false,
+ output_stream_params, output_device)) != CUBEB_OK)
+ return ret;
+ }
+
+ stm = calloc(1, sizeof(cubeb_stream));
assert(stm);
+ /* These could be different in the future if we have both
+ * full-duplex stream and different devices for input vs output. */
+ stm->input_unit = (input_stream_params != NULL) ? input_unit : NULL;
+ stm->output_unit = (output_stream_params != NULL) ? output_unit : NULL;
stm->context = context;
stm->data_callback = data_callback;
stm->state_callback = state_callback;
stm->user_ptr = user_ptr;
stm->device_changed_callback = NULL;
- stm->sample_spec = ss;
-
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
+#ifdef DEBUG
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
+#else
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
- r = pthread_mutex_init(&stm->mutex, &attr);
+#endif
+ ret = pthread_mutex_init(&stm->mutex, &attr);
+ assert(0 == ret);
pthread_mutexattr_destroy(&attr);
- assert(r == 0);
+ stm->draining = false;
+ stm->shutdown = 0;
stm->frames_played = 0;
stm->frames_queued = 0;
+ stm->frames_read = 0;
stm->current_latency_frames = 0;
stm->hw_latency_frames = UINT64_MAX;
-#if MACOSX_LESS_THAN_106
- r = OpenAComponent(comp, &stm->unit);
-#else
- r = AudioComponentInstanceNew(comp, &stm->unit);
-#endif
- if (r != 0) {
- audiounit_stream_destroy(stm);
- return CUBEB_ERROR;
+ /* Setup Input Stream! */
+ if (input_stream_params != NULL) {
+
+ size = sizeof(UInt32);
+ if (AudioUnitGetProperty(stm->input_unit,
+ kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Input,
+ AU_IN_BUS,
+ &stm->input_buffer_frames,
+ &size) != 0) {
+ audiounit_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ if (AudioUnitSetProperty(stm->input_unit,
+ kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Output,
+ AU_IN_BUS,
+ &stm->input_buffer_frames,
+ size) != 0) {
+ audiounit_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ /* Get input device sample rate. */
+ AudioStreamBasicDescription input_hw_desc;
+ size = sizeof(AudioStreamBasicDescription);
+ if (AudioUnitGetProperty(stm->input_unit,
+ kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input,
+ AU_IN_BUS,
+ &input_hw_desc,
+ &size) !=0) {
+ audiounit_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+ stm->input_hw_rate = input_hw_desc.mSampleRate;
+
+ /* Set format description according to the input params. */
+ if ((ret = audio_stream_desc_init(&stm->input_desc, input_stream_params)) != CUBEB_OK) {
+ audiounit_stream_destroy(stm);
+ return ret;
+ }
+
+ AudioStreamBasicDescription src_desc = stm->input_desc;
+ /* Input AudioUnit must be configured with device's sample rate. */
+ if (src_desc.mSampleRate != stm->input_hw_rate) {
+ /* Set the sample rate of the device we will resample inside input callback. */
+ src_desc.mSampleRate = stm->input_hw_rate;
+ }
+
+ if (AudioUnitSetProperty(stm->input_unit,
+ kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Output,
+ AU_IN_BUS,
+ &src_desc,
+ sizeof(AudioStreamBasicDescription)) !=0) {
+ audiounit_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ /* Frames per buffer in the input callback. */
+ if (AudioUnitSetProperty(stm->input_unit,
+ kAudioUnitProperty_MaximumFramesPerSlice,
+ kAudioUnitScope_Output,
+ AU_IN_BUS,
+ &stm->input_buffer_frames,
+ sizeof(UInt32))) {
+ audiounit_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ if (audiounit_init_input_buffer_list_array(stm) != CUBEB_OK) {
+ audiounit_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ assert(stm->input_unit != NULL);
+ aurcbs_in.inputProc = audiounit_input_callback;
+ aurcbs_in.inputProcRefCon = stm;
+ if (AudioUnitSetProperty(stm->input_unit,
+ kAudioOutputUnitProperty_SetInputCallback,
+ kAudioUnitScope_Global,
+ AU_OUT_BUS,
+ &aurcbs_in,
+ sizeof(aurcbs_in)) != 0) {
+ audiounit_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
}
- input.inputProc = audiounit_output_callback;
- input.inputProcRefCon = stm;
- r = AudioUnitSetProperty(stm->unit, kAudioUnitProperty_SetRenderCallback,
- kAudioUnitScope_Global, 0, &input, sizeof(input));
- if (r != 0) {
- audiounit_stream_destroy(stm);
- return CUBEB_ERROR;
+ /* Setup Output Stream! */
+ if (output_stream_params != NULL) {
+
+ if ((ret = audio_stream_desc_init(&stm->output_desc, output_stream_params)) != CUBEB_OK) {
+ audiounit_stream_destroy(stm);
+ return ret;
+ }
+
+ if (AudioUnitSetProperty(stm->output_unit,
+ kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input,
+ AU_OUT_BUS,
+ &stm->output_desc,
+ sizeof(AudioStreamBasicDescription)) != 0) {
+ audiounit_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ assert(stm->output_unit != NULL);
+ aurcbs_out.inputProc = audiounit_output_callback;
+ aurcbs_out.inputProcRefCon = stm;
+ if (AudioUnitSetProperty(stm->output_unit,
+ kAudioUnitProperty_SetRenderCallback,
+ kAudioUnitScope_Global,
+ AU_OUT_BUS,
+ &aurcbs_out,
+ sizeof(aurcbs_out)) != 0) {
+ audiounit_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
}
+ // Setting the latency doesn't work well for USB headsets (eg. plantronics).
+ // Keep the default latency for now.
+#if 0
buffer_size = latency / 1000.0 * ss.mSampleRate;
/* Get the range of latency this particular device can work with, and clamp
@@ -694,42 +1105,67 @@ audiounit_stream_init(cubeb * context, cubeb_stream ** stream, char const * stre
* set it. Otherwise, use the default latency.
**/
size = sizeof(default_buffer_size);
- r = AudioUnitGetProperty(stm->unit, kAudioDevicePropertyBufferFrameSize,
- kAudioUnitScope_Output, 0, &default_buffer_size, &size);
-
- if (r != 0) {
+ if (AudioUnitGetProperty(stm->output_unit, kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Output, 0, &default_buffer_size, &size) != 0) {
audiounit_stream_destroy(stm);
return CUBEB_ERROR;
}
-#else // TARGET_OS_IPHONE
- //TODO: [[AVAudioSession sharedInstance] inputLatency]
- // http://stackoverflow.com/questions/13157523/kaudiodevicepropertybufferframesize-replacement-for-ios
-#endif
- // Setting the latency doesn't work well for USB headsets (eg. plantronics).
- // Keep the default latency for now.
-#if 0
if (buffer_size < default_buffer_size) {
/* Set the maximum number of frame that the render callback will ask for,
* effectively setting the latency of the stream. This is process-wide. */
- r = AudioUnitSetProperty(stm->unit, kAudioDevicePropertyBufferFrameSize,
- kAudioUnitScope_Output, 0, &buffer_size, sizeof(buffer_size));
- if (r != 0) {
+ if (AudioUnitSetProperty(stm->output_unit, kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Output, 0, &buffer_size, sizeof(buffer_size)) != 0) {
audiounit_stream_destroy(stm);
return CUBEB_ERROR;
}
}
+#else // TARGET_OS_IPHONE
+ //TODO: [[AVAudioSession sharedInstance] inputLatency]
+ // http://stackoverflow.com/questions/13157523/kaudiodevicepropertybufferframesize-replacement-for-ios
+#endif
#endif
- r = AudioUnitSetProperty(stm->unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
- 0, &ss, sizeof(ss));
- if (r != 0) {
- audiounit_stream_destroy(stm);
+ /* We use a resampler because input AudioUnit operates
+ * reliable only in the capture device sample rate.
+ * Resampler will convert it to the user sample rate
+ * and deliver it to the callback. */
+ int32_t target_sample_rate;
+ if (input_stream_params) {
+ target_sample_rate = input_stream_params->rate;
+ } else {
+ assert(output_stream_params);
+ target_sample_rate = output_stream_params->rate;
+ }
+
+ cubeb_stream_params input_unconverted_params;
+ if (input_stream_params) {
+ input_unconverted_params = *input_stream_params;
+ /* Use the rate of the input device. */
+ input_unconverted_params.rate = stm->input_hw_rate;
+ }
+
+ /* Create resampler. Output params are unchanged
+ * because we do not need conversion on the output. */
+ stm->resampler = cubeb_resampler_create(stm,
+ input_stream_params ? &input_unconverted_params : NULL,
+ output_stream_params,
+ target_sample_rate,
+ stm->data_callback,
+ stm->user_ptr,
+ CUBEB_RESAMPLER_QUALITY_DESKTOP);
+ if (!stm->resampler) {
+ LOG("Could not get a resampler\n");
return CUBEB_ERROR;
}
- r = AudioUnitInitialize(stm->unit);
- if (r != 0) {
+ if (stm->input_unit != NULL &&
+ AudioUnitInitialize(stm->input_unit) != 0) {
+ audiounit_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+ if (stm->output_unit != NULL &&
+ AudioUnitInitialize(stm->output_unit) != 0) {
audiounit_stream_destroy(stm);
return CUBEB_ERROR;
}
@@ -748,25 +1184,29 @@ audiounit_stream_init(cubeb * context, cubeb_stream ** stream, char const * stre
static void
audiounit_stream_destroy(cubeb_stream * stm)
{
- int r;
-
stm->shutdown = 1;
- if (stm->unit) {
- AudioOutputUnitStop(stm->unit);
- AudioUnitUninitialize(stm->unit);
-#if MACOSX_LESS_THAN_106
- CloseComponent(stm->unit);
-#else
- AudioComponentInstanceDispose(stm->unit);
-#endif
+ if (stm->input_unit != NULL) {
+ AudioOutputUnitStop(stm->input_unit);
+ AudioUnitUninitialize(stm->input_unit);
+ AudioComponentInstanceDispose(stm->input_unit);
+ stm->input_unit = NULL;
+ }
+
+ audiounit_destroy_input_buffer_list_array(stm);
+
+ if (stm->output_unit != NULL) {
+ AudioOutputUnitStop(stm->output_unit);
+ AudioUnitUninitialize(stm->output_unit);
+ AudioComponentInstanceDispose(stm->output_unit);
+ stm->output_unit = NULL;
}
#if !TARGET_OS_IPHONE
audiounit_uninstall_device_changed_callback(stm);
#endif
- r = pthread_mutex_destroy(&stm->mutex);
+ int r = pthread_mutex_destroy(&stm->mutex);
assert(r == 0);
pthread_mutex_lock(&stm->context->mutex);
@@ -781,8 +1221,14 @@ static int
audiounit_stream_start(cubeb_stream * stm)
{
OSStatus r;
- r = AudioOutputUnitStart(stm->unit);
- assert(r == 0);
+ if (stm->input_unit != NULL) {
+ r = AudioOutputUnitStart(stm->input_unit);
+ assert(r == 0);
+ }
+ if (stm->output_unit != NULL) {
+ r = AudioOutputUnitStart(stm->output_unit);
+ assert(r == 0);
+ }
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
return CUBEB_OK;
}
@@ -791,8 +1237,14 @@ static int
audiounit_stream_stop(cubeb_stream * stm)
{
OSStatus r;
- r = AudioOutputUnitStop(stm->unit);
- assert(r == 0);
+ if (stm->input_unit != NULL) {
+ r = AudioOutputUnitStop(stm->input_unit);
+ assert(r == 0);
+ }
+ if (stm->output_unit != NULL) {
+ r = AudioOutputUnitStop(stm->output_unit);
+ assert(r == 0);
+ }
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
return CUBEB_OK;
}
@@ -839,7 +1291,7 @@ audiounit_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
}
size = sizeof(unit_latency_sec);
- r = AudioUnitGetProperty(stm->unit,
+ r = AudioUnitGetProperty(stm->output_unit,
kAudioUnitProperty_Latency,
kAudioUnitScope_Global,
0,
@@ -876,7 +1328,7 @@ audiounit_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
/* This part is fixed and depend on the stream parameter and the hardware. */
stm->hw_latency_frames =
- (uint32_t)(unit_latency_sec * stm->sample_spec.mSampleRate)
+ (uint32_t)(unit_latency_sec * stm->output_desc.mSampleRate)
+ device_latency_frames
+ device_safety_offset;
}
@@ -892,7 +1344,7 @@ int audiounit_stream_set_volume(cubeb_stream * stm, float volume)
{
OSStatus r;
- r = AudioUnitSetParameter(stm->unit,
+ r = AudioUnitSetParameter(stm->output_unit,
kHALOutputParam_Volume,
kAudioUnitScope_Global,
0, volume, 0);
@@ -905,7 +1357,7 @@ int audiounit_stream_set_volume(cubeb_stream * stm, float volume)
int audiounit_stream_set_panning(cubeb_stream * stm, float panning)
{
- if (stm->sample_spec.mChannelsPerFrame > 2) {
+ if (stm->output_desc.mChannelsPerFrame > 2) {
return CUBEB_ERROR_INVALID_PARAMETER;
}
@@ -989,7 +1441,7 @@ int audiounit_stream_get_current_device(cubeb_stream * stm,
size = sizeof(UInt32);
r = AudioObjectGetPropertyData(input_device_id, &datasource_address_input, 0, NULL, &size, &data);
if (r != noErr) {
- printf("Error when getting device !\n");
+ LOG("Error when getting device !\n");
size = 0;
data = 0;
}
@@ -1196,7 +1648,8 @@ audiounit_create_device_from_hwdev(AudioObjectID devid, cubeb_device_type type)
adr.mSelector = kAudioDevicePropertyDeviceUID;
if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &str) == noErr && str != NULL) {
ret->device_id = audiounit_strref_to_cstr_utf8(str);
- ret->devid = (cubeb_devid)ret->device_id;
+ ret->devid = malloc(sizeof(UInt32));
+ memcpy(ret->devid, &devid, sizeof(UInt32));
ret->group_id = strdup(ret->device_id);
CFRelease(str);
}
diff --git a/src/cubeb_pulse.c b/src/cubeb_pulse.c
index f523bef..033ce89 100644
--- a/src/cubeb_pulse.c
+++ b/src/cubeb_pulse.c
@@ -1036,7 +1036,7 @@ pulse_sink_info_cb(pa_context * context, const pa_sink_info * info,
devinfo = calloc(1, sizeof(cubeb_device_info));
devinfo->device_id = strdup(info->name);
- devinfo->devid = (cubeb_devid)devinfo->device_id;
+ devinfo->devid = strdup(devinfo->device_id);
devinfo->friendly_name = strdup(info->description);
prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path");
if (prop)
@@ -1096,7 +1096,7 @@ pulse_source_info_cb(pa_context * context, const pa_source_info * info,
devinfo = calloc(1, sizeof(cubeb_device_info));
devinfo->device_id = strdup(info->name);
- devinfo->devid = (cubeb_devid)devinfo->device_id;
+ devinfo->devid = strdup(devinfo->device_id);
devinfo->friendly_name = strdup(info->description);
prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path");
if (prop)
diff --git a/src/cubeb_resampler.cpp b/src/cubeb_resampler.cpp
index 45b9b5c..6f9ba92 100644
--- a/src/cubeb_resampler.cpp
+++ b/src/cubeb_resampler.cpp
@@ -33,27 +33,6 @@ to_speex_quality(cubeb_resampler_quality q)
}
}
-template<typename T>
-cubeb_resampler_speex_one_way<T>::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)
-{
- int r;
- speex_resampler = speex_resampler_init(channels, source_rate,
- target_rate, quality, &r);
- assert(r == RESAMPLER_ERR_SUCCESS && "resampler allocation failure");
-}
-
-template<typename T>
-cubeb_resampler_speex_one_way<T>::~cubeb_resampler_speex_one_way()
-{
- speex_resampler_destroy(speex_resampler);
-}
-
long noop_resampler::fill(void * input_buffer, long * input_frames_count,
void * output_buffer, long output_frames)
{
diff --git a/src/cubeb_resampler_internal.h b/src/cubeb_resampler_internal.h
index c845d91..eed16c4 100644
--- a/src/cubeb_resampler_internal.h
+++ b/src/cubeb_resampler_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright � 2016 Mozilla Foundation
+ * Copyright © 2016 Mozilla Foundation
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
@@ -32,9 +32,6 @@ namespace std
int to_speex_quality(cubeb_resampler_quality q);
-template<typename T>
-class cubeb_resampler_speex_one_way;
-
struct cubeb_resampler {
virtual long fill(void * input_buffer, long * input_frames_count,
void * output_buffer, long frames_needed) = 0;
@@ -155,10 +152,22 @@ public:
cubeb_resampler_speex_one_way(uint32_t channels,
uint32_t source_rate,
uint32_t target_rate,
- int quality);
+ int quality)
+ : processor(channels)
+ , resampling_ratio(static_cast<float>(source_rate) / target_rate)
+ , additional_latency(0)
+ {
+ int r;
+ speex_resampler = speex_resampler_init(channels, source_rate,
+ target_rate, quality, &r);
+ assert(r == RESAMPLER_ERR_SUCCESS && "resampler allocation failure");
+ }
/** Destructor, deallocate the resampler */
- virtual ~cubeb_resampler_speex_one_way();
+ virtual ~cubeb_resampler_speex_one_way()
+ {
+ 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
diff --git a/src/cubeb_ring_array.h b/src/cubeb_ring_array.h
new file mode 100644
index 0000000..93498ec
--- /dev/null
+++ b/src/cubeb_ring_array.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_RING_ARRAY_H
+#define CUBEB_RING_ARRAY_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/** Ring array of pointers is used to hold buffers. In case that
+ asynchronus producer/consumer callbacks do not arrive in a
+ repeated order the ring array stores the buffers and fetch
+ them in the correct order. */
+#define RING_ARRAY_CAPACITY 8
+
+typedef struct {
+ void* pointer_array[RING_ARRAY_CAPACITY]; /**< Array that hold pointers of the allocated space for the buffers. */
+ unsigned int tail; /**< Index of the last element (first to deliver). */
+ int count; /**< Number of elements in the array. */
+ unsigned int capacity; /**< Total length of the array. */
+ pthread_mutex_t mutex; /**< Mutex to synchronize store/fetch. */
+} ring_array;
+
+/** Initialize the ring array.
+ @param ra The ring_array pointer of allocated structure.
+ @retval 0 on success. */
+int
+ring_array_init(ring_array * ra)
+{
+ ra->capacity = RING_ARRAY_CAPACITY;
+ ra->tail = 0;
+ ra->count = 0;
+ memset(ra->pointer_array, 0, sizeof(ra->pointer_array));
+
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
+ int ret = pthread_mutex_init(&ra->mutex, &attr);
+ assert(0 == ret);
+ pthread_mutexattr_destroy(&attr);
+
+ assert(ra->pointer_array[0] == NULL);
+
+ return ret;
+}
+
+/** Set the allocated space to store the data.
+ This must be done before store/fetch.
+ @param ra The ring_array pointer.
+ @param data Pointer to allocated space of buffers.
+ @param index Index between 0 and capacity-1 to store the allocated data buffer.
+ @retval The data pointer on success or NULL on failure. */
+void *
+ring_array_set_data(ring_array * ra, void * data, unsigned int index)
+{
+ if (index < ra->capacity) {
+ ra->pointer_array[index] = data;
+ return data;
+ }
+ return NULL;
+}
+
+/** Destroy the ring array.
+ @param ra The ring_array pointer.*/
+void
+ring_array_destroy(ring_array * ra)
+{
+ pthread_mutex_destroy(&ra->mutex);
+}
+
+/** 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. */
+void *
+ring_array_store_buffer(ring_array * ra)
+{
+ int rv = pthread_mutex_lock(&ra->mutex);
+ assert(rv == 0);
+
+ assert(ra->pointer_array[0] != NULL);
+
+ if (ra->count == (int)ra->capacity) {
+ pthread_mutex_unlock(&ra->mutex);
+ return NULL;
+ }
+
+ assert(ra->count == 0 || (ra->tail + ra->count) % ra->capacity != ra->tail);
+ void * ret = ra->pointer_array[(ra->tail + ra->count) % ra->capacity];
+
+ ++ra->count;
+ assert(ra->count <= (int)ra->capacity);
+
+ pthread_mutex_unlock(&ra->mutex);
+ return ret;
+}
+
+/** Get the next available buffer with data.
+ @param ra The ring_array pointer.
+ @retval Pointer of the next in order data buffer or NULL if empty. */
+void *
+ring_array_fetch_buffer(ring_array * ra)
+{
+ int rv = pthread_mutex_lock(&ra->mutex);
+ assert(rv == 0);
+
+ assert(ra->pointer_array[0] != NULL);
+
+ if (ra->count == 0) {
+ pthread_mutex_unlock(&ra->mutex);
+ return NULL;
+ }
+ void * ret = ra->pointer_array[ra->tail];
+
+ ra->tail = (ra->tail + 1) % ra->capacity;
+ assert(ra->tail < ra->capacity);
+
+ ra->count--;
+ assert(ra->count >= 0);
+
+ pthread_mutex_unlock(&ra->mutex);
+ return ret;
+}
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif //CUBEB_RING_ARRAY_H
diff --git a/test/test_resampler.cpp b/test/test_resampler.cpp
index 5a5cd69..c4d9e51 100644
--- a/test/test_resampler.cpp
+++ b/test/test_resampler.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright � 2016 Mozilla Foundation
+ * Copyright © 2016 Mozilla Foundation
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
@@ -8,9 +8,6 @@
#define OUTSIDE_SPEEX
#define RANDOM_PREFIX speex
-#include "cubeb/cubeb.h"
-#include "cubeb_utils.h"
-#include "cubeb_resampler.h"
#include "cubeb_resampler_internal.h"
#include <assert.h>
#include <stdio.h>
@@ -312,9 +309,10 @@ bool array_fuzzy_equal(const auto_array<T>& lhs, const auto_array<T>& rhs, T eps
for (uint32_t i = 0; i < len; i++) {
if (abs(lhs.at(i) - rhs.at(i)) > epsi) {
- std::cout << "not fuzzy equal at index " << i
- << "lhs: " << lhs.at(i) << " rhs: " << rhs.at(i)
- << "delta: " << abs(lhs.at(i) - rhs.at(i)) << std::endl;
+ std::cout << "not fuzzy equal at index: " << i
+ << " lhs: " << lhs.at(i) << " rhs: " << rhs.at(i)
+ << " delta: " << abs(lhs.at(i) - rhs.at(i))
+ << " epsilon: "<< epsi << std::endl;
return false;
}
}
@@ -430,7 +428,7 @@ void test_resamplers_duplex()
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 chanenls:%d output_channels:%d input_rate:%d"
+ printf("input chanenls:%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],
diff --git a/test/test_ring_array.c b/test/test_ring_array.c
new file mode 100644
index 0000000..c2cdfc8
--- /dev/null
+++ b/test/test_ring_array.c
@@ -0,0 +1,70 @@
+#include <pthread.h>
+#include <assert.h>
+#include <string.h>
+
+#include "cubeb_ring_array.h"
+
+int test_ring_array()
+{
+ ring_array ra;
+ ring_array_init(&ra);
+ int data[RING_ARRAY_CAPACITY] ;// {1,2,3,4,5,6,7,8};
+ void * p_data = NULL;
+
+ for (int i =0; i < RING_ARRAY_CAPACITY; ++i) {
+ data[i] = i; // in case RING_ARRAY_CAPACITY change value
+ p_data = ring_array_set_data(&ra, &data[i], i);
+ assert(p_data);
+ assert(p_data == &data[i]);
+ }
+
+ p_data = NULL;
+ /* Get store buffers*/
+ for (int i =0; i < RING_ARRAY_CAPACITY; ++i) {
+ p_data = ring_array_store_buffer(&ra);
+ assert(p_data == &data[i]);
+ }
+ /*Now array is full extra store should give NULL*/
+ assert(NULL == ring_array_store_buffer(&ra));
+ /* Get fetch buffers*/
+ for (int i =0; i < RING_ARRAY_CAPACITY; ++i) {
+ p_data = ring_array_fetch_buffer(&ra);
+ assert(p_data == &data[i]);
+ }
+ /*Now array is empty extra fetch should give NULL*/
+ assert(NULL == ring_array_fetch_buffer(&ra));
+
+ p_data = NULL;
+ /* Repeated store fetch should can go for ever*/
+ for (int i =0; i < 2*RING_ARRAY_CAPACITY; ++i) {
+ p_data = ring_array_store_buffer(&ra);
+ assert(p_data);
+ assert(ring_array_fetch_buffer(&ra) == p_data);
+ }
+
+ p_data = NULL;
+ /* Verify/modify buffer data*/
+ for (int i =0; i < RING_ARRAY_CAPACITY; ++i) {
+ p_data = ring_array_store_buffer(&ra);
+ assert(p_data);
+ assert(*((int*)p_data) == data[i]);
+ (*((int*)p_data))++; // Modify data
+ }
+ for (int i =0; i < RING_ARRAY_CAPACITY; ++i) {
+ p_data = ring_array_fetch_buffer(&ra);
+ assert(p_data);
+ assert(*((int*)p_data) == i+1); // Verify modified data
+ }
+
+ ring_array_destroy(&ra);
+
+ return 0;
+}
+
+
+int main()
+{
+ test_ring_array();
+ return 0;
+}
+