blob: fd77c993f4befccd1ad3d34079b947c3bcdd26a3 [file] [log] [blame]
Sauw Ming55a73cd2010-05-17 12:51:06 +00001/* $Id$ */
2/*
Nanang Izzuddina62ffc92011-05-05 06:14:19 +00003 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
Sauw Ming55a73cd2010-05-17 12:51:06 +00004 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19#include <pjmedia-audiodev/audiodev_imp.h>
20#include <pj/assert.h>
21#include <pj/log.h>
22#include <pj/os.h>
23
24#if PJMEDIA_AUDIO_DEV_HAS_COREAUDIO
25
26#include "TargetConditionals.h"
27#if TARGET_OS_IPHONE
28 #define COREAUDIO_MAC 0
29#else
30 #define COREAUDIO_MAC 1
31#endif
32
33#include <AudioUnit/AudioUnit.h>
Sauw Ming4a59e2a2010-12-17 07:10:13 +000034#include <AudioToolbox/AudioConverter.h>
Sauw Ming55a73cd2010-05-17 12:51:06 +000035#if !COREAUDIO_MAC
36 #include <AudioToolbox/AudioServices.h>
37
38 #define AudioDeviceID unsigned
Sauw Mingc1f500c2011-03-16 05:44:14 +000039
40 /**
41 * As in iOS SDK 4 or later, audio route change property listener is
42 * no longer necessary. Just make surethat your application can receive
43 * remote control events by adding the code:
44 * [[UIApplication sharedApplication]
45 * beginReceivingRemoteControlEvents];
46 * Otherwise audio route change (such as headset plug/unplug) will not be
47 * processed while your application is in the background mode.
48 */
49 #define USE_AUDIO_ROUTE_CHANGE_PROP_LISTENER 0
50
Sauw Ming55a73cd2010-05-17 12:51:06 +000051#endif
52
53/* For Mac OS 10.5.x and earlier */
54#if AUDIO_UNIT_VERSION < 1060
55 #define AudioComponent Component
56 #define AudioComponentDescription ComponentDescription
57 #define AudioComponentInstance ComponentInstance
58 #define AudioComponentFindNext FindNextComponent
59 #define AudioComponentInstanceNew OpenAComponent
60 #define AudioComponentInstanceDispose CloseComponent
61#endif
62
63
64#define THIS_FILE "coreaudio_dev.c"
65
66/* coreaudio device info */
67struct coreaudio_dev_info
68{
69 pjmedia_aud_dev_info info;
70 AudioDeviceID dev_id;
71};
72
Sauw Ming4a59e2a2010-12-17 07:10:13 +000073/* linked list of streams */
74struct stream_list
75{
76 PJ_DECL_LIST_MEMBER(struct stream_list);
77 struct coreaudio_stream *stream;
78};
79
Sauw Ming55a73cd2010-05-17 12:51:06 +000080/* coreaudio factory */
81struct coreaudio_factory
82{
83 pjmedia_aud_dev_factory base;
Sauw Ming4a59e2a2010-12-17 07:10:13 +000084 pj_pool_t *base_pool;
Sauw Ming55a73cd2010-05-17 12:51:06 +000085 pj_pool_t *pool;
86 pj_pool_factory *pf;
Sauw Ming4a59e2a2010-12-17 07:10:13 +000087 pj_mutex_t *mutex;
Sauw Ming55a73cd2010-05-17 12:51:06 +000088
89 unsigned dev_count;
90 struct coreaudio_dev_info *dev_info;
91
92 AudioComponent io_comp;
Sauw Ming4a59e2a2010-12-17 07:10:13 +000093 struct stream_list streams;
Sauw Ming55a73cd2010-05-17 12:51:06 +000094};
95
96/* Sound stream. */
97struct coreaudio_stream
98{
Sauw Ming4a59e2a2010-12-17 07:10:13 +000099 pjmedia_aud_stream base; /**< Base stream */
100 pjmedia_aud_param param; /**< Settings */
101 pj_pool_t *pool; /**< Memory pool. */
Sauw Ming55a73cd2010-05-17 12:51:06 +0000102 struct coreaudio_factory *cf;
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000103 struct stream_list list_entry;
Sauw Ming55a73cd2010-05-17 12:51:06 +0000104
Sauw Mingc7ec9912010-12-19 05:14:22 +0000105 pjmedia_aud_rec_cb rec_cb; /**< Capture callback. */
106 pjmedia_aud_play_cb play_cb; /**< Playback callback. */
Sauw Ming55a73cd2010-05-17 12:51:06 +0000107 void *user_data; /**< Application data. */
108
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000109 pj_timestamp play_timestamp;
110 pj_timestamp rec_timestamp;
Sauw Ming55a73cd2010-05-17 12:51:06 +0000111
112 pj_int16_t *rec_buf;
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000113 unsigned rec_buf_count;
Sauw Ming55a73cd2010-05-17 12:51:06 +0000114 pj_int16_t *play_buf;
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000115 unsigned play_buf_count;
Sauw Ming55a73cd2010-05-17 12:51:06 +0000116
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000117 pj_bool_t interrupted;
118 pj_bool_t quit_flag;
119 pj_bool_t running;
Sauw Ming55a73cd2010-05-17 12:51:06 +0000120
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000121 pj_bool_t rec_thread_initialized;
122 pj_thread_desc rec_thread_desc;
Sauw Ming55a73cd2010-05-17 12:51:06 +0000123 pj_thread_t *rec_thread;
124
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000125 pj_bool_t play_thread_initialized;
126 pj_thread_desc play_thread_desc;
Sauw Ming55a73cd2010-05-17 12:51:06 +0000127 pj_thread_t *play_thread;
128
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000129 AudioUnit io_units[2];
130 AudioStreamBasicDescription streamFormat;
Sauw Ming55a73cd2010-05-17 12:51:06 +0000131 AudioBufferList *audio_buf;
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000132
133 AudioConverterRef resample;
134 pj_int16_t *resample_buf;
Sauw Ming3e7630a2011-01-20 03:32:14 +0000135 void *resample_buf_ptr;
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000136 unsigned resample_buf_count;
137 unsigned resample_buf_size;
Sauw Ming55a73cd2010-05-17 12:51:06 +0000138};
139
Sauw Mingaa6fd412011-02-15 05:33:23 +0000140/* Static variable */
141static struct coreaudio_factory *cf_instance = NULL;
Sauw Ming55a73cd2010-05-17 12:51:06 +0000142
143/* Prototypes */
144static pj_status_t ca_factory_init(pjmedia_aud_dev_factory *f);
145static pj_status_t ca_factory_destroy(pjmedia_aud_dev_factory *f);
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000146static pj_status_t ca_factory_refresh(pjmedia_aud_dev_factory *f);
Sauw Ming55a73cd2010-05-17 12:51:06 +0000147static unsigned ca_factory_get_dev_count(pjmedia_aud_dev_factory *f);
148static pj_status_t ca_factory_get_dev_info(pjmedia_aud_dev_factory *f,
149 unsigned index,
150 pjmedia_aud_dev_info *info);
151static pj_status_t ca_factory_default_param(pjmedia_aud_dev_factory *f,
152 unsigned index,
153 pjmedia_aud_param *param);
154static pj_status_t ca_factory_create_stream(pjmedia_aud_dev_factory *f,
155 const pjmedia_aud_param *param,
156 pjmedia_aud_rec_cb rec_cb,
157 pjmedia_aud_play_cb play_cb,
158 void *user_data,
159 pjmedia_aud_stream **p_aud_strm);
160
161static pj_status_t ca_stream_get_param(pjmedia_aud_stream *strm,
162 pjmedia_aud_param *param);
163static pj_status_t ca_stream_get_cap(pjmedia_aud_stream *strm,
164 pjmedia_aud_dev_cap cap,
165 void *value);
166static pj_status_t ca_stream_set_cap(pjmedia_aud_stream *strm,
167 pjmedia_aud_dev_cap cap,
168 const void *value);
169static pj_status_t ca_stream_start(pjmedia_aud_stream *strm);
170static pj_status_t ca_stream_stop(pjmedia_aud_stream *strm);
171static pj_status_t ca_stream_destroy(pjmedia_aud_stream *strm);
172static pj_status_t create_audio_unit(AudioComponent io_comp,
173 AudioDeviceID dev_id,
174 pjmedia_dir dir,
175 struct coreaudio_stream *strm,
176 AudioUnit *io_unit);
177#if !COREAUDIO_MAC
178static void interruptionListener(void *inClientData, UInt32 inInterruption);
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000179static void propListener(void * inClientData,
180 AudioSessionPropertyID inID,
181 UInt32 inDataSize,
182 const void * inData);
Sauw Ming55a73cd2010-05-17 12:51:06 +0000183#endif
184
185/* Operations */
186static pjmedia_aud_dev_factory_op factory_op =
187{
188 &ca_factory_init,
189 &ca_factory_destroy,
190 &ca_factory_get_dev_count,
191 &ca_factory_get_dev_info,
192 &ca_factory_default_param,
Sauw Ming98766c72011-03-11 06:57:24 +0000193 &ca_factory_create_stream,
194 &ca_factory_refresh
Sauw Ming55a73cd2010-05-17 12:51:06 +0000195};
196
197static pjmedia_aud_stream_op stream_op =
198{
199 &ca_stream_get_param,
200 &ca_stream_get_cap,
201 &ca_stream_set_cap,
202 &ca_stream_start,
203 &ca_stream_stop,
204 &ca_stream_destroy
205};
206
207
208/****************************************************************************
209 * Factory operations
210 */
211/*
212 * Init coreaudio audio driver.
213 */
214pjmedia_aud_dev_factory* pjmedia_coreaudio_factory(pj_pool_factory *pf)
215{
216 struct coreaudio_factory *f;
217 pj_pool_t *pool;
218
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000219 pool = pj_pool_create(pf, "core audio base", 1000, 1000, NULL);
Sauw Ming55a73cd2010-05-17 12:51:06 +0000220 f = PJ_POOL_ZALLOC_T(pool, struct coreaudio_factory);
221 f->pf = pf;
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000222 f->base_pool = pool;
Sauw Ming55a73cd2010-05-17 12:51:06 +0000223 f->base.op = &factory_op;
224
225 return &f->base;
226}
227
228
229/* API: init factory */
230static pj_status_t ca_factory_init(pjmedia_aud_dev_factory *f)
231{
232 struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
Sauw Ming55a73cd2010-05-17 12:51:06 +0000233 AudioComponentDescription desc;
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000234 pj_status_t status;
235#if !COREAUDIO_MAC
236 unsigned i;
Sauw Ming55a73cd2010-05-17 12:51:06 +0000237 OSStatus ostatus;
238#endif
239
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000240 pj_list_init(&cf->streams);
241 status = pj_mutex_create_recursive(cf->base_pool,
242 "coreaudio",
243 &cf->mutex);
244 if (status != PJ_SUCCESS)
245 return status;
246
Sauw Ming55a73cd2010-05-17 12:51:06 +0000247 desc.componentType = kAudioUnitType_Output;
248#if COREAUDIO_MAC
249 desc.componentSubType = kAudioUnitSubType_HALOutput;
250#else
Sauw Ming5b0f2302011-02-10 06:41:39 +0000251 desc.componentSubType = kAudioUnitSubType_RemoteIO;
Sauw Ming55a73cd2010-05-17 12:51:06 +0000252#endif
253 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
254 desc.componentFlags = 0;
255 desc.componentFlagsMask = 0;
256
257 cf->io_comp = AudioComponentFindNext(NULL, &desc);
258 if (cf->io_comp == NULL)
259 return PJMEDIA_EAUD_INIT; // cannot find IO unit;
260
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000261 status = ca_factory_refresh(f);
262 if (status != PJ_SUCCESS)
263 return status;
Sauw Ming55a73cd2010-05-17 12:51:06 +0000264
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000265#if !COREAUDIO_MAC
266 cf->pool = pj_pool_create(cf->pf, "core audio", 1000, 1000, NULL);
267 cf->dev_count = 1;
268 cf->dev_info = (struct coreaudio_dev_info*)
269 pj_pool_calloc(cf->pool, cf->dev_count,
270 sizeof(struct coreaudio_dev_info));
271 for (i = 0; i < cf->dev_count; i++) {
272 struct coreaudio_dev_info *cdi;
273
274 cdi = &cf->dev_info[i];
275 pj_bzero(cdi, sizeof(*cdi));
276 cdi->dev_id = 0;
277 strcpy(cdi->info.name, "iPhone IO device");
278 strcpy(cdi->info.driver, "apple");
279 cdi->info.input_count = 1;
280 cdi->info.output_count = 1;
281 cdi->info.default_samples_per_sec = 8000;
282
283 /* Set the device capabilities here */
284 cdi->info.caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
285 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY |
286 PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING |
287 PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE |
288 PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE |
289 PJMEDIA_AUD_DEV_CAP_EC;
290 cdi->info.routes = PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER |
291 PJMEDIA_AUD_DEV_ROUTE_EARPIECE |
292 PJMEDIA_AUD_DEV_ROUTE_BLUETOOTH;
293
294 PJ_LOG(4, (THIS_FILE, " dev_id %d: %s (in=%d, out=%d) %dHz",
295 i,
296 cdi->info.name,
297 cdi->info.input_count,
298 cdi->info.output_count,
299 cdi->info.default_samples_per_sec));
300 }
301
302 /* Initialize the Audio Session */
Sauw Mingaa6fd412011-02-15 05:33:23 +0000303 ostatus = AudioSessionInitialize(NULL, NULL, interruptionListener, NULL);
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000304 if (ostatus != kAudioSessionNoError) {
305 PJ_LOG(4, (THIS_FILE,
Benny Prijono29531ae2011-02-28 07:18:33 +0000306 "Warning: cannot initialize audio session services (%i)",
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000307 ostatus));
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000308 }
309
Sauw Mingc1f500c2011-03-16 05:44:14 +0000310 /* Listen for audio routing change notifications. */
311#if USE_AUDIO_ROUTE_CHANGE_PROP_LISTENER != 0
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000312 ostatus = AudioSessionAddPropertyListener(
313 kAudioSessionProperty_AudioRouteChange,
314 propListener, cf);
315 if (ostatus != kAudioSessionNoError) {
316 PJ_LOG(4, (THIS_FILE,
Benny Prijono29531ae2011-02-28 07:18:33 +0000317 "Warning: cannot listen for audio route change "
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000318 "notifications (%i)", ostatus));
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000319 }
Sauw Mingc1f500c2011-03-16 05:44:14 +0000320#endif
321
Sauw Mingaa6fd412011-02-15 05:33:23 +0000322 cf_instance = cf;
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000323#endif
324
325 PJ_LOG(4, (THIS_FILE, "core audio initialized"));
326
327 return PJ_SUCCESS;
328}
329
330/* API: destroy factory */
331static pj_status_t ca_factory_destroy(pjmedia_aud_dev_factory *f)
332{
333 struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
334 pj_pool_t *pool;
335
336 pj_assert(cf);
337 pj_assert(cf->base_pool);
338 pj_assert(pj_list_empty(&cf->streams));
339
340#if !COREAUDIO_MAC
Sauw Mingc1f500c2011-03-16 05:44:14 +0000341#if USE_AUDIO_ROUTE_CHANGE_PROP_LISTENER != 0
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000342 AudioSessionRemovePropertyListenerWithUserData(
343 kAudioSessionProperty_AudioRouteChange, propListener, cf);
344#endif
Sauw Mingc1f500c2011-03-16 05:44:14 +0000345#endif
Sauw Mingaa6fd412011-02-15 05:33:23 +0000346
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000347 if (cf->pool) {
348 pj_pool_release(cf->pool);
349 cf->pool = NULL;
350 }
351
352 if (cf->mutex) {
Sauw Mingaa6fd412011-02-15 05:33:23 +0000353 pj_mutex_lock(cf->mutex);
354 cf_instance = NULL;
355 pj_mutex_unlock(cf->mutex);
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000356 pj_mutex_destroy(cf->mutex);
357 cf->mutex = NULL;
358 }
359
360 pool = cf->base_pool;
361 cf->base_pool = NULL;
362 pj_pool_release(pool);
363
364 return PJ_SUCCESS;
365}
366
367/* API: refresh the device list */
368static pj_status_t ca_factory_refresh(pjmedia_aud_dev_factory *f)
369{
370#if !COREAUDIO_MAC
371 /* iPhone doesn't support refreshing the device list */
372 PJ_UNUSED_ARG(f);
373 return PJ_SUCCESS;
374#else
375 struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
376 unsigned i;
377 unsigned dev_count;
378 AudioObjectPropertyAddress addr;
379 AudioDeviceID *dev_ids;
380 UInt32 buf_size, dev_size, size = sizeof(AudioDeviceID);
381 AudioBufferList *buf = NULL;
382 OSStatus ostatus;
383
384 if (cf->pool != NULL) {
385 pj_pool_release(cf->pool);
386 cf->pool = NULL;
387 }
388
389 cf->dev_count = 0;
390 cf->pool = pj_pool_create(cf->pf, "core audio", 1000, 1000, NULL);
391
Sauw Ming55a73cd2010-05-17 12:51:06 +0000392 /* Find out how many audio devices there are */
393 addr.mSelector = kAudioHardwarePropertyDevices;
394 addr.mScope = kAudioObjectPropertyScopeGlobal;
395 addr.mElement = kAudioObjectPropertyElementMaster;
396 ostatus = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr,
397 0, NULL, &dev_size);
398 if (ostatus != noErr) {
399 dev_size = 0;
400 }
401
402 /* Calculate the number of audio devices available */
403 dev_count = dev_size / size;
404 if (dev_count==0) {
405 PJ_LOG(4,(THIS_FILE, "core audio found no sound devices"));
406 /* Enabling this will cause pjsua-lib initialization to fail when
407 * there is no sound device installed in the system, even when pjsua
408 * has been run with --null-audio. Moreover, it might be better to
409 * think that the core audio backend initialization is successful,
410 * regardless there is no audio device installed, as later application
411 * can check it using get_dev_count().
412 return PJMEDIA_EAUD_NODEV;
413 */
414 return PJ_SUCCESS;
415 }
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000416 PJ_LOG(4, (THIS_FILE, "core audio detected %d devices",
417 dev_count));
Sauw Ming55a73cd2010-05-17 12:51:06 +0000418
419 /* Get all the audio device IDs */
420 dev_ids = (AudioDeviceID *)pj_pool_calloc(cf->pool, dev_size, size);
421 if (!dev_ids)
422 return PJ_ENOMEM;
423 pj_bzero(dev_ids, dev_count);
424 ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr,
425 0, NULL,
426 &dev_size, (void *)dev_ids);
427 if (ostatus != noErr ) {
428 /* This should not happen since we have successfully retrieved
429 * the property data size before
430 */
431 return PJMEDIA_EAUD_INIT;
432 }
Sauw Ming4fc590f2011-01-13 16:42:21 +0000433
434 if (dev_size > 1) {
435 AudioDeviceID dev_id = kAudioObjectUnknown;
436 unsigned idx = 0;
437
438 /* Find default audio input device */
439 addr.mSelector = kAudioHardwarePropertyDefaultInputDevice;
440 addr.mScope = kAudioObjectPropertyScopeGlobal;
441 addr.mElement = kAudioObjectPropertyElementMaster;
442 size = sizeof(dev_id);
443
444 ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject,
445 &addr, 0, NULL,
446 &size, (void *)&dev_id);
447 if (ostatus != noErr && dev_id != dev_ids[idx]) {
448 AudioDeviceID temp_id = dev_ids[idx];
449
450 for (i = idx + 1; i < dev_size; i++) {
451 if (dev_ids[i] == dev_id) {
452 dev_ids[idx++] = dev_id;
453 dev_ids[i] = temp_id;
454 break;
455 }
456 }
457 }
458
459 /* Find default audio output device */
460 addr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
461 ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject,
462 &addr, 0, NULL,
463 &size, (void *)&dev_id);
464 if (ostatus != noErr && dev_id != dev_ids[idx]) {
465 AudioDeviceID temp_id = dev_ids[idx];
466
467 for (i = idx + 1; i < dev_size; i++) {
468 if (dev_ids[i] == dev_id) {
469 dev_ids[idx] = dev_id;
470 dev_ids[i] = temp_id;
471 break;
472 }
473 }
474 }
475 }
Sauw Ming55a73cd2010-05-17 12:51:06 +0000476
477 /* Build the devices' info */
478 cf->dev_info = (struct coreaudio_dev_info*)
479 pj_pool_calloc(cf->pool, dev_count,
480 sizeof(struct coreaudio_dev_info));
481 buf_size = 0;
482 for (i = 0; i < dev_count; i++) {
483 struct coreaudio_dev_info *cdi;
484 Float64 sampleRate;
485
486 cdi = &cf->dev_info[i];
487 pj_bzero(cdi, sizeof(*cdi));
488 cdi->dev_id = dev_ids[i];
489
490 /* Get device name */
491 addr.mSelector = kAudioDevicePropertyDeviceName;
492 addr.mScope = kAudioObjectPropertyScopeGlobal;
493 addr.mElement = kAudioObjectPropertyElementMaster;
494 size = sizeof(cdi->info.name);
495 AudioObjectGetPropertyData(cdi->dev_id, &addr,
496 0, NULL,
497 &size, (void *)cdi->info.name);
498
499 strcpy(cdi->info.driver, "core audio");
500
501 /* Get the number of input channels */
502 addr.mSelector = kAudioDevicePropertyStreamConfiguration;
503 addr.mScope = kAudioDevicePropertyScopeInput;
504 size = 0;
505 ostatus = AudioObjectGetPropertyDataSize(cdi->dev_id, &addr,
506 0, NULL, &size);
507 if (ostatus == noErr && size > 0) {
508
509 if (size > buf_size) {
510 buf = pj_pool_alloc(cf->pool, size);
511 buf_size = size;
512 }
513 if (buf) {
514 UInt32 idx;
515
516 /* Get the input stream configuration */
517 ostatus = AudioObjectGetPropertyData(cdi->dev_id, &addr,
518 0, NULL,
519 &size, buf);
520 if (ostatus == noErr) {
521 /* Count the total number of input channels in
522 * the stream
523 */
524 for (idx = 0; idx < buf->mNumberBuffers; idx++) {
525 cdi->info.input_count +=
526 buf->mBuffers[idx].mNumberChannels;
527 }
528 }
529 }
530 }
531
532 /* Get the number of output channels */
533 addr.mScope = kAudioDevicePropertyScopeOutput;
534 size = 0;
535 ostatus = AudioObjectGetPropertyDataSize(cdi->dev_id, &addr,
536 0, NULL, &size);
537 if (ostatus == noErr && size > 0) {
538
539 if (size > buf_size) {
540 buf = pj_pool_alloc(cf->pool, size);
541 buf_size = size;
542 }
543 if (buf) {
544 UInt32 idx;
545
546 /* Get the output stream configuration */
547 ostatus = AudioObjectGetPropertyData(cdi->dev_id, &addr,
548 0, NULL,
549 &size, buf);
550 if (ostatus == noErr) {
551 /* Count the total number of output channels in
552 * the stream
553 */
554 for (idx = 0; idx < buf->mNumberBuffers; idx++) {
555 cdi->info.output_count +=
556 buf->mBuffers[idx].mNumberChannels;
557 }
558 }
559 }
560 }
561
562 /* Get default sample rate */
563 addr.mSelector = kAudioDevicePropertyNominalSampleRate;
564 addr.mScope = kAudioObjectPropertyScopeGlobal;
565 size = sizeof(Float64);
566 ostatus = AudioObjectGetPropertyData (cdi->dev_id, &addr,
567 0, NULL,
568 &size, &sampleRate);
569 cdi->info.default_samples_per_sec = (ostatus == noErr ?
570 sampleRate:
571 16000);
572
573 /* Set device capabilities here */
574 if (cdi->info.input_count > 0) {
575 cdi->info.caps |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
576 }
577 if (cdi->info.output_count > 0) {
578 cdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
579 addr.mSelector = kAudioDevicePropertyVolumeScalar;
580 addr.mScope = kAudioDevicePropertyScopeOutput;
581 if (AudioObjectHasProperty(cdi->dev_id, &addr)) {
582 cdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
583 }
584 }
585
586 cf->dev_count++;
587
588 PJ_LOG(4, (THIS_FILE, " dev_id %d: %s (in=%d, out=%d) %dHz",
589 i,
590 cdi->info.name,
591 cdi->info.input_count,
592 cdi->info.output_count,
593 cdi->info.default_samples_per_sec));
594 }
Sauw Ming55a73cd2010-05-17 12:51:06 +0000595
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000596 return PJ_SUCCESS;
Sauw Ming55a73cd2010-05-17 12:51:06 +0000597#endif
Sauw Ming55a73cd2010-05-17 12:51:06 +0000598}
599
600/* API: get number of devices */
601static unsigned ca_factory_get_dev_count(pjmedia_aud_dev_factory *f)
602{
603 struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
604 return cf->dev_count;
605}
606
607/* API: get device info */
608static pj_status_t ca_factory_get_dev_info(pjmedia_aud_dev_factory *f,
609 unsigned index,
610 pjmedia_aud_dev_info *info)
611{
612 struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
613
614 PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EAUD_INVDEV);
615
616 pj_memcpy(info, &cf->dev_info[index].info, sizeof(*info));
617
618 return PJ_SUCCESS;
619}
620
621/* API: create default device parameter */
622static pj_status_t ca_factory_default_param(pjmedia_aud_dev_factory *f,
623 unsigned index,
624 pjmedia_aud_param *param)
625{
626 struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
627 struct coreaudio_dev_info *di = &cf->dev_info[index];
628
629 PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EAUD_INVDEV);
630
631 pj_bzero(param, sizeof(*param));
632 if (di->info.input_count && di->info.output_count) {
633 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
634 param->rec_id = index;
635 param->play_id = index;
636 } else if (di->info.input_count) {
637 param->dir = PJMEDIA_DIR_CAPTURE;
638 param->rec_id = index;
639 param->play_id = PJMEDIA_AUD_INVALID_DEV;
640 } else if (di->info.output_count) {
641 param->dir = PJMEDIA_DIR_PLAYBACK;
642 param->play_id = index;
643 param->rec_id = PJMEDIA_AUD_INVALID_DEV;
644 } else {
645 return PJMEDIA_EAUD_INVDEV;
646 }
647
648 /* Set the mandatory settings here */
649 param->clock_rate = di->info.default_samples_per_sec;
650 param->channel_count = 1;
651 param->samples_per_frame = di->info.default_samples_per_sec * 20 / 1000;
652 param->bits_per_sample = 16;
653
654 /* Set the param for device capabilities here */
655 param->flags = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
656 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
657 param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
658 param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
659
660 return PJ_SUCCESS;
661}
662
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000663OSStatus resampleProc(AudioConverterRef inAudioConverter,
664 UInt32 *ioNumberDataPackets,
665 AudioBufferList *ioData,
666 AudioStreamPacketDescription **outDataPacketDescription,
667 void *inUserData)
668{
669 struct coreaudio_stream *strm = (struct coreaudio_stream*)inUserData;
670
Sauw Ming3e7630a2011-01-20 03:32:14 +0000671 if (*ioNumberDataPackets > strm->resample_buf_size)
672 *ioNumberDataPackets = strm->resample_buf_size;
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000673
674 ioData->mNumberBuffers = 1;
Sauw Mingf3cfb882011-01-31 11:27:48 +0000675 ioData->mBuffers[0].mNumberChannels = strm->streamFormat.mChannelsPerFrame;
Sauw Ming3e7630a2011-01-20 03:32:14 +0000676 ioData->mBuffers[0].mData = strm->resample_buf_ptr;
677 ioData->mBuffers[0].mDataByteSize = *ioNumberDataPackets *
Sauw Mingf3cfb882011-01-31 11:27:48 +0000678 strm->streamFormat.mChannelsPerFrame *
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000679 strm->param.bits_per_sample >> 3;
Sauw Ming3e7630a2011-01-20 03:32:14 +0000680
681 return noErr;
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000682}
683
684static OSStatus resample_callback(void *inRefCon,
685 AudioUnitRenderActionFlags *ioActionFlags,
686 const AudioTimeStamp *inTimeStamp,
687 UInt32 inBusNumber,
688 UInt32 inNumberFrames,
689 AudioBufferList *ioData)
690{
691 struct coreaudio_stream *strm = (struct coreaudio_stream*)inRefCon;
692 OSStatus ostatus;
693 pj_status_t status = 0;
694 unsigned nsamples;
695 AudioBufferList *buf = strm->audio_buf;
696 pj_int16_t *input;
697 UInt32 resampleSize;
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000698
699 pj_assert(!strm->quit_flag);
700
701 /* Known cases of callback's thread:
702 * - The thread may be changed in the middle of a session
703 * it happens when plugging/unplugging headphone.
704 * - The same thread may be reused in consecutive sessions. The first
705 * session will leave TLS set, but release the TLS data address,
706 * so the second session must re-register the callback's thread.
707 */
708 if (strm->rec_thread_initialized == 0 || !pj_thread_is_registered())
709 {
Sauw Ming4fc590f2011-01-13 16:42:21 +0000710 pj_bzero(strm->rec_thread_desc, sizeof(pj_thread_desc));
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000711 status = pj_thread_register("ca_rec", strm->rec_thread_desc,
712 &strm->rec_thread);
713 strm->rec_thread_initialized = 1;
714 PJ_LOG(5,(THIS_FILE, "Recorder thread started, (%i frames)",
715 inNumberFrames));
716 }
717
718 buf->mBuffers[0].mData = NULL;
719 buf->mBuffers[0].mDataByteSize = inNumberFrames *
720 strm->streamFormat.mChannelsPerFrame;
721 /* Render the unit to get input data */
722 ostatus = AudioUnitRender(strm->io_units[0],
723 ioActionFlags,
724 inTimeStamp,
725 inBusNumber,
726 inNumberFrames,
727 buf);
728
729 if (ostatus != noErr) {
730 PJ_LOG(5, (THIS_FILE, "Core audio unit render error %i", ostatus));
731 goto on_break;
732 }
733 input = (pj_int16_t *)buf->mBuffers[0].mData;
734
Sauw Ming3e7630a2011-01-20 03:32:14 +0000735 resampleSize = strm->resample_buf_size;
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000736 nsamples = inNumberFrames * strm->param.channel_count +
737 strm->resample_buf_count;
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000738
739 if (nsamples >= resampleSize) {
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000740 pjmedia_frame frame;
Sauw Mingf3cfb882011-01-31 11:27:48 +0000741 UInt32 resampleOutput = strm->param.samples_per_frame /
742 strm->streamFormat.mChannelsPerFrame;
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000743 AudioBufferList ab;
744
745 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
Sauw Ming3e7630a2011-01-20 03:32:14 +0000746 frame.buf = (void*) strm->rec_buf;
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000747 frame.size = strm->param.samples_per_frame *
748 strm->param.bits_per_sample >> 3;
749 frame.bit_info = 0;
Sauw Ming3e7630a2011-01-20 03:32:14 +0000750
751 ab.mNumberBuffers = 1;
Sauw Mingf3cfb882011-01-31 11:27:48 +0000752 ab.mBuffers[0].mNumberChannels = strm->streamFormat.mChannelsPerFrame;
Sauw Ming3e7630a2011-01-20 03:32:14 +0000753 ab.mBuffers[0].mData = strm->rec_buf;
754 ab.mBuffers[0].mDataByteSize = frame.size;
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000755
756 /* If buffer is not empty, combine the buffer with the just incoming
757 * samples, then call put_frame.
758 */
Sauw Ming3e7630a2011-01-20 03:32:14 +0000759 if (strm->resample_buf_count) {
760 unsigned chunk_count = resampleSize - strm->resample_buf_count;
761 pjmedia_copy_samples(strm->resample_buf + strm->resample_buf_count,
762 input, chunk_count);
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000763
Sauw Ming3e7630a2011-01-20 03:32:14 +0000764 /* Do the resample */
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000765
Sauw Ming3e7630a2011-01-20 03:32:14 +0000766 strm->resample_buf_ptr = strm->resample_buf;
767 ostatus = AudioConverterFillComplexBuffer(strm->resample,
768 resampleProc,
769 strm,
770 &resampleOutput,
771 &ab,
772 NULL);
773 if (ostatus != noErr) {
774 goto on_break;
775 }
776 frame.timestamp.u64 = strm->rec_timestamp.u64;
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000777
Sauw Ming3e7630a2011-01-20 03:32:14 +0000778 status = (*strm->rec_cb)(strm->user_data, &frame);
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000779
Sauw Ming3e7630a2011-01-20 03:32:14 +0000780 input = input + chunk_count;
781 nsamples -= resampleSize;
782 strm->resample_buf_count = 0;
783 strm->rec_timestamp.u64 += strm->param.samples_per_frame /
784 strm->param.channel_count;
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000785 }
Sauw Ming3e7630a2011-01-20 03:32:14 +0000786
787
788 /* Give all frames we have */
789 while (nsamples >= resampleSize && status == 0) {
790 frame.timestamp.u64 = strm->rec_timestamp.u64;
791
792 /* Do the resample */
793 strm->resample_buf_ptr = input;
794 ab.mBuffers[0].mDataByteSize = frame.size;
Sauw Mingf3cfb882011-01-31 11:27:48 +0000795 resampleOutput = strm->param.samples_per_frame /
796 strm->streamFormat.mChannelsPerFrame;
Sauw Ming3e7630a2011-01-20 03:32:14 +0000797 ostatus = AudioConverterFillComplexBuffer(strm->resample,
798 resampleProc,
799 strm,
800 &resampleOutput,
801 &ab,
802 NULL);
803 if (ostatus != noErr) {
804 goto on_break;
805 }
806
807 status = (*strm->rec_cb)(strm->user_data, &frame);
808
809 input = (pj_int16_t*) input + resampleSize;
810 nsamples -= resampleSize;
811 strm->rec_timestamp.u64 += strm->param.samples_per_frame /
812 strm->param.channel_count;
813 }
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000814
815 /* Store the remaining samples into the buffer */
816 if (nsamples && status == 0) {
817 strm->resample_buf_count = nsamples;
818 pjmedia_copy_samples(strm->resample_buf, input,
819 nsamples);
820 }
821
822 } else {
823 /* Not enough samples, let's just store them in the buffer */
824 pjmedia_copy_samples(strm->resample_buf + strm->resample_buf_count,
825 input,
826 inNumberFrames * strm->param.channel_count);
827 strm->resample_buf_count += inNumberFrames *
828 strm->param.channel_count;
829 }
830
831 return noErr;
832
833on_break:
834 return -1;
835}
836
Sauw Ming55a73cd2010-05-17 12:51:06 +0000837static OSStatus input_callback(void *inRefCon,
838 AudioUnitRenderActionFlags *ioActionFlags,
839 const AudioTimeStamp *inTimeStamp,
840 UInt32 inBusNumber,
841 UInt32 inNumberFrames,
842 AudioBufferList *ioData)
843{
844 struct coreaudio_stream *strm = (struct coreaudio_stream*)inRefCon;
845 OSStatus ostatus;
846 pj_status_t status = 0;
847 unsigned nsamples;
848 AudioBufferList *buf = strm->audio_buf;
849 pj_int16_t *input;
850
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000851 pj_assert(!strm->quit_flag);
Sauw Ming55a73cd2010-05-17 12:51:06 +0000852
853 /* Known cases of callback's thread:
854 * - The thread may be changed in the middle of a session
855 * it happens when plugging/unplugging headphone.
856 * - The same thread may be reused in consecutive sessions. The first
857 * session will leave TLS set, but release the TLS data address,
858 * so the second session must re-register the callback's thread.
859 */
860 if (strm->rec_thread_initialized == 0 || !pj_thread_is_registered())
861 {
Sauw Ming4fc590f2011-01-13 16:42:21 +0000862 pj_bzero(strm->rec_thread_desc, sizeof(pj_thread_desc));
Sauw Ming55a73cd2010-05-17 12:51:06 +0000863 status = pj_thread_register("ca_rec", strm->rec_thread_desc,
864 &strm->rec_thread);
865 strm->rec_thread_initialized = 1;
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000866 PJ_LOG(5,(THIS_FILE, "Recorder thread started, (%i frames)",
867 inNumberFrames));
Sauw Ming55a73cd2010-05-17 12:51:06 +0000868 }
869
870 buf->mBuffers[0].mData = NULL;
871 buf->mBuffers[0].mDataByteSize = inNumberFrames *
872 strm->streamFormat.mChannelsPerFrame;
873 /* Render the unit to get input data */
874 ostatus = AudioUnitRender(strm->io_units[0],
875 ioActionFlags,
876 inTimeStamp,
877 inBusNumber,
878 inNumberFrames,
879 buf);
880
881 if (ostatus != noErr) {
882 PJ_LOG(5, (THIS_FILE, "Core audio unit render error %i", ostatus));
883 goto on_break;
884 }
885 input = (pj_int16_t *)buf->mBuffers[0].mData;
886
887 /* Calculate number of samples we've got */
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000888 nsamples = inNumberFrames * strm->param.channel_count +
889 strm->rec_buf_count;
Sauw Ming3e7630a2011-01-20 03:32:14 +0000890 if (nsamples >= strm->param.samples_per_frame) {
Sauw Ming55a73cd2010-05-17 12:51:06 +0000891 pjmedia_frame frame;
892
893 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
894 frame.size = strm->param.samples_per_frame *
895 strm->param.bits_per_sample >> 3;
896 frame.bit_info = 0;
897
898 /* If buffer is not empty, combine the buffer with the just incoming
899 * samples, then call put_frame.
900 */
901 if (strm->rec_buf_count) {
902 unsigned chunk_count = 0;
903
904 chunk_count = strm->param.samples_per_frame - strm->rec_buf_count;
905 pjmedia_copy_samples(strm->rec_buf + strm->rec_buf_count,
906 input, chunk_count);
907
908 frame.buf = (void*) strm->rec_buf;
909 frame.timestamp.u64 = strm->rec_timestamp.u64;
910
911 status = (*strm->rec_cb)(strm->user_data, &frame);
912
913 input = input + chunk_count;
914 nsamples -= strm->param.samples_per_frame;
915 strm->rec_buf_count = 0;
916 strm->rec_timestamp.u64 += strm->param.samples_per_frame /
917 strm->param.channel_count;
918 }
919
920 /* Give all frames we have */
921 while (nsamples >= strm->param.samples_per_frame && status == 0) {
922 frame.buf = (void*) input;
923 frame.timestamp.u64 = strm->rec_timestamp.u64;
924
925 status = (*strm->rec_cb)(strm->user_data, &frame);
926
927 input = (pj_int16_t*) input + strm->param.samples_per_frame;
928 nsamples -= strm->param.samples_per_frame;
929 strm->rec_timestamp.u64 += strm->param.samples_per_frame /
930 strm->param.channel_count;
931 }
932
933 /* Store the remaining samples into the buffer */
934 if (nsamples && status == 0) {
935 strm->rec_buf_count = nsamples;
936 pjmedia_copy_samples(strm->rec_buf, input,
937 nsamples);
938 }
939
940 } else {
941 /* Not enough samples, let's just store them in the buffer */
942 pjmedia_copy_samples(strm->rec_buf + strm->rec_buf_count,
943 input,
944 inNumberFrames * strm->param.channel_count);
945 strm->rec_buf_count += inNumberFrames * strm->param.channel_count;
946 }
947
948 return noErr;
949
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000950on_break:
951 return -1;
Sauw Ming55a73cd2010-05-17 12:51:06 +0000952}
953
954static OSStatus output_renderer(void *inRefCon,
955 AudioUnitRenderActionFlags *ioActionFlags,
956 const AudioTimeStamp *inTimeStamp,
957 UInt32 inBusNumber,
958 UInt32 inNumberFrames,
959 AudioBufferList *ioData)
960{
961 struct coreaudio_stream *stream = (struct coreaudio_stream*)inRefCon;
962 pj_status_t status = 0;
963 unsigned nsamples_req = inNumberFrames * stream->param.channel_count;
964 pj_int16_t *output = ioData->mBuffers[0].mData;
965
Sauw Ming4a59e2a2010-12-17 07:10:13 +0000966 pj_assert(!stream->quit_flag);
Sauw Ming55a73cd2010-05-17 12:51:06 +0000967
968 /* Known cases of callback's thread:
969 * - The thread may be changed in the middle of a session
970 * it happens when plugging/unplugging headphone.
971 * - The same thread may be reused in consecutive sessions. The first
972 * session will leave TLS set, but release the TLS data address,
973 * so the second session must re-register the callback's thread.
974 */
975 if (stream->play_thread_initialized == 0 || !pj_thread_is_registered())
976 {
Sauw Ming4fc590f2011-01-13 16:42:21 +0000977 pj_bzero(stream->play_thread_desc, sizeof(pj_thread_desc));
Sauw Ming55a73cd2010-05-17 12:51:06 +0000978 status = pj_thread_register("coreaudio", stream->play_thread_desc,
979 &stream->play_thread);
980 stream->play_thread_initialized = 1;
Sauw Mingc7ec9912010-12-19 05:14:22 +0000981 PJ_LOG(5,(THIS_FILE, "Player thread started, (%i frames)",
982 inNumberFrames));
Sauw Ming55a73cd2010-05-17 12:51:06 +0000983 }
984
985
986 /* Check if any buffered samples */
987 if (stream->play_buf_count) {
988 /* samples buffered >= requested by sound device */
989 if (stream->play_buf_count >= nsamples_req) {
990 pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
991 nsamples_req);
992 stream->play_buf_count -= nsamples_req;
993 pjmedia_move_samples(stream->play_buf,
994 stream->play_buf + nsamples_req,
995 stream->play_buf_count);
996 nsamples_req = 0;
997
998 return noErr;
999 }
1000
1001 /* samples buffered < requested by sound device */
1002 pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
1003 stream->play_buf_count);
1004 nsamples_req -= stream->play_buf_count;
1005 output = (pj_int16_t*)output + stream->play_buf_count;
1006 stream->play_buf_count = 0;
1007 }
1008
1009 /* Fill output buffer as requested */
1010 while (nsamples_req && status == 0) {
1011 pjmedia_frame frame;
1012
1013 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
1014 frame.size = stream->param.samples_per_frame *
1015 stream->param.bits_per_sample >> 3;
1016 frame.timestamp.u64 = stream->play_timestamp.u64;
1017 frame.bit_info = 0;
1018
1019 if (nsamples_req >= stream->param.samples_per_frame) {
1020 frame.buf = output;
1021 status = (*stream->play_cb)(stream->user_data, &frame);
1022 if (status != PJ_SUCCESS)
1023 goto on_break;
1024
1025 if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
1026 pj_bzero(frame.buf, frame.size);
1027
1028 nsamples_req -= stream->param.samples_per_frame;
1029 output = (pj_int16_t*)output + stream->param.samples_per_frame;
1030 } else {
1031 frame.buf = stream->play_buf;
1032 status = (*stream->play_cb)(stream->user_data, &frame);
1033 if (status != PJ_SUCCESS)
1034 goto on_break;
1035
1036 if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
1037 pj_bzero(frame.buf, frame.size);
1038
1039 pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
1040 nsamples_req);
1041 stream->play_buf_count = stream->param.samples_per_frame -
1042 nsamples_req;
1043 pjmedia_move_samples(stream->play_buf,
1044 stream->play_buf+nsamples_req,
1045 stream->play_buf_count);
1046 nsamples_req = 0;
1047 }
1048
1049 stream->play_timestamp.u64 += stream->param.samples_per_frame /
1050 stream->param.channel_count;
1051 }
1052
1053 return noErr;
1054
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001055on_break:
1056 return -1;
Sauw Ming55a73cd2010-05-17 12:51:06 +00001057}
1058
1059#if !COREAUDIO_MAC
1060static void propListener(void *inClientData,
1061 AudioSessionPropertyID inID,
1062 UInt32 inDataSize,
1063 const void * inData)
1064{
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001065 struct coreaudio_factory *cf = (struct coreaudio_factory*)inClientData;
1066 struct stream_list *it, *itBegin;
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001067 CFDictionaryRef routeDictionary;
1068 CFNumberRef reason;
1069 SInt32 reasonVal;
1070 pj_assert(cf);
Sauw Ming55a73cd2010-05-17 12:51:06 +00001071
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001072 if (inID != kAudioSessionProperty_AudioRouteChange)
1073 return;
Sauw Ming55a73cd2010-05-17 12:51:06 +00001074
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001075 routeDictionary = (CFDictionaryRef)inData;
1076 reason = (CFNumberRef)
1077 CFDictionaryGetValue(
1078 routeDictionary,
1079 CFSTR(kAudioSession_AudioRouteChangeKey_Reason));
1080 CFNumberGetValue(reason, kCFNumberSInt32Type, &reasonVal);
Sauw Ming55a73cd2010-05-17 12:51:06 +00001081
Sauw Mingaa6fd412011-02-15 05:33:23 +00001082 if (reasonVal != kAudioSessionRouteChangeReason_OldDeviceUnavailable) {
1083 PJ_LOG(3, (THIS_FILE, "ignoring audio route change..."));
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001084 return;
1085 }
Sauw Ming55a73cd2010-05-17 12:51:06 +00001086
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001087 PJ_LOG(3, (THIS_FILE, "audio route changed"));
1088
1089 pj_mutex_lock(cf->mutex);
1090 itBegin = &cf->streams;
1091 for (it = itBegin->next; it != itBegin; it = it->next) {
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001092 if (it->stream->interrupted)
1093 continue;
1094
Sauw Mingaa6fd412011-02-15 05:33:23 +00001095 /*
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001096 status = ca_stream_stop((pjmedia_aud_stream *)it->stream);
Sauw Mingaa6fd412011-02-15 05:33:23 +00001097 status = ca_stream_start((pjmedia_aud_stream *)it->stream);
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001098 if (status != PJ_SUCCESS) {
Sauw Ming55a73cd2010-05-17 12:51:06 +00001099 PJ_LOG(3, (THIS_FILE,
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001100 "Error: failed to restart the audio unit (%i)",
1101 status));
1102 continue;
Sauw Ming55a73cd2010-05-17 12:51:06 +00001103 }
Sauw Mingaa6fd412011-02-15 05:33:23 +00001104 PJ_LOG(3, (THIS_FILE, "core audio unit successfully restarted"));
1105 */
Sauw Ming55a73cd2010-05-17 12:51:06 +00001106 }
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001107 pj_mutex_unlock(cf->mutex);
Sauw Ming55a73cd2010-05-17 12:51:06 +00001108}
1109
1110static void interruptionListener(void *inClientData, UInt32 inInterruption)
1111{
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001112 struct stream_list *it, *itBegin;
1113 pj_status_t status;
Sauw Ming55a73cd2010-05-17 12:51:06 +00001114
1115 PJ_LOG(3, (THIS_FILE, "Session interrupted! --- %s ---",
1116 inInterruption == kAudioSessionBeginInterruption ?
1117 "Begin Interruption" : "End Interruption"));
1118
Sauw Mingaa6fd412011-02-15 05:33:23 +00001119 if (!cf_instance)
1120 return;
1121
1122 pj_mutex_lock(cf_instance->mutex);
1123 itBegin = &cf_instance->streams;
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001124 for (it = itBegin->next; it != itBegin; it = it->next) {
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001125 if (inInterruption == kAudioSessionEndInterruption &&
1126 it->stream->interrupted == PJ_TRUE)
1127 {
Sauw Mingc1f500c2011-03-16 05:44:14 +00001128 UInt32 audioCategory;
1129 OSStatus ostatus;
1130
Sauw Mingaa6fd412011-02-15 05:33:23 +00001131 /* Make sure that your application can receive remote control
1132 * events by adding the code:
1133 * [[UIApplication sharedApplication]
1134 * beginReceivingRemoteControlEvents];
1135 * Otherwise audio unit will fail to restart while your
1136 * application is in the background mode.
1137 */
Sauw Mingc1f500c2011-03-16 05:44:14 +00001138 /* Make sure we set the correct audio category before restarting */
1139 audioCategory = kAudioSessionCategory_PlayAndRecord;
1140 ostatus = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,
1141 sizeof(audioCategory),
1142 &audioCategory);
1143 if (ostatus != kAudioSessionNoError) {
1144 PJ_LOG(4, (THIS_FILE,
1145 "Warning: cannot set the audio session category (%i)",
1146 ostatus));
1147 }
1148
1149 /* Restart the stream */
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001150 status = ca_stream_start((pjmedia_aud_stream*)it->stream);
1151 if (status != PJ_SUCCESS) {
1152 PJ_LOG(3, (THIS_FILE,
1153 "Error: failed to restart the audio unit (%i)",
Sauw Mingaa6fd412011-02-15 05:33:23 +00001154 status));
1155 continue;
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001156 }
Sauw Mingaa6fd412011-02-15 05:33:23 +00001157 PJ_LOG(3, (THIS_FILE, "core audio unit successfully resumed"
1158 " after interruption"));
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001159 } else if (inInterruption == kAudioSessionBeginInterruption &&
1160 it->stream->running == PJ_TRUE)
1161 {
1162 status = ca_stream_stop((pjmedia_aud_stream*)it->stream);
1163 it->stream->interrupted = PJ_TRUE;
1164 }
Sauw Ming55a73cd2010-05-17 12:51:06 +00001165 }
Sauw Mingaa6fd412011-02-15 05:33:23 +00001166 pj_mutex_unlock(cf_instance->mutex);
Sauw Ming55a73cd2010-05-17 12:51:06 +00001167}
1168
1169#endif
1170
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001171#if COREAUDIO_MAC
1172/* Internal: create audio converter for resampling the recorder device */
1173static pj_status_t create_audio_resample(struct coreaudio_stream *strm,
1174 AudioStreamBasicDescription *desc)
1175{
1176 OSStatus ostatus;
1177
1178 pj_assert(strm->streamFormat.mSampleRate != desc->mSampleRate);
1179 pj_assert(NULL == strm->resample);
1180 pj_assert(NULL == strm->resample_buf);
1181
1182 /* Create the audio converter */
1183 ostatus = AudioConverterNew(desc, &strm->streamFormat, &strm->resample);
1184 if (ostatus != noErr) {
1185 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1186 }
Sauw Ming3e7630a2011-01-20 03:32:14 +00001187
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001188 /*
Sauw Ming3e7630a2011-01-20 03:32:14 +00001189 * Allocate the buffer required to hold enough input data
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001190 */
Sauw Ming3e7630a2011-01-20 03:32:14 +00001191 strm->resample_buf_size = (unsigned)(desc->mSampleRate *
1192 strm->param.samples_per_frame /
1193 strm->param.clock_rate);
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001194 strm->resample_buf = (pj_int16_t*)
1195 pj_pool_alloc(strm->pool,
1196 strm->resample_buf_size *
1197 strm->param.bits_per_sample >> 3);
1198 if (!strm->resample_buf)
1199 return PJ_ENOMEM;
1200 strm->resample_buf_count = 0;
1201
1202 return PJ_SUCCESS;
1203}
1204#endif
1205
Sauw Ming55a73cd2010-05-17 12:51:06 +00001206/* Internal: create audio unit for recorder/playback device */
1207static pj_status_t create_audio_unit(AudioComponent io_comp,
1208 AudioDeviceID dev_id,
1209 pjmedia_dir dir,
1210 struct coreaudio_stream *strm,
1211 AudioUnit *io_unit)
1212{
1213 OSStatus ostatus;
Sauw Mingc1f500c2011-03-16 05:44:14 +00001214#if !COREAUDIO_MAC
1215 UInt32 audioCategory = kAudioSessionCategory_PlayAndRecord;
1216 /* We want to be able to open playback and recording streams */
1217 ostatus = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,
1218 sizeof(audioCategory),
1219 &audioCategory);
1220 if (ostatus != kAudioSessionNoError) {
1221 PJ_LOG(4, (THIS_FILE,
1222 "Warning: cannot set the audio session category (%i)",
1223 ostatus));
1224 }
1225#endif
1226
Sauw Ming55a73cd2010-05-17 12:51:06 +00001227 /* Create an audio unit to interface with the device */
1228 ostatus = AudioComponentInstanceNew(io_comp, io_unit);
1229 if (ostatus != noErr) {
1230 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1231 }
1232
1233 /* Set audio unit's properties for capture device */
1234 if (dir & PJMEDIA_DIR_CAPTURE) {
1235 UInt32 enable = 1;
1236
1237 /* Enable input */
1238 ostatus = AudioUnitSetProperty(*io_unit,
1239 kAudioOutputUnitProperty_EnableIO,
1240 kAudioUnitScope_Input,
1241 1,
1242 &enable,
1243 sizeof(enable));
1244 if (ostatus != noErr) {
1245 PJ_LOG(4, (THIS_FILE,
1246 "Warning: cannot enable IO of capture device %d",
1247 dev_id));
1248 }
1249
1250 /* Disable output */
1251 if (!(dir & PJMEDIA_DIR_PLAYBACK)) {
1252 enable = 0;
1253 ostatus = AudioUnitSetProperty(*io_unit,
1254 kAudioOutputUnitProperty_EnableIO,
1255 kAudioUnitScope_Output,
1256 0,
1257 &enable,
1258 sizeof(enable));
1259 if (ostatus != noErr) {
1260 PJ_LOG(4, (THIS_FILE,
1261 "Warning: cannot disable IO of capture device %d",
1262 dev_id));
1263 }
1264 }
1265 }
1266
1267 /* Set audio unit's properties for playback device */
1268 if (dir & PJMEDIA_DIR_PLAYBACK) {
1269 UInt32 enable = 1;
1270
1271 /* Enable output */
1272 ostatus = AudioUnitSetProperty(*io_unit,
1273 kAudioOutputUnitProperty_EnableIO,
1274 kAudioUnitScope_Output,
1275 0,
1276 &enable,
1277 sizeof(enable));
1278 if (ostatus != noErr) {
1279 PJ_LOG(4, (THIS_FILE,
1280 "Warning: cannot enable IO of playback device %d",
1281 dev_id));
1282 }
1283
1284 }
1285
1286#if COREAUDIO_MAC
1287 PJ_LOG(5, (THIS_FILE, "Opening device %d", dev_id));
1288 ostatus = AudioUnitSetProperty(*io_unit,
1289 kAudioOutputUnitProperty_CurrentDevice,
1290 kAudioUnitScope_Global,
1291 0,
1292 &dev_id,
1293 sizeof(dev_id));
1294 if (ostatus != noErr) {
1295 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1296 }
1297#endif
1298
1299 if (dir & PJMEDIA_DIR_CAPTURE) {
1300#if COREAUDIO_MAC
1301 AudioStreamBasicDescription deviceFormat;
1302 UInt32 size;
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001303
1304 /*
1305 * Keep the sample rate from the device, otherwise we will confuse
1306 * AUHAL
1307 */
1308 size = sizeof(AudioStreamBasicDescription);
1309 ostatus = AudioUnitGetProperty(*io_unit,
1310 kAudioUnitProperty_StreamFormat,
1311 kAudioUnitScope_Input,
1312 1,
1313 &deviceFormat,
1314 &size);
1315 if (ostatus != noErr) {
1316 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1317 }
1318 strm->streamFormat.mSampleRate = deviceFormat.mSampleRate;
Sauw Ming55a73cd2010-05-17 12:51:06 +00001319#endif
1320
1321 /* When setting the stream format, we have to make sure the sample
1322 * rate is supported. Setting an unsupported sample rate will cause
1323 * AudioUnitRender() to fail later.
1324 */
1325 ostatus = AudioUnitSetProperty(*io_unit,
1326 kAudioUnitProperty_StreamFormat,
1327 kAudioUnitScope_Output,
1328 1,
1329 &strm->streamFormat,
1330 sizeof(strm->streamFormat));
1331 if (ostatus != noErr) {
1332 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1333 }
1334
1335#if COREAUDIO_MAC
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001336 strm->streamFormat.mSampleRate = strm->param.clock_rate;
Sauw Ming55a73cd2010-05-17 12:51:06 +00001337 size = sizeof(AudioStreamBasicDescription);
1338 ostatus = AudioUnitGetProperty (*io_unit,
1339 kAudioUnitProperty_StreamFormat,
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001340 kAudioUnitScope_Output,
Sauw Ming55a73cd2010-05-17 12:51:06 +00001341 1,
1342 &deviceFormat,
1343 &size);
1344 if (ostatus == noErr) {
1345 if (strm->streamFormat.mSampleRate != deviceFormat.mSampleRate) {
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001346 pj_status_t rc = create_audio_resample(strm, &deviceFormat);
1347 if (PJ_SUCCESS != rc)
1348 return rc;
Sauw Ming55a73cd2010-05-17 12:51:06 +00001349 }
1350 } else {
1351 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1352 }
1353#endif
1354 }
1355
1356 if (dir & PJMEDIA_DIR_PLAYBACK) {
1357 AURenderCallbackStruct output_cb;
1358
1359 /* Set the stream format */
1360 ostatus = AudioUnitSetProperty(*io_unit,
1361 kAudioUnitProperty_StreamFormat,
1362 kAudioUnitScope_Input,
1363 0,
1364 &strm->streamFormat,
1365 sizeof(strm->streamFormat));
1366 if (ostatus != noErr) {
1367 PJ_LOG(4, (THIS_FILE,
1368 "Warning: cannot set playback stream format of dev %d",
1369 dev_id));
1370 }
1371
1372 /* Set render callback */
1373 output_cb.inputProc = output_renderer;
1374 output_cb.inputProcRefCon = strm;
1375 ostatus = AudioUnitSetProperty(*io_unit,
1376 kAudioUnitProperty_SetRenderCallback,
1377 kAudioUnitScope_Input,
1378 0,
1379 &output_cb,
1380 sizeof(output_cb));
1381 if (ostatus != noErr) {
1382 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1383 }
1384
1385 /* Allocate playback buffer */
1386 strm->play_buf = (pj_int16_t*)pj_pool_alloc(strm->pool,
1387 strm->param.samples_per_frame *
1388 strm->param.bits_per_sample >> 3);
1389 if (!strm->play_buf)
1390 return PJ_ENOMEM;
1391 strm->play_buf_count = 0;
1392 }
1393
1394 if (dir & PJMEDIA_DIR_CAPTURE) {
1395 AURenderCallbackStruct input_cb;
1396#if COREAUDIO_MAC
1397 AudioBuffer *ab;
1398 UInt32 size, buf_size;
1399#endif
1400
1401 /* Set input callback */
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001402 input_cb.inputProc = strm->resample ? resample_callback :
1403 input_callback;
Sauw Ming55a73cd2010-05-17 12:51:06 +00001404 input_cb.inputProcRefCon = strm;
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001405 ostatus = AudioUnitSetProperty(
1406 *io_unit,
1407 kAudioOutputUnitProperty_SetInputCallback,
1408 kAudioUnitScope_Global,
1409 0,
1410 &input_cb,
1411 sizeof(input_cb));
Sauw Ming55a73cd2010-05-17 12:51:06 +00001412 if (ostatus != noErr) {
1413 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1414 }
1415
1416#if COREAUDIO_MAC
1417 /* Get device's buffer frame size */
1418 size = sizeof(UInt32);
1419 ostatus = AudioUnitGetProperty(*io_unit,
1420 kAudioDevicePropertyBufferFrameSize,
1421 kAudioUnitScope_Global,
1422 0,
1423 &buf_size,
1424 &size);
1425 if (ostatus != noErr)
1426 {
1427 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1428 }
1429
1430 /* Allocate audio buffer */
1431 strm->audio_buf = (AudioBufferList*)pj_pool_alloc(strm->pool,
1432 sizeof(AudioBufferList) + sizeof(AudioBuffer));
1433 if (!strm->audio_buf)
1434 return PJ_ENOMEM;
1435
1436 strm->audio_buf->mNumberBuffers = 1;
1437 ab = &strm->audio_buf->mBuffers[0];
1438 ab->mNumberChannels = strm->streamFormat.mChannelsPerFrame;
1439 ab->mDataByteSize = buf_size * ab->mNumberChannels *
1440 strm->param.bits_per_sample >> 3;
1441 ab->mData = pj_pool_alloc(strm->pool,
1442 ab->mDataByteSize);
1443 if (!ab->mData)
1444 return PJ_ENOMEM;
1445
1446#else
1447 /* We will let AudioUnitRender() to allocate the buffer
1448 * for us later
1449 */
1450 strm->audio_buf = (AudioBufferList*)pj_pool_alloc(strm->pool,
1451 sizeof(AudioBufferList) + sizeof(AudioBuffer));
1452 if (!strm->audio_buf)
1453 return PJ_ENOMEM;
1454
1455 strm->audio_buf->mNumberBuffers = 1;
1456 strm->audio_buf->mBuffers[0].mNumberChannels =
1457 strm->streamFormat.mChannelsPerFrame;
Sauw Mingc1f500c2011-03-16 05:44:14 +00001458
Sauw Ming55a73cd2010-05-17 12:51:06 +00001459#endif
1460
1461 /* Allocate recording buffer */
1462 strm->rec_buf = (pj_int16_t*)pj_pool_alloc(strm->pool,
1463 strm->param.samples_per_frame *
1464 strm->param.bits_per_sample >> 3);
1465 if (!strm->rec_buf)
1466 return PJ_ENOMEM;
1467 strm->rec_buf_count = 0;
1468 }
1469
1470 /* Initialize the audio unit */
1471 ostatus = AudioUnitInitialize(*io_unit);
1472 if (ostatus != noErr) {
1473 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1474 }
1475
1476 return PJ_SUCCESS;
1477}
1478
1479/* API: create stream */
1480static pj_status_t ca_factory_create_stream(pjmedia_aud_dev_factory *f,
1481 const pjmedia_aud_param *param,
1482 pjmedia_aud_rec_cb rec_cb,
1483 pjmedia_aud_play_cb play_cb,
1484 void *user_data,
1485 pjmedia_aud_stream **p_aud_strm)
1486{
1487 struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
1488 pj_pool_t *pool;
1489 struct coreaudio_stream *strm;
1490 pj_status_t status;
1491
1492 /* Create and Initialize stream descriptor */
1493 pool = pj_pool_create(cf->pf, "coreaudio-dev", 1000, 1000, NULL);
1494 PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
1495
1496 strm = PJ_POOL_ZALLOC_T(pool, struct coreaudio_stream);
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001497 pj_list_init(&strm->list_entry);
1498 strm->list_entry.stream = strm;
Sauw Ming55a73cd2010-05-17 12:51:06 +00001499 strm->cf = cf;
1500 pj_memcpy(&strm->param, param, sizeof(*param));
1501 strm->pool = pool;
1502 strm->rec_cb = rec_cb;
1503 strm->play_cb = play_cb;
1504 strm->user_data = user_data;
1505
1506 /* Set the stream format */
1507 strm->streamFormat.mSampleRate = param->clock_rate;
1508 strm->streamFormat.mFormatID = kAudioFormatLinearPCM;
1509 strm->streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger
1510 | kLinearPCMFormatFlagIsPacked;
1511 strm->streamFormat.mBitsPerChannel = strm->param.bits_per_sample;
1512 strm->streamFormat.mChannelsPerFrame = param->channel_count;
1513 strm->streamFormat.mBytesPerFrame = strm->streamFormat.mChannelsPerFrame
1514 * strm->param.bits_per_sample >> 3;
1515 strm->streamFormat.mFramesPerPacket = 1;
1516 strm->streamFormat.mBytesPerPacket = strm->streamFormat.mBytesPerFrame *
1517 strm->streamFormat.mFramesPerPacket;
1518
1519 /* Apply input/output routes settings before we create the audio units */
1520 if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE) {
1521 ca_stream_set_cap(&strm->base,
1522 PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE,
1523 &param->input_route);
1524 }
1525 if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE) {
1526 ca_stream_set_cap(&strm->base,
1527 PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
1528 &param->output_route);
1529 }
1530 if (param->flags & PJMEDIA_AUD_DEV_CAP_EC) {
1531 ca_stream_set_cap(&strm->base,
1532 PJMEDIA_AUD_DEV_CAP_EC,
1533 &param->ec_enabled);
Sauw Mingaa6fd412011-02-15 05:33:23 +00001534 } else {
1535 pj_bool_t ec = PJ_FALSE;
1536 ca_stream_set_cap(&strm->base,
1537 PJMEDIA_AUD_DEV_CAP_EC, &ec);
Sauw Ming55a73cd2010-05-17 12:51:06 +00001538 }
1539
1540 strm->io_units[0] = strm->io_units[1] = NULL;
1541 if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK &&
1542 param->rec_id == param->play_id)
1543 {
1544 /* If both input and output are on the same device, only create
1545 * one audio unit to interface with the device.
1546 */
1547 status = create_audio_unit(cf->io_comp,
1548 cf->dev_info[param->rec_id].dev_id,
1549 param->dir, strm, &strm->io_units[0]);
1550 if (status != PJ_SUCCESS)
1551 goto on_error;
1552 } else {
1553 unsigned nunits = 0;
1554
1555 if (param->dir & PJMEDIA_DIR_CAPTURE) {
1556 status = create_audio_unit(cf->io_comp,
1557 cf->dev_info[param->rec_id].dev_id,
1558 PJMEDIA_DIR_CAPTURE,
1559 strm, &strm->io_units[nunits++]);
1560 if (status != PJ_SUCCESS)
1561 goto on_error;
1562 }
1563 if (param->dir & PJMEDIA_DIR_PLAYBACK) {
1564
1565 status = create_audio_unit(cf->io_comp,
1566 cf->dev_info[param->play_id].dev_id,
1567 PJMEDIA_DIR_PLAYBACK,
1568 strm, &strm->io_units[nunits++]);
1569 if (status != PJ_SUCCESS)
1570 goto on_error;
1571 }
1572 }
1573
1574 /* Apply the remaining settings */
1575 if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY) {
1576 ca_stream_get_cap(&strm->base,
1577 PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY,
1578 &strm->param.input_latency_ms);
1579 }
1580 if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY) {
1581 ca_stream_get_cap(&strm->base,
1582 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY,
1583 &strm->param.output_latency_ms);
1584 }
1585 if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
1586 ca_stream_set_cap(&strm->base,
1587 PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
1588 &param->output_vol);
1589 }
1590
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001591 pj_mutex_lock(strm->cf->mutex);
1592 pj_assert(pj_list_empty(&strm->list_entry));
1593 pj_list_insert_after(&strm->cf->streams, &strm->list_entry);
1594 pj_mutex_unlock(strm->cf->mutex);
1595
Sauw Ming55a73cd2010-05-17 12:51:06 +00001596 /* Done */
1597 strm->base.op = &stream_op;
1598 *p_aud_strm = &strm->base;
1599
1600 return PJ_SUCCESS;
1601
1602 on_error:
1603 ca_stream_destroy((pjmedia_aud_stream *)strm);
1604 return status;
1605}
1606
1607/* API: Get stream info. */
1608static pj_status_t ca_stream_get_param(pjmedia_aud_stream *s,
1609 pjmedia_aud_param *pi)
1610{
1611 struct coreaudio_stream *strm = (struct coreaudio_stream*)s;
1612
1613 PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
1614
1615 pj_memcpy(pi, &strm->param, sizeof(*pi));
1616
1617 /* Update the device capabilities' values */
1618 if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY,
1619 &pi->input_latency_ms) == PJ_SUCCESS)
1620 {
1621 pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
1622 }
1623 if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY,
1624 &pi->output_latency_ms) == PJ_SUCCESS)
1625 {
1626 pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
1627 }
1628 if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
1629 &pi->output_vol) == PJ_SUCCESS)
1630 {
1631 pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
1632 }
1633 if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE,
1634 &pi->input_route) == PJ_SUCCESS)
1635 {
1636 pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE;
1637 }
1638 if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
1639 &pi->output_route) == PJ_SUCCESS)
1640 {
1641 pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE;
1642 }
1643 if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_EC,
1644 &pi->ec_enabled) == PJ_SUCCESS)
1645 {
1646 pi->flags |= PJMEDIA_AUD_DEV_CAP_EC;
1647 }
1648
1649 return PJ_SUCCESS;
1650}
1651
1652/* API: get capability */
1653static pj_status_t ca_stream_get_cap(pjmedia_aud_stream *s,
1654 pjmedia_aud_dev_cap cap,
1655 void *pval)
1656{
1657 struct coreaudio_stream *strm = (struct coreaudio_stream*)s;
1658
1659 PJ_UNUSED_ARG(strm);
1660
1661 PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
1662
1663 if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY &&
1664 (strm->param.dir & PJMEDIA_DIR_CAPTURE))
1665 {
1666#if COREAUDIO_MAC
1667 UInt32 latency, size = sizeof(UInt32);
1668
1669 /* Recording latency */
1670 if (AudioUnitGetProperty (strm->io_units[0],
1671 kAudioDevicePropertyLatency,
1672 kAudioUnitScope_Input,
1673 1,
1674 &latency,
1675 &size) == noErr)
1676 {
1677 UInt32 latency2;
1678 if (AudioUnitGetProperty (strm->io_units[0],
1679 kAudioDevicePropertyBufferFrameSize,
1680 kAudioUnitScope_Input,
1681 1,
1682 &latency2,
1683 &size) == noErr)
1684 {
1685 strm->param.input_latency_ms = (latency + latency2) * 1000 /
1686 strm->param.clock_rate;
1687 strm->param.input_latency_ms++;
1688 }
1689 }
1690#else
1691 Float32 latency, latency2;
1692 UInt32 size = sizeof(Float32);
1693
1694 if ((AudioSessionGetProperty(
1695 kAudioSessionProperty_CurrentHardwareInputLatency,
1696 &size, &latency) == kAudioSessionNoError) &&
1697 (AudioSessionGetProperty(
1698 kAudioSessionProperty_CurrentHardwareIOBufferDuration,
1699 &size, &latency2) == kAudioSessionNoError))
1700 {
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001701 strm->param.input_latency_ms = (unsigned)
1702 ((latency + latency2) * 1000);
Sauw Ming55a73cd2010-05-17 12:51:06 +00001703 strm->param.input_latency_ms++;
1704 }
1705#endif
1706
1707 *(unsigned*)pval = strm->param.input_latency_ms;
1708 return PJ_SUCCESS;
1709 } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY &&
1710 (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
1711 {
1712#if COREAUDIO_MAC
1713 UInt32 latency, size = sizeof(UInt32);
1714 AudioUnit *io_unit = strm->io_units[1] ? &strm->io_units[1] :
1715 &strm->io_units[0];
1716
1717 /* Playback latency */
1718 if (AudioUnitGetProperty (*io_unit,
1719 kAudioDevicePropertyLatency,
1720 kAudioUnitScope_Output,
1721 0,
1722 &latency,
1723 &size) == noErr)
1724 {
1725 UInt32 latency2;
1726 if (AudioUnitGetProperty (*io_unit,
1727 kAudioDevicePropertyBufferFrameSize,
1728 kAudioUnitScope_Output,
1729 0,
1730 &latency2,
1731 &size) == noErr)
1732 {
1733 strm->param.output_latency_ms = (latency + latency2) * 1000 /
1734 strm->param.clock_rate;
1735 strm->param.output_latency_ms++;
1736 }
1737 }
1738#else
1739 Float32 latency, latency2;
1740 UInt32 size = sizeof(Float32);
1741
1742 if ((AudioSessionGetProperty(
1743 kAudioSessionProperty_CurrentHardwareOutputLatency,
1744 &size, &latency) == kAudioSessionNoError) &&
1745 (AudioSessionGetProperty(
1746 kAudioSessionProperty_CurrentHardwareIOBufferDuration,
1747 &size, &latency2) == kAudioSessionNoError))
1748 {
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001749 strm->param.output_latency_ms = (unsigned)
1750 ((latency + latency2) * 1000);
Sauw Ming55a73cd2010-05-17 12:51:06 +00001751 strm->param.output_latency_ms++;
1752 }
1753#endif
1754 *(unsigned*)pval = (++strm->param.output_latency_ms * 2);
1755 return PJ_SUCCESS;
1756 } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
1757 (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
1758 {
1759 OSStatus ostatus;
1760 Float32 volume;
1761 UInt32 size = sizeof(Float32);
1762
1763 /* Output volume setting */
1764#if COREAUDIO_MAC
1765 ostatus = AudioUnitGetProperty (strm->io_units[1] ? strm->io_units[1] :
1766 strm->io_units[0],
1767 kAudioDevicePropertyVolumeScalar,
1768 kAudioUnitScope_Output,
1769 0,
1770 &volume,
1771 &size);
1772 if (ostatus != noErr)
1773 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1774#else
1775 ostatus = AudioSessionGetProperty(
1776 kAudioSessionProperty_CurrentHardwareOutputVolume,
1777 &size, &volume);
1778 if (ostatus != kAudioSessionNoError) {
1779 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1780 }
1781#endif
1782
1783 *(unsigned*)pval = (unsigned)(volume * 100);
1784 return PJ_SUCCESS;
1785#if !COREAUDIO_MAC
1786 } else if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE &&
1787 (strm->param.dir & PJMEDIA_DIR_CAPTURE))
1788 {
1789 UInt32 btooth, size = sizeof(UInt32);
1790 OSStatus ostatus;
1791
1792 ostatus = AudioSessionGetProperty (
1793 kAudioSessionProperty_OverrideCategoryEnableBluetoothInput,
1794 &size, &btooth);
1795 if (ostatus != kAudioSessionNoError) {
1796 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1797 }
1798
1799 *(pjmedia_aud_dev_route*)pval = btooth?
1800 PJMEDIA_AUD_DEV_ROUTE_BLUETOOTH:
1801 PJMEDIA_AUD_DEV_ROUTE_DEFAULT;
1802 return PJ_SUCCESS;
1803 } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE &&
1804 (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
1805 {
1806 CFStringRef route;
1807 UInt32 size = sizeof(CFStringRef);
1808 OSStatus ostatus;
1809
1810 ostatus = AudioSessionGetProperty (kAudioSessionProperty_AudioRoute,
1811 &size, &route);
1812 if (ostatus != kAudioSessionNoError) {
1813 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1814 }
1815
1816 if (!route) {
1817 *(pjmedia_aud_dev_route*)pval = PJMEDIA_AUD_DEV_ROUTE_DEFAULT;
1818 } else if (CFStringHasPrefix(route, CFSTR("Headset"))) {
1819 *(pjmedia_aud_dev_route*)pval = PJMEDIA_AUD_DEV_ROUTE_EARPIECE;
1820 } else {
1821 *(pjmedia_aud_dev_route*)pval = PJMEDIA_AUD_DEV_ROUTE_DEFAULT;
1822 }
1823
Sauw Ming5c884042010-10-10 15:51:08 +00001824 CFRelease(route);
1825
Sauw Ming55a73cd2010-05-17 12:51:06 +00001826 return PJ_SUCCESS;
1827 } else if (cap==PJMEDIA_AUD_DEV_CAP_EC) {
1828 AudioComponentDescription desc;
1829 OSStatus ostatus;
1830
1831 ostatus = AudioComponentGetDescription(strm->cf->io_comp, &desc);
1832 if (ostatus != noErr) {
1833 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1834 }
1835
1836 *(pj_bool_t*)pval = (desc.componentSubType ==
1837 kAudioUnitSubType_VoiceProcessingIO);
1838 return PJ_SUCCESS;
1839#endif
1840 } else {
1841 return PJMEDIA_EAUD_INVCAP;
1842 }
1843}
1844
1845/* API: set capability */
1846static pj_status_t ca_stream_set_cap(pjmedia_aud_stream *s,
1847 pjmedia_aud_dev_cap cap,
1848 const void *pval)
1849{
1850 struct coreaudio_stream *strm = (struct coreaudio_stream*)s;
1851
Sauw Ming55a73cd2010-05-17 12:51:06 +00001852 PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
1853
1854#if COREAUDIO_MAC
1855 if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
1856 (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
1857 {
1858 OSStatus ostatus;
1859 Float32 volume = *(unsigned*)pval;
1860
1861 /* Output volume setting */
1862 volume /= 100.0;
1863 ostatus = AudioUnitSetProperty (strm->io_units[1] ? strm->io_units[1] :
1864 strm->io_units[0],
1865 kAudioDevicePropertyVolumeScalar,
1866 kAudioUnitScope_Output,
1867 0,
1868 &volume,
1869 sizeof(Float32));
1870 if (ostatus != noErr) {
1871 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1872 }
1873 strm->param.output_vol = *(unsigned*)pval;
1874 return PJ_SUCCESS;
1875 }
Sauw Ming55a73cd2010-05-17 12:51:06 +00001876
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001877#else
1878
1879 if ((cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY &&
1880 (strm->param.dir & PJMEDIA_DIR_CAPTURE)) ||
1881 (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY &&
1882 (strm->param.dir & PJMEDIA_DIR_PLAYBACK)))
1883 {
1884 Float32 bufferDuration = *(unsigned *)pval;
1885 OSStatus ostatus;
1886 unsigned latency;
1887
1888 /* For low-latency audio streaming, you can set this value to
1889 * as low as 5 ms (the default is 23ms). However, lowering the
1890 * latency may cause a decrease in audio quality.
1891 */
1892 bufferDuration /= 1000;
1893 ostatus = AudioSessionSetProperty(
1894 kAudioSessionProperty_PreferredHardwareIOBufferDuration,
1895 sizeof(bufferDuration), &bufferDuration);
1896 if (ostatus != kAudioSessionNoError) {
1897 PJ_LOG(4, (THIS_FILE,
1898 "Error: cannot set the preferred buffer duration (%i)",
1899 ostatus));
1900 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1901 }
1902
1903 ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY, &latency);
1904 ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY, &latency);
1905
1906 return PJ_SUCCESS;
1907 } else if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE &&
Sauw Ming55a73cd2010-05-17 12:51:06 +00001908 (strm->param.dir & PJMEDIA_DIR_CAPTURE))
1909 {
1910 UInt32 btooth = *(pjmedia_aud_dev_route*)pval ==
1911 PJMEDIA_AUD_DEV_ROUTE_BLUETOOTH ? 1 : 0;
1912 OSStatus ostatus;
1913
1914 ostatus = AudioSessionSetProperty (
1915 kAudioSessionProperty_OverrideCategoryEnableBluetoothInput,
1916 sizeof(btooth), &btooth);
1917 if (ostatus != kAudioSessionNoError) {
1918 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1919 }
1920 strm->param.input_route = *(pjmedia_aud_dev_route*)pval;
1921 return PJ_SUCCESS;
1922 } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE &&
1923 (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
1924 {
1925 OSStatus ostatus;
1926 UInt32 route = *(pjmedia_aud_dev_route*)pval ==
1927 PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER ?
1928 kAudioSessionOverrideAudioRoute_Speaker :
1929 kAudioSessionOverrideAudioRoute_None;
1930
1931 ostatus = AudioSessionSetProperty (
1932 kAudioSessionProperty_OverrideAudioRoute,
1933 sizeof(route), &route);
1934 if (ostatus != kAudioSessionNoError) {
1935 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1936 }
1937 strm->param.output_route = *(pjmedia_aud_dev_route*)pval;
1938 return PJ_SUCCESS;
1939 } else if (cap==PJMEDIA_AUD_DEV_CAP_EC) {
1940 AudioComponentDescription desc;
1941 AudioComponent io_comp;
1942
1943 desc.componentType = kAudioUnitType_Output;
1944 desc.componentSubType = (*(pj_bool_t*)pval)?
1945 kAudioUnitSubType_VoiceProcessingIO :
1946 kAudioUnitSubType_RemoteIO;
1947 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
1948 desc.componentFlags = 0;
1949 desc.componentFlagsMask = 0;
1950
1951 io_comp = AudioComponentFindNext(NULL, &desc);
1952 if (io_comp == NULL)
1953 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(-1);
1954 strm->cf->io_comp = io_comp;
1955 strm->param.ec_enabled = *(pj_bool_t*)pval;
1956
1957 return PJ_SUCCESS;
1958 }
1959#endif
1960
1961 return PJMEDIA_EAUD_INVCAP;
1962}
1963
1964/* API: Start stream. */
1965static pj_status_t ca_stream_start(pjmedia_aud_stream *strm)
1966{
1967 struct coreaudio_stream *stream = (struct coreaudio_stream*)strm;
1968 OSStatus ostatus;
1969 UInt32 i;
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001970
1971 if (stream->running)
1972 return PJ_SUCCESS;
Sauw Ming55a73cd2010-05-17 12:51:06 +00001973
1974 stream->quit_flag = 0;
Sauw Ming55a73cd2010-05-17 12:51:06 +00001975 stream->interrupted = PJ_FALSE;
Sauw Ming4a59e2a2010-12-17 07:10:13 +00001976 stream->rec_buf_count = 0;
1977 stream->play_buf_count = 0;
1978 stream->resample_buf_count = 0;
1979
1980 if (stream->resample) {
1981 ostatus = AudioConverterReset(stream->resample);
1982 if (ostatus != noErr)
1983 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1984 }
Sauw Ming55a73cd2010-05-17 12:51:06 +00001985
Sauw Mingaa6fd412011-02-15 05:33:23 +00001986#if !COREAUDIO_MAC
1987 AudioSessionSetActive(true);
1988#endif
1989
Sauw Ming55a73cd2010-05-17 12:51:06 +00001990 for (i = 0; i < 2; i++) {
1991 if (stream->io_units[i] == NULL) break;
1992 ostatus = AudioOutputUnitStart(stream->io_units[i]);
1993 if (ostatus != noErr) {
1994 if (i == 1)
1995 AudioOutputUnitStop(stream->io_units[0]);
1996 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1997 }
1998 }
1999
Sauw Ming4a59e2a2010-12-17 07:10:13 +00002000 stream->running = PJ_TRUE;
Sauw Ming55a73cd2010-05-17 12:51:06 +00002001
2002 PJ_LOG(4, (THIS_FILE, "core audio stream started"));
2003
2004 return PJ_SUCCESS;
2005}
2006
2007/* API: Stop stream. */
2008static pj_status_t ca_stream_stop(pjmedia_aud_stream *strm)
2009{
2010 struct coreaudio_stream *stream = (struct coreaudio_stream*)strm;
2011 OSStatus ostatus;
2012 unsigned i;
Sauw Ming4a59e2a2010-12-17 07:10:13 +00002013 int should_deactivate;
2014 struct stream_list *it, *itBegin;
Sauw Ming55a73cd2010-05-17 12:51:06 +00002015
Sauw Ming4a59e2a2010-12-17 07:10:13 +00002016 if (!stream->running)
2017 return PJ_SUCCESS;
Sauw Ming55a73cd2010-05-17 12:51:06 +00002018
2019 for (i = 0; i < 2; i++) {
2020 if (stream->io_units[i] == NULL) break;
2021 ostatus = AudioOutputUnitStop(stream->io_units[i]);
2022 if (ostatus != noErr) {
2023 if (i == 0 && stream->io_units[1])
2024 AudioOutputUnitStop(stream->io_units[1]);
2025 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
2026 }
2027 }
Sauw Ming4a59e2a2010-12-17 07:10:13 +00002028
Sauw Mingaa6fd412011-02-15 05:33:23 +00002029 /* Check whether we need to deactivate the audio session. */
Sauw Ming4a59e2a2010-12-17 07:10:13 +00002030 pj_mutex_lock(stream->cf->mutex);
2031 pj_assert(!pj_list_empty(&stream->cf->streams));
2032 pj_assert(!pj_list_empty(&stream->list_entry));
2033 stream->running = PJ_FALSE;
2034 should_deactivate = PJ_TRUE;
2035 itBegin = &stream->cf->streams;
2036 for (it = itBegin->next; it != itBegin; it = it->next) {
2037 if (it->stream->running) {
2038 should_deactivate = PJ_FALSE;
2039 break;
2040 }
2041 }
2042 pj_mutex_unlock(stream->cf->mutex);
2043
2044#if !COREAUDIO_MAC
2045 if (should_deactivate)
2046 AudioSessionSetActive(false);
2047#endif
2048
2049 stream->quit_flag = 1;
Sauw Ming55a73cd2010-05-17 12:51:06 +00002050 stream->play_thread_initialized = 0;
2051 stream->rec_thread_initialized = 0;
Sauw Ming4a59e2a2010-12-17 07:10:13 +00002052 pj_bzero(stream->rec_thread_desc, sizeof(pj_thread_desc));
2053 pj_bzero(stream->play_thread_desc, sizeof(pj_thread_desc));
Sauw Ming55a73cd2010-05-17 12:51:06 +00002054
2055 PJ_LOG(4, (THIS_FILE, "core audio stream stopped"));
2056
2057 return PJ_SUCCESS;
2058}
2059
2060
2061/* API: Destroy stream. */
2062static pj_status_t ca_stream_destroy(pjmedia_aud_stream *strm)
2063{
2064 struct coreaudio_stream *stream = (struct coreaudio_stream*)strm;
2065 unsigned i;
2066
2067 PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
2068
Sauw Ming55a73cd2010-05-17 12:51:06 +00002069 ca_stream_stop(strm);
2070
2071 for (i = 0; i < 2; i++) {
2072 if (stream->io_units[i]) {
2073 AudioUnitUninitialize(stream->io_units[i]);
2074 AudioComponentInstanceDispose(stream->io_units[i]);
2075 stream->io_units[i] = NULL;
2076 }
2077 }
2078
Sauw Ming4a59e2a2010-12-17 07:10:13 +00002079 if (stream->resample)
2080 AudioConverterDispose(stream->resample);
2081
2082 pj_mutex_lock(stream->cf->mutex);
2083 if (!pj_list_empty(&stream->list_entry))
2084 pj_list_erase(&stream->list_entry);
2085 pj_mutex_unlock(stream->cf->mutex);
2086
Sauw Ming55a73cd2010-05-17 12:51:06 +00002087 pj_pool_release(stream->pool);
2088
2089 return PJ_SUCCESS;
2090}
2091
2092#endif /* PJMEDIA_AUDIO_DEV_HAS_COREAUDIO */