summaryrefslogtreecommitdiff
path: root/pjmedia
diff options
context:
space:
mode:
authorLiong Sauw Ming <ming@teluu.com>2010-12-17 07:10:13 +0000
committerLiong Sauw Ming <ming@teluu.com>2010-12-17 07:10:13 +0000
commitb7f18c455d80a8574574332878db3ce8736db317 (patch)
treee5f1db2c45f5a368d23152f9800a5be1b21befdd /pjmedia
parentd985a7525e8958d387bfe26062fc0187cce44dac (diff)
Fixed #1174, #1191, #1192
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@3398 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjmedia')
-rw-r--r--pjmedia/src/pjmedia-audiodev/coreaudio_dev.c823
1 files changed, 644 insertions, 179 deletions
diff --git a/pjmedia/src/pjmedia-audiodev/coreaudio_dev.c b/pjmedia/src/pjmedia-audiodev/coreaudio_dev.c
index 6727a2dd..0eeb4c8d 100644
--- a/pjmedia/src/pjmedia-audiodev/coreaudio_dev.c
+++ b/pjmedia/src/pjmedia-audiodev/coreaudio_dev.c
@@ -31,6 +31,7 @@
#endif
#include <AudioUnit/AudioUnit.h>
+#include <AudioToolbox/AudioConverter.h>
#if !COREAUDIO_MAC
#include <AudioToolbox/AudioServices.h>
@@ -64,62 +65,77 @@ struct coreaudio_dev_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 coreaudio_stream *stream;
+ 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. */
+ 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. */
+ 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_timestamp play_timestamp;
+ pj_timestamp rec_timestamp;
pj_int16_t *rec_buf;
- unsigned rec_buf_count;
+ unsigned rec_buf_count;
pj_int16_t *play_buf;
- unsigned play_buf_count;
+ unsigned play_buf_count;
- pj_bool_t interrupted;
- pj_bool_t quit_flag;
+ pj_bool_t interrupted;
+ pj_bool_t quit_flag;
+ pj_bool_t running;
- pj_bool_t rec_thread_exited;
- pj_bool_t rec_thread_initialized;
- pj_thread_desc rec_thread_desc;
+ pj_bool_t rec_thread_initialized;
+ pj_thread_desc rec_thread_desc;
pj_thread_t *rec_thread;
- pj_bool_t play_thread_exited;
- pj_bool_t play_thread_initialized;
- pj_thread_desc play_thread_desc;
+ pj_bool_t play_thread_initialized;
+ pj_thread_desc play_thread_desc;
pj_thread_t *play_thread;
- AudioUnit io_units[2];
- AudioStreamBasicDescription streamFormat;
+ AudioUnit io_units[2];
+ AudioStreamBasicDescription streamFormat;
AudioBufferList *audio_buf;
+
+ AudioConverterRef resample;
+ pj_int16_t *resample_buf;
+ unsigned resample_buf_count;
+ unsigned resample_buf_size;
};
/* 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,
@@ -152,6 +168,10 @@ static pj_status_t create_audio_unit(AudioComponent io_comp,
AudioUnit *io_unit);
#if !COREAUDIO_MAC
static void interruptionListener(void *inClientData, UInt32 inInterruption);
+static void propListener(void * inClientData,
+ AudioSessionPropertyID inID,
+ UInt32 inDataSize,
+ const void * inData);
#endif
/* Operations */
@@ -187,10 +207,10 @@ 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", 1000, 1000, NULL);
+ pool = pj_pool_create(pf, "core audio base", 1000, 1000, NULL);
f = PJ_POOL_ZALLOC_T(pool, struct coreaudio_factory);
f->pf = pf;
- f->pool = pool;
+ f->base_pool = pool;
f->base.op = &factory_op;
return &f->base;
@@ -201,17 +221,21 @@ pjmedia_aud_dev_factory* pjmedia_coreaudio_factory(pj_pool_factory *pf)
static pj_status_t ca_factory_init(pjmedia_aud_dev_factory *f)
{
struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
- unsigned i;
AudioComponentDescription desc;
-#if COREAUDIO_MAC
- unsigned dev_count;
- AudioObjectPropertyAddress addr;
- AudioDeviceID *dev_ids;
- UInt32 buf_size, dev_size, size = sizeof(AudioDeviceID);
- AudioBufferList *buf = NULL;
+ pj_status_t status;
+#if !COREAUDIO_MAC
+ unsigned i;
OSStatus ostatus;
+ UInt32 audioCategory;
#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;
@@ -226,9 +250,142 @@ static pj_status_t ca_factory_init(pjmedia_aud_dev_factory *f)
if (cf->io_comp == NULL)
return PJMEDIA_EAUD_INIT; // cannot find IO unit;
- cf->stream = NULL;
+ 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 |
+ PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE |
+ 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));
+ }
+
+ /* Initialize the Audio Session */
+ ostatus = AudioSessionInitialize(NULL, NULL, interruptionListener, cf);
+ if (ostatus != kAudioSessionNoError) {
+ PJ_LOG(4, (THIS_FILE,
+ "Error: cannot initialize audio session services (%i)",
+ ostatus));
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+ /* We want to be able to open playback and recording streams */
+ audioCategory = kAudioSessionCategory_PlayAndRecord;
+ ostatus = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,
+ sizeof(audioCategory),
+ &audioCategory);
+ if (ostatus != kAudioSessionNoError) {
+ PJ_LOG(4, (THIS_FILE,
+ "Error: cannot set the audio session category (%i)",
+ ostatus));
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+ /* Listen for audio routing change notifications */
+ ostatus = AudioSessionAddPropertyListener(
+ kAudioSessionProperty_AudioRouteChange,
+ propListener, cf);
+ if (ostatus != kAudioSessionNoError) {
+ PJ_LOG(4, (THIS_FILE,
+ "Error: cannot listen for audio route change "
+ "notifications (%i)", ostatus));
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+#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
+ AudioSessionRemovePropertyListenerWithUserData(
+ kAudioSessionProperty_AudioRouteChange, propListener, cf);
+#endif
+
+ if (cf->pool) {
+ pj_pool_release(cf->pool);
+ cf->pool = NULL;
+ }
+
+ if (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);
-#if COREAUDIO_MAC
/* Find out how many audio devices there are */
addr.mSelector = kAudioHardwarePropertyDevices;
addr.mScope = kAudioObjectPropertyScopeGlobal;
@@ -253,8 +410,8 @@ static pj_status_t ca_factory_init(pjmedia_aud_dev_factory *f)
*/
return PJ_SUCCESS;
}
- PJ_LOG(4, (THIS_FILE, "core audio initialized with %d devices",
- dev_count));
+ 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);
@@ -389,59 +546,9 @@ static pj_status_t ca_factory_init(pjmedia_aud_dev_factory *f)
cdi->info.output_count,
cdi->info.default_samples_per_sec));
}
-#else
- 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 |
- PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE |
- PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE |
- PJMEDIA_AUD_DEV_CAP_EC;
- cdi->info.routes = PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER |
- PJMEDIA_AUD_DEV_ROUTE_EARPIECE |
- PJMEDIA_AUD_DEV_ROUTE_BLUETOOTH;
- }
-
- if (AudioSessionInitialize(NULL, NULL, interruptionListener, cf) !=
- kAudioSessionNoError)
- {
- PJ_LOG(4, (THIS_FILE,
- "Warning: cannot initialize audio session services"));
- }
-
- PJ_LOG(4, (THIS_FILE, "core audio initialized"));
-
-#endif
-
- 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 = cf->pool;
-
- cf->pool = NULL;
- pj_pool_release(pool);
return PJ_SUCCESS;
+#endif
}
/* API: get number of devices */
@@ -507,6 +614,166 @@ static pj_status_t ca_factory_default_param(pjmedia_aud_dev_factory *f,
return PJ_SUCCESS;
}
+OSStatus resampleProc(AudioConverterRef inAudioConverter,
+ UInt32 *ioNumberDataPackets,
+ AudioBufferList *ioData,
+ AudioStreamPacketDescription **outDataPacketDescription,
+ void *inUserData)
+{
+ struct coreaudio_stream *strm = (struct coreaudio_stream*)inUserData;
+
+ pj_assert(*ioNumberDataPackets == strm->resample_buf_count);
+
+ ioData->mNumberBuffers = 1;
+ ioData->mBuffers[0].mNumberChannels = 1;
+ ioData->mBuffers[0].mData = strm->resample_buf;
+ ioData->mBuffers[0].mDataByteSize = strm->resample_buf_count *
+ strm->param.bits_per_sample >> 3;
+ return 0;
+}
+
+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;
+ UInt32 size;
+
+ 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())
+ {
+ 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 how many frames we need to fill an entire packet */
+ resampleSize = strm->param.samples_per_frame *
+ strm->param.bits_per_sample >> 3;
+ size = sizeof(resampleSize);
+ ostatus = AudioConverterGetProperty(
+ strm->resample,
+ kAudioConverterPropertyCalculateInputBufferSize,
+ &size,
+ &resampleSize);
+ if (ostatus != noErr) {
+ PJ_LOG(5, (THIS_FILE, "Core audio converter measure error %i",
+ ostatus));
+ goto on_break;
+ }
+ resampleSize /= strm->param.bits_per_sample >> 3;
+
+ nsamples = inNumberFrames * strm->param.channel_count +
+ strm->resample_buf_count;
+ pj_assert(nsamples < strm->resample_buf_size);
+
+ if (nsamples >= resampleSize) {
+ UInt32 resampleOutput = strm->param.samples_per_frame;
+ unsigned chunk_count = 0;
+ pjmedia_frame frame;
+ AudioBufferList ab;
+
+ 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.
+ */
+
+ chunk_count = resampleSize - strm->resample_buf_count;
+ pjmedia_copy_samples(strm->resample_buf + strm->resample_buf_count,
+ input, chunk_count);
+ strm->resample_buf_count += chunk_count;
+
+ frame.buf = (void*) strm->rec_buf;
+ frame.timestamp.u64 = strm->rec_timestamp.u64;
+
+ ab.mNumberBuffers = 1;
+ ab.mBuffers[0].mNumberChannels = 1;
+ ab.mBuffers[0].mDataByteSize = frame.size;
+ ab.mBuffers[0].mData = frame.buf;
+
+ /* Do the resample */
+ ostatus = AudioConverterFillComplexBuffer(strm->resample,
+ resampleProc,
+ strm,
+ &resampleOutput,
+ &ab,
+ NULL);
+ if (ostatus != noErr) {
+ goto on_break;
+ }
+
+ 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;
+
+ pj_assert(nsamples < resampleSize);
+
+ /* 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,
@@ -521,8 +788,7 @@ static OSStatus input_callback(void *inRefCon,
AudioBufferList *buf = strm->audio_buf;
pj_int16_t *input;
- if (strm->quit_flag)
- goto on_break;
+ pj_assert(!strm->quit_flag);
/* Known cases of callback's thread:
* - The thread may be changed in the middle of a session
@@ -536,7 +802,8 @@ static OSStatus input_callback(void *inRefCon,
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"));
+ PJ_LOG(5,(THIS_FILE, "Recorder thread started, (%i frames)",
+ inNumberFrames));
}
buf->mBuffers[0].mData = NULL;
@@ -557,7 +824,8 @@ static OSStatus input_callback(void *inRefCon,
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;
+ nsamples = inNumberFrames * strm->param.channel_count +
+ strm->rec_buf_count;
if (nsamples >= strm->param.samples_per_frame)
{
pjmedia_frame frame;
@@ -619,9 +887,8 @@ static OSStatus input_callback(void *inRefCon,
return noErr;
- on_break:
- strm->rec_thread_exited = 1;
- return -1;
+on_break:
+ return -1;
}
static OSStatus output_renderer(void *inRefCon,
@@ -636,8 +903,7 @@ static OSStatus output_renderer(void *inRefCon,
unsigned nsamples_req = inNumberFrames * stream->param.channel_count;
pj_int16_t *output = ioData->mBuffers[0].mData;
- if (stream->quit_flag)
- goto on_break;
+ pj_assert(!stream->quit_flag);
/* Known cases of callback's thread:
* - The thread may be changed in the middle of a session
@@ -651,7 +917,7 @@ static OSStatus output_renderer(void *inRefCon,
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"));
+ PJ_LOG(5,(THIS_FILE, "Player thread started, (%i frames)", inNumberFrames));
}
@@ -724,9 +990,8 @@ static OSStatus output_renderer(void *inRefCon,
return noErr;
- on_break:
- stream->play_thread_exited = 1;
- return -1;
+on_break:
+ return -1;
}
#if !COREAUDIO_MAC
@@ -735,61 +1000,166 @@ static void propListener(void *inClientData,
UInt32 inDataSize,
const void * inData)
{
- struct coreaudio_stream *strm = (struct coreaudio_stream*)inClientData;
+ struct coreaudio_factory *cf = (struct coreaudio_factory*)inClientData;
+ struct stream_list *it, *itBegin;
+ pj_status_t status;
+ OSStatus ostatus;
+ 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 (inID == kAudioSessionProperty_AudioRouteChange) {
+ if (reasonVal == kAudioSessionRouteChangeReason_CategoryChange) {
+ PJ_LOG(3, (THIS_FILE, "audio route changed due to category change, "
+ "ignoring..."));
+ return;
+ }
+ if (reasonVal == kAudioSessionRouteChangeReason_Override) {
+ PJ_LOG(3, (THIS_FILE, "audio route changed due to user override, "
+ "ignoring..."));
+ return;
+ }
- PJ_LOG(3, (THIS_FILE, "audio route changed"));
- if (strm->interrupted)
- return;
+ PJ_LOG(3, (THIS_FILE, "audio route changed"));
- ca_stream_stop((pjmedia_aud_stream *)strm);
- AudioUnitUninitialize(strm->io_units[0]);
- AudioComponentInstanceDispose(strm->io_units[0]);
+ pj_mutex_lock(cf->mutex);
+ itBegin = &cf->streams;
+ for (it = itBegin->next; it != itBegin; it = it->next) {
+ pj_bool_t running = it->stream->running;
- if (create_audio_unit(strm->cf->io_comp, 0,
- strm->param.dir, strm,
- &strm->io_units[0]) != PJ_SUCCESS)
- {
+ if (it->stream->interrupted)
+ continue;
+
+ status = ca_stream_stop((pjmedia_aud_stream *)it->stream);
+
+ ostatus = AudioUnitUninitialize(it->stream->io_units[0]);
+ ostatus = AudioComponentInstanceDispose(it->stream->io_units[0]);
+
+ status = create_audio_unit(it->stream->cf->io_comp, 0,
+ it->stream->param.dir,
+ it->stream, &it->stream->io_units[0]);
+ if (status != PJ_SUCCESS) {
PJ_LOG(3, (THIS_FILE,
- "Error: failed to create a new instance of audio unit"));
- return;
+ "Error: failed to create a replacement audio unit (%i)",
+ status));
+ continue;
}
- if (ca_stream_start((pjmedia_aud_stream *)strm) != PJ_SUCCESS) {
+
+ if (running) {
+ status = ca_stream_start((pjmedia_aud_stream *)it->stream);
+ }
+ if (status != PJ_SUCCESS) {
PJ_LOG(3, (THIS_FILE,
- "Error: failed to restart audio unit"));
+ "Error: failed to restart the audio unit (%i)",
+ status));
+ continue;
}
PJ_LOG(3, (THIS_FILE, "core audio unit successfully reinstantiated"));
}
+ pj_mutex_unlock(cf->mutex);
}
static void interruptionListener(void *inClientData, UInt32 inInterruption)
{
- struct coreaudio_stream *strm = ((struct coreaudio_factory*)inClientData)->
- stream;
- if (!strm)
- return;
+ struct coreaudio_factory *cf = (struct coreaudio_factory*)inClientData;
+ struct stream_list *it, *itBegin;
+ pj_status_t status;
+ OSStatus ostatus;
+ pj_assert(cf);
PJ_LOG(3, (THIS_FILE, "Session interrupted! --- %s ---",
inInterruption == kAudioSessionBeginInterruption ?
"Begin Interruption" : "End Interruption"));
- if (inInterruption == kAudioSessionEndInterruption) {
- strm->interrupted = PJ_FALSE;
- /* There may be an audio route change during the interruption
- * (such as when the alarm rings), so we have to notify the
- * listener as well.
- */
- propListener(strm, kAudioSessionProperty_AudioRouteChange,
- 0, NULL);
- } else if (inInterruption == kAudioSessionBeginInterruption) {
- strm->interrupted = PJ_TRUE;
- AudioOutputUnitStop(strm->io_units[0]);
+ pj_mutex_lock(cf->mutex);
+ itBegin = &cf->streams;
+ for (it = itBegin->next; it != itBegin; it = it->next) {
+ if (!it->stream->running)
+ continue;
+
+ if (inInterruption == kAudioSessionEndInterruption &&
+ it->stream->interrupted == PJ_TRUE)
+ {
+ ostatus = AudioUnitUninitialize(it->stream->io_units[0]);
+ ostatus = AudioComponentInstanceDispose(it->stream->io_units[0]);
+
+ status = create_audio_unit(it->stream->cf->io_comp, 0,
+ it->stream->param.dir,
+ it->stream, &it->stream->io_units[0]);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE,
+ "Error: failed to create a replacement "
+ "audio unit (%i)", ostatus));
+ continue;
+ }
+
+ 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)",
+ ostatus));
+ continue;
+ }
+ PJ_LOG(3, (THIS_FILE, "core audio unit successfully "
+ "reinstantiated"));
+ } 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->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 the enough input data
+ * for two complete frames of audio.
+ */
+ strm->resample_buf_size = (unsigned)(desc->mSampleRate *
+ strm->param.samples_per_frame /
+ strm->param.clock_rate * 2);
+ 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,
@@ -798,16 +1168,6 @@ static pj_status_t create_audio_unit(AudioComponent io_comp,
AudioUnit *io_unit)
{
OSStatus ostatus;
-#if !COREAUDIO_MAC
- UInt32 audioCategory = kAudioSessionCategory_PlayAndRecord;
- if (!(dir & PJMEDIA_DIR_CAPTURE)) {
- audioCategory = kAudioSessionCategory_MediaPlayback;
- } else if (!(dir & PJMEDIA_DIR_PLAYBACK)) {
- audioCategory = kAudioSessionCategory_RecordAudio;
- }
- AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,
- sizeof(audioCategory), &audioCategory);
-#endif
/* Create an audio unit to interface with the device */
ostatus = AudioComponentInstanceNew(io_comp, io_unit);
@@ -885,6 +1245,22 @@ static pj_status_t create_audio_unit(AudioComponent io_comp,
#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
@@ -902,16 +1278,19 @@ static pj_status_t create_audio_unit(AudioComponent io_comp,
}
#if COREAUDIO_MAC
+ strm->streamFormat.mSampleRate = strm->param.clock_rate;
size = sizeof(AudioStreamBasicDescription);
ostatus = AudioUnitGetProperty (*io_unit,
kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Input,
+ kAudioUnitScope_Output,
1,
&deviceFormat,
&size);
if (ostatus == noErr) {
if (strm->streamFormat.mSampleRate != deviceFormat.mSampleRate) {
- return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ pj_status_t rc = create_audio_resample(strm, &deviceFormat);
+ if (PJ_SUCCESS != rc)
+ return rc;
}
} else {
return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
@@ -965,14 +1344,16 @@ static pj_status_t create_audio_unit(AudioComponent io_comp,
#endif
/* Set input callback */
- input_cb.inputProc = 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));
+ ostatus = AudioUnitSetProperty(
+ *io_unit,
+ kAudioOutputUnitProperty_SetInputCallback,
+ kAudioUnitScope_Global,
+ 0,
+ &input_cb,
+ sizeof(input_cb));
if (ostatus != noErr) {
return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
}
@@ -1057,7 +1438,8 @@ static pj_status_t ca_factory_create_stream(pjmedia_aud_dev_factory *f,
PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
strm = PJ_POOL_ZALLOC_T(pool, struct coreaudio_stream);
- cf->stream = strm;
+ pj_list_init(&strm->list_entry);
+ strm->list_entry.stream = strm;
strm->cf = cf;
pj_memcpy(&strm->param, param, sizeof(*param));
strm->pool = pool;
@@ -1146,6 +1528,11 @@ static pj_status_t ca_factory_create_stream(pjmedia_aud_dev_factory *f,
&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;
@@ -1251,7 +1638,8 @@ static pj_status_t ca_stream_get_cap(pjmedia_aud_stream *s,
kAudioSessionProperty_CurrentHardwareIOBufferDuration,
&size, &latency2) == kAudioSessionNoError))
{
- strm->param.input_latency_ms = (unsigned)((latency + latency2) * 1000);
+ strm->param.input_latency_ms = (unsigned)
+ ((latency + latency2) * 1000);
strm->param.input_latency_ms++;
}
#endif
@@ -1298,7 +1686,8 @@ static pj_status_t ca_stream_get_cap(pjmedia_aud_stream *s,
kAudioSessionProperty_CurrentHardwareIOBufferDuration,
&size, &latency2) == kAudioSessionNoError))
{
- strm->param.output_latency_ms = (unsigned)((latency + latency2) * 1000);
+ strm->param.output_latency_ms = (unsigned)
+ ((latency + latency2) * 1000);
strm->param.output_latency_ms++;
}
#endif
@@ -1400,8 +1789,6 @@ static pj_status_t ca_stream_set_cap(pjmedia_aud_stream *s,
{
struct coreaudio_stream *strm = (struct coreaudio_stream*)s;
- PJ_UNUSED_ARG(strm);
-
PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
#if COREAUDIO_MAC
@@ -1426,10 +1813,38 @@ static pj_status_t ca_stream_set_cap(pjmedia_aud_stream *s,
strm->param.output_vol = *(unsigned*)pval;
return PJ_SUCCESS;
}
-#endif
-#if !COREAUDIO_MAC
- if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE &&
+#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)))
+ {
+ Float32 bufferDuration = *(unsigned *)pval;
+ OSStatus ostatus;
+ 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.
+ */
+ bufferDuration /= 1000;
+ ostatus = AudioSessionSetProperty(
+ kAudioSessionProperty_PreferredHardwareIOBufferDuration,
+ sizeof(bufferDuration), &bufferDuration);
+ if (ostatus != kAudioSessionNoError) {
+ PJ_LOG(4, (THIS_FILE,
+ "Error: cannot set the preferred buffer duration (%i)",
+ ostatus));
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+ 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;
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE &&
(strm->param.dir & PJMEDIA_DIR_CAPTURE))
{
UInt32 btooth = *(pjmedia_aud_dev_route*)pval ==
@@ -1492,11 +1907,23 @@ static pj_status_t ca_stream_start(pjmedia_aud_stream *strm)
struct coreaudio_stream *stream = (struct coreaudio_stream*)strm;
OSStatus ostatus;
UInt32 i;
+ pj_bool_t should_activate;
+ struct stream_list *it, *itBegin;
+
+ if (stream->running)
+ return PJ_SUCCESS;
stream->quit_flag = 0;
- stream->rec_thread_exited = 0;
- stream->play_thread_exited = 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);
+ }
for (i = 0; i < 2; i++) {
if (stream->io_units[i] == NULL) break;
@@ -1508,11 +1935,28 @@ static pj_status_t ca_stream_start(pjmedia_aud_stream *strm)
}
}
+ /*
+ * Make sure this stream is not in the list of running streams.
+ * If this is the 1st stream that is running we need to activate
+ * 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));
+ should_activate = PJ_TRUE;
+ itBegin = &stream->cf->streams;
+ for (it = itBegin->next; it != itBegin; it = it->next) {
+ if (it->stream->running) {
+ should_activate = PJ_FALSE;
+ break;
+ }
+ }
+ stream->running = PJ_TRUE;
+ pj_mutex_unlock(stream->cf->mutex);
+
#if !COREAUDIO_MAC
+ if (should_activate)
AudioSessionSetActive(true);
-
- AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange,
- propListener, strm);
#endif
PJ_LOG(4, (THIS_FILE, "core audio stream started"));
@@ -1526,20 +1970,11 @@ 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;
- stream->quit_flag = 1;
- for (i=0; !stream->rec_thread_exited && i<100; ++i)
- pj_thread_sleep(10);
- for (i=0; !stream->play_thread_exited && i<100; ++i)
- pj_thread_sleep(10);
-
- pj_thread_sleep(1);
- pj_bzero(stream->rec_thread_desc, sizeof(pj_thread_desc));
- pj_bzero(stream->play_thread_desc, sizeof(pj_thread_desc));
-
-#if !COREAUDIO_MAC
- AudioSessionSetActive(false);
-#endif
+ if (!stream->running)
+ return PJ_SUCCESS;
for (i = 0; i < 2; i++) {
if (stream->io_units[i] == NULL) break;
@@ -1550,8 +1985,36 @@ static pj_status_t ca_stream_stop(pjmedia_aud_stream *strm)
return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
}
}
+
+ /*
+ * Make sure this stream is not in the list of running streams.
+ * If this is the 1st stream that is running we need to activate
+ * 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)
+ AudioSessionSetActive(false);
+#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"));
@@ -1567,11 +2030,6 @@ static pj_status_t ca_stream_destroy(pjmedia_aud_stream *strm)
PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
-#if !COREAUDIO_MAC
- AudioSessionRemovePropertyListenerWithUserData(
- kAudioSessionProperty_AudioRouteChange, propListener, strm);
-#endif
-
ca_stream_stop(strm);
for (i = 0; i < 2; i++) {
@@ -1582,7 +2040,14 @@ static pj_status_t ca_stream_destroy(pjmedia_aud_stream *strm)
}
}
- stream->cf->stream = 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;