summaryrefslogtreecommitdiff
path: root/pjmedia/src/pjmedia-audiodev/coreaudio_dev.m
diff options
context:
space:
mode:
Diffstat (limited to 'pjmedia/src/pjmedia-audiodev/coreaudio_dev.m')
-rw-r--r--pjmedia/src/pjmedia-audiodev/coreaudio_dev.m2115
1 files changed, 2115 insertions, 0 deletions
diff --git a/pjmedia/src/pjmedia-audiodev/coreaudio_dev.m b/pjmedia/src/pjmedia-audiodev/coreaudio_dev.m
new file mode 100644
index 00000000..b2206824
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/coreaudio_dev.m
@@ -0,0 +1,2115 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjmedia-audiodev/audiodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_COREAUDIO
+
+#include "TargetConditionals.h"
+#if TARGET_OS_IPHONE
+ #define COREAUDIO_MAC 0
+#else
+ #define COREAUDIO_MAC 1
+#endif
+
+#include <AudioUnit/AudioUnit.h>
+#include <AudioToolbox/AudioConverter.h>
+#if COREAUDIO_MAC
+ #include <CoreAudio/CoreAudio.h>
+#else
+ #include <AVFoundation/AVAudioSession.h>
+
+ #define AudioDeviceID unsigned
+
+ /**
+ * As in iOS SDK 4 or later, audio route change property listener is
+ * no longer necessary. Just make surethat your application can receive
+ * remote control events by adding the code:
+ * [[UIApplication sharedApplication]
+ * beginReceivingRemoteControlEvents];
+ * Otherwise audio route change (such as headset plug/unplug) will not be
+ * processed while your application is in the background mode.
+ */
+ #define USE_AUDIO_ROUTE_CHANGE_PROP_LISTENER 0
+
+ /* Starting iOS SDK 7, Audio Session API is deprecated. */
+ #define USE_AUDIO_SESSION_API 0
+#endif
+
+/* For Mac OS 10.5.x and earlier */
+#if AUDIO_UNIT_VERSION < 1060
+ #define AudioComponent Component
+ #define AudioComponentDescription ComponentDescription
+ #define AudioComponentInstance ComponentInstance
+ #define AudioComponentFindNext FindNextComponent
+ #define AudioComponentInstanceNew OpenAComponent
+ #define AudioComponentInstanceDispose CloseComponent
+#endif
+
+
+#define THIS_FILE "coreaudio_dev.c"
+
+/* coreaudio device info */
+struct coreaudio_dev_info
+{
+ pjmedia_aud_dev_info info;
+ AudioDeviceID dev_id;
+};
+
+/* linked list of streams */
+struct stream_list
+{
+ PJ_DECL_LIST_MEMBER(struct stream_list);
+ struct coreaudio_stream *stream;
+};
+
+/* coreaudio factory */
+struct coreaudio_factory
+{
+ pjmedia_aud_dev_factory base;
+ pj_pool_t *base_pool;
+ pj_pool_t *pool;
+ pj_pool_factory *pf;
+ pj_mutex_t *mutex;
+
+ unsigned dev_count;
+ struct coreaudio_dev_info *dev_info;
+
+ AudioComponent io_comp;
+ struct stream_list streams;
+};
+
+/* Sound stream. */
+struct coreaudio_stream
+{
+ pjmedia_aud_stream base; /**< Base stream */
+ pjmedia_aud_param param; /**< Settings */
+ pj_pool_t *pool; /**< Memory pool. */
+ struct coreaudio_factory *cf;
+ struct stream_list list_entry;
+
+ pjmedia_aud_rec_cb rec_cb; /**< Capture callback. */
+ pjmedia_aud_play_cb play_cb; /**< Playback callback. */
+ void *user_data; /**< Application data. */
+
+ pj_timestamp play_timestamp;
+ pj_timestamp rec_timestamp;
+
+ pj_int16_t *rec_buf;
+ unsigned rec_buf_count;
+ pj_int16_t *play_buf;
+ unsigned play_buf_count;
+
+ pj_bool_t interrupted;
+ pj_bool_t quit_flag;
+ pj_bool_t running;
+
+ pj_bool_t rec_thread_initialized;
+ pj_thread_desc rec_thread_desc;
+ pj_thread_t *rec_thread;
+
+ pj_bool_t play_thread_initialized;
+ pj_thread_desc play_thread_desc;
+ pj_thread_t *play_thread;
+
+ AudioUnit io_units[2];
+ AudioStreamBasicDescription streamFormat;
+ AudioBufferList *audio_buf;
+
+ AudioConverterRef resample;
+ pj_int16_t *resample_buf;
+ void *resample_buf_ptr;
+ unsigned resample_buf_count;
+ unsigned resample_buf_size;
+
+#if !COREAUDIO_MAC
+ AVAudioSession *sess;
+#endif
+};
+
+/* Static variable */
+static struct coreaudio_factory *cf_instance = NULL;
+
+/* Prototypes */
+static pj_status_t ca_factory_init(pjmedia_aud_dev_factory *f);
+static pj_status_t ca_factory_destroy(pjmedia_aud_dev_factory *f);
+static pj_status_t ca_factory_refresh(pjmedia_aud_dev_factory *f);
+static unsigned ca_factory_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t ca_factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info);
+static pj_status_t ca_factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param);
+static pj_status_t ca_factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm);
+
+static pj_status_t ca_stream_get_param(pjmedia_aud_stream *strm,
+ pjmedia_aud_param *param);
+static pj_status_t ca_stream_get_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ void *value);
+static pj_status_t ca_stream_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value);
+static pj_status_t ca_stream_start(pjmedia_aud_stream *strm);
+static pj_status_t ca_stream_stop(pjmedia_aud_stream *strm);
+static pj_status_t ca_stream_destroy(pjmedia_aud_stream *strm);
+static pj_status_t create_audio_unit(AudioComponent io_comp,
+ AudioDeviceID dev_id,
+ pjmedia_dir dir,
+ struct coreaudio_stream *strm,
+ AudioUnit *io_unit);
+#if !COREAUDIO_MAC && USE_AUDIO_SESSION_API != 0
+static void interruptionListener(void *inClientData, UInt32 inInterruption);
+static void propListener(void * inClientData,
+ AudioSessionPropertyID inID,
+ UInt32 inDataSize,
+ const void * inData);
+#endif
+
+/* Operations */
+static pjmedia_aud_dev_factory_op factory_op =
+{
+ &ca_factory_init,
+ &ca_factory_destroy,
+ &ca_factory_get_dev_count,
+ &ca_factory_get_dev_info,
+ &ca_factory_default_param,
+ &ca_factory_create_stream,
+ &ca_factory_refresh
+};
+
+static pjmedia_aud_stream_op stream_op =
+{
+ &ca_stream_get_param,
+ &ca_stream_get_cap,
+ &ca_stream_set_cap,
+ &ca_stream_start,
+ &ca_stream_stop,
+ &ca_stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Init coreaudio audio driver.
+ */
+pjmedia_aud_dev_factory* pjmedia_coreaudio_factory(pj_pool_factory *pf)
+{
+ struct coreaudio_factory *f;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, "core audio base", 1000, 1000, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, struct coreaudio_factory);
+ f->pf = pf;
+ f->base_pool = pool;
+ f->base.op = &factory_op;
+
+ return &f->base;
+}
+
+
+/* API: init factory */
+static pj_status_t ca_factory_init(pjmedia_aud_dev_factory *f)
+{
+ struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
+ AudioComponentDescription desc;
+ pj_status_t status;
+#if !COREAUDIO_MAC
+ unsigned i;
+#endif
+
+ pj_list_init(&cf->streams);
+ status = pj_mutex_create_recursive(cf->base_pool,
+ "coreaudio",
+ &cf->mutex);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ desc.componentType = kAudioUnitType_Output;
+#if COREAUDIO_MAC
+ desc.componentSubType = kAudioUnitSubType_HALOutput;
+#else
+ desc.componentSubType = kAudioUnitSubType_RemoteIO;
+#endif
+ desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+ desc.componentFlags = 0;
+ desc.componentFlagsMask = 0;
+
+ cf->io_comp = AudioComponentFindNext(NULL, &desc);
+ if (cf->io_comp == NULL)
+ return PJMEDIA_EAUD_INIT; // cannot find IO unit;
+
+ status = ca_factory_refresh(f);
+ if (status != PJ_SUCCESS)
+ return status;
+
+#if !COREAUDIO_MAC
+ cf->pool = pj_pool_create(cf->pf, "core audio", 1000, 1000, NULL);
+ cf->dev_count = 1;
+ cf->dev_info = (struct coreaudio_dev_info*)
+ pj_pool_calloc(cf->pool, cf->dev_count,
+ sizeof(struct coreaudio_dev_info));
+ for (i = 0; i < cf->dev_count; i++) {
+ struct coreaudio_dev_info *cdi;
+
+ cdi = &cf->dev_info[i];
+ pj_bzero(cdi, sizeof(*cdi));
+ cdi->dev_id = 0;
+ strcpy(cdi->info.name, "iPhone IO device");
+ strcpy(cdi->info.driver, "apple");
+ cdi->info.input_count = 1;
+ cdi->info.output_count = 1;
+ cdi->info.default_samples_per_sec = 8000;
+
+ /* Set the device capabilities here */
+ cdi->info.caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING |
+#if USE_AUDIO_SESSION_API != 0
+ PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE |
+#endif
+ PJMEDIA_AUD_DEV_CAP_EC;
+ cdi->info.routes = PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER |
+ PJMEDIA_AUD_DEV_ROUTE_EARPIECE |
+ PJMEDIA_AUD_DEV_ROUTE_BLUETOOTH;
+
+ PJ_LOG(4, (THIS_FILE, " dev_id %d: %s (in=%d, out=%d) %dHz",
+ i,
+ cdi->info.name,
+ cdi->info.input_count,
+ cdi->info.output_count,
+ cdi->info.default_samples_per_sec));
+ }
+
+#if USE_AUDIO_SESSION_API != 0
+ {
+ OSStatus ostatus;
+
+ /* Initialize the Audio Session */
+ ostatus = AudioSessionInitialize(NULL, NULL, interruptionListener,
+ NULL);
+ if (ostatus != kAudioSessionNoError) {
+ PJ_LOG(4, (THIS_FILE,
+ "Warning: cannot initialize audio session services (%i)",
+ ostatus));
+ }
+
+ /* Listen for audio routing change notifications. */
+#if USE_AUDIO_ROUTE_CHANGE_PROP_LISTENER != 0
+ ostatus = AudioSessionAddPropertyListener(
+ kAudioSessionProperty_AudioRouteChange,
+ propListener, cf);
+ if (ostatus != kAudioSessionNoError) {
+ PJ_LOG(4, (THIS_FILE,
+ "Warning: cannot listen for audio route change "
+ "notifications (%i)", ostatus));
+ }
+#endif
+ }
+#endif
+
+ cf_instance = cf;
+#endif
+
+ PJ_LOG(4, (THIS_FILE, "core audio initialized"));
+
+ return PJ_SUCCESS;
+}
+
+/* API: destroy factory */
+static pj_status_t ca_factory_destroy(pjmedia_aud_dev_factory *f)
+{
+ struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
+ pj_pool_t *pool;
+
+ pj_assert(cf);
+ pj_assert(cf->base_pool);
+ pj_assert(pj_list_empty(&cf->streams));
+
+#if !COREAUDIO_MAC
+#if USE_AUDIO_SESSION_API != 0 && USE_AUDIO_ROUTE_CHANGE_PROP_LISTENER != 0
+ AudioSessionRemovePropertyListenerWithUserData(
+ kAudioSessionProperty_AudioRouteChange, propListener, cf);
+#endif
+#endif
+
+ if (cf->pool) {
+ pj_pool_release(cf->pool);
+ cf->pool = NULL;
+ }
+
+ if (cf->mutex) {
+ pj_mutex_lock(cf->mutex);
+ cf_instance = NULL;
+ pj_mutex_unlock(cf->mutex);
+ pj_mutex_destroy(cf->mutex);
+ cf->mutex = NULL;
+ }
+
+ pool = cf->base_pool;
+ cf->base_pool = NULL;
+ pj_pool_release(pool);
+
+ return PJ_SUCCESS;
+}
+
+/* API: refresh the device list */
+static pj_status_t ca_factory_refresh(pjmedia_aud_dev_factory *f)
+{
+#if !COREAUDIO_MAC
+ /* iPhone doesn't support refreshing the device list */
+ PJ_UNUSED_ARG(f);
+ return PJ_SUCCESS;
+#else
+ struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
+ unsigned i;
+ unsigned dev_count;
+ AudioObjectPropertyAddress addr;
+ AudioDeviceID *dev_ids;
+ UInt32 buf_size, dev_size, size = sizeof(AudioDeviceID);
+ AudioBufferList *buf = NULL;
+ OSStatus ostatus;
+
+ if (cf->pool != NULL) {
+ pj_pool_release(cf->pool);
+ cf->pool = NULL;
+ }
+
+ cf->dev_count = 0;
+ cf->pool = pj_pool_create(cf->pf, "core audio", 1000, 1000, NULL);
+
+ /* Find out how many audio devices there are */
+ addr.mSelector = kAudioHardwarePropertyDevices;
+ addr.mScope = kAudioObjectPropertyScopeGlobal;
+ addr.mElement = kAudioObjectPropertyElementMaster;
+ ostatus = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr,
+ 0, NULL, &dev_size);
+ if (ostatus != noErr) {
+ dev_size = 0;
+ }
+
+ /* Calculate the number of audio devices available */
+ dev_count = dev_size / size;
+ if (dev_count==0) {
+ PJ_LOG(4,(THIS_FILE, "core audio found no sound devices"));
+ /* Enabling this will cause pjsua-lib initialization to fail when
+ * there is no sound device installed in the system, even when pjsua
+ * has been run with --null-audio. Moreover, it might be better to
+ * think that the core audio backend initialization is successful,
+ * regardless there is no audio device installed, as later application
+ * can check it using get_dev_count().
+ return PJMEDIA_EAUD_NODEV;
+ */
+ return PJ_SUCCESS;
+ }
+ PJ_LOG(4, (THIS_FILE, "core audio detected %d devices",
+ dev_count));
+
+ /* Get all the audio device IDs */
+ dev_ids = (AudioDeviceID *)pj_pool_calloc(cf->pool, dev_size, size);
+ if (!dev_ids)
+ return PJ_ENOMEM;
+ pj_bzero(dev_ids, dev_count);
+ ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr,
+ 0, NULL,
+ &dev_size, (void *)dev_ids);
+ if (ostatus != noErr ) {
+ /* This should not happen since we have successfully retrieved
+ * the property data size before
+ */
+ return PJMEDIA_EAUD_INIT;
+ }
+
+ if (dev_size > 1) {
+ AudioDeviceID dev_id = kAudioObjectUnknown;
+ unsigned idx = 0;
+
+ /* Find default audio input device */
+ addr.mSelector = kAudioHardwarePropertyDefaultInputDevice;
+ addr.mScope = kAudioObjectPropertyScopeGlobal;
+ addr.mElement = kAudioObjectPropertyElementMaster;
+ size = sizeof(dev_id);
+
+ ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject,
+ &addr, 0, NULL,
+ &size, (void *)&dev_id);
+ if (ostatus == noErr && dev_id != dev_ids[idx]) {
+ AudioDeviceID temp_id = dev_ids[idx];
+
+ for (i = idx + 1; i < dev_size; i++) {
+ if (dev_ids[i] == dev_id) {
+ dev_ids[idx++] = dev_id;
+ dev_ids[i] = temp_id;
+ break;
+ }
+ }
+ }
+
+ /* Find default audio output device */
+ addr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
+ ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject,
+ &addr, 0, NULL,
+ &size, (void *)&dev_id);
+ if (ostatus == noErr && dev_id != dev_ids[idx]) {
+ AudioDeviceID temp_id = dev_ids[idx];
+
+ for (i = idx + 1; i < dev_size; i++) {
+ if (dev_ids[i] == dev_id) {
+ dev_ids[idx] = dev_id;
+ dev_ids[i] = temp_id;
+ break;
+ }
+ }
+ }
+ }
+
+ /* Build the devices' info */
+ cf->dev_info = (struct coreaudio_dev_info*)
+ pj_pool_calloc(cf->pool, dev_count,
+ sizeof(struct coreaudio_dev_info));
+ buf_size = 0;
+ for (i = 0; i < dev_count; i++) {
+ struct coreaudio_dev_info *cdi;
+ Float64 sampleRate;
+
+ cdi = &cf->dev_info[i];
+ pj_bzero(cdi, sizeof(*cdi));
+ cdi->dev_id = dev_ids[i];
+
+ /* Get device name */
+ addr.mSelector = kAudioDevicePropertyDeviceName;
+ addr.mScope = kAudioObjectPropertyScopeGlobal;
+ addr.mElement = kAudioObjectPropertyElementMaster;
+ size = sizeof(cdi->info.name);
+ AudioObjectGetPropertyData(cdi->dev_id, &addr,
+ 0, NULL,
+ &size, (void *)cdi->info.name);
+
+ strcpy(cdi->info.driver, "core audio");
+
+ /* Get the number of input channels */
+ addr.mSelector = kAudioDevicePropertyStreamConfiguration;
+ addr.mScope = kAudioDevicePropertyScopeInput;
+ size = 0;
+ ostatus = AudioObjectGetPropertyDataSize(cdi->dev_id, &addr,
+ 0, NULL, &size);
+ if (ostatus == noErr && size > 0) {
+
+ if (size > buf_size) {
+ buf = pj_pool_alloc(cf->pool, size);
+ buf_size = size;
+ }
+ if (buf) {
+ UInt32 idx;
+
+ /* Get the input stream configuration */
+ ostatus = AudioObjectGetPropertyData(cdi->dev_id, &addr,
+ 0, NULL,
+ &size, buf);
+ if (ostatus == noErr) {
+ /* Count the total number of input channels in
+ * the stream
+ */
+ for (idx = 0; idx < buf->mNumberBuffers; idx++) {
+ cdi->info.input_count +=
+ buf->mBuffers[idx].mNumberChannels;
+ }
+ }
+ }
+ }
+
+ /* Get the number of output channels */
+ addr.mScope = kAudioDevicePropertyScopeOutput;
+ size = 0;
+ ostatus = AudioObjectGetPropertyDataSize(cdi->dev_id, &addr,
+ 0, NULL, &size);
+ if (ostatus == noErr && size > 0) {
+
+ if (size > buf_size) {
+ buf = pj_pool_alloc(cf->pool, size);
+ buf_size = size;
+ }
+ if (buf) {
+ UInt32 idx;
+
+ /* Get the output stream configuration */
+ ostatus = AudioObjectGetPropertyData(cdi->dev_id, &addr,
+ 0, NULL,
+ &size, buf);
+ if (ostatus == noErr) {
+ /* Count the total number of output channels in
+ * the stream
+ */
+ for (idx = 0; idx < buf->mNumberBuffers; idx++) {
+ cdi->info.output_count +=
+ buf->mBuffers[idx].mNumberChannels;
+ }
+ }
+ }
+ }
+
+ /* Get default sample rate */
+ addr.mSelector = kAudioDevicePropertyNominalSampleRate;
+ addr.mScope = kAudioObjectPropertyScopeGlobal;
+ size = sizeof(Float64);
+ ostatus = AudioObjectGetPropertyData (cdi->dev_id, &addr,
+ 0, NULL,
+ &size, &sampleRate);
+ cdi->info.default_samples_per_sec = (ostatus == noErr ?
+ sampleRate:
+ 16000);
+
+ /* Set device capabilities here */
+ if (cdi->info.input_count > 0) {
+ cdi->info.caps |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+ }
+ if (cdi->info.output_count > 0) {
+ cdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+ addr.mSelector = kAudioDevicePropertyVolumeScalar;
+ addr.mScope = kAudioDevicePropertyScopeOutput;
+ if (AudioObjectHasProperty(cdi->dev_id, &addr)) {
+ cdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
+ }
+ }
+
+ cf->dev_count++;
+
+ PJ_LOG(4, (THIS_FILE, " dev_id %d: %s (in=%d, out=%d) %dHz",
+ i,
+ cdi->info.name,
+ cdi->info.input_count,
+ cdi->info.output_count,
+ cdi->info.default_samples_per_sec));
+ }
+
+ return PJ_SUCCESS;
+#endif
+}
+
+/* API: get number of devices */
+static unsigned ca_factory_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+ struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
+ return cf->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t ca_factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info)
+{
+ struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
+
+ PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EAUD_INVDEV);
+
+ pj_memcpy(info, &cf->dev_info[index].info, sizeof(*info));
+
+ return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t ca_factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param)
+{
+ struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
+ struct coreaudio_dev_info *di = &cf->dev_info[index];
+
+ PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EAUD_INVDEV);
+
+ pj_bzero(param, sizeof(*param));
+ if (di->info.input_count && di->info.output_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param->rec_id = index;
+ param->play_id = index;
+ } else if (di->info.input_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE;
+ param->rec_id = index;
+ param->play_id = PJMEDIA_AUD_INVALID_DEV;
+ } else if (di->info.output_count) {
+ param->dir = PJMEDIA_DIR_PLAYBACK;
+ param->play_id = index;
+ param->rec_id = PJMEDIA_AUD_INVALID_DEV;
+ } else {
+ return PJMEDIA_EAUD_INVDEV;
+ }
+
+ /* Set the mandatory settings here */
+ param->clock_rate = di->info.default_samples_per_sec;
+ param->channel_count = 1;
+ param->samples_per_frame = di->info.default_samples_per_sec * 20 / 1000;
+ param->bits_per_sample = 16;
+
+ /* Set the param for device capabilities here */
+ param->flags = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+ param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+ param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+
+ return PJ_SUCCESS;
+}
+
+OSStatus resampleProc(AudioConverterRef inAudioConverter,
+ UInt32 *ioNumberDataPackets,
+ AudioBufferList *ioData,
+ AudioStreamPacketDescription **outDataPacketDescription,
+ void *inUserData)
+{
+ struct coreaudio_stream *strm = (struct coreaudio_stream*)inUserData;
+
+ if (*ioNumberDataPackets > strm->resample_buf_size)
+ *ioNumberDataPackets = strm->resample_buf_size;
+
+ ioData->mNumberBuffers = 1;
+ ioData->mBuffers[0].mNumberChannels = strm->streamFormat.mChannelsPerFrame;
+ ioData->mBuffers[0].mData = strm->resample_buf_ptr;
+ ioData->mBuffers[0].mDataByteSize = *ioNumberDataPackets *
+ strm->streamFormat.mChannelsPerFrame *
+ strm->param.bits_per_sample >> 3;
+
+ return noErr;
+}
+
+static OSStatus resample_callback(void *inRefCon,
+ AudioUnitRenderActionFlags *ioActionFlags,
+ const AudioTimeStamp *inTimeStamp,
+ UInt32 inBusNumber,
+ UInt32 inNumberFrames,
+ AudioBufferList *ioData)
+{
+ struct coreaudio_stream *strm = (struct coreaudio_stream*)inRefCon;
+ OSStatus ostatus;
+ pj_status_t status = 0;
+ unsigned nsamples;
+ AudioBufferList *buf = strm->audio_buf;
+ pj_int16_t *input;
+ UInt32 resampleSize;
+
+ pj_assert(!strm->quit_flag);
+
+ /* Known cases of callback's thread:
+ * - The thread may be changed in the middle of a session
+ * it happens when plugging/unplugging headphone.
+ * - The same thread may be reused in consecutive sessions. The first
+ * session will leave TLS set, but release the TLS data address,
+ * so the second session must re-register the callback's thread.
+ */
+ if (strm->rec_thread_initialized == 0 || !pj_thread_is_registered())
+ {
+ pj_bzero(strm->rec_thread_desc, sizeof(pj_thread_desc));
+ status = pj_thread_register("ca_rec", strm->rec_thread_desc,
+ &strm->rec_thread);
+ strm->rec_thread_initialized = 1;
+ PJ_LOG(5,(THIS_FILE, "Recorder thread started, (%i frames)",
+ inNumberFrames));
+ }
+
+ buf->mBuffers[0].mData = NULL;
+ buf->mBuffers[0].mDataByteSize = inNumberFrames *
+ strm->streamFormat.mChannelsPerFrame;
+ /* Render the unit to get input data */
+ ostatus = AudioUnitRender(strm->io_units[0],
+ ioActionFlags,
+ inTimeStamp,
+ inBusNumber,
+ inNumberFrames,
+ buf);
+
+ if (ostatus != noErr) {
+ PJ_LOG(5, (THIS_FILE, "Core audio unit render error %i", ostatus));
+ goto on_break;
+ }
+ input = (pj_int16_t *)buf->mBuffers[0].mData;
+
+ resampleSize = strm->resample_buf_size;
+ nsamples = inNumberFrames * strm->param.channel_count +
+ strm->resample_buf_count;
+
+ if (nsamples >= resampleSize) {
+ pjmedia_frame frame;
+ UInt32 resampleOutput = strm->param.samples_per_frame /
+ strm->streamFormat.mChannelsPerFrame;
+ AudioBufferList ab;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.buf = (void*) strm->rec_buf;
+ frame.size = strm->param.samples_per_frame *
+ strm->param.bits_per_sample >> 3;
+ frame.bit_info = 0;
+
+ ab.mNumberBuffers = 1;
+ ab.mBuffers[0].mNumberChannels = strm->streamFormat.mChannelsPerFrame;
+ ab.mBuffers[0].mData = strm->rec_buf;
+ ab.mBuffers[0].mDataByteSize = frame.size;
+
+ /* If buffer is not empty, combine the buffer with the just incoming
+ * samples, then call put_frame.
+ */
+ if (strm->resample_buf_count) {
+ unsigned chunk_count = resampleSize - strm->resample_buf_count;
+ pjmedia_copy_samples(strm->resample_buf + strm->resample_buf_count,
+ input, chunk_count);
+
+ /* Do the resample */
+
+ strm->resample_buf_ptr = strm->resample_buf;
+ ostatus = AudioConverterFillComplexBuffer(strm->resample,
+ resampleProc,
+ strm,
+ &resampleOutput,
+ &ab,
+ NULL);
+ if (ostatus != noErr) {
+ goto on_break;
+ }
+ frame.timestamp.u64 = strm->rec_timestamp.u64;
+
+ status = (*strm->rec_cb)(strm->user_data, &frame);
+
+ input = input + chunk_count;
+ nsamples -= resampleSize;
+ strm->resample_buf_count = 0;
+ strm->rec_timestamp.u64 += strm->param.samples_per_frame /
+ strm->param.channel_count;
+ }
+
+
+ /* Give all frames we have */
+ while (nsamples >= resampleSize && status == 0) {
+ frame.timestamp.u64 = strm->rec_timestamp.u64;
+
+ /* Do the resample */
+ strm->resample_buf_ptr = input;
+ ab.mBuffers[0].mDataByteSize = frame.size;
+ resampleOutput = strm->param.samples_per_frame /
+ strm->streamFormat.mChannelsPerFrame;
+ ostatus = AudioConverterFillComplexBuffer(strm->resample,
+ resampleProc,
+ strm,
+ &resampleOutput,
+ &ab,
+ NULL);
+ if (ostatus != noErr) {
+ goto on_break;
+ }
+
+ status = (*strm->rec_cb)(strm->user_data, &frame);
+
+ input = (pj_int16_t*) input + resampleSize;
+ nsamples -= resampleSize;
+ strm->rec_timestamp.u64 += strm->param.samples_per_frame /
+ strm->param.channel_count;
+ }
+
+ /* Store the remaining samples into the buffer */
+ if (nsamples && status == 0) {
+ strm->resample_buf_count = nsamples;
+ pjmedia_copy_samples(strm->resample_buf, input,
+ nsamples);
+ }
+
+ } else {
+ /* Not enough samples, let's just store them in the buffer */
+ pjmedia_copy_samples(strm->resample_buf + strm->resample_buf_count,
+ input,
+ inNumberFrames * strm->param.channel_count);
+ strm->resample_buf_count += inNumberFrames *
+ strm->param.channel_count;
+ }
+
+ return noErr;
+
+on_break:
+ return -1;
+}
+
+static OSStatus input_callback(void *inRefCon,
+ AudioUnitRenderActionFlags *ioActionFlags,
+ const AudioTimeStamp *inTimeStamp,
+ UInt32 inBusNumber,
+ UInt32 inNumberFrames,
+ AudioBufferList *ioData)
+{
+ struct coreaudio_stream *strm = (struct coreaudio_stream*)inRefCon;
+ OSStatus ostatus;
+ pj_status_t status = 0;
+ unsigned nsamples;
+ AudioBufferList *buf = strm->audio_buf;
+ pj_int16_t *input;
+
+ pj_assert(!strm->quit_flag);
+
+ /* Known cases of callback's thread:
+ * - The thread may be changed in the middle of a session
+ * it happens when plugging/unplugging headphone.
+ * - The same thread may be reused in consecutive sessions. The first
+ * session will leave TLS set, but release the TLS data address,
+ * so the second session must re-register the callback's thread.
+ */
+ if (strm->rec_thread_initialized == 0 || !pj_thread_is_registered())
+ {
+ pj_bzero(strm->rec_thread_desc, sizeof(pj_thread_desc));
+ status = pj_thread_register("ca_rec", strm->rec_thread_desc,
+ &strm->rec_thread);
+ strm->rec_thread_initialized = 1;
+ PJ_LOG(5,(THIS_FILE, "Recorder thread started, (%i frames)",
+ inNumberFrames));
+ }
+
+ buf->mBuffers[0].mData = NULL;
+ buf->mBuffers[0].mDataByteSize = inNumberFrames *
+ strm->streamFormat.mChannelsPerFrame;
+ /* Render the unit to get input data */
+ ostatus = AudioUnitRender(strm->io_units[0],
+ ioActionFlags,
+ inTimeStamp,
+ inBusNumber,
+ inNumberFrames,
+ buf);
+
+ if (ostatus != noErr) {
+ PJ_LOG(5, (THIS_FILE, "Core audio unit render error %i", ostatus));
+ goto on_break;
+ }
+ input = (pj_int16_t *)buf->mBuffers[0].mData;
+
+ /* Calculate number of samples we've got */
+ nsamples = inNumberFrames * strm->param.channel_count +
+ strm->rec_buf_count;
+ if (nsamples >= strm->param.samples_per_frame) {
+ pjmedia_frame frame;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.size = strm->param.samples_per_frame *
+ strm->param.bits_per_sample >> 3;
+ frame.bit_info = 0;
+
+ /* If buffer is not empty, combine the buffer with the just incoming
+ * samples, then call put_frame.
+ */
+ if (strm->rec_buf_count) {
+ unsigned chunk_count = 0;
+
+ chunk_count = strm->param.samples_per_frame - strm->rec_buf_count;
+ pjmedia_copy_samples(strm->rec_buf + strm->rec_buf_count,
+ input, chunk_count);
+
+ frame.buf = (void*) strm->rec_buf;
+ frame.timestamp.u64 = strm->rec_timestamp.u64;
+
+ status = (*strm->rec_cb)(strm->user_data, &frame);
+
+ input = input + chunk_count;
+ nsamples -= strm->param.samples_per_frame;
+ strm->rec_buf_count = 0;
+ strm->rec_timestamp.u64 += strm->param.samples_per_frame /
+ strm->param.channel_count;
+ }
+
+ /* Give all frames we have */
+ while (nsamples >= strm->param.samples_per_frame && status == 0) {
+ frame.buf = (void*) input;
+ frame.timestamp.u64 = strm->rec_timestamp.u64;
+
+ status = (*strm->rec_cb)(strm->user_data, &frame);
+
+ input = (pj_int16_t*) input + strm->param.samples_per_frame;
+ nsamples -= strm->param.samples_per_frame;
+ strm->rec_timestamp.u64 += strm->param.samples_per_frame /
+ strm->param.channel_count;
+ }
+
+ /* Store the remaining samples into the buffer */
+ if (nsamples && status == 0) {
+ strm->rec_buf_count = nsamples;
+ pjmedia_copy_samples(strm->rec_buf, input,
+ nsamples);
+ }
+
+ } else {
+ /* Not enough samples, let's just store them in the buffer */
+ pjmedia_copy_samples(strm->rec_buf + strm->rec_buf_count,
+ input,
+ inNumberFrames * strm->param.channel_count);
+ strm->rec_buf_count += inNumberFrames * strm->param.channel_count;
+ }
+
+ return noErr;
+
+on_break:
+ return -1;
+}
+
+static OSStatus output_renderer(void *inRefCon,
+ AudioUnitRenderActionFlags *ioActionFlags,
+ const AudioTimeStamp *inTimeStamp,
+ UInt32 inBusNumber,
+ UInt32 inNumberFrames,
+ AudioBufferList *ioData)
+{
+ struct coreaudio_stream *stream = (struct coreaudio_stream*)inRefCon;
+ pj_status_t status = 0;
+ unsigned nsamples_req = inNumberFrames * stream->param.channel_count;
+ pj_int16_t *output = ioData->mBuffers[0].mData;
+
+ pj_assert(!stream->quit_flag);
+
+ /* Known cases of callback's thread:
+ * - The thread may be changed in the middle of a session
+ * it happens when plugging/unplugging headphone.
+ * - The same thread may be reused in consecutive sessions. The first
+ * session will leave TLS set, but release the TLS data address,
+ * so the second session must re-register the callback's thread.
+ */
+ if (stream->play_thread_initialized == 0 || !pj_thread_is_registered())
+ {
+ pj_bzero(stream->play_thread_desc, sizeof(pj_thread_desc));
+ status = pj_thread_register("coreaudio", stream->play_thread_desc,
+ &stream->play_thread);
+ stream->play_thread_initialized = 1;
+ PJ_LOG(5,(THIS_FILE, "Player thread started, (%i frames)",
+ inNumberFrames));
+ }
+
+
+ /* Check if any buffered samples */
+ if (stream->play_buf_count) {
+ /* samples buffered >= requested by sound device */
+ if (stream->play_buf_count >= nsamples_req) {
+ pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
+ nsamples_req);
+ stream->play_buf_count -= nsamples_req;
+ pjmedia_move_samples(stream->play_buf,
+ stream->play_buf + nsamples_req,
+ stream->play_buf_count);
+ nsamples_req = 0;
+
+ return noErr;
+ }
+
+ /* samples buffered < requested by sound device */
+ pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
+ stream->play_buf_count);
+ nsamples_req -= stream->play_buf_count;
+ output = (pj_int16_t*)output + stream->play_buf_count;
+ stream->play_buf_count = 0;
+ }
+
+ /* Fill output buffer as requested */
+ while (nsamples_req && status == 0) {
+ pjmedia_frame frame;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.size = stream->param.samples_per_frame *
+ stream->param.bits_per_sample >> 3;
+ frame.timestamp.u64 = stream->play_timestamp.u64;
+ frame.bit_info = 0;
+
+ if (nsamples_req >= stream->param.samples_per_frame) {
+ frame.buf = output;
+ status = (*stream->play_cb)(stream->user_data, &frame);
+ if (status != PJ_SUCCESS)
+ goto on_break;
+
+ if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
+ pj_bzero(frame.buf, frame.size);
+
+ nsamples_req -= stream->param.samples_per_frame;
+ output = (pj_int16_t*)output + stream->param.samples_per_frame;
+ } else {
+ frame.buf = stream->play_buf;
+ status = (*stream->play_cb)(stream->user_data, &frame);
+ if (status != PJ_SUCCESS)
+ goto on_break;
+
+ if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
+ pj_bzero(frame.buf, frame.size);
+
+ pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
+ nsamples_req);
+ stream->play_buf_count = stream->param.samples_per_frame -
+ nsamples_req;
+ pjmedia_move_samples(stream->play_buf,
+ stream->play_buf+nsamples_req,
+ stream->play_buf_count);
+ nsamples_req = 0;
+ }
+
+ stream->play_timestamp.u64 += stream->param.samples_per_frame /
+ stream->param.channel_count;
+ }
+
+ return noErr;
+
+on_break:
+ return -1;
+}
+
+#if !COREAUDIO_MAC && USE_AUDIO_SESSION_API != 0
+static void propListener(void *inClientData,
+ AudioSessionPropertyID inID,
+ UInt32 inDataSize,
+ const void * inData)
+{
+ struct coreaudio_factory *cf = (struct coreaudio_factory*)inClientData;
+ struct stream_list *it, *itBegin;
+ CFDictionaryRef routeDictionary;
+ CFNumberRef reason;
+ SInt32 reasonVal;
+ pj_assert(cf);
+
+ if (inID != kAudioSessionProperty_AudioRouteChange)
+ return;
+
+ routeDictionary = (CFDictionaryRef)inData;
+ reason = (CFNumberRef)
+ CFDictionaryGetValue(
+ routeDictionary,
+ CFSTR(kAudioSession_AudioRouteChangeKey_Reason));
+ CFNumberGetValue(reason, kCFNumberSInt32Type, &reasonVal);
+
+ if (reasonVal != kAudioSessionRouteChangeReason_OldDeviceUnavailable) {
+ PJ_LOG(3, (THIS_FILE, "ignoring audio route change..."));
+ return;
+ }
+
+ PJ_LOG(3, (THIS_FILE, "audio route changed"));
+
+ pj_mutex_lock(cf->mutex);
+ itBegin = &cf->streams;
+ for (it = itBegin->next; it != itBegin; it = it->next) {
+ if (it->stream->interrupted)
+ continue;
+
+ /*
+ status = ca_stream_stop((pjmedia_aud_stream *)it->stream);
+ status = ca_stream_start((pjmedia_aud_stream *)it->stream);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE,
+ "Error: failed to restart the audio unit (%i)",
+ status));
+ continue;
+ }
+ PJ_LOG(3, (THIS_FILE, "core audio unit successfully restarted"));
+ */
+ }
+ pj_mutex_unlock(cf->mutex);
+}
+
+static void interruptionListener(void *inClientData, UInt32 inInterruption)
+{
+ struct stream_list *it, *itBegin;
+ pj_status_t status;
+ pj_thread_desc thread_desc;
+ pj_thread_t *thread;
+
+ /* Register the thread with PJLIB, this is must for any external threads
+ * which need to use the PJLIB framework.
+ */
+ if (!pj_thread_is_registered()) {
+ pj_bzero(thread_desc, sizeof(pj_thread_desc));
+ status = pj_thread_register("intListener", thread_desc, &thread);
+ }
+
+ PJ_LOG(3, (THIS_FILE, "Session interrupted! --- %s ---",
+ inInterruption == kAudioSessionBeginInterruption ?
+ "Begin Interruption" : "End Interruption"));
+
+ if (!cf_instance)
+ return;
+
+ pj_mutex_lock(cf_instance->mutex);
+ itBegin = &cf_instance->streams;
+ for (it = itBegin->next; it != itBegin; it = it->next) {
+ if (inInterruption == kAudioSessionEndInterruption &&
+ it->stream->interrupted == PJ_TRUE)
+ {
+ UInt32 audioCategory;
+ OSStatus ostatus;
+
+ /* Make sure that your application can receive remote control
+ * events by adding the code:
+ * [[UIApplication sharedApplication]
+ * beginReceivingRemoteControlEvents];
+ * Otherwise audio unit will fail to restart while your
+ * application is in the background mode.
+ */
+ /* Make sure we set the correct audio category before restarting */
+ audioCategory = kAudioSessionCategory_PlayAndRecord;
+ ostatus = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,
+ sizeof(audioCategory),
+ &audioCategory);
+ if (ostatus != kAudioSessionNoError) {
+ PJ_LOG(4, (THIS_FILE,
+ "Warning: cannot set the audio session category (%i)",
+ ostatus));
+ }
+
+ /* Restart the stream */
+ status = ca_stream_start((pjmedia_aud_stream*)it->stream);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE,
+ "Error: failed to restart the audio unit (%i)",
+ status));
+ continue;
+ }
+ PJ_LOG(3, (THIS_FILE, "core audio unit successfully resumed"
+ " after interruption"));
+ } else if (inInterruption == kAudioSessionBeginInterruption &&
+ it->stream->running == PJ_TRUE)
+ {
+ status = ca_stream_stop((pjmedia_aud_stream*)it->stream);
+ it->stream->interrupted = PJ_TRUE;
+ }
+ }
+ pj_mutex_unlock(cf_instance->mutex);
+}
+
+#endif
+
+#if COREAUDIO_MAC
+/* Internal: create audio converter for resampling the recorder device */
+static pj_status_t create_audio_resample(struct coreaudio_stream *strm,
+ AudioStreamBasicDescription *desc)
+{
+ OSStatus ostatus;
+
+ pj_assert(strm->streamFormat.mSampleRate != desc->mSampleRate);
+ pj_assert(NULL == strm->resample);
+ pj_assert(NULL == strm->resample_buf);
+
+ /* Create the audio converter */
+ ostatus = AudioConverterNew(desc, &strm->streamFormat, &strm->resample);
+ if (ostatus != noErr) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+ /*
+ * Allocate the buffer required to hold enough input data
+ */
+ strm->resample_buf_size = (unsigned)(desc->mSampleRate *
+ strm->param.samples_per_frame /
+ strm->param.clock_rate);
+ strm->resample_buf = (pj_int16_t*)
+ pj_pool_alloc(strm->pool,
+ strm->resample_buf_size *
+ strm->param.bits_per_sample >> 3);
+ if (!strm->resample_buf)
+ return PJ_ENOMEM;
+ strm->resample_buf_count = 0;
+
+ return PJ_SUCCESS;
+}
+#endif
+
+/* Internal: create audio unit for recorder/playback device */
+static pj_status_t create_audio_unit(AudioComponent io_comp,
+ AudioDeviceID dev_id,
+ pjmedia_dir dir,
+ struct coreaudio_stream *strm,
+ AudioUnit *io_unit)
+{
+ OSStatus ostatus;
+#if !COREAUDIO_MAC
+ /* We want to be able to open playback and recording streams */
+ strm->sess = [AVAudioSession sharedInstance];
+ if ([strm->sess setCategory:AVAudioSessionCategoryPlayAndRecord
+ error:nil] != YES)
+ {
+ PJ_LOG(4, (THIS_FILE,
+ "Warning: cannot set the audio session category"));
+ }
+#endif
+
+ /* Create an audio unit to interface with the device */
+ ostatus = AudioComponentInstanceNew(io_comp, io_unit);
+ if (ostatus != noErr) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+ /* Set audio unit's properties for capture device */
+ if (dir & PJMEDIA_DIR_CAPTURE) {
+ UInt32 enable = 1;
+
+ /* Enable input */
+ ostatus = AudioUnitSetProperty(*io_unit,
+ kAudioOutputUnitProperty_EnableIO,
+ kAudioUnitScope_Input,
+ 1,
+ &enable,
+ sizeof(enable));
+ if (ostatus != noErr) {
+ PJ_LOG(4, (THIS_FILE,
+ "Warning: cannot enable IO of capture device %d",
+ dev_id));
+ }
+
+ /* Disable output */
+ if (!(dir & PJMEDIA_DIR_PLAYBACK)) {
+ enable = 0;
+ ostatus = AudioUnitSetProperty(*io_unit,
+ kAudioOutputUnitProperty_EnableIO,
+ kAudioUnitScope_Output,
+ 0,
+ &enable,
+ sizeof(enable));
+ if (ostatus != noErr) {
+ PJ_LOG(4, (THIS_FILE,
+ "Warning: cannot disable IO of capture device %d",
+ dev_id));
+ }
+ }
+ }
+
+ /* Set audio unit's properties for playback device */
+ if (dir & PJMEDIA_DIR_PLAYBACK) {
+ UInt32 enable = 1;
+
+ /* Enable output */
+ ostatus = AudioUnitSetProperty(*io_unit,
+ kAudioOutputUnitProperty_EnableIO,
+ kAudioUnitScope_Output,
+ 0,
+ &enable,
+ sizeof(enable));
+ if (ostatus != noErr) {
+ PJ_LOG(4, (THIS_FILE,
+ "Warning: cannot enable IO of playback device %d",
+ dev_id));
+ }
+
+ }
+
+#if COREAUDIO_MAC
+ PJ_LOG(5, (THIS_FILE, "Opening device %d", dev_id));
+ ostatus = AudioUnitSetProperty(*io_unit,
+ kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global,
+ 0,
+ &dev_id,
+ sizeof(dev_id));
+ if (ostatus != noErr) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+#endif
+
+ if (dir & PJMEDIA_DIR_CAPTURE) {
+#if COREAUDIO_MAC
+ AudioStreamBasicDescription deviceFormat;
+ UInt32 size;
+
+ /*
+ * Keep the sample rate from the device, otherwise we will confuse
+ * AUHAL
+ */
+ size = sizeof(AudioStreamBasicDescription);
+ ostatus = AudioUnitGetProperty(*io_unit,
+ kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input,
+ 1,
+ &deviceFormat,
+ &size);
+ if (ostatus != noErr) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+ strm->streamFormat.mSampleRate = deviceFormat.mSampleRate;
+#endif
+
+ /* When setting the stream format, we have to make sure the sample
+ * rate is supported. Setting an unsupported sample rate will cause
+ * AudioUnitRender() to fail later.
+ */
+ ostatus = AudioUnitSetProperty(*io_unit,
+ kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Output,
+ 1,
+ &strm->streamFormat,
+ sizeof(strm->streamFormat));
+ if (ostatus != noErr) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+#if COREAUDIO_MAC
+ strm->streamFormat.mSampleRate = strm->param.clock_rate;
+ size = sizeof(AudioStreamBasicDescription);
+ ostatus = AudioUnitGetProperty (*io_unit,
+ kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Output,
+ 1,
+ &deviceFormat,
+ &size);
+ if (ostatus == noErr) {
+ if (strm->streamFormat.mSampleRate != deviceFormat.mSampleRate) {
+ pj_status_t rc = create_audio_resample(strm, &deviceFormat);
+ if (PJ_SUCCESS != rc)
+ return rc;
+ }
+ } else {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+#endif
+ }
+
+ if (dir & PJMEDIA_DIR_PLAYBACK) {
+ AURenderCallbackStruct output_cb;
+
+ /* Set the stream format */
+ ostatus = AudioUnitSetProperty(*io_unit,
+ kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input,
+ 0,
+ &strm->streamFormat,
+ sizeof(strm->streamFormat));
+ if (ostatus != noErr) {
+ PJ_LOG(4, (THIS_FILE,
+ "Warning: cannot set playback stream format of dev %d",
+ dev_id));
+ }
+
+ /* Set render callback */
+ output_cb.inputProc = output_renderer;
+ output_cb.inputProcRefCon = strm;
+ ostatus = AudioUnitSetProperty(*io_unit,
+ kAudioUnitProperty_SetRenderCallback,
+ kAudioUnitScope_Input,
+ 0,
+ &output_cb,
+ sizeof(output_cb));
+ if (ostatus != noErr) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+ /* Allocate playback buffer */
+ strm->play_buf = (pj_int16_t*)pj_pool_alloc(strm->pool,
+ strm->param.samples_per_frame *
+ strm->param.bits_per_sample >> 3);
+ if (!strm->play_buf)
+ return PJ_ENOMEM;
+ strm->play_buf_count = 0;
+ }
+
+ if (dir & PJMEDIA_DIR_CAPTURE) {
+ AURenderCallbackStruct input_cb;
+#if COREAUDIO_MAC
+ AudioBuffer *ab;
+ UInt32 size, buf_size;
+#endif
+
+ /* Set input callback */
+ input_cb.inputProc = strm->resample ? resample_callback :
+ input_callback;
+ input_cb.inputProcRefCon = strm;
+ ostatus = AudioUnitSetProperty(
+ *io_unit,
+ kAudioOutputUnitProperty_SetInputCallback,
+ kAudioUnitScope_Global,
+ 0,
+ &input_cb,
+ sizeof(input_cb));
+ if (ostatus != noErr) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+#if COREAUDIO_MAC
+ /* Get device's buffer frame size */
+ size = sizeof(UInt32);
+ ostatus = AudioUnitGetProperty(*io_unit,
+ kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Global,
+ 0,
+ &buf_size,
+ &size);
+ if (ostatus != noErr)
+ {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+ /* Allocate audio buffer */
+ strm->audio_buf = (AudioBufferList*)pj_pool_alloc(strm->pool,
+ sizeof(AudioBufferList) + sizeof(AudioBuffer));
+ if (!strm->audio_buf)
+ return PJ_ENOMEM;
+
+ strm->audio_buf->mNumberBuffers = 1;
+ ab = &strm->audio_buf->mBuffers[0];
+ ab->mNumberChannels = strm->streamFormat.mChannelsPerFrame;
+ ab->mDataByteSize = buf_size * ab->mNumberChannels *
+ strm->param.bits_per_sample >> 3;
+ ab->mData = pj_pool_alloc(strm->pool,
+ ab->mDataByteSize);
+ if (!ab->mData)
+ return PJ_ENOMEM;
+
+#else
+ /* We will let AudioUnitRender() to allocate the buffer
+ * for us later
+ */
+ strm->audio_buf = (AudioBufferList*)pj_pool_alloc(strm->pool,
+ sizeof(AudioBufferList) + sizeof(AudioBuffer));
+ if (!strm->audio_buf)
+ return PJ_ENOMEM;
+
+ strm->audio_buf->mNumberBuffers = 1;
+ strm->audio_buf->mBuffers[0].mNumberChannels =
+ strm->streamFormat.mChannelsPerFrame;
+
+#endif
+
+ /* Allocate recording buffer */
+ strm->rec_buf = (pj_int16_t*)pj_pool_alloc(strm->pool,
+ strm->param.samples_per_frame *
+ strm->param.bits_per_sample >> 3);
+ if (!strm->rec_buf)
+ return PJ_ENOMEM;
+ strm->rec_buf_count = 0;
+ }
+
+ /* Initialize the audio unit */
+ ostatus = AudioUnitInitialize(*io_unit);
+ if (ostatus != noErr) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: create stream */
+static pj_status_t ca_factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm)
+{
+ struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
+ pj_pool_t *pool;
+ struct coreaudio_stream *strm;
+ pj_status_t status;
+
+ /* Create and Initialize stream descriptor */
+ pool = pj_pool_create(cf->pf, "coreaudio-dev", 1000, 1000, NULL);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+ strm = PJ_POOL_ZALLOC_T(pool, struct coreaudio_stream);
+ pj_list_init(&strm->list_entry);
+ strm->list_entry.stream = strm;
+ strm->cf = cf;
+ pj_memcpy(&strm->param, param, sizeof(*param));
+ strm->pool = pool;
+ strm->rec_cb = rec_cb;
+ strm->play_cb = play_cb;
+ strm->user_data = user_data;
+
+ /* Set the stream format */
+ strm->streamFormat.mSampleRate = param->clock_rate;
+ strm->streamFormat.mFormatID = kAudioFormatLinearPCM;
+ strm->streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger
+ | kLinearPCMFormatFlagIsPacked;
+ strm->streamFormat.mBitsPerChannel = strm->param.bits_per_sample;
+ strm->streamFormat.mChannelsPerFrame = param->channel_count;
+ strm->streamFormat.mBytesPerFrame = strm->streamFormat.mChannelsPerFrame
+ * strm->param.bits_per_sample >> 3;
+ strm->streamFormat.mFramesPerPacket = 1;
+ strm->streamFormat.mBytesPerPacket = strm->streamFormat.mBytesPerFrame *
+ strm->streamFormat.mFramesPerPacket;
+
+ /* Apply input/output routes settings before we create the audio units */
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE) {
+ ca_stream_set_cap(&strm->base,
+ PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE,
+ &param->input_route);
+ }
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE) {
+ ca_stream_set_cap(&strm->base,
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
+ &param->output_route);
+ }
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_EC) {
+ ca_stream_set_cap(&strm->base,
+ PJMEDIA_AUD_DEV_CAP_EC,
+ &param->ec_enabled);
+ } else {
+ pj_bool_t ec = PJ_FALSE;
+ ca_stream_set_cap(&strm->base,
+ PJMEDIA_AUD_DEV_CAP_EC, &ec);
+ }
+
+ strm->io_units[0] = strm->io_units[1] = NULL;
+ if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK &&
+ param->rec_id == param->play_id)
+ {
+ /* If both input and output are on the same device, only create
+ * one audio unit to interface with the device.
+ */
+ status = create_audio_unit(cf->io_comp,
+ cf->dev_info[param->rec_id].dev_id,
+ param->dir, strm, &strm->io_units[0]);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ } else {
+ unsigned nunits = 0;
+
+ if (param->dir & PJMEDIA_DIR_CAPTURE) {
+ status = create_audio_unit(cf->io_comp,
+ cf->dev_info[param->rec_id].dev_id,
+ PJMEDIA_DIR_CAPTURE,
+ strm, &strm->io_units[nunits++]);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+ if (param->dir & PJMEDIA_DIR_PLAYBACK) {
+
+ status = create_audio_unit(cf->io_comp,
+ cf->dev_info[param->play_id].dev_id,
+ PJMEDIA_DIR_PLAYBACK,
+ strm, &strm->io_units[nunits++]);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+ }
+
+ /* Apply the remaining settings */
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY) {
+ ca_stream_get_cap(&strm->base,
+ PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY,
+ &strm->param.input_latency_ms);
+ }
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY) {
+ ca_stream_get_cap(&strm->base,
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY,
+ &strm->param.output_latency_ms);
+ }
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
+ ca_stream_set_cap(&strm->base,
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+ &param->output_vol);
+ }
+
+ pj_mutex_lock(strm->cf->mutex);
+ pj_assert(pj_list_empty(&strm->list_entry));
+ pj_list_insert_after(&strm->cf->streams, &strm->list_entry);
+ pj_mutex_unlock(strm->cf->mutex);
+
+ /* Done */
+ strm->base.op = &stream_op;
+ *p_aud_strm = &strm->base;
+
+ return PJ_SUCCESS;
+
+ on_error:
+ ca_stream_destroy((pjmedia_aud_stream *)strm);
+ return status;
+}
+
+/* API: Get stream info. */
+static pj_status_t ca_stream_get_param(pjmedia_aud_stream *s,
+ pjmedia_aud_param *pi)
+{
+ struct coreaudio_stream *strm = (struct coreaudio_stream*)s;
+
+ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+ /* Update the device capabilities' values */
+ if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY,
+ &pi->input_latency_ms) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+ }
+ if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY,
+ &pi->output_latency_ms) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+ }
+ if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+ &pi->output_vol) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
+ }
+ if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE,
+ &pi->input_route) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE;
+ }
+ if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
+ &pi->output_route) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE;
+ }
+ if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_EC,
+ &pi->ec_enabled) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_EC;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t ca_stream_get_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ void *pval)
+{
+ struct coreaudio_stream *strm = (struct coreaudio_stream*)s;
+
+ PJ_UNUSED_ARG(strm);
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY &&
+ (strm->param.dir & PJMEDIA_DIR_CAPTURE))
+ {
+#if COREAUDIO_MAC
+ UInt32 latency, size = sizeof(UInt32);
+
+ /* Recording latency */
+ if (AudioUnitGetProperty (strm->io_units[0],
+ kAudioDevicePropertyLatency,
+ kAudioUnitScope_Input,
+ 1,
+ &latency,
+ &size) == noErr)
+ {
+ UInt32 latency2;
+ if (AudioUnitGetProperty (strm->io_units[0],
+ kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Input,
+ 1,
+ &latency2,
+ &size) == noErr)
+ {
+ strm->param.input_latency_ms = (latency + latency2) * 1000 /
+ strm->param.clock_rate;
+ strm->param.input_latency_ms++;
+ }
+ }
+#else
+ if ([strm->sess respondsToSelector:@selector(inputLatency)]) {
+ strm->param.input_latency_ms =
+ (unsigned)(([strm->sess inputLatency] +
+ [strm->sess IOBufferDuration]) * 1000);
+ strm->param.input_latency_ms++;
+ } else
+ return PJMEDIA_EAUD_INVCAP;
+#endif
+
+ *(unsigned*)pval = strm->param.input_latency_ms;
+ return PJ_SUCCESS;
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY &&
+ (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
+ {
+#if COREAUDIO_MAC
+ UInt32 latency, size = sizeof(UInt32);
+ AudioUnit *io_unit = strm->io_units[1] ? &strm->io_units[1] :
+ &strm->io_units[0];
+
+ /* Playback latency */
+ if (AudioUnitGetProperty (*io_unit,
+ kAudioDevicePropertyLatency,
+ kAudioUnitScope_Output,
+ 0,
+ &latency,
+ &size) == noErr)
+ {
+ UInt32 latency2;
+ if (AudioUnitGetProperty (*io_unit,
+ kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Output,
+ 0,
+ &latency2,
+ &size) == noErr)
+ {
+ strm->param.output_latency_ms = (latency + latency2) * 1000 /
+ strm->param.clock_rate;
+ strm->param.output_latency_ms++;
+ }
+ }
+#else
+ if ([strm->sess respondsToSelector:@selector(outputLatency)]) {
+ strm->param.output_latency_ms =
+ (unsigned)(([strm->sess outputLatency] +
+ [strm->sess IOBufferDuration]) * 1000);
+ strm->param.output_latency_ms++;
+ } else
+ return PJMEDIA_EAUD_INVCAP;
+#endif
+ *(unsigned*)pval = (++strm->param.output_latency_ms * 2);
+ return PJ_SUCCESS;
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
+ (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
+ {
+#if COREAUDIO_MAC
+ OSStatus ostatus;
+ Float32 volume;
+ UInt32 size = sizeof(Float32);
+
+ /* Output volume setting */
+ ostatus = AudioUnitGetProperty (strm->io_units[1] ? strm->io_units[1] :
+ strm->io_units[0],
+ kAudioDevicePropertyVolumeScalar,
+ kAudioUnitScope_Output,
+ 0,
+ &volume,
+ &size);
+ if (ostatus != noErr)
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+
+ *(unsigned*)pval = (unsigned)(volume * 100);
+ return PJ_SUCCESS;
+#else
+ if ([strm->sess respondsToSelector:@selector(outputVolume)]) {
+ *(unsigned*)pval = (unsigned)([strm->sess outputVolume] * 100);
+ return PJ_SUCCESS;
+ } else
+ return PJMEDIA_EAUD_INVCAP;
+#endif
+
+#if !COREAUDIO_MAC
+#if USE_AUDIO_SESSION_API != 0
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE &&
+ (strm->param.dir & PJMEDIA_DIR_CAPTURE))
+ {
+ UInt32 btooth, size = sizeof(UInt32);
+ OSStatus ostatus;
+
+ ostatus = AudioSessionGetProperty (
+ kAudioSessionProperty_OverrideCategoryEnableBluetoothInput,
+ &size, &btooth);
+ if (ostatus != kAudioSessionNoError) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+ *(pjmedia_aud_dev_route*)pval = btooth?
+ PJMEDIA_AUD_DEV_ROUTE_BLUETOOTH:
+ PJMEDIA_AUD_DEV_ROUTE_DEFAULT;
+ return PJ_SUCCESS;
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE &&
+ (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
+ {
+ CFStringRef route;
+ UInt32 size = sizeof(CFStringRef);
+ OSStatus ostatus;
+
+ ostatus = AudioSessionGetProperty (kAudioSessionProperty_AudioRoute,
+ &size, &route);
+ if (ostatus != kAudioSessionNoError) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+ if (!route) {
+ *(pjmedia_aud_dev_route*)pval = PJMEDIA_AUD_DEV_ROUTE_DEFAULT;
+ } else if (CFStringHasPrefix(route, CFSTR("Headset"))) {
+ *(pjmedia_aud_dev_route*)pval = PJMEDIA_AUD_DEV_ROUTE_EARPIECE;
+ } else {
+ *(pjmedia_aud_dev_route*)pval = PJMEDIA_AUD_DEV_ROUTE_DEFAULT;
+ }
+
+ CFRelease(route);
+
+ return PJ_SUCCESS;
+#endif
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_EC) {
+ AudioComponentDescription desc;
+ OSStatus ostatus;
+
+ ostatus = AudioComponentGetDescription(strm->cf->io_comp, &desc);
+ if (ostatus != noErr) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+ *(pj_bool_t*)pval = (desc.componentSubType ==
+ kAudioUnitSubType_VoiceProcessingIO);
+ return PJ_SUCCESS;
+#endif
+ } else {
+ return PJMEDIA_EAUD_INVCAP;
+ }
+}
+
+/* API: set capability */
+static pj_status_t ca_stream_set_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ const void *pval)
+{
+ struct coreaudio_stream *strm = (struct coreaudio_stream*)s;
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+#if COREAUDIO_MAC
+ if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
+ (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
+ {
+ OSStatus ostatus;
+ Float32 volume = *(unsigned*)pval;
+
+ /* Output volume setting */
+ volume /= 100.0;
+ ostatus = AudioUnitSetProperty (strm->io_units[1] ? strm->io_units[1] :
+ strm->io_units[0],
+ kAudioDevicePropertyVolumeScalar,
+ kAudioUnitScope_Output,
+ 0,
+ &volume,
+ sizeof(Float32));
+ if (ostatus != noErr) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+ strm->param.output_vol = *(unsigned*)pval;
+ return PJ_SUCCESS;
+ }
+
+#else
+ if (cap==PJMEDIA_AUD_DEV_CAP_EC) {
+ AudioComponentDescription desc;
+ AudioComponent io_comp;
+
+ desc.componentType = kAudioUnitType_Output;
+ desc.componentSubType = (*(pj_bool_t*)pval)?
+ kAudioUnitSubType_VoiceProcessingIO :
+ kAudioUnitSubType_RemoteIO;
+ desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+ desc.componentFlags = 0;
+ desc.componentFlagsMask = 0;
+
+ io_comp = AudioComponentFindNext(NULL, &desc);
+ if (io_comp == NULL)
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(-1);
+ strm->cf->io_comp = io_comp;
+ strm->param.ec_enabled = *(pj_bool_t*)pval;
+
+ PJ_LOG(4, (THIS_FILE, "Using %s audio unit",
+ (desc.componentSubType ==
+ kAudioUnitSubType_RemoteIO? "RemoteIO":
+ "VoiceProcessingIO")));
+
+ return PJ_SUCCESS;
+ } else if ((cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY &&
+ (strm->param.dir & PJMEDIA_DIR_CAPTURE)) ||
+ (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY &&
+ (strm->param.dir & PJMEDIA_DIR_PLAYBACK)))
+ {
+ NSTimeInterval duration = *(unsigned *)pval;
+ unsigned latency;
+
+ /* For low-latency audio streaming, you can set this value to
+ * as low as 5 ms (the default is 23ms). However, lowering the
+ * latency may cause a decrease in audio quality.
+ */
+ duration /= 1000;
+ if ([strm->sess setPreferredIOBufferDuration:duration error:nil]
+ != YES)
+ {
+ PJ_LOG(4, (THIS_FILE,
+ "Error: cannot set the preferred buffer duration"));
+ return PJMEDIA_EAUD_INVOP;
+ }
+
+ ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY, &latency);
+ ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY, &latency);
+
+ return PJ_SUCCESS;
+ }
+
+#if USE_AUDIO_SESSION_API != 0
+
+ else if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE &&
+ (strm->param.dir & PJMEDIA_DIR_CAPTURE))
+ {
+ UInt32 btooth = *(pjmedia_aud_dev_route*)pval ==
+ PJMEDIA_AUD_DEV_ROUTE_BLUETOOTH ? 1 : 0;
+ OSStatus ostatus;
+
+ ostatus = AudioSessionSetProperty (
+ kAudioSessionProperty_OverrideCategoryEnableBluetoothInput,
+ sizeof(btooth), &btooth);
+ if (ostatus != kAudioSessionNoError) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+ strm->param.input_route = *(pjmedia_aud_dev_route*)pval;
+ return PJ_SUCCESS;
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE &&
+ (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
+ {
+ OSStatus ostatus;
+ UInt32 route = *(pjmedia_aud_dev_route*)pval ==
+ PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER ?
+ kAudioSessionOverrideAudioRoute_Speaker :
+ kAudioSessionOverrideAudioRoute_None;
+
+ ostatus = AudioSessionSetProperty (
+ kAudioSessionProperty_OverrideAudioRoute,
+ sizeof(route), &route);
+ if (ostatus != kAudioSessionNoError) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+ strm->param.output_route = *(pjmedia_aud_dev_route*)pval;
+ return PJ_SUCCESS;
+ }
+#endif
+#endif
+
+ return PJMEDIA_EAUD_INVCAP;
+}
+
+/* API: Start stream. */
+static pj_status_t ca_stream_start(pjmedia_aud_stream *strm)
+{
+ struct coreaudio_stream *stream = (struct coreaudio_stream*)strm;
+ OSStatus ostatus;
+ UInt32 i;
+
+ if (stream->running)
+ return PJ_SUCCESS;
+
+ stream->quit_flag = 0;
+ stream->interrupted = PJ_FALSE;
+ stream->rec_buf_count = 0;
+ stream->play_buf_count = 0;
+ stream->resample_buf_count = 0;
+
+ if (stream->resample) {
+ ostatus = AudioConverterReset(stream->resample);
+ if (ostatus != noErr)
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+#if !COREAUDIO_MAC
+ if ([stream->sess setActive:true error:nil] != YES) {
+ PJ_LOG(4, (THIS_FILE, "Warning: cannot activate audio session"));
+ }
+#endif
+
+ for (i = 0; i < 2; i++) {
+ if (stream->io_units[i] == NULL) break;
+ ostatus = AudioOutputUnitStart(stream->io_units[i]);
+ if (ostatus != noErr) {
+ if (i == 1)
+ AudioOutputUnitStop(stream->io_units[0]);
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+ }
+
+ stream->running = PJ_TRUE;
+
+ PJ_LOG(4, (THIS_FILE, "core audio stream started"));
+
+ return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t ca_stream_stop(pjmedia_aud_stream *strm)
+{
+ struct coreaudio_stream *stream = (struct coreaudio_stream*)strm;
+ OSStatus ostatus;
+ unsigned i;
+ int should_deactivate;
+ struct stream_list *it, *itBegin;
+
+ if (!stream->running)
+ return PJ_SUCCESS;
+
+ for (i = 0; i < 2; i++) {
+ if (stream->io_units[i] == NULL) break;
+ ostatus = AudioOutputUnitStop(stream->io_units[i]);
+ if (ostatus != noErr) {
+ if (i == 0 && stream->io_units[1])
+ AudioOutputUnitStop(stream->io_units[1]);
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+ }
+
+ /* Check whether we need to deactivate the audio session. */
+ pj_mutex_lock(stream->cf->mutex);
+ pj_assert(!pj_list_empty(&stream->cf->streams));
+ pj_assert(!pj_list_empty(&stream->list_entry));
+ stream->running = PJ_FALSE;
+ should_deactivate = PJ_TRUE;
+ itBegin = &stream->cf->streams;
+ for (it = itBegin->next; it != itBegin; it = it->next) {
+ if (it->stream->running) {
+ should_deactivate = PJ_FALSE;
+ break;
+ }
+ }
+ pj_mutex_unlock(stream->cf->mutex);
+
+#if !COREAUDIO_MAC
+ if (should_deactivate) {
+ if ([stream->sess setActive:false error:nil] != YES) {
+ PJ_LOG(4, (THIS_FILE, "Warning: cannot deactivate audio session"));
+ }
+ }
+#endif
+
+ stream->quit_flag = 1;
+ stream->play_thread_initialized = 0;
+ stream->rec_thread_initialized = 0;
+ pj_bzero(stream->rec_thread_desc, sizeof(pj_thread_desc));
+ pj_bzero(stream->play_thread_desc, sizeof(pj_thread_desc));
+
+ PJ_LOG(4, (THIS_FILE, "core audio stream stopped"));
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t ca_stream_destroy(pjmedia_aud_stream *strm)
+{
+ struct coreaudio_stream *stream = (struct coreaudio_stream*)strm;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+ ca_stream_stop(strm);
+
+ for (i = 0; i < 2; i++) {
+ if (stream->io_units[i]) {
+ AudioUnitUninitialize(stream->io_units[i]);
+ AudioComponentInstanceDispose(stream->io_units[i]);
+ stream->io_units[i] = NULL;
+ }
+ }
+
+ if (stream->resample)
+ AudioConverterDispose(stream->resample);
+
+ pj_mutex_lock(stream->cf->mutex);
+ if (!pj_list_empty(&stream->list_entry))
+ pj_list_erase(&stream->list_entry);
+ pj_mutex_unlock(stream->cf->mutex);
+
+ pj_pool_release(stream->pool);
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_AUDIO_DEV_HAS_COREAUDIO */