blob: b22068245a6622ed222b7d9a6bdf2da6d8a9975c [file] [log] [blame]
Alexandre Lision94f06ba2013-12-09 16:28:33 -05001/* $Id$ */
Tristan Matthews0a329cc2013-07-17 13:20:14 -04002/*
3 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4 *
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>
34#include <AudioToolbox/AudioConverter.h>
35#if COREAUDIO_MAC
36 #include <CoreAudio/CoreAudio.h>
37#else
Alexandre Lision8af73cb2013-12-10 14:11:20 -050038 #include <AVFoundation/AVAudioSession.h>
Tristan Matthews0a329cc2013-07-17 13:20:14 -040039
40 #define AudioDeviceID unsigned
41
42 /**
43 * As in iOS SDK 4 or later, audio route change property listener is
44 * no longer necessary. Just make surethat your application can receive
45 * remote control events by adding the code:
46 * [[UIApplication sharedApplication]
47 * beginReceivingRemoteControlEvents];
48 * Otherwise audio route change (such as headset plug/unplug) will not be
49 * processed while your application is in the background mode.
50 */
51 #define USE_AUDIO_ROUTE_CHANGE_PROP_LISTENER 0
52
Alexandre Lision94f06ba2013-12-09 16:28:33 -050053 /* Starting iOS SDK 7, Audio Session API is deprecated. */
Alexandre Lision8af73cb2013-12-10 14:11:20 -050054 #define USE_AUDIO_SESSION_API 0
Tristan Matthews0a329cc2013-07-17 13:20:14 -040055#endif
56
57/* For Mac OS 10.5.x and earlier */
58#if AUDIO_UNIT_VERSION < 1060
59 #define AudioComponent Component
60 #define AudioComponentDescription ComponentDescription
61 #define AudioComponentInstance ComponentInstance
62 #define AudioComponentFindNext FindNextComponent
63 #define AudioComponentInstanceNew OpenAComponent
64 #define AudioComponentInstanceDispose CloseComponent
65#endif
66
67
68#define THIS_FILE "coreaudio_dev.c"
69
70/* coreaudio device info */
71struct coreaudio_dev_info
72{
73 pjmedia_aud_dev_info info;
74 AudioDeviceID dev_id;
75};
76
77/* linked list of streams */
78struct stream_list
79{
80 PJ_DECL_LIST_MEMBER(struct stream_list);
81 struct coreaudio_stream *stream;
82};
83
84/* coreaudio factory */
85struct coreaudio_factory
86{
87 pjmedia_aud_dev_factory base;
88 pj_pool_t *base_pool;
89 pj_pool_t *pool;
90 pj_pool_factory *pf;
91 pj_mutex_t *mutex;
92
93 unsigned dev_count;
94 struct coreaudio_dev_info *dev_info;
95
96 AudioComponent io_comp;
97 struct stream_list streams;
98};
99
100/* Sound stream. */
101struct coreaudio_stream
102{
103 pjmedia_aud_stream base; /**< Base stream */
104 pjmedia_aud_param param; /**< Settings */
105 pj_pool_t *pool; /**< Memory pool. */
106 struct coreaudio_factory *cf;
107 struct stream_list list_entry;
108
109 pjmedia_aud_rec_cb rec_cb; /**< Capture callback. */
110 pjmedia_aud_play_cb play_cb; /**< Playback callback. */
111 void *user_data; /**< Application data. */
112
113 pj_timestamp play_timestamp;
114 pj_timestamp rec_timestamp;
115
116 pj_int16_t *rec_buf;
117 unsigned rec_buf_count;
118 pj_int16_t *play_buf;
119 unsigned play_buf_count;
120
121 pj_bool_t interrupted;
122 pj_bool_t quit_flag;
123 pj_bool_t running;
124
125 pj_bool_t rec_thread_initialized;
126 pj_thread_desc rec_thread_desc;
127 pj_thread_t *rec_thread;
128
129 pj_bool_t play_thread_initialized;
130 pj_thread_desc play_thread_desc;
131 pj_thread_t *play_thread;
132
133 AudioUnit io_units[2];
134 AudioStreamBasicDescription streamFormat;
135 AudioBufferList *audio_buf;
136
137 AudioConverterRef resample;
138 pj_int16_t *resample_buf;
139 void *resample_buf_ptr;
140 unsigned resample_buf_count;
141 unsigned resample_buf_size;
Alexandre Lision8af73cb2013-12-10 14:11:20 -0500142
143#if !COREAUDIO_MAC
144 AVAudioSession *sess;
145#endif
Tristan Matthews0a329cc2013-07-17 13:20:14 -0400146};
147
148/* Static variable */
149static struct coreaudio_factory *cf_instance = NULL;
150
151/* Prototypes */
152static pj_status_t ca_factory_init(pjmedia_aud_dev_factory *f);
153static pj_status_t ca_factory_destroy(pjmedia_aud_dev_factory *f);
154static pj_status_t ca_factory_refresh(pjmedia_aud_dev_factory *f);
155static unsigned ca_factory_get_dev_count(pjmedia_aud_dev_factory *f);
156static pj_status_t ca_factory_get_dev_info(pjmedia_aud_dev_factory *f,
157 unsigned index,
158 pjmedia_aud_dev_info *info);
159static pj_status_t ca_factory_default_param(pjmedia_aud_dev_factory *f,
160 unsigned index,
161 pjmedia_aud_param *param);
162static pj_status_t ca_factory_create_stream(pjmedia_aud_dev_factory *f,
163 const pjmedia_aud_param *param,
164 pjmedia_aud_rec_cb rec_cb,
165 pjmedia_aud_play_cb play_cb,
166 void *user_data,
167 pjmedia_aud_stream **p_aud_strm);
168
169static pj_status_t ca_stream_get_param(pjmedia_aud_stream *strm,
170 pjmedia_aud_param *param);
171static pj_status_t ca_stream_get_cap(pjmedia_aud_stream *strm,
172 pjmedia_aud_dev_cap cap,
173 void *value);
174static pj_status_t ca_stream_set_cap(pjmedia_aud_stream *strm,
175 pjmedia_aud_dev_cap cap,
176 const void *value);
177static pj_status_t ca_stream_start(pjmedia_aud_stream *strm);
178static pj_status_t ca_stream_stop(pjmedia_aud_stream *strm);
179static pj_status_t ca_stream_destroy(pjmedia_aud_stream *strm);
180static pj_status_t create_audio_unit(AudioComponent io_comp,
181 AudioDeviceID dev_id,
182 pjmedia_dir dir,
183 struct coreaudio_stream *strm,
184 AudioUnit *io_unit);
Alexandre Lision94f06ba2013-12-09 16:28:33 -0500185#if !COREAUDIO_MAC && USE_AUDIO_SESSION_API != 0
Tristan Matthews0a329cc2013-07-17 13:20:14 -0400186static void interruptionListener(void *inClientData, UInt32 inInterruption);
187static void propListener(void * inClientData,
188 AudioSessionPropertyID inID,
189 UInt32 inDataSize,
190 const void * inData);
191#endif
192
193/* Operations */
194static pjmedia_aud_dev_factory_op factory_op =
195{
196 &ca_factory_init,
197 &ca_factory_destroy,
198 &ca_factory_get_dev_count,
199 &ca_factory_get_dev_info,
200 &ca_factory_default_param,
201 &ca_factory_create_stream,
202 &ca_factory_refresh
203};
204
205static pjmedia_aud_stream_op stream_op =
206{
207 &ca_stream_get_param,
208 &ca_stream_get_cap,
209 &ca_stream_set_cap,
210 &ca_stream_start,
211 &ca_stream_stop,
212 &ca_stream_destroy
213};
214
215
216/****************************************************************************
217 * Factory operations
218 */
219/*
220 * Init coreaudio audio driver.
221 */
222pjmedia_aud_dev_factory* pjmedia_coreaudio_factory(pj_pool_factory *pf)
223{
224 struct coreaudio_factory *f;
225 pj_pool_t *pool;
226
227 pool = pj_pool_create(pf, "core audio base", 1000, 1000, NULL);
228 f = PJ_POOL_ZALLOC_T(pool, struct coreaudio_factory);
229 f->pf = pf;
230 f->base_pool = pool;
231 f->base.op = &factory_op;
232
233 return &f->base;
234}
235
236
237/* API: init factory */
238static pj_status_t ca_factory_init(pjmedia_aud_dev_factory *f)
239{
240 struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
241 AudioComponentDescription desc;
242 pj_status_t status;
243#if !COREAUDIO_MAC
244 unsigned i;
Tristan Matthews0a329cc2013-07-17 13:20:14 -0400245#endif
246
247 pj_list_init(&cf->streams);
248 status = pj_mutex_create_recursive(cf->base_pool,
249 "coreaudio",
250 &cf->mutex);
251 if (status != PJ_SUCCESS)
252 return status;
253
254 desc.componentType = kAudioUnitType_Output;
255#if COREAUDIO_MAC
256 desc.componentSubType = kAudioUnitSubType_HALOutput;
257#else
258 desc.componentSubType = kAudioUnitSubType_RemoteIO;
259#endif
260 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
261 desc.componentFlags = 0;
262 desc.componentFlagsMask = 0;
263
264 cf->io_comp = AudioComponentFindNext(NULL, &desc);
265 if (cf->io_comp == NULL)
266 return PJMEDIA_EAUD_INIT; // cannot find IO unit;
267
268 status = ca_factory_refresh(f);
269 if (status != PJ_SUCCESS)
270 return status;
271
272#if !COREAUDIO_MAC
273 cf->pool = pj_pool_create(cf->pf, "core audio", 1000, 1000, NULL);
274 cf->dev_count = 1;
275 cf->dev_info = (struct coreaudio_dev_info*)
276 pj_pool_calloc(cf->pool, cf->dev_count,
277 sizeof(struct coreaudio_dev_info));
278 for (i = 0; i < cf->dev_count; i++) {
279 struct coreaudio_dev_info *cdi;
280
281 cdi = &cf->dev_info[i];
282 pj_bzero(cdi, sizeof(*cdi));
283 cdi->dev_id = 0;
284 strcpy(cdi->info.name, "iPhone IO device");
285 strcpy(cdi->info.driver, "apple");
286 cdi->info.input_count = 1;
287 cdi->info.output_count = 1;
288 cdi->info.default_samples_per_sec = 8000;
289
290 /* Set the device capabilities here */
291 cdi->info.caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
292 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY |
293 PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING |
Alexandre Lision8af73cb2013-12-10 14:11:20 -0500294#if USE_AUDIO_SESSION_API != 0
Tristan Matthews0a329cc2013-07-17 13:20:14 -0400295 PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE |
296 PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE |
Alexandre Lision8af73cb2013-12-10 14:11:20 -0500297#endif
Tristan Matthews0a329cc2013-07-17 13:20:14 -0400298 PJMEDIA_AUD_DEV_CAP_EC;
299 cdi->info.routes = PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER |
300 PJMEDIA_AUD_DEV_ROUTE_EARPIECE |
301 PJMEDIA_AUD_DEV_ROUTE_BLUETOOTH;
302
303 PJ_LOG(4, (THIS_FILE, " dev_id %d: %s (in=%d, out=%d) %dHz",
304 i,
305 cdi->info.name,
306 cdi->info.input_count,
307 cdi->info.output_count,
308 cdi->info.default_samples_per_sec));
309 }
310
Alexandre Lision94f06ba2013-12-09 16:28:33 -0500311#if USE_AUDIO_SESSION_API != 0
312 {
313 OSStatus ostatus;
Tristan Matthews0a329cc2013-07-17 13:20:14 -0400314
Alexandre Lision94f06ba2013-12-09 16:28:33 -0500315 /* Initialize the Audio Session */
316 ostatus = AudioSessionInitialize(NULL, NULL, interruptionListener,
317 NULL);
318 if (ostatus != kAudioSessionNoError) {
319 PJ_LOG(4, (THIS_FILE,
320 "Warning: cannot initialize audio session services (%i)",
321 ostatus));
322 }
323
324 /* Listen for audio routing change notifications. */
Tristan Matthews0a329cc2013-07-17 13:20:14 -0400325#if USE_AUDIO_ROUTE_CHANGE_PROP_LISTENER != 0
Alexandre Lision94f06ba2013-12-09 16:28:33 -0500326 ostatus = AudioSessionAddPropertyListener(
327 kAudioSessionProperty_AudioRouteChange,
328 propListener, cf);
329 if (ostatus != kAudioSessionNoError) {
330 PJ_LOG(4, (THIS_FILE,
331 "Warning: cannot listen for audio route change "
332 "notifications (%i)", ostatus));
333 }
334#endif
Tristan Matthews0a329cc2013-07-17 13:20:14 -0400335 }
336#endif
337
338 cf_instance = cf;
339#endif
340
341 PJ_LOG(4, (THIS_FILE, "core audio initialized"));
342
343 return PJ_SUCCESS;
344}
345
346/* API: destroy factory */
347static pj_status_t ca_factory_destroy(pjmedia_aud_dev_factory *f)
348{
349 struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
350 pj_pool_t *pool;
351
352 pj_assert(cf);
353 pj_assert(cf->base_pool);
354 pj_assert(pj_list_empty(&cf->streams));
355
356#if !COREAUDIO_MAC
Alexandre Lision94f06ba2013-12-09 16:28:33 -0500357#if USE_AUDIO_SESSION_API != 0 && USE_AUDIO_ROUTE_CHANGE_PROP_LISTENER != 0
Tristan Matthews0a329cc2013-07-17 13:20:14 -0400358 AudioSessionRemovePropertyListenerWithUserData(
359 kAudioSessionProperty_AudioRouteChange, propListener, cf);
360#endif
361#endif
362
363 if (cf->pool) {
364 pj_pool_release(cf->pool);
365 cf->pool = NULL;
366 }
367
368 if (cf->mutex) {
369 pj_mutex_lock(cf->mutex);
370 cf_instance = NULL;
371 pj_mutex_unlock(cf->mutex);
372 pj_mutex_destroy(cf->mutex);
373 cf->mutex = NULL;
374 }
375
376 pool = cf->base_pool;
377 cf->base_pool = NULL;
378 pj_pool_release(pool);
379
380 return PJ_SUCCESS;
381}
382
383/* API: refresh the device list */
384static pj_status_t ca_factory_refresh(pjmedia_aud_dev_factory *f)
385{
386#if !COREAUDIO_MAC
387 /* iPhone doesn't support refreshing the device list */
388 PJ_UNUSED_ARG(f);
389 return PJ_SUCCESS;
390#else
391 struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
392 unsigned i;
393 unsigned dev_count;
394 AudioObjectPropertyAddress addr;
395 AudioDeviceID *dev_ids;
396 UInt32 buf_size, dev_size, size = sizeof(AudioDeviceID);
397 AudioBufferList *buf = NULL;
398 OSStatus ostatus;
399
400 if (cf->pool != NULL) {
401 pj_pool_release(cf->pool);
402 cf->pool = NULL;
403 }
404
405 cf->dev_count = 0;
406 cf->pool = pj_pool_create(cf->pf, "core audio", 1000, 1000, NULL);
407
408 /* Find out how many audio devices there are */
409 addr.mSelector = kAudioHardwarePropertyDevices;
410 addr.mScope = kAudioObjectPropertyScopeGlobal;
411 addr.mElement = kAudioObjectPropertyElementMaster;
412 ostatus = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr,
413 0, NULL, &dev_size);
414 if (ostatus != noErr) {
415 dev_size = 0;
416 }
417
418 /* Calculate the number of audio devices available */
419 dev_count = dev_size / size;
420 if (dev_count==0) {
421 PJ_LOG(4,(THIS_FILE, "core audio found no sound devices"));
422 /* Enabling this will cause pjsua-lib initialization to fail when
423 * there is no sound device installed in the system, even when pjsua
424 * has been run with --null-audio. Moreover, it might be better to
425 * think that the core audio backend initialization is successful,
426 * regardless there is no audio device installed, as later application
427 * can check it using get_dev_count().
428 return PJMEDIA_EAUD_NODEV;
429 */
430 return PJ_SUCCESS;
431 }
432 PJ_LOG(4, (THIS_FILE, "core audio detected %d devices",
433 dev_count));
434
435 /* Get all the audio device IDs */
436 dev_ids = (AudioDeviceID *)pj_pool_calloc(cf->pool, dev_size, size);
437 if (!dev_ids)
438 return PJ_ENOMEM;
439 pj_bzero(dev_ids, dev_count);
440 ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr,
441 0, NULL,
442 &dev_size, (void *)dev_ids);
443 if (ostatus != noErr ) {
444 /* This should not happen since we have successfully retrieved
445 * the property data size before
446 */
447 return PJMEDIA_EAUD_INIT;
448 }
449
450 if (dev_size > 1) {
451 AudioDeviceID dev_id = kAudioObjectUnknown;
452 unsigned idx = 0;
453
454 /* Find default audio input device */
455 addr.mSelector = kAudioHardwarePropertyDefaultInputDevice;
456 addr.mScope = kAudioObjectPropertyScopeGlobal;
457 addr.mElement = kAudioObjectPropertyElementMaster;
458 size = sizeof(dev_id);
459
460 ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject,
461 &addr, 0, NULL,
462 &size, (void *)&dev_id);
463 if (ostatus == noErr && dev_id != dev_ids[idx]) {
464 AudioDeviceID temp_id = dev_ids[idx];
465
466 for (i = idx + 1; i < dev_size; i++) {
467 if (dev_ids[i] == dev_id) {
468 dev_ids[idx++] = dev_id;
469 dev_ids[i] = temp_id;
470 break;
471 }
472 }
473 }
474
475 /* Find default audio output device */
476 addr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
477 ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject,
478 &addr, 0, NULL,
479 &size, (void *)&dev_id);
480 if (ostatus == noErr && dev_id != dev_ids[idx]) {
481 AudioDeviceID temp_id = dev_ids[idx];
482
483 for (i = idx + 1; i < dev_size; i++) {
484 if (dev_ids[i] == dev_id) {
485 dev_ids[idx] = dev_id;
486 dev_ids[i] = temp_id;
487 break;
488 }
489 }
490 }
491 }
492
493 /* Build the devices' info */
494 cf->dev_info = (struct coreaudio_dev_info*)
495 pj_pool_calloc(cf->pool, dev_count,
496 sizeof(struct coreaudio_dev_info));
497 buf_size = 0;
498 for (i = 0; i < dev_count; i++) {
499 struct coreaudio_dev_info *cdi;
500 Float64 sampleRate;
501
502 cdi = &cf->dev_info[i];
503 pj_bzero(cdi, sizeof(*cdi));
504 cdi->dev_id = dev_ids[i];
505
506 /* Get device name */
507 addr.mSelector = kAudioDevicePropertyDeviceName;
508 addr.mScope = kAudioObjectPropertyScopeGlobal;
509 addr.mElement = kAudioObjectPropertyElementMaster;
510 size = sizeof(cdi->info.name);
511 AudioObjectGetPropertyData(cdi->dev_id, &addr,
512 0, NULL,
513 &size, (void *)cdi->info.name);
514
515 strcpy(cdi->info.driver, "core audio");
516
517 /* Get the number of input channels */
518 addr.mSelector = kAudioDevicePropertyStreamConfiguration;
519 addr.mScope = kAudioDevicePropertyScopeInput;
520 size = 0;
521 ostatus = AudioObjectGetPropertyDataSize(cdi->dev_id, &addr,
522 0, NULL, &size);
523 if (ostatus == noErr && size > 0) {
524
525 if (size > buf_size) {
526 buf = pj_pool_alloc(cf->pool, size);
527 buf_size = size;
528 }
529 if (buf) {
530 UInt32 idx;
531
532 /* Get the input stream configuration */
533 ostatus = AudioObjectGetPropertyData(cdi->dev_id, &addr,
534 0, NULL,
535 &size, buf);
536 if (ostatus == noErr) {
537 /* Count the total number of input channels in
538 * the stream
539 */
540 for (idx = 0; idx < buf->mNumberBuffers; idx++) {
541 cdi->info.input_count +=
542 buf->mBuffers[idx].mNumberChannels;
543 }
544 }
545 }
546 }
547
548 /* Get the number of output channels */
549 addr.mScope = kAudioDevicePropertyScopeOutput;
550 size = 0;
551 ostatus = AudioObjectGetPropertyDataSize(cdi->dev_id, &addr,
552 0, NULL, &size);
553 if (ostatus == noErr && size > 0) {
554
555 if (size > buf_size) {
556 buf = pj_pool_alloc(cf->pool, size);
557 buf_size = size;
558 }
559 if (buf) {
560 UInt32 idx;
561
562 /* Get the output stream configuration */
563 ostatus = AudioObjectGetPropertyData(cdi->dev_id, &addr,
564 0, NULL,
565 &size, buf);
566 if (ostatus == noErr) {
567 /* Count the total number of output channels in
568 * the stream
569 */
570 for (idx = 0; idx < buf->mNumberBuffers; idx++) {
571 cdi->info.output_count +=
572 buf->mBuffers[idx].mNumberChannels;
573 }
574 }
575 }
576 }
577
578 /* Get default sample rate */
579 addr.mSelector = kAudioDevicePropertyNominalSampleRate;
580 addr.mScope = kAudioObjectPropertyScopeGlobal;
581 size = sizeof(Float64);
582 ostatus = AudioObjectGetPropertyData (cdi->dev_id, &addr,
583 0, NULL,
584 &size, &sampleRate);
585 cdi->info.default_samples_per_sec = (ostatus == noErr ?
586 sampleRate:
587 16000);
588
589 /* Set device capabilities here */
590 if (cdi->info.input_count > 0) {
591 cdi->info.caps |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
592 }
593 if (cdi->info.output_count > 0) {
594 cdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
595 addr.mSelector = kAudioDevicePropertyVolumeScalar;
596 addr.mScope = kAudioDevicePropertyScopeOutput;
597 if (AudioObjectHasProperty(cdi->dev_id, &addr)) {
598 cdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
599 }
600 }
601
602 cf->dev_count++;
603
604 PJ_LOG(4, (THIS_FILE, " dev_id %d: %s (in=%d, out=%d) %dHz",
605 i,
606 cdi->info.name,
607 cdi->info.input_count,
608 cdi->info.output_count,
609 cdi->info.default_samples_per_sec));
610 }
611
612 return PJ_SUCCESS;
613#endif
614}
615
616/* API: get number of devices */
617static unsigned ca_factory_get_dev_count(pjmedia_aud_dev_factory *f)
618{
619 struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
620 return cf->dev_count;
621}
622
623/* API: get device info */
624static pj_status_t ca_factory_get_dev_info(pjmedia_aud_dev_factory *f,
625 unsigned index,
626 pjmedia_aud_dev_info *info)
627{
628 struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
629
630 PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EAUD_INVDEV);
631
632 pj_memcpy(info, &cf->dev_info[index].info, sizeof(*info));
633
634 return PJ_SUCCESS;
635}
636
637/* API: create default device parameter */
638static pj_status_t ca_factory_default_param(pjmedia_aud_dev_factory *f,
639 unsigned index,
640 pjmedia_aud_param *param)
641{
642 struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
643 struct coreaudio_dev_info *di = &cf->dev_info[index];
644
645 PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EAUD_INVDEV);
646
647 pj_bzero(param, sizeof(*param));
648 if (di->info.input_count && di->info.output_count) {
649 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
650 param->rec_id = index;
651 param->play_id = index;
652 } else if (di->info.input_count) {
653 param->dir = PJMEDIA_DIR_CAPTURE;
654 param->rec_id = index;
655 param->play_id = PJMEDIA_AUD_INVALID_DEV;
656 } else if (di->info.output_count) {
657 param->dir = PJMEDIA_DIR_PLAYBACK;
658 param->play_id = index;
659 param->rec_id = PJMEDIA_AUD_INVALID_DEV;
660 } else {
661 return PJMEDIA_EAUD_INVDEV;
662 }
663
664 /* Set the mandatory settings here */
665 param->clock_rate = di->info.default_samples_per_sec;
666 param->channel_count = 1;
667 param->samples_per_frame = di->info.default_samples_per_sec * 20 / 1000;
668 param->bits_per_sample = 16;
669
670 /* Set the param for device capabilities here */
671 param->flags = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
672 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
673 param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
674 param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
675
676 return PJ_SUCCESS;
677}
678
679OSStatus resampleProc(AudioConverterRef inAudioConverter,
680 UInt32 *ioNumberDataPackets,
681 AudioBufferList *ioData,
682 AudioStreamPacketDescription **outDataPacketDescription,
683 void *inUserData)
684{
685 struct coreaudio_stream *strm = (struct coreaudio_stream*)inUserData;
686
687 if (*ioNumberDataPackets > strm->resample_buf_size)
688 *ioNumberDataPackets = strm->resample_buf_size;
689
690 ioData->mNumberBuffers = 1;
691 ioData->mBuffers[0].mNumberChannels = strm->streamFormat.mChannelsPerFrame;
692 ioData->mBuffers[0].mData = strm->resample_buf_ptr;
693 ioData->mBuffers[0].mDataByteSize = *ioNumberDataPackets *
694 strm->streamFormat.mChannelsPerFrame *
695 strm->param.bits_per_sample >> 3;
696
697 return noErr;
698}
699
700static OSStatus resample_callback(void *inRefCon,
701 AudioUnitRenderActionFlags *ioActionFlags,
702 const AudioTimeStamp *inTimeStamp,
703 UInt32 inBusNumber,
704 UInt32 inNumberFrames,
705 AudioBufferList *ioData)
706{
707 struct coreaudio_stream *strm = (struct coreaudio_stream*)inRefCon;
708 OSStatus ostatus;
709 pj_status_t status = 0;
710 unsigned nsamples;
711 AudioBufferList *buf = strm->audio_buf;
712 pj_int16_t *input;
713 UInt32 resampleSize;
714
715 pj_assert(!strm->quit_flag);
716
717 /* Known cases of callback's thread:
718 * - The thread may be changed in the middle of a session
719 * it happens when plugging/unplugging headphone.
720 * - The same thread may be reused in consecutive sessions. The first
721 * session will leave TLS set, but release the TLS data address,
722 * so the second session must re-register the callback's thread.
723 */
724 if (strm->rec_thread_initialized == 0 || !pj_thread_is_registered())
725 {
726 pj_bzero(strm->rec_thread_desc, sizeof(pj_thread_desc));
727 status = pj_thread_register("ca_rec", strm->rec_thread_desc,
728 &strm->rec_thread);
729 strm->rec_thread_initialized = 1;
730 PJ_LOG(5,(THIS_FILE, "Recorder thread started, (%i frames)",
731 inNumberFrames));
732 }
733
734 buf->mBuffers[0].mData = NULL;
735 buf->mBuffers[0].mDataByteSize = inNumberFrames *
736 strm->streamFormat.mChannelsPerFrame;
737 /* Render the unit to get input data */
738 ostatus = AudioUnitRender(strm->io_units[0],
739 ioActionFlags,
740 inTimeStamp,
741 inBusNumber,
742 inNumberFrames,
743 buf);
744
745 if (ostatus != noErr) {
746 PJ_LOG(5, (THIS_FILE, "Core audio unit render error %i", ostatus));
747 goto on_break;
748 }
749 input = (pj_int16_t *)buf->mBuffers[0].mData;
750
751 resampleSize = strm->resample_buf_size;
752 nsamples = inNumberFrames * strm->param.channel_count +
753 strm->resample_buf_count;
754
755 if (nsamples >= resampleSize) {
756 pjmedia_frame frame;
757 UInt32 resampleOutput = strm->param.samples_per_frame /
758 strm->streamFormat.mChannelsPerFrame;
759 AudioBufferList ab;
760
761 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
762 frame.buf = (void*) strm->rec_buf;
763 frame.size = strm->param.samples_per_frame *
764 strm->param.bits_per_sample >> 3;
765 frame.bit_info = 0;
766
767 ab.mNumberBuffers = 1;
768 ab.mBuffers[0].mNumberChannels = strm->streamFormat.mChannelsPerFrame;
769 ab.mBuffers[0].mData = strm->rec_buf;
770 ab.mBuffers[0].mDataByteSize = frame.size;
771
772 /* If buffer is not empty, combine the buffer with the just incoming
773 * samples, then call put_frame.
774 */
775 if (strm->resample_buf_count) {
776 unsigned chunk_count = resampleSize - strm->resample_buf_count;
777 pjmedia_copy_samples(strm->resample_buf + strm->resample_buf_count,
778 input, chunk_count);
779
780 /* Do the resample */
781
782 strm->resample_buf_ptr = strm->resample_buf;
783 ostatus = AudioConverterFillComplexBuffer(strm->resample,
784 resampleProc,
785 strm,
786 &resampleOutput,
787 &ab,
788 NULL);
789 if (ostatus != noErr) {
790 goto on_break;
791 }
792 frame.timestamp.u64 = strm->rec_timestamp.u64;
793
794 status = (*strm->rec_cb)(strm->user_data, &frame);
795
796 input = input + chunk_count;
797 nsamples -= resampleSize;
798 strm->resample_buf_count = 0;
799 strm->rec_timestamp.u64 += strm->param.samples_per_frame /
800 strm->param.channel_count;
801 }
802
803
804 /* Give all frames we have */
805 while (nsamples >= resampleSize && status == 0) {
806 frame.timestamp.u64 = strm->rec_timestamp.u64;
807
808 /* Do the resample */
809 strm->resample_buf_ptr = input;
810 ab.mBuffers[0].mDataByteSize = frame.size;
811 resampleOutput = strm->param.samples_per_frame /
812 strm->streamFormat.mChannelsPerFrame;
813 ostatus = AudioConverterFillComplexBuffer(strm->resample,
814 resampleProc,
815 strm,
816 &resampleOutput,
817 &ab,
818 NULL);
819 if (ostatus != noErr) {
820 goto on_break;
821 }
822
823 status = (*strm->rec_cb)(strm->user_data, &frame);
824
825 input = (pj_int16_t*) input + resampleSize;
826 nsamples -= resampleSize;
827 strm->rec_timestamp.u64 += strm->param.samples_per_frame /
828 strm->param.channel_count;
829 }
830
831 /* Store the remaining samples into the buffer */
832 if (nsamples && status == 0) {
833 strm->resample_buf_count = nsamples;
834 pjmedia_copy_samples(strm->resample_buf, input,
835 nsamples);
836 }
837
838 } else {
839 /* Not enough samples, let's just store them in the buffer */
840 pjmedia_copy_samples(strm->resample_buf + strm->resample_buf_count,
841 input,
842 inNumberFrames * strm->param.channel_count);
843 strm->resample_buf_count += inNumberFrames *
844 strm->param.channel_count;
845 }
846
847 return noErr;
848
849on_break:
850 return -1;
851}
852
853static OSStatus input_callback(void *inRefCon,
854 AudioUnitRenderActionFlags *ioActionFlags,
855 const AudioTimeStamp *inTimeStamp,
856 UInt32 inBusNumber,
857 UInt32 inNumberFrames,
858 AudioBufferList *ioData)
859{
860 struct coreaudio_stream *strm = (struct coreaudio_stream*)inRefCon;
861 OSStatus ostatus;
862 pj_status_t status = 0;
863 unsigned nsamples;
864 AudioBufferList *buf = strm->audio_buf;
865 pj_int16_t *input;
866
867 pj_assert(!strm->quit_flag);
868
869 /* Known cases of callback's thread:
870 * - The thread may be changed in the middle of a session
871 * it happens when plugging/unplugging headphone.
872 * - The same thread may be reused in consecutive sessions. The first
873 * session will leave TLS set, but release the TLS data address,
874 * so the second session must re-register the callback's thread.
875 */
876 if (strm->rec_thread_initialized == 0 || !pj_thread_is_registered())
877 {
878 pj_bzero(strm->rec_thread_desc, sizeof(pj_thread_desc));
879 status = pj_thread_register("ca_rec", strm->rec_thread_desc,
880 &strm->rec_thread);
881 strm->rec_thread_initialized = 1;
882 PJ_LOG(5,(THIS_FILE, "Recorder thread started, (%i frames)",
883 inNumberFrames));
884 }
885
886 buf->mBuffers[0].mData = NULL;
887 buf->mBuffers[0].mDataByteSize = inNumberFrames *
888 strm->streamFormat.mChannelsPerFrame;
889 /* Render the unit to get input data */
890 ostatus = AudioUnitRender(strm->io_units[0],
891 ioActionFlags,
892 inTimeStamp,
893 inBusNumber,
894 inNumberFrames,
895 buf);
896
897 if (ostatus != noErr) {
898 PJ_LOG(5, (THIS_FILE, "Core audio unit render error %i", ostatus));
899 goto on_break;
900 }
901 input = (pj_int16_t *)buf->mBuffers[0].mData;
902
903 /* Calculate number of samples we've got */
904 nsamples = inNumberFrames * strm->param.channel_count +
905 strm->rec_buf_count;
906 if (nsamples >= strm->param.samples_per_frame) {
907 pjmedia_frame frame;
908
909 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
910 frame.size = strm->param.samples_per_frame *
911 strm->param.bits_per_sample >> 3;
912 frame.bit_info = 0;
913
914 /* If buffer is not empty, combine the buffer with the just incoming
915 * samples, then call put_frame.
916 */
917 if (strm->rec_buf_count) {
918 unsigned chunk_count = 0;
919
920 chunk_count = strm->param.samples_per_frame - strm->rec_buf_count;
921 pjmedia_copy_samples(strm->rec_buf + strm->rec_buf_count,
922 input, chunk_count);
923
924 frame.buf = (void*) strm->rec_buf;
925 frame.timestamp.u64 = strm->rec_timestamp.u64;
926
927 status = (*strm->rec_cb)(strm->user_data, &frame);
928
929 input = input + chunk_count;
930 nsamples -= strm->param.samples_per_frame;
931 strm->rec_buf_count = 0;
932 strm->rec_timestamp.u64 += strm->param.samples_per_frame /
933 strm->param.channel_count;
934 }
935
936 /* Give all frames we have */
937 while (nsamples >= strm->param.samples_per_frame && status == 0) {
938 frame.buf = (void*) input;
939 frame.timestamp.u64 = strm->rec_timestamp.u64;
940
941 status = (*strm->rec_cb)(strm->user_data, &frame);
942
943 input = (pj_int16_t*) input + strm->param.samples_per_frame;
944 nsamples -= strm->param.samples_per_frame;
945 strm->rec_timestamp.u64 += strm->param.samples_per_frame /
946 strm->param.channel_count;
947 }
948
949 /* Store the remaining samples into the buffer */
950 if (nsamples && status == 0) {
951 strm->rec_buf_count = nsamples;
952 pjmedia_copy_samples(strm->rec_buf, input,
953 nsamples);
954 }
955
956 } else {
957 /* Not enough samples, let's just store them in the buffer */
958 pjmedia_copy_samples(strm->rec_buf + strm->rec_buf_count,
959 input,
960 inNumberFrames * strm->param.channel_count);
961 strm->rec_buf_count += inNumberFrames * strm->param.channel_count;
962 }
963
964 return noErr;
965
966on_break:
967 return -1;
968}
969
970static OSStatus output_renderer(void *inRefCon,
971 AudioUnitRenderActionFlags *ioActionFlags,
972 const AudioTimeStamp *inTimeStamp,
973 UInt32 inBusNumber,
974 UInt32 inNumberFrames,
975 AudioBufferList *ioData)
976{
977 struct coreaudio_stream *stream = (struct coreaudio_stream*)inRefCon;
978 pj_status_t status = 0;
979 unsigned nsamples_req = inNumberFrames * stream->param.channel_count;
980 pj_int16_t *output = ioData->mBuffers[0].mData;
981
982 pj_assert(!stream->quit_flag);
983
984 /* Known cases of callback's thread:
985 * - The thread may be changed in the middle of a session
986 * it happens when plugging/unplugging headphone.
987 * - The same thread may be reused in consecutive sessions. The first
988 * session will leave TLS set, but release the TLS data address,
989 * so the second session must re-register the callback's thread.
990 */
991 if (stream->play_thread_initialized == 0 || !pj_thread_is_registered())
992 {
993 pj_bzero(stream->play_thread_desc, sizeof(pj_thread_desc));
994 status = pj_thread_register("coreaudio", stream->play_thread_desc,
995 &stream->play_thread);
996 stream->play_thread_initialized = 1;
997 PJ_LOG(5,(THIS_FILE, "Player thread started, (%i frames)",
998 inNumberFrames));
999 }
1000
1001
1002 /* Check if any buffered samples */
1003 if (stream->play_buf_count) {
1004 /* samples buffered >= requested by sound device */
1005 if (stream->play_buf_count >= nsamples_req) {
1006 pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
1007 nsamples_req);
1008 stream->play_buf_count -= nsamples_req;
1009 pjmedia_move_samples(stream->play_buf,
1010 stream->play_buf + nsamples_req,
1011 stream->play_buf_count);
1012 nsamples_req = 0;
1013
1014 return noErr;
1015 }
1016
1017 /* samples buffered < requested by sound device */
1018 pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
1019 stream->play_buf_count);
1020 nsamples_req -= stream->play_buf_count;
1021 output = (pj_int16_t*)output + stream->play_buf_count;
1022 stream->play_buf_count = 0;
1023 }
1024
1025 /* Fill output buffer as requested */
1026 while (nsamples_req && status == 0) {
1027 pjmedia_frame frame;
1028
1029 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
1030 frame.size = stream->param.samples_per_frame *
1031 stream->param.bits_per_sample >> 3;
1032 frame.timestamp.u64 = stream->play_timestamp.u64;
1033 frame.bit_info = 0;
1034
1035 if (nsamples_req >= stream->param.samples_per_frame) {
1036 frame.buf = output;
1037 status = (*stream->play_cb)(stream->user_data, &frame);
1038 if (status != PJ_SUCCESS)
1039 goto on_break;
1040
1041 if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
1042 pj_bzero(frame.buf, frame.size);
1043
1044 nsamples_req -= stream->param.samples_per_frame;
1045 output = (pj_int16_t*)output + stream->param.samples_per_frame;
1046 } else {
1047 frame.buf = stream->play_buf;
1048 status = (*stream->play_cb)(stream->user_data, &frame);
1049 if (status != PJ_SUCCESS)
1050 goto on_break;
1051
1052 if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
1053 pj_bzero(frame.buf, frame.size);
1054
1055 pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
1056 nsamples_req);
1057 stream->play_buf_count = stream->param.samples_per_frame -
1058 nsamples_req;
1059 pjmedia_move_samples(stream->play_buf,
1060 stream->play_buf+nsamples_req,
1061 stream->play_buf_count);
1062 nsamples_req = 0;
1063 }
1064
1065 stream->play_timestamp.u64 += stream->param.samples_per_frame /
1066 stream->param.channel_count;
1067 }
1068
1069 return noErr;
1070
1071on_break:
1072 return -1;
1073}
1074
Alexandre Lision94f06ba2013-12-09 16:28:33 -05001075#if !COREAUDIO_MAC && USE_AUDIO_SESSION_API != 0
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001076static void propListener(void *inClientData,
1077 AudioSessionPropertyID inID,
1078 UInt32 inDataSize,
1079 const void * inData)
1080{
1081 struct coreaudio_factory *cf = (struct coreaudio_factory*)inClientData;
1082 struct stream_list *it, *itBegin;
1083 CFDictionaryRef routeDictionary;
1084 CFNumberRef reason;
1085 SInt32 reasonVal;
1086 pj_assert(cf);
1087
1088 if (inID != kAudioSessionProperty_AudioRouteChange)
1089 return;
1090
1091 routeDictionary = (CFDictionaryRef)inData;
1092 reason = (CFNumberRef)
1093 CFDictionaryGetValue(
1094 routeDictionary,
1095 CFSTR(kAudioSession_AudioRouteChangeKey_Reason));
1096 CFNumberGetValue(reason, kCFNumberSInt32Type, &reasonVal);
1097
1098 if (reasonVal != kAudioSessionRouteChangeReason_OldDeviceUnavailable) {
1099 PJ_LOG(3, (THIS_FILE, "ignoring audio route change..."));
1100 return;
1101 }
1102
1103 PJ_LOG(3, (THIS_FILE, "audio route changed"));
1104
1105 pj_mutex_lock(cf->mutex);
1106 itBegin = &cf->streams;
1107 for (it = itBegin->next; it != itBegin; it = it->next) {
1108 if (it->stream->interrupted)
1109 continue;
1110
1111 /*
1112 status = ca_stream_stop((pjmedia_aud_stream *)it->stream);
1113 status = ca_stream_start((pjmedia_aud_stream *)it->stream);
1114 if (status != PJ_SUCCESS) {
1115 PJ_LOG(3, (THIS_FILE,
1116 "Error: failed to restart the audio unit (%i)",
1117 status));
1118 continue;
1119 }
1120 PJ_LOG(3, (THIS_FILE, "core audio unit successfully restarted"));
1121 */
1122 }
1123 pj_mutex_unlock(cf->mutex);
1124}
1125
1126static void interruptionListener(void *inClientData, UInt32 inInterruption)
1127{
1128 struct stream_list *it, *itBegin;
1129 pj_status_t status;
1130 pj_thread_desc thread_desc;
1131 pj_thread_t *thread;
1132
1133 /* Register the thread with PJLIB, this is must for any external threads
1134 * which need to use the PJLIB framework.
1135 */
1136 if (!pj_thread_is_registered()) {
1137 pj_bzero(thread_desc, sizeof(pj_thread_desc));
1138 status = pj_thread_register("intListener", thread_desc, &thread);
1139 }
1140
1141 PJ_LOG(3, (THIS_FILE, "Session interrupted! --- %s ---",
1142 inInterruption == kAudioSessionBeginInterruption ?
1143 "Begin Interruption" : "End Interruption"));
1144
1145 if (!cf_instance)
1146 return;
1147
1148 pj_mutex_lock(cf_instance->mutex);
1149 itBegin = &cf_instance->streams;
1150 for (it = itBegin->next; it != itBegin; it = it->next) {
1151 if (inInterruption == kAudioSessionEndInterruption &&
1152 it->stream->interrupted == PJ_TRUE)
1153 {
1154 UInt32 audioCategory;
1155 OSStatus ostatus;
1156
1157 /* Make sure that your application can receive remote control
1158 * events by adding the code:
1159 * [[UIApplication sharedApplication]
1160 * beginReceivingRemoteControlEvents];
1161 * Otherwise audio unit will fail to restart while your
1162 * application is in the background mode.
1163 */
1164 /* Make sure we set the correct audio category before restarting */
1165 audioCategory = kAudioSessionCategory_PlayAndRecord;
1166 ostatus = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,
1167 sizeof(audioCategory),
1168 &audioCategory);
1169 if (ostatus != kAudioSessionNoError) {
1170 PJ_LOG(4, (THIS_FILE,
1171 "Warning: cannot set the audio session category (%i)",
1172 ostatus));
1173 }
1174
1175 /* Restart the stream */
1176 status = ca_stream_start((pjmedia_aud_stream*)it->stream);
1177 if (status != PJ_SUCCESS) {
1178 PJ_LOG(3, (THIS_FILE,
1179 "Error: failed to restart the audio unit (%i)",
1180 status));
1181 continue;
1182 }
1183 PJ_LOG(3, (THIS_FILE, "core audio unit successfully resumed"
1184 " after interruption"));
1185 } else if (inInterruption == kAudioSessionBeginInterruption &&
1186 it->stream->running == PJ_TRUE)
1187 {
1188 status = ca_stream_stop((pjmedia_aud_stream*)it->stream);
1189 it->stream->interrupted = PJ_TRUE;
1190 }
1191 }
1192 pj_mutex_unlock(cf_instance->mutex);
1193}
1194
1195#endif
1196
1197#if COREAUDIO_MAC
1198/* Internal: create audio converter for resampling the recorder device */
1199static pj_status_t create_audio_resample(struct coreaudio_stream *strm,
1200 AudioStreamBasicDescription *desc)
1201{
1202 OSStatus ostatus;
1203
1204 pj_assert(strm->streamFormat.mSampleRate != desc->mSampleRate);
1205 pj_assert(NULL == strm->resample);
1206 pj_assert(NULL == strm->resample_buf);
1207
1208 /* Create the audio converter */
1209 ostatus = AudioConverterNew(desc, &strm->streamFormat, &strm->resample);
1210 if (ostatus != noErr) {
1211 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1212 }
1213
1214 /*
1215 * Allocate the buffer required to hold enough input data
1216 */
1217 strm->resample_buf_size = (unsigned)(desc->mSampleRate *
1218 strm->param.samples_per_frame /
1219 strm->param.clock_rate);
1220 strm->resample_buf = (pj_int16_t*)
1221 pj_pool_alloc(strm->pool,
1222 strm->resample_buf_size *
1223 strm->param.bits_per_sample >> 3);
1224 if (!strm->resample_buf)
1225 return PJ_ENOMEM;
1226 strm->resample_buf_count = 0;
1227
1228 return PJ_SUCCESS;
1229}
1230#endif
1231
1232/* Internal: create audio unit for recorder/playback device */
1233static pj_status_t create_audio_unit(AudioComponent io_comp,
1234 AudioDeviceID dev_id,
1235 pjmedia_dir dir,
1236 struct coreaudio_stream *strm,
1237 AudioUnit *io_unit)
1238{
1239 OSStatus ostatus;
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001240#if !COREAUDIO_MAC
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001241 /* We want to be able to open playback and recording streams */
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001242 strm->sess = [AVAudioSession sharedInstance];
1243 if ([strm->sess setCategory:AVAudioSessionCategoryPlayAndRecord
1244 error:nil] != YES)
1245 {
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001246 PJ_LOG(4, (THIS_FILE,
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001247 "Warning: cannot set the audio session category"));
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001248 }
1249#endif
1250
1251 /* Create an audio unit to interface with the device */
1252 ostatus = AudioComponentInstanceNew(io_comp, io_unit);
1253 if (ostatus != noErr) {
1254 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1255 }
1256
1257 /* Set audio unit's properties for capture device */
1258 if (dir & PJMEDIA_DIR_CAPTURE) {
1259 UInt32 enable = 1;
1260
1261 /* Enable input */
1262 ostatus = AudioUnitSetProperty(*io_unit,
1263 kAudioOutputUnitProperty_EnableIO,
1264 kAudioUnitScope_Input,
1265 1,
1266 &enable,
1267 sizeof(enable));
1268 if (ostatus != noErr) {
1269 PJ_LOG(4, (THIS_FILE,
1270 "Warning: cannot enable IO of capture device %d",
1271 dev_id));
1272 }
1273
1274 /* Disable output */
1275 if (!(dir & PJMEDIA_DIR_PLAYBACK)) {
1276 enable = 0;
1277 ostatus = AudioUnitSetProperty(*io_unit,
1278 kAudioOutputUnitProperty_EnableIO,
1279 kAudioUnitScope_Output,
1280 0,
1281 &enable,
1282 sizeof(enable));
1283 if (ostatus != noErr) {
1284 PJ_LOG(4, (THIS_FILE,
1285 "Warning: cannot disable IO of capture device %d",
1286 dev_id));
1287 }
1288 }
1289 }
1290
1291 /* Set audio unit's properties for playback device */
1292 if (dir & PJMEDIA_DIR_PLAYBACK) {
1293 UInt32 enable = 1;
1294
1295 /* Enable output */
1296 ostatus = AudioUnitSetProperty(*io_unit,
1297 kAudioOutputUnitProperty_EnableIO,
1298 kAudioUnitScope_Output,
1299 0,
1300 &enable,
1301 sizeof(enable));
1302 if (ostatus != noErr) {
1303 PJ_LOG(4, (THIS_FILE,
1304 "Warning: cannot enable IO of playback device %d",
1305 dev_id));
1306 }
1307
1308 }
1309
1310#if COREAUDIO_MAC
1311 PJ_LOG(5, (THIS_FILE, "Opening device %d", dev_id));
1312 ostatus = AudioUnitSetProperty(*io_unit,
1313 kAudioOutputUnitProperty_CurrentDevice,
1314 kAudioUnitScope_Global,
1315 0,
1316 &dev_id,
1317 sizeof(dev_id));
1318 if (ostatus != noErr) {
1319 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1320 }
1321#endif
1322
1323 if (dir & PJMEDIA_DIR_CAPTURE) {
1324#if COREAUDIO_MAC
1325 AudioStreamBasicDescription deviceFormat;
1326 UInt32 size;
1327
1328 /*
1329 * Keep the sample rate from the device, otherwise we will confuse
1330 * AUHAL
1331 */
1332 size = sizeof(AudioStreamBasicDescription);
1333 ostatus = AudioUnitGetProperty(*io_unit,
1334 kAudioUnitProperty_StreamFormat,
1335 kAudioUnitScope_Input,
1336 1,
1337 &deviceFormat,
1338 &size);
1339 if (ostatus != noErr) {
1340 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1341 }
1342 strm->streamFormat.mSampleRate = deviceFormat.mSampleRate;
1343#endif
1344
1345 /* When setting the stream format, we have to make sure the sample
1346 * rate is supported. Setting an unsupported sample rate will cause
1347 * AudioUnitRender() to fail later.
1348 */
1349 ostatus = AudioUnitSetProperty(*io_unit,
1350 kAudioUnitProperty_StreamFormat,
1351 kAudioUnitScope_Output,
1352 1,
1353 &strm->streamFormat,
1354 sizeof(strm->streamFormat));
1355 if (ostatus != noErr) {
1356 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1357 }
1358
1359#if COREAUDIO_MAC
1360 strm->streamFormat.mSampleRate = strm->param.clock_rate;
1361 size = sizeof(AudioStreamBasicDescription);
1362 ostatus = AudioUnitGetProperty (*io_unit,
1363 kAudioUnitProperty_StreamFormat,
1364 kAudioUnitScope_Output,
1365 1,
1366 &deviceFormat,
1367 &size);
1368 if (ostatus == noErr) {
1369 if (strm->streamFormat.mSampleRate != deviceFormat.mSampleRate) {
1370 pj_status_t rc = create_audio_resample(strm, &deviceFormat);
1371 if (PJ_SUCCESS != rc)
1372 return rc;
1373 }
1374 } else {
1375 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1376 }
1377#endif
1378 }
1379
1380 if (dir & PJMEDIA_DIR_PLAYBACK) {
1381 AURenderCallbackStruct output_cb;
1382
1383 /* Set the stream format */
1384 ostatus = AudioUnitSetProperty(*io_unit,
1385 kAudioUnitProperty_StreamFormat,
1386 kAudioUnitScope_Input,
1387 0,
1388 &strm->streamFormat,
1389 sizeof(strm->streamFormat));
1390 if (ostatus != noErr) {
1391 PJ_LOG(4, (THIS_FILE,
1392 "Warning: cannot set playback stream format of dev %d",
1393 dev_id));
1394 }
1395
1396 /* Set render callback */
1397 output_cb.inputProc = output_renderer;
1398 output_cb.inputProcRefCon = strm;
1399 ostatus = AudioUnitSetProperty(*io_unit,
1400 kAudioUnitProperty_SetRenderCallback,
1401 kAudioUnitScope_Input,
1402 0,
1403 &output_cb,
1404 sizeof(output_cb));
1405 if (ostatus != noErr) {
1406 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1407 }
1408
1409 /* Allocate playback buffer */
1410 strm->play_buf = (pj_int16_t*)pj_pool_alloc(strm->pool,
1411 strm->param.samples_per_frame *
1412 strm->param.bits_per_sample >> 3);
1413 if (!strm->play_buf)
1414 return PJ_ENOMEM;
1415 strm->play_buf_count = 0;
1416 }
1417
1418 if (dir & PJMEDIA_DIR_CAPTURE) {
1419 AURenderCallbackStruct input_cb;
1420#if COREAUDIO_MAC
1421 AudioBuffer *ab;
1422 UInt32 size, buf_size;
1423#endif
1424
1425 /* Set input callback */
1426 input_cb.inputProc = strm->resample ? resample_callback :
1427 input_callback;
1428 input_cb.inputProcRefCon = strm;
1429 ostatus = AudioUnitSetProperty(
1430 *io_unit,
1431 kAudioOutputUnitProperty_SetInputCallback,
1432 kAudioUnitScope_Global,
1433 0,
1434 &input_cb,
1435 sizeof(input_cb));
1436 if (ostatus != noErr) {
1437 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1438 }
1439
1440#if COREAUDIO_MAC
1441 /* Get device's buffer frame size */
1442 size = sizeof(UInt32);
1443 ostatus = AudioUnitGetProperty(*io_unit,
1444 kAudioDevicePropertyBufferFrameSize,
1445 kAudioUnitScope_Global,
1446 0,
1447 &buf_size,
1448 &size);
1449 if (ostatus != noErr)
1450 {
1451 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1452 }
1453
1454 /* Allocate audio buffer */
1455 strm->audio_buf = (AudioBufferList*)pj_pool_alloc(strm->pool,
1456 sizeof(AudioBufferList) + sizeof(AudioBuffer));
1457 if (!strm->audio_buf)
1458 return PJ_ENOMEM;
1459
1460 strm->audio_buf->mNumberBuffers = 1;
1461 ab = &strm->audio_buf->mBuffers[0];
1462 ab->mNumberChannels = strm->streamFormat.mChannelsPerFrame;
1463 ab->mDataByteSize = buf_size * ab->mNumberChannels *
1464 strm->param.bits_per_sample >> 3;
1465 ab->mData = pj_pool_alloc(strm->pool,
1466 ab->mDataByteSize);
1467 if (!ab->mData)
1468 return PJ_ENOMEM;
1469
1470#else
1471 /* We will let AudioUnitRender() to allocate the buffer
1472 * for us later
1473 */
1474 strm->audio_buf = (AudioBufferList*)pj_pool_alloc(strm->pool,
1475 sizeof(AudioBufferList) + sizeof(AudioBuffer));
1476 if (!strm->audio_buf)
1477 return PJ_ENOMEM;
1478
1479 strm->audio_buf->mNumberBuffers = 1;
1480 strm->audio_buf->mBuffers[0].mNumberChannels =
1481 strm->streamFormat.mChannelsPerFrame;
1482
1483#endif
1484
1485 /* Allocate recording buffer */
1486 strm->rec_buf = (pj_int16_t*)pj_pool_alloc(strm->pool,
1487 strm->param.samples_per_frame *
1488 strm->param.bits_per_sample >> 3);
1489 if (!strm->rec_buf)
1490 return PJ_ENOMEM;
1491 strm->rec_buf_count = 0;
1492 }
1493
1494 /* Initialize the audio unit */
1495 ostatus = AudioUnitInitialize(*io_unit);
1496 if (ostatus != noErr) {
1497 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1498 }
1499
1500 return PJ_SUCCESS;
1501}
1502
1503/* API: create stream */
1504static pj_status_t ca_factory_create_stream(pjmedia_aud_dev_factory *f,
1505 const pjmedia_aud_param *param,
1506 pjmedia_aud_rec_cb rec_cb,
1507 pjmedia_aud_play_cb play_cb,
1508 void *user_data,
1509 pjmedia_aud_stream **p_aud_strm)
1510{
1511 struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
1512 pj_pool_t *pool;
1513 struct coreaudio_stream *strm;
1514 pj_status_t status;
1515
1516 /* Create and Initialize stream descriptor */
1517 pool = pj_pool_create(cf->pf, "coreaudio-dev", 1000, 1000, NULL);
1518 PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
1519
1520 strm = PJ_POOL_ZALLOC_T(pool, struct coreaudio_stream);
1521 pj_list_init(&strm->list_entry);
1522 strm->list_entry.stream = strm;
1523 strm->cf = cf;
1524 pj_memcpy(&strm->param, param, sizeof(*param));
1525 strm->pool = pool;
1526 strm->rec_cb = rec_cb;
1527 strm->play_cb = play_cb;
1528 strm->user_data = user_data;
1529
1530 /* Set the stream format */
1531 strm->streamFormat.mSampleRate = param->clock_rate;
1532 strm->streamFormat.mFormatID = kAudioFormatLinearPCM;
1533 strm->streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger
1534 | kLinearPCMFormatFlagIsPacked;
1535 strm->streamFormat.mBitsPerChannel = strm->param.bits_per_sample;
1536 strm->streamFormat.mChannelsPerFrame = param->channel_count;
1537 strm->streamFormat.mBytesPerFrame = strm->streamFormat.mChannelsPerFrame
1538 * strm->param.bits_per_sample >> 3;
1539 strm->streamFormat.mFramesPerPacket = 1;
1540 strm->streamFormat.mBytesPerPacket = strm->streamFormat.mBytesPerFrame *
1541 strm->streamFormat.mFramesPerPacket;
1542
1543 /* Apply input/output routes settings before we create the audio units */
1544 if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE) {
1545 ca_stream_set_cap(&strm->base,
1546 PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE,
1547 &param->input_route);
1548 }
1549 if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE) {
1550 ca_stream_set_cap(&strm->base,
1551 PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
1552 &param->output_route);
1553 }
1554 if (param->flags & PJMEDIA_AUD_DEV_CAP_EC) {
1555 ca_stream_set_cap(&strm->base,
1556 PJMEDIA_AUD_DEV_CAP_EC,
1557 &param->ec_enabled);
1558 } else {
1559 pj_bool_t ec = PJ_FALSE;
1560 ca_stream_set_cap(&strm->base,
1561 PJMEDIA_AUD_DEV_CAP_EC, &ec);
1562 }
1563
1564 strm->io_units[0] = strm->io_units[1] = NULL;
1565 if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK &&
1566 param->rec_id == param->play_id)
1567 {
1568 /* If both input and output are on the same device, only create
1569 * one audio unit to interface with the device.
1570 */
1571 status = create_audio_unit(cf->io_comp,
1572 cf->dev_info[param->rec_id].dev_id,
1573 param->dir, strm, &strm->io_units[0]);
1574 if (status != PJ_SUCCESS)
1575 goto on_error;
1576 } else {
1577 unsigned nunits = 0;
1578
1579 if (param->dir & PJMEDIA_DIR_CAPTURE) {
1580 status = create_audio_unit(cf->io_comp,
1581 cf->dev_info[param->rec_id].dev_id,
1582 PJMEDIA_DIR_CAPTURE,
1583 strm, &strm->io_units[nunits++]);
1584 if (status != PJ_SUCCESS)
1585 goto on_error;
1586 }
1587 if (param->dir & PJMEDIA_DIR_PLAYBACK) {
1588
1589 status = create_audio_unit(cf->io_comp,
1590 cf->dev_info[param->play_id].dev_id,
1591 PJMEDIA_DIR_PLAYBACK,
1592 strm, &strm->io_units[nunits++]);
1593 if (status != PJ_SUCCESS)
1594 goto on_error;
1595 }
1596 }
1597
1598 /* Apply the remaining settings */
1599 if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY) {
1600 ca_stream_get_cap(&strm->base,
1601 PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY,
1602 &strm->param.input_latency_ms);
1603 }
1604 if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY) {
1605 ca_stream_get_cap(&strm->base,
1606 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY,
1607 &strm->param.output_latency_ms);
1608 }
1609 if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
1610 ca_stream_set_cap(&strm->base,
1611 PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
1612 &param->output_vol);
1613 }
1614
1615 pj_mutex_lock(strm->cf->mutex);
1616 pj_assert(pj_list_empty(&strm->list_entry));
1617 pj_list_insert_after(&strm->cf->streams, &strm->list_entry);
1618 pj_mutex_unlock(strm->cf->mutex);
1619
1620 /* Done */
1621 strm->base.op = &stream_op;
1622 *p_aud_strm = &strm->base;
1623
1624 return PJ_SUCCESS;
1625
1626 on_error:
1627 ca_stream_destroy((pjmedia_aud_stream *)strm);
1628 return status;
1629}
1630
1631/* API: Get stream info. */
1632static pj_status_t ca_stream_get_param(pjmedia_aud_stream *s,
1633 pjmedia_aud_param *pi)
1634{
1635 struct coreaudio_stream *strm = (struct coreaudio_stream*)s;
1636
1637 PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
1638
1639 pj_memcpy(pi, &strm->param, sizeof(*pi));
1640
1641 /* Update the device capabilities' values */
1642 if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY,
1643 &pi->input_latency_ms) == PJ_SUCCESS)
1644 {
1645 pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
1646 }
1647 if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY,
1648 &pi->output_latency_ms) == PJ_SUCCESS)
1649 {
1650 pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
1651 }
1652 if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
1653 &pi->output_vol) == PJ_SUCCESS)
1654 {
1655 pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
1656 }
1657 if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE,
1658 &pi->input_route) == PJ_SUCCESS)
1659 {
1660 pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE;
1661 }
1662 if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
1663 &pi->output_route) == PJ_SUCCESS)
1664 {
1665 pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE;
1666 }
1667 if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_EC,
1668 &pi->ec_enabled) == PJ_SUCCESS)
1669 {
1670 pi->flags |= PJMEDIA_AUD_DEV_CAP_EC;
1671 }
1672
1673 return PJ_SUCCESS;
1674}
1675
1676/* API: get capability */
1677static pj_status_t ca_stream_get_cap(pjmedia_aud_stream *s,
1678 pjmedia_aud_dev_cap cap,
1679 void *pval)
1680{
1681 struct coreaudio_stream *strm = (struct coreaudio_stream*)s;
1682
1683 PJ_UNUSED_ARG(strm);
1684
1685 PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
1686
1687 if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY &&
1688 (strm->param.dir & PJMEDIA_DIR_CAPTURE))
1689 {
1690#if COREAUDIO_MAC
1691 UInt32 latency, size = sizeof(UInt32);
1692
1693 /* Recording latency */
1694 if (AudioUnitGetProperty (strm->io_units[0],
1695 kAudioDevicePropertyLatency,
1696 kAudioUnitScope_Input,
1697 1,
1698 &latency,
1699 &size) == noErr)
1700 {
1701 UInt32 latency2;
1702 if (AudioUnitGetProperty (strm->io_units[0],
1703 kAudioDevicePropertyBufferFrameSize,
1704 kAudioUnitScope_Input,
1705 1,
1706 &latency2,
1707 &size) == noErr)
1708 {
1709 strm->param.input_latency_ms = (latency + latency2) * 1000 /
1710 strm->param.clock_rate;
1711 strm->param.input_latency_ms++;
1712 }
1713 }
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001714#else
1715 if ([strm->sess respondsToSelector:@selector(inputLatency)]) {
1716 strm->param.input_latency_ms =
1717 (unsigned)(([strm->sess inputLatency] +
1718 [strm->sess IOBufferDuration]) * 1000);
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001719 strm->param.input_latency_ms++;
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001720 } else
1721 return PJMEDIA_EAUD_INVCAP;
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001722#endif
1723
1724 *(unsigned*)pval = strm->param.input_latency_ms;
1725 return PJ_SUCCESS;
1726 } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY &&
1727 (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
1728 {
1729#if COREAUDIO_MAC
1730 UInt32 latency, size = sizeof(UInt32);
1731 AudioUnit *io_unit = strm->io_units[1] ? &strm->io_units[1] :
1732 &strm->io_units[0];
1733
1734 /* Playback latency */
1735 if (AudioUnitGetProperty (*io_unit,
1736 kAudioDevicePropertyLatency,
1737 kAudioUnitScope_Output,
1738 0,
1739 &latency,
1740 &size) == noErr)
1741 {
1742 UInt32 latency2;
1743 if (AudioUnitGetProperty (*io_unit,
1744 kAudioDevicePropertyBufferFrameSize,
1745 kAudioUnitScope_Output,
1746 0,
1747 &latency2,
1748 &size) == noErr)
1749 {
1750 strm->param.output_latency_ms = (latency + latency2) * 1000 /
1751 strm->param.clock_rate;
1752 strm->param.output_latency_ms++;
1753 }
1754 }
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001755#else
1756 if ([strm->sess respondsToSelector:@selector(outputLatency)]) {
1757 strm->param.output_latency_ms =
1758 (unsigned)(([strm->sess outputLatency] +
1759 [strm->sess IOBufferDuration]) * 1000);
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001760 strm->param.output_latency_ms++;
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001761 } else
1762 return PJMEDIA_EAUD_INVCAP;
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001763#endif
1764 *(unsigned*)pval = (++strm->param.output_latency_ms * 2);
1765 return PJ_SUCCESS;
1766 } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
1767 (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
1768 {
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001769#if COREAUDIO_MAC
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001770 OSStatus ostatus;
1771 Float32 volume;
1772 UInt32 size = sizeof(Float32);
1773
1774 /* Output volume setting */
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001775 ostatus = AudioUnitGetProperty (strm->io_units[1] ? strm->io_units[1] :
1776 strm->io_units[0],
1777 kAudioDevicePropertyVolumeScalar,
1778 kAudioUnitScope_Output,
1779 0,
1780 &volume,
1781 &size);
1782 if (ostatus != noErr)
1783 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001784
1785 *(unsigned*)pval = (unsigned)(volume * 100);
1786 return PJ_SUCCESS;
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001787#else
1788 if ([strm->sess respondsToSelector:@selector(outputVolume)]) {
1789 *(unsigned*)pval = (unsigned)([strm->sess outputVolume] * 100);
1790 return PJ_SUCCESS;
1791 } else
1792 return PJMEDIA_EAUD_INVCAP;
Alexandre Lision94f06ba2013-12-09 16:28:33 -05001793#endif
1794
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001795#if !COREAUDIO_MAC
Alexandre Lision94f06ba2013-12-09 16:28:33 -05001796#if USE_AUDIO_SESSION_API != 0
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001797 } else if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE &&
1798 (strm->param.dir & PJMEDIA_DIR_CAPTURE))
1799 {
1800 UInt32 btooth, size = sizeof(UInt32);
1801 OSStatus ostatus;
1802
1803 ostatus = AudioSessionGetProperty (
1804 kAudioSessionProperty_OverrideCategoryEnableBluetoothInput,
1805 &size, &btooth);
1806 if (ostatus != kAudioSessionNoError) {
1807 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1808 }
1809
1810 *(pjmedia_aud_dev_route*)pval = btooth?
1811 PJMEDIA_AUD_DEV_ROUTE_BLUETOOTH:
1812 PJMEDIA_AUD_DEV_ROUTE_DEFAULT;
1813 return PJ_SUCCESS;
1814 } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE &&
1815 (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
1816 {
1817 CFStringRef route;
1818 UInt32 size = sizeof(CFStringRef);
1819 OSStatus ostatus;
1820
1821 ostatus = AudioSessionGetProperty (kAudioSessionProperty_AudioRoute,
1822 &size, &route);
1823 if (ostatus != kAudioSessionNoError) {
1824 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1825 }
1826
1827 if (!route) {
1828 *(pjmedia_aud_dev_route*)pval = PJMEDIA_AUD_DEV_ROUTE_DEFAULT;
1829 } else if (CFStringHasPrefix(route, CFSTR("Headset"))) {
1830 *(pjmedia_aud_dev_route*)pval = PJMEDIA_AUD_DEV_ROUTE_EARPIECE;
1831 } else {
1832 *(pjmedia_aud_dev_route*)pval = PJMEDIA_AUD_DEV_ROUTE_DEFAULT;
1833 }
1834
1835 CFRelease(route);
1836
1837 return PJ_SUCCESS;
Alexandre Lision94f06ba2013-12-09 16:28:33 -05001838#endif
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001839 } else if (cap==PJMEDIA_AUD_DEV_CAP_EC) {
1840 AudioComponentDescription desc;
1841 OSStatus ostatus;
1842
1843 ostatus = AudioComponentGetDescription(strm->cf->io_comp, &desc);
1844 if (ostatus != noErr) {
1845 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1846 }
1847
1848 *(pj_bool_t*)pval = (desc.componentSubType ==
1849 kAudioUnitSubType_VoiceProcessingIO);
1850 return PJ_SUCCESS;
1851#endif
1852 } else {
1853 return PJMEDIA_EAUD_INVCAP;
1854 }
1855}
1856
1857/* API: set capability */
1858static pj_status_t ca_stream_set_cap(pjmedia_aud_stream *s,
1859 pjmedia_aud_dev_cap cap,
1860 const void *pval)
1861{
1862 struct coreaudio_stream *strm = (struct coreaudio_stream*)s;
1863
1864 PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
1865
1866#if COREAUDIO_MAC
1867 if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
1868 (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
1869 {
1870 OSStatus ostatus;
1871 Float32 volume = *(unsigned*)pval;
1872
1873 /* Output volume setting */
1874 volume /= 100.0;
1875 ostatus = AudioUnitSetProperty (strm->io_units[1] ? strm->io_units[1] :
1876 strm->io_units[0],
1877 kAudioDevicePropertyVolumeScalar,
1878 kAudioUnitScope_Output,
1879 0,
1880 &volume,
1881 sizeof(Float32));
1882 if (ostatus != noErr) {
1883 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1884 }
1885 strm->param.output_vol = *(unsigned*)pval;
1886 return PJ_SUCCESS;
1887 }
1888
1889#else
Alexandre Lision94f06ba2013-12-09 16:28:33 -05001890 if (cap==PJMEDIA_AUD_DEV_CAP_EC) {
1891 AudioComponentDescription desc;
1892 AudioComponent io_comp;
1893
1894 desc.componentType = kAudioUnitType_Output;
1895 desc.componentSubType = (*(pj_bool_t*)pval)?
1896 kAudioUnitSubType_VoiceProcessingIO :
1897 kAudioUnitSubType_RemoteIO;
1898 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
1899 desc.componentFlags = 0;
1900 desc.componentFlagsMask = 0;
1901
1902 io_comp = AudioComponentFindNext(NULL, &desc);
1903 if (io_comp == NULL)
1904 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(-1);
1905 strm->cf->io_comp = io_comp;
1906 strm->param.ec_enabled = *(pj_bool_t*)pval;
1907
1908 PJ_LOG(4, (THIS_FILE, "Using %s audio unit",
1909 (desc.componentSubType ==
1910 kAudioUnitSubType_RemoteIO? "RemoteIO":
1911 "VoiceProcessingIO")));
1912
1913 return PJ_SUCCESS;
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001914 } else if ((cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY &&
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001915 (strm->param.dir & PJMEDIA_DIR_CAPTURE)) ||
1916 (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY &&
1917 (strm->param.dir & PJMEDIA_DIR_PLAYBACK)))
1918 {
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001919 NSTimeInterval duration = *(unsigned *)pval;
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001920 unsigned latency;
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001921
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001922 /* For low-latency audio streaming, you can set this value to
1923 * as low as 5 ms (the default is 23ms). However, lowering the
1924 * latency may cause a decrease in audio quality.
1925 */
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001926 duration /= 1000;
1927 if ([strm->sess setPreferredIOBufferDuration:duration error:nil]
1928 != YES)
1929 {
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001930 PJ_LOG(4, (THIS_FILE,
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001931 "Error: cannot set the preferred buffer duration"));
1932 return PJMEDIA_EAUD_INVOP;
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001933 }
1934
1935 ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY, &latency);
1936 ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY, &latency);
1937
1938 return PJ_SUCCESS;
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001939 }
1940
1941#if USE_AUDIO_SESSION_API != 0
1942
1943 else if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE &&
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001944 (strm->param.dir & PJMEDIA_DIR_CAPTURE))
1945 {
1946 UInt32 btooth = *(pjmedia_aud_dev_route*)pval ==
1947 PJMEDIA_AUD_DEV_ROUTE_BLUETOOTH ? 1 : 0;
1948 OSStatus ostatus;
1949
1950 ostatus = AudioSessionSetProperty (
1951 kAudioSessionProperty_OverrideCategoryEnableBluetoothInput,
1952 sizeof(btooth), &btooth);
1953 if (ostatus != kAudioSessionNoError) {
1954 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1955 }
1956 strm->param.input_route = *(pjmedia_aud_dev_route*)pval;
1957 return PJ_SUCCESS;
1958 } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE &&
1959 (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
1960 {
1961 OSStatus ostatus;
1962 UInt32 route = *(pjmedia_aud_dev_route*)pval ==
1963 PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER ?
1964 kAudioSessionOverrideAudioRoute_Speaker :
1965 kAudioSessionOverrideAudioRoute_None;
1966
1967 ostatus = AudioSessionSetProperty (
1968 kAudioSessionProperty_OverrideAudioRoute,
1969 sizeof(route), &route);
1970 if (ostatus != kAudioSessionNoError) {
1971 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
1972 }
1973 strm->param.output_route = *(pjmedia_aud_dev_route*)pval;
1974 return PJ_SUCCESS;
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001975 }
1976#endif
Alexandre Lision94f06ba2013-12-09 16:28:33 -05001977#endif
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001978
1979 return PJMEDIA_EAUD_INVCAP;
1980}
1981
1982/* API: Start stream. */
1983static pj_status_t ca_stream_start(pjmedia_aud_stream *strm)
1984{
1985 struct coreaudio_stream *stream = (struct coreaudio_stream*)strm;
1986 OSStatus ostatus;
1987 UInt32 i;
1988
1989 if (stream->running)
1990 return PJ_SUCCESS;
1991
1992 stream->quit_flag = 0;
1993 stream->interrupted = PJ_FALSE;
1994 stream->rec_buf_count = 0;
1995 stream->play_buf_count = 0;
1996 stream->resample_buf_count = 0;
1997
1998 if (stream->resample) {
1999 ostatus = AudioConverterReset(stream->resample);
2000 if (ostatus != noErr)
2001 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
2002 }
2003
Alexandre Lision8af73cb2013-12-10 14:11:20 -05002004#if !COREAUDIO_MAC
2005 if ([stream->sess setActive:true error:nil] != YES) {
2006 PJ_LOG(4, (THIS_FILE, "Warning: cannot activate audio session"));
2007 }
Tristan Matthews0a329cc2013-07-17 13:20:14 -04002008#endif
2009
2010 for (i = 0; i < 2; i++) {
2011 if (stream->io_units[i] == NULL) break;
2012 ostatus = AudioOutputUnitStart(stream->io_units[i]);
2013 if (ostatus != noErr) {
2014 if (i == 1)
2015 AudioOutputUnitStop(stream->io_units[0]);
2016 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
2017 }
2018 }
2019
2020 stream->running = PJ_TRUE;
2021
2022 PJ_LOG(4, (THIS_FILE, "core audio stream started"));
2023
2024 return PJ_SUCCESS;
2025}
2026
2027/* API: Stop stream. */
2028static pj_status_t ca_stream_stop(pjmedia_aud_stream *strm)
2029{
2030 struct coreaudio_stream *stream = (struct coreaudio_stream*)strm;
2031 OSStatus ostatus;
2032 unsigned i;
2033 int should_deactivate;
2034 struct stream_list *it, *itBegin;
2035
2036 if (!stream->running)
2037 return PJ_SUCCESS;
2038
2039 for (i = 0; i < 2; i++) {
2040 if (stream->io_units[i] == NULL) break;
2041 ostatus = AudioOutputUnitStop(stream->io_units[i]);
2042 if (ostatus != noErr) {
2043 if (i == 0 && stream->io_units[1])
2044 AudioOutputUnitStop(stream->io_units[1]);
2045 return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
2046 }
2047 }
2048
2049 /* Check whether we need to deactivate the audio session. */
2050 pj_mutex_lock(stream->cf->mutex);
2051 pj_assert(!pj_list_empty(&stream->cf->streams));
2052 pj_assert(!pj_list_empty(&stream->list_entry));
2053 stream->running = PJ_FALSE;
2054 should_deactivate = PJ_TRUE;
2055 itBegin = &stream->cf->streams;
2056 for (it = itBegin->next; it != itBegin; it = it->next) {
2057 if (it->stream->running) {
2058 should_deactivate = PJ_FALSE;
2059 break;
2060 }
2061 }
2062 pj_mutex_unlock(stream->cf->mutex);
2063
Alexandre Lision8af73cb2013-12-10 14:11:20 -05002064#if !COREAUDIO_MAC
2065 if (should_deactivate) {
2066 if ([stream->sess setActive:false error:nil] != YES) {
2067 PJ_LOG(4, (THIS_FILE, "Warning: cannot deactivate audio session"));
2068 }
2069 }
Tristan Matthews0a329cc2013-07-17 13:20:14 -04002070#endif
2071
2072 stream->quit_flag = 1;
2073 stream->play_thread_initialized = 0;
2074 stream->rec_thread_initialized = 0;
2075 pj_bzero(stream->rec_thread_desc, sizeof(pj_thread_desc));
2076 pj_bzero(stream->play_thread_desc, sizeof(pj_thread_desc));
2077
2078 PJ_LOG(4, (THIS_FILE, "core audio stream stopped"));
2079
2080 return PJ_SUCCESS;
2081}
2082
2083
2084/* API: Destroy stream. */
2085static pj_status_t ca_stream_destroy(pjmedia_aud_stream *strm)
2086{
2087 struct coreaudio_stream *stream = (struct coreaudio_stream*)strm;
2088 unsigned i;
2089
2090 PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
2091
2092 ca_stream_stop(strm);
2093
2094 for (i = 0; i < 2; i++) {
2095 if (stream->io_units[i]) {
2096 AudioUnitUninitialize(stream->io_units[i]);
2097 AudioComponentInstanceDispose(stream->io_units[i]);
2098 stream->io_units[i] = NULL;
2099 }
2100 }
2101
2102 if (stream->resample)
2103 AudioConverterDispose(stream->resample);
2104
2105 pj_mutex_lock(stream->cf->mutex);
2106 if (!pj_list_empty(&stream->list_entry))
2107 pj_list_erase(&stream->list_entry);
2108 pj_mutex_unlock(stream->cf->mutex);
2109
2110 pj_pool_release(stream->pool);
2111
2112 return PJ_SUCCESS;
2113}
2114
2115#endif /* PJMEDIA_AUDIO_DEV_HAS_COREAUDIO */