diff options
author | Mamadou DIOP <bossiel@yahoo.fr> | 2015-08-17 01:56:35 +0200 |
---|---|---|
committer | Mamadou DIOP <bossiel@yahoo.fr> | 2015-08-17 01:56:35 +0200 |
commit | 631fffee8a28b1bec5ed1f1d26a20e0135967f99 (patch) | |
tree | 74afe3bf3efe15aa82bcd0272b2b0f4d48c2d837 /tinyDAV/src/audio/coreaudio | |
parent | 7908865936604036e6f200f1b5e069f8752f3a3a (diff) | |
download | doubango-631fffee8a28b1bec5ed1f1d26a20e0135967f99.zip doubango-631fffee8a28b1bec5ed1f1d26a20e0135967f99.tar.gz |
-
Diffstat (limited to 'tinyDAV/src/audio/coreaudio')
-rw-r--r-- | tinyDAV/src/audio/coreaudio/tdav_audiounit.c | 425 | ||||
-rw-r--r-- | tinyDAV/src/audio/coreaudio/tdav_consumer_audioqueue.c | 268 | ||||
-rw-r--r-- | tinyDAV/src/audio/coreaudio/tdav_consumer_audiounit.c | 447 | ||||
-rw-r--r-- | tinyDAV/src/audio/coreaudio/tdav_producer_audioqueue.c | 253 | ||||
-rw-r--r-- | tinyDAV/src/audio/coreaudio/tdav_producer_audiounit.c | 422 |
5 files changed, 1815 insertions, 0 deletions
diff --git a/tinyDAV/src/audio/coreaudio/tdav_audiounit.c b/tinyDAV/src/audio/coreaudio/tdav_audiounit.c new file mode 100644 index 0000000..dc11f10 --- /dev/null +++ b/tinyDAV/src/audio/coreaudio/tdav_audiounit.c @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2010-2011 Mamadou Diop. + * + * Contact: Mamadou Diop <diopmamadou(at)doubango.org> + * + * This file is part of Open Source Doubango Framework. + * + * DOUBANGO is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DOUBANGO is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DOUBANGO. + * + */ +#include "tinydav/audio/coreaudio/tdav_audiounit.h" + +#if HAVE_COREAUDIO_AUDIO_UNIT + +#include "tinydav/tdav_apple.h" + +#include "tsk_string.h" +#include "tsk_list.h" +#include "tsk_safeobj.h" +#include "tsk_debug.h" + +#if TARGET_OS_IPHONE +static UInt32 kOne = 1; +static UInt32 kZero = 0; +#endif /* TARGET_OS_IPHONE */ + +#if TARGET_OS_IPHONE + #if TARGET_IPHONE_SIMULATOR // VoiceProcessingIO will give unexpected result on the simulator when using iOS 5 + #define kDoubangoAudioUnitSubType kAudioUnitSubType_RemoteIO + #else // Echo cancellation, AGC, ... + #define kDoubangoAudioUnitSubType kAudioUnitSubType_VoiceProcessingIO + #endif +#elif TARGET_OS_MAC + #define kDoubangoAudioUnitSubType kAudioUnitSubType_HALOutput +#else + #error "Unknown target" +#endif + +#undef kInputBus +#define kInputBus 1 +#undef kOutputBus +#define kOutputBus 0 + +typedef struct tdav_audiounit_instance_s +{ + TSK_DECLARE_OBJECT; + uint64_t session_id; + uint32_t frame_duration; + AudioComponentInstance audioUnit; + struct{ + unsigned consumer:1; + unsigned producer:1; + } prepared; + unsigned started:1; + unsigned interrupted:1; + + TSK_DECLARE_SAFEOBJ; + +} +tdav_audiounit_instance_t; +TINYDAV_GEXTERN const tsk_object_def_t *tdav_audiounit_instance_def_t; +typedef tsk_list_t tdav_audiounit_instances_L_t; + + +static AudioComponent __audioSystem = tsk_null; +static tdav_audiounit_instances_L_t* __audioUnitInstances = tsk_null; + +static int _tdav_audiounit_handle_signal_xxx_prepared(tdav_audiounit_handle_t* self, tsk_bool_t consumer) +{ + tdav_audiounit_instance_t* inst = (tdav_audiounit_instance_t*)self; + if(!inst || !inst->audioUnit){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + tsk_safeobj_lock(inst); + + if(consumer){ + inst->prepared.consumer = tsk_true; + } + else { + inst->prepared.producer = tsk_true; + } + + OSStatus status; + + // For iOS we are using full-duplex AudioUnit and we wait for both consumer and producer to be prepared +#if TARGET_OS_IPHONE + if(inst->prepared.consumer && inst->prepared.producer) +#endif + { + status = AudioUnitInitialize(inst->audioUnit); + if(status != noErr){ + TSK_DEBUG_ERROR("AudioUnitInitialize failed with status =%ld", (signed long)status); + tsk_safeobj_unlock(inst); + return -2; + } + } + + tsk_safeobj_unlock(inst); + return 0; +} + +tdav_audiounit_handle_t* tdav_audiounit_handle_create(uint64_t session_id) +{ + tdav_audiounit_instance_t* inst = tsk_null; + + // create audio unit component + if(!__audioSystem){ + AudioComponentDescription audioDescription; + audioDescription.componentType = kAudioUnitType_Output; + audioDescription.componentSubType = kDoubangoAudioUnitSubType; + audioDescription.componentManufacturer = kAudioUnitManufacturer_Apple; + audioDescription.componentFlags = 0; + audioDescription.componentFlagsMask = 0; + if((__audioSystem = AudioComponentFindNext(NULL, &audioDescription))){ + // leave blank + } + else { + TSK_DEBUG_ERROR("Failed to find new audio component"); + goto done; + } + + } + // create list used to hold instances + if(!__audioUnitInstances && !(__audioUnitInstances = tsk_list_create())){ + TSK_DEBUG_ERROR("Failed to create new list"); + goto done; + } + + //= lock the list + tsk_list_lock(__audioUnitInstances); + + // For iOS we are using full-duplex AudioUnit and to keep it unique for both + // the consumer and producer we use the session id. +#if TARGET_OS_IPHONE + // find the instance from the list + const tsk_list_item_t* item; + tsk_list_foreach(item,__audioUnitInstances){ + if(((tdav_audiounit_instance_t*)item->data)->session_id == session_id){ + inst = tsk_object_ref(item->data); + goto done; + } + } +#endif + + // create instance object and put it into the list + if((inst = tsk_object_new(tdav_audiounit_instance_def_t))){ + OSStatus status = noErr; + tdav_audiounit_instance_t* _inst; + + // create new instance + if((status= AudioComponentInstanceNew(__audioSystem, &inst->audioUnit)) != noErr){ + TSK_DEBUG_ERROR("AudioComponentInstanceNew() failed with status=%ld", (signed long)status); + TSK_OBJECT_SAFE_FREE(inst); + goto done; + } + _inst = inst, _inst->session_id = session_id; + tsk_list_push_back_data(__audioUnitInstances, (void**)&_inst); + } + +done: + //= unlock the list + tsk_list_unlock(__audioUnitInstances); + return (tdav_audiounit_handle_t*)inst; +} + +AudioComponentInstance tdav_audiounit_handle_get_instance(tdav_audiounit_handle_t* self) +{ + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_null; + } + return ((tdav_audiounit_instance_t*)self)->audioUnit; +} + +int tdav_audiounit_handle_signal_consumer_prepared(tdav_audiounit_handle_t* self) +{ + return _tdav_audiounit_handle_signal_xxx_prepared(self, tsk_true); +} + +int tdav_audiounit_handle_signal_producer_prepared(tdav_audiounit_handle_t* self) +{ + return _tdav_audiounit_handle_signal_xxx_prepared(self, tsk_false); +} + +int tdav_audiounit_handle_start(tdav_audiounit_handle_t* self) +{ + tdav_audiounit_instance_t* inst = (tdav_audiounit_instance_t*)self; + OSStatus status = noErr; + if(!inst || !inst->audioUnit){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + tsk_safeobj_lock(inst); + status = (OSStatus)tdav_apple_enable_audio(); + if (status == noErr) { + if ((!inst->started || inst->interrupted) && (status = AudioOutputUnitStart(inst->audioUnit))) { + TSK_DEBUG_ERROR("AudioOutputUnitStart failed with status=%ld", (signed long)status); + } + } + else { + TSK_DEBUG_ERROR("tdav_apple_enable_audio() failed with status=%ld", (signed long)status); + } + inst->started = (status == noErr) ? tsk_true : tsk_false; + if (inst->started) inst->interrupted = 0; + tsk_safeobj_unlock(inst); + return status ? -2 : 0; +} + +uint32_t tdav_audiounit_handle_get_frame_duration(tdav_audiounit_handle_t* self) +{ + if(self){ + return ((tdav_audiounit_instance_t*)self)->frame_duration; + } + return 0; +} + +int tdav_audiounit_handle_configure(tdav_audiounit_handle_t* self, tsk_bool_t consumer, uint32_t ptime, AudioStreamBasicDescription* audioFormat) +{ + OSStatus status = noErr; + tdav_audiounit_instance_t* inst = (tdav_audiounit_instance_t*)self; + + if(!inst || !audioFormat){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + +#if TARGET_OS_IPHONE + // set preferred buffer size + Float32 preferredBufferSize = ((Float32)ptime / 1000.f); // in seconds + UInt32 size = sizeof(preferredBufferSize); + status = AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration, sizeof(preferredBufferSize), &preferredBufferSize); + if(status != noErr){ + TSK_DEBUG_ERROR("AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration) failed with status=%d", (int)status); + TSK_OBJECT_SAFE_FREE(inst); + goto done; + } + status = AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareIOBufferDuration, &size, &preferredBufferSize); + if(status == noErr){ + inst->frame_duration = (preferredBufferSize * 1000); + TSK_DEBUG_INFO("Frame duration=%d", inst->frame_duration); + } + else { + TSK_DEBUG_ERROR("AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareIOBufferDuration, %f) failed", preferredBufferSize); + } + + + UInt32 audioCategory = kAudioSessionCategory_PlayAndRecord; + status = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(audioCategory), &audioCategory); + if(status != noErr){ + TSK_DEBUG_ERROR("AudioSessionSetProperty(kAudioSessionProperty_AudioCategory) failed with status code=%d", (int)status); + goto done; + } + +#elif TARGET_OS_MAC +#if 1 + // set preferred buffer size + UInt32 preferredBufferSize = ((ptime * audioFormat->mSampleRate)/1000); // in bytes + UInt32 size = sizeof(preferredBufferSize); + status = AudioUnitSetProperty(inst->audioUnit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global, 0, &preferredBufferSize, size); + if(status != noErr){ + TSK_DEBUG_ERROR("AudioUnitSetProperty(kAudioOutputUnitProperty_SetInputCallback) failed with status=%ld", (signed long)status); + } + status = AudioUnitGetProperty(inst->audioUnit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global, 0, &preferredBufferSize, &size); + if(status == noErr){ + inst->frame_duration = ((preferredBufferSize * 1000)/audioFormat->mSampleRate); + TSK_DEBUG_INFO("Frame duration=%d", inst->frame_duration); + } + else { + TSK_DEBUG_ERROR("AudioUnitGetProperty(kAudioDevicePropertyBufferFrameSize, %lu) failed", (unsigned long)preferredBufferSize); + } +#endif + +#endif + +done: + return (status == noErr) ? 0 : -2; +} + +int tdav_audiounit_handle_mute(tdav_audiounit_handle_t* self, tsk_bool_t mute) +{ + tdav_audiounit_instance_t* inst = (tdav_audiounit_instance_t*)self; + if(!inst || !inst->audioUnit){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } +#if TARGET_OS_IPHONE + OSStatus status = noErr; + status = AudioUnitSetProperty(inst->audioUnit, kAUVoiceIOProperty_MuteOutput, + kAudioUnitScope_Output, kOutputBus, mute ? &kOne : &kZero, mute ? sizeof(kOne) : sizeof(kZero)); + + return (status == noErr) ? 0 : -2; +#else + return 0; +#endif +} + +int tdav_audiounit_handle_interrupt(tdav_audiounit_handle_t* self, tsk_bool_t interrupt) +{ + tdav_audiounit_instance_t* inst = (tdav_audiounit_instance_t*)self; + if (!inst){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + OSStatus status = noErr; + if (inst->interrupted != interrupt && inst->started) { + if (interrupt) { + status = AudioOutputUnitStop(inst->audioUnit); + if (status != noErr) { + TSK_DEBUG_ERROR("AudioOutputUnitStop failed with status=%ld", (signed long)status); + goto bail; + } + } + else { +#if TARGET_OS_IPHONE + status = (OSStatus)tdav_apple_enable_audio(); + if (status != noErr) { + TSK_DEBUG_ERROR("AudioSessionSetActive failed with status=%ld", (signed long)status); + goto bail; + } +#endif + status = AudioOutputUnitStart(inst->audioUnit); + if (status != noErr) { + TSK_DEBUG_ERROR("AudioOutputUnitStart failed with status=%ld", (signed long)status); + goto bail; + } + } + } + inst->interrupted = interrupt ? 1: 0; +bail: + return (status != noErr) ? -2 : 0; +} + +int tdav_audiounit_handle_stop(tdav_audiounit_handle_t* self) +{ + tdav_audiounit_instance_t* inst = (tdav_audiounit_instance_t*)self; + OSStatus status = noErr; + if(!inst || (inst->started && !inst->audioUnit)){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + tsk_safeobj_lock(inst); + if(inst->started && (status = AudioOutputUnitStop(inst->audioUnit))){ + TSK_DEBUG_ERROR("AudioOutputUnitStop failed with status=%ld", (signed long)status); + } + inst->started = (status == noErr ? tsk_false : tsk_true); + tsk_safeobj_unlock(inst); + return (status != noErr) ? -2 : 0; +} + +int tdav_audiounit_handle_destroy(tdav_audiounit_handle_t** self){ + if(!self || !*self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + tsk_list_lock(__audioUnitInstances); + if(tsk_object_get_refcount(*self)==1){ + tsk_list_remove_item_by_data(__audioUnitInstances, *self); + } + else { + tsk_object_unref(*self); + } + tsk_list_unlock(__audioUnitInstances); + *self = tsk_null; + return 0; +} + +// +// Object definition for and AudioUnit instance +// +static tsk_object_t* tdav_audiounit_instance_ctor(tsk_object_t * self, va_list * app) +{ + tdav_audiounit_instance_t* inst = self; + if(inst){ + tsk_safeobj_init(inst); + } + return self; +} +static tsk_object_t* tdav_audiounit_instance_dtor(tsk_object_t * self) +{ + tdav_audiounit_instance_t* inst = self; + if(inst){ + tsk_safeobj_lock(inst); + if(inst->audioUnit){ + AudioUnitUninitialize(inst->audioUnit); + AudioComponentInstanceDispose(inst->audioUnit); + inst->audioUnit = tsk_null; + } + tsk_safeobj_unlock(inst); + + tsk_safeobj_deinit(inst); + TSK_DEBUG_INFO("*** AudioUnit Instance destroyed ***"); + } + return self; +} +static int tdav_audiounit_instance_cmp(const tsk_object_t *_ai1, const tsk_object_t *_ai2) +{ + return (int)(_ai1 - _ai2); +} +static const tsk_object_def_t tdav_audiounit_instance_def_s = +{ + sizeof(tdav_audiounit_instance_t), + tdav_audiounit_instance_ctor, + tdav_audiounit_instance_dtor, + tdav_audiounit_instance_cmp, +}; +const tsk_object_def_t *tdav_audiounit_instance_def_t = &tdav_audiounit_instance_def_s; + + + +#endif /* HAVE_COREAUDIO_AUDIO_UNIT */ diff --git a/tinyDAV/src/audio/coreaudio/tdav_consumer_audioqueue.c b/tinyDAV/src/audio/coreaudio/tdav_consumer_audioqueue.c new file mode 100644 index 0000000..2f5fd90 --- /dev/null +++ b/tinyDAV/src/audio/coreaudio/tdav_consumer_audioqueue.c @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2010-2011 Mamadou Diop. + * + * Contact: Mamadou Diop <diopmamadou(at)doubango.org> + * + * This file is part of Open Source Doubango Framework. + * + * DOUBANGO is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DOUBANGO is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DOUBANGO. + * + */ + +/**@file tdav_consumer_audioqueue.c + * @brief Audio Consumer for MacOSX and iOS platforms. + * + * @authors + * - Laurent Etiemble <laurent.etiemble(at)gmail.com> + * - Mamadou Diop <diopmamadou(at)doubango(dot)org> + * + * @date Created: Sat Nov 8 16:54:58 2009 letiemble + */ +#include "tinydav/audio/coreaudio/tdav_consumer_audioqueue.h" + + +// http://developer.apple.com/library/mac/#documentation/MusicAudio/Reference/AudioQueueReference/Reference/reference.html +#if HAVE_COREAUDIO_AUDIO_QUEUE + +#include "tsk_string.h" +#include "tsk_thread.h" +#include "tsk_memory.h" +#include "tsk_debug.h" + +static void __handle_output_buffer(void *userdata, AudioQueueRef queue, AudioQueueBufferRef buffer) { + tdav_consumer_audioqueue_t* consumer = (tdav_consumer_audioqueue_t*)userdata; + + if (!consumer->started) { + return; + } + + if(!tdav_consumer_audio_get(TDAV_CONSUMER_AUDIO(consumer), buffer->mAudioData, consumer->buffer_size)){ + // Put silence + memset(buffer->mAudioData, 0, consumer->buffer_size); + } + + // Re-enqueue the buffer + AudioQueueEnqueueBuffer(consumer->queue, buffer, 0, NULL); + // alert the jitter buffer + tdav_consumer_audio_tick(TDAV_CONSUMER_AUDIO(consumer)); +} + +/* ============ Media Consumer Interface ================= */ +#define tdav_consumer_audioqueue_set tsk_null + +int tdav_consumer_audioqueue_prepare(tmedia_consumer_t* self, const tmedia_codec_t* codec) +{ + OSStatus ret; + tsk_size_t i; + tdav_consumer_audioqueue_t* consumer = (tdav_consumer_audioqueue_t*)self; + + if(!consumer || !codec && codec->plugin){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + TMEDIA_CONSUMER(consumer)->audio.ptime = TMEDIA_CODEC_PTIME_AUDIO_DECODING(codec); + TMEDIA_CONSUMER(consumer)->audio.in.channels = TMEDIA_CODEC_CHANNELS_AUDIO_DECODING(codec); + TMEDIA_CONSUMER(consumer)->audio.in.rate = TMEDIA_CODEC_RATE_DECODING(codec); + /* codec should have ptime */ + + // Set audio category +#if TARGET_OS_IPHONE + UInt32 category = kAudioSessionCategory_PlayAndRecord; + AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(category), &category); +#endif + + // Create the audio stream description + AudioStreamBasicDescription *description = &(consumer->description); + description->mSampleRate = TMEDIA_CONSUMER(consumer)->audio.out.rate ? TMEDIA_CONSUMER(consumer)->audio.out.rate : TMEDIA_CONSUMER(consumer)->audio.in.rate; + description->mFormatID = kAudioFormatLinearPCM; + description->mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; + description->mChannelsPerFrame = TMEDIA_CONSUMER(consumer)->audio.in.channels; + description->mFramesPerPacket = 1; + description->mBitsPerChannel = TMEDIA_CONSUMER(consumer)->audio.bits_per_sample; + description->mBytesPerPacket = description->mBitsPerChannel / 8 * description->mChannelsPerFrame; + description->mBytesPerFrame = description->mBytesPerPacket; + description->mReserved = 0; + + int packetperbuffer = 1000 / TMEDIA_CONSUMER(consumer)->audio.ptime; + consumer->buffer_size = description->mSampleRate * description->mBytesPerFrame / packetperbuffer; + + // Create the playback audio queue + ret = AudioQueueNewOutput(&(consumer->description), + __handle_output_buffer, + consumer, + NULL, + NULL, + 0, + &(consumer->queue)); + + for(i = 0; i < CoreAudioPlayBuffers; i++) { + // Create the buffer for the queue + ret = AudioQueueAllocateBuffer(consumer->queue, consumer->buffer_size, &(consumer->buffers[i])); + if (ret) { + break; + } + + // Clear the data + memset(consumer->buffers[i]->mAudioData, 0, consumer->buffer_size); + consumer->buffers[i]->mAudioDataByteSize = consumer->buffer_size; + + // Enqueue the buffer + ret = AudioQueueEnqueueBuffer(consumer->queue, consumer->buffers[i], 0, NULL); + if (ret) { + break; + } + } + + return ret; +} + +int tdav_consumer_audioqueue_start(tmedia_consumer_t* self) +{ + OSStatus ret; + tdav_consumer_audioqueue_t* consumer = (tdav_consumer_audioqueue_t*)self; + + if(!consumer){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + if(consumer->started){ + TSK_DEBUG_WARN("Consumer already started"); + return 0; + } + + consumer->started = tsk_true; + ret = AudioQueueStart(consumer->queue, NULL); + + return ret; +} + +int tdav_consumer_audioqueue_consume(tmedia_consumer_t* self, const void* buffer, tsk_size_t size, const tsk_object_t* proto_hdr) +{ + tdav_consumer_audioqueue_t* consumer = (tdav_consumer_audioqueue_t*)self; + + if(!consumer || !buffer || !size){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + // buffer is already decoded + return tdav_consumer_audio_put(TDAV_CONSUMER_AUDIO(consumer), buffer, size, proto_hdr); +} + +int tdav_consumer_audioqueue_pause(tmedia_consumer_t* self) +{ + OSStatus ret; + tdav_consumer_audioqueue_t* consumer = (tdav_consumer_audioqueue_t*)self; + + if(!consumer){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + ret = AudioQueuePause(consumer->queue); + + return ret; +} + +int tdav_consumer_audioqueue_stop(tmedia_consumer_t* self) +{ + OSStatus ret; + tdav_consumer_audioqueue_t* consumer = (tdav_consumer_audioqueue_t*)self; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + if(!consumer->started){ + TSK_DEBUG_WARN("Consumer not started"); + return 0; + } + + consumer->started = tsk_false; + ret = AudioQueueStop(consumer->queue, false); + + return ret; +} + +// +// coreaudio consumer object definition +// +/* constructor */ +static tsk_object_t* tdav_consumer_audioqueue_ctor(tsk_object_t * self, va_list * app) +{ + tdav_consumer_audioqueue_t *consumer = self; + if(consumer){ + /* init base */ + tdav_consumer_audio_init(TDAV_CONSUMER_AUDIO(consumer)); + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_consumer_audioqueue_dtor(tsk_object_t * self) +{ + tdav_consumer_audioqueue_t *consumer = self; + if(consumer){ + // Stop the consumer if not done + if(consumer->started){ + tdav_consumer_audioqueue_stop(self); + } + + // Free all buffers and dispose the queue + if (consumer->queue) { + tsk_size_t i; + + for(i=0; i<CoreAudioPlayBuffers; i++){ + AudioQueueFreeBuffer(consumer->queue, consumer->buffers[i]); + } + + AudioQueueDispose(consumer->queue, true); + } + + /* deinit base */ + tdav_consumer_audio_deinit(TDAV_CONSUMER_AUDIO(consumer)); + } + + return self; +} + +/* object definition */ +static const tsk_object_def_t tdav_consumer_audioqueue_def_s = +{ + sizeof(tdav_consumer_audioqueue_t), + tdav_consumer_audioqueue_ctor, + tdav_consumer_audioqueue_dtor, + tdav_consumer_audio_cmp, +}; + +/* plugin definition*/ +static const tmedia_consumer_plugin_def_t tdav_consumer_audioqueue_plugin_def_s = +{ + &tdav_consumer_audioqueue_def_s, + + tmedia_audio, + "Apple CoreAudio consumer(AudioQueue)", + + tdav_consumer_audioqueue_set, + tdav_consumer_audioqueue_prepare, + tdav_consumer_audioqueue_start, + tdav_consumer_audioqueue_consume, + tdav_consumer_audioqueue_pause, + tdav_consumer_audioqueue_stop +}; + +const tmedia_consumer_plugin_def_t *tdav_consumer_audioqueue_plugin_def_t = &tdav_consumer_audioqueue_plugin_def_s; + +#endif /* HAVE_COREAUDIO_AUDIO_QUEUE */ diff --git a/tinyDAV/src/audio/coreaudio/tdav_consumer_audiounit.c b/tinyDAV/src/audio/coreaudio/tdav_consumer_audiounit.c new file mode 100644 index 0000000..947d782 --- /dev/null +++ b/tinyDAV/src/audio/coreaudio/tdav_consumer_audiounit.c @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2010-2011 Mamadou Diop. + * + * Contact: Mamadou Diop <diopmamadou(at)doubango.org> + * + * This file is part of Open Source Doubango Framework. + * + * DOUBANGO is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DOUBANGO is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DOUBANGO. + * + */ +#include "tinydav/audio/coreaudio/tdav_consumer_audiounit.h" + +// http://developer.apple.com/library/ios/#documentation/MusicAudio/Conceptual/AudioUnitHostingGuide_iOS/Introduction/Introduction.html%23//apple_ref/doc/uid/TP40009492-CH1-SW1 +// Resampler: http://developer.apple.com/library/mac/#technotes/tn2097/_index.html + +#if HAVE_COREAUDIO_AUDIO_UNIT + +#undef DISABLE_JITTER_BUFFER +#define DISABLE_JITTER_BUFFER 0 + +#include "tsk_debug.h" +#include "tsk_memory.h" +#include "tsk_string.h" + +#define kNoDataError -1 +#define kRingPacketCount +10 + +static tsk_size_t tdav_consumer_audiounit_get(tdav_consumer_audiounit_t* self, void* data, tsk_size_t size); + +static OSStatus __handle_output_buffer(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData) { + OSStatus status = noErr; + // tsk_size_t out_size; + tdav_consumer_audiounit_t* consumer = (tdav_consumer_audiounit_t* )inRefCon; + + if(!consumer->started || consumer->paused){ + goto done; + } + + if(!ioData){ + TSK_DEBUG_ERROR("Invalid argument"); + status = kNoDataError; + goto done; + } + // read from jitter buffer and fill ioData buffers + tsk_mutex_lock(consumer->ring.mutex); + for(int i=0; i<ioData->mNumberBuffers; i++){ + /* int ret = */ tdav_consumer_audiounit_get(consumer, ioData->mBuffers[i].mData, ioData->mBuffers[i].mDataByteSize); + } + tsk_mutex_unlock(consumer->ring.mutex); + +done: + return status; +} + +static tsk_size_t tdav_consumer_audiounit_get(tdav_consumer_audiounit_t* self, void* data, tsk_size_t size) +{ + tsk_ssize_t retSize = 0; + +#if DISABLE_JITTER_BUFFER + retSize = speex_buffer_read(self->ring.buffer, data, size); + if(retSize < size){ + memset(((uint8_t*)data)+retSize, 0, (size - retSize)); + } +#else + self->ring.leftBytes += size; + while (self->ring.leftBytes >= self->ring.chunck.size) { + self->ring.leftBytes -= self->ring.chunck.size; + retSize = (tsk_ssize_t)tdav_consumer_audio_get(TDAV_CONSUMER_AUDIO(self), self->ring.chunck.buffer, self->ring.chunck.size); + tdav_consumer_audio_tick(TDAV_CONSUMER_AUDIO(self)); + speex_buffer_write(self->ring.buffer, self->ring.chunck.buffer, retSize); + } + // IMPORTANT: looks like there is a bug in speex: continously trying to read more than avail + // many times can corrupt the buffer. At least on OS X 1.5 + if(speex_buffer_get_available(self->ring.buffer) >= size){ + retSize = (tsk_ssize_t)speex_buffer_read(self->ring.buffer, data, (int)size); + } + else{ + memset(data, 0, size); + } +#endif + + return retSize; +} + +/* ============ Media Consumer Interface ================= */ +int tdav_consumer_audiounit_set(tmedia_consumer_t* self, const tmedia_param_t* param) +{ + tdav_consumer_audiounit_t* consumer = (tdav_consumer_audiounit_t*)self; + if (param->plugin_type == tmedia_ppt_consumer) { + if (param->value_type == tmedia_pvt_int32) { + if (tsk_striequals(param->key, "interrupt")) { + int32_t interrupt = *((uint8_t*)param->value) ? 1 : 0; + return tdav_audiounit_handle_interrupt(consumer->audioUnitHandle, interrupt); + } + } + } + return tdav_consumer_audio_set(TDAV_CONSUMER_AUDIO(self), param); +} + +static int tdav_consumer_audiounit_prepare(tmedia_consumer_t* self, const tmedia_codec_t* codec) +{ + static UInt32 flagOne = 1; + AudioStreamBasicDescription audioFormat; +#define kOutputBus 0 + + tdav_consumer_audiounit_t* consumer = (tdav_consumer_audiounit_t*)self; + OSStatus status = noErr; + + if(!consumer || !codec || !codec->plugin){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + if(!consumer->audioUnitHandle){ + if(!(consumer->audioUnitHandle = tdav_audiounit_handle_create(TMEDIA_CONSUMER(consumer)->session_id))){ + TSK_DEBUG_ERROR("Failed to get audio unit instance for session with id=%lld", TMEDIA_CONSUMER(consumer)->session_id); + return -3; + } + } + + // enable + status = AudioUnitSetProperty(tdav_audiounit_handle_get_instance(consumer->audioUnitHandle), + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Output, + kOutputBus, + &flagOne, + sizeof(flagOne)); + if(status){ + TSK_DEBUG_ERROR("AudioUnitSetProperty(kAudioOutputUnitProperty_EnableIO) failed with status=%d", (int32_t)status); + return -4; + } + else { + +#if !TARGET_OS_IPHONE // strange: TARGET_OS_MAC is equal to '1' on Smulator + UInt32 param; + + // disable input + param = 0; + status = AudioUnitSetProperty(tdav_audiounit_handle_get_instance(consumer->audioUnitHandle), kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, ¶m, sizeof(UInt32)); + if(status != noErr){ + TSK_DEBUG_ERROR("AudioUnitSetProperty(kAudioOutputUnitProperty_EnableIO) failed with status=%ld", (signed long)status); + return -4; + } + + // set default audio device + param = sizeof(AudioDeviceID); + AudioDeviceID outputDeviceID; + status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, ¶m, &outputDeviceID); + if(status != noErr){ + TSK_DEBUG_ERROR("AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice) failed with status=%ld", (signed long)status); + return -4; + } + + // set the current device to the default input unit + status = AudioUnitSetProperty(tdav_audiounit_handle_get_instance(consumer->audioUnitHandle), + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, + 0, + &outputDeviceID, + sizeof(AudioDeviceID)); + if(status != noErr){ + TSK_DEBUG_ERROR("AudioUnitSetProperty(kAudioOutputUnitProperty_CurrentDevice) failed with status=%ld", (signed long)status); + return -4; + } + +#endif + + TMEDIA_CONSUMER(consumer)->audio.ptime = TMEDIA_CODEC_PTIME_AUDIO_DECODING(codec); + TMEDIA_CONSUMER(consumer)->audio.in.channels = TMEDIA_CODEC_CHANNELS_AUDIO_DECODING(codec); + TMEDIA_CONSUMER(consumer)->audio.in.rate = TMEDIA_CODEC_RATE_DECODING(codec); + + TSK_DEBUG_INFO("AudioUnit consumer: in.channels=%d, out.channles=%d, in.rate=%d, out.rate=%d, ptime=%d", + TMEDIA_CONSUMER(consumer)->audio.in.channels, + TMEDIA_CONSUMER(consumer)->audio.out.channels, + TMEDIA_CONSUMER(consumer)->audio.in.rate, + TMEDIA_CONSUMER(consumer)->audio.out.rate, + TMEDIA_CONSUMER(consumer)->audio.ptime); + + audioFormat.mSampleRate = TMEDIA_CONSUMER(consumer)->audio.out.rate ? TMEDIA_CONSUMER(consumer)->audio.out.rate : TMEDIA_CONSUMER(consumer)->audio.in.rate; + audioFormat.mFormatID = kAudioFormatLinearPCM; + audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; + audioFormat.mChannelsPerFrame = TMEDIA_CONSUMER(consumer)->audio.in.channels; + audioFormat.mFramesPerPacket = 1; + audioFormat.mBitsPerChannel = TMEDIA_CONSUMER(consumer)->audio.bits_per_sample; + audioFormat.mBytesPerPacket = audioFormat.mBitsPerChannel / 8 * audioFormat.mChannelsPerFrame; + audioFormat.mBytesPerFrame = audioFormat.mBytesPerPacket; + audioFormat.mReserved = 0; + status = AudioUnitSetProperty(tdav_audiounit_handle_get_instance(consumer->audioUnitHandle), + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + kOutputBus, + &audioFormat, + sizeof(audioFormat)); + + if(status){ + TSK_DEBUG_ERROR("AudioUnitSetProperty(kAudioUnitProperty_StreamFormat) failed with status=%ld", (signed long)status); + return -5; + } + else { + // configure + if(tdav_audiounit_handle_configure(consumer->audioUnitHandle, tsk_true, TMEDIA_CONSUMER(consumer)->audio.ptime, &audioFormat)){ + TSK_DEBUG_ERROR("tdav_audiounit_handle_set_rate(%d) failed", TMEDIA_CONSUMER(consumer)->audio.out.rate); + return -4; + } + + // set callback function + AURenderCallbackStruct callback; + callback.inputProc = __handle_output_buffer; + callback.inputProcRefCon = consumer; + status = AudioUnitSetProperty(tdav_audiounit_handle_get_instance(consumer->audioUnitHandle), + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, + kOutputBus, + &callback, + sizeof(callback)); + if(status){ + TSK_DEBUG_ERROR("AudioUnitSetProperty(kAudioOutputUnitProperty_SetInputCallback) failed with status=%ld", (signed long)status); + return -6; + } + } + } + + // allocate the chunck buffer and create the ring + consumer->ring.chunck.size = (TMEDIA_CONSUMER(consumer)->audio.ptime * audioFormat.mSampleRate * audioFormat.mBytesPerFrame) / 1000; + consumer->ring.size = kRingPacketCount * consumer->ring.chunck.size; + if(!(consumer->ring.chunck.buffer = tsk_realloc(consumer->ring.chunck.buffer, consumer->ring.chunck.size))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + return -7; + } + if(!consumer->ring.buffer){ + consumer->ring.buffer = speex_buffer_init((int)consumer->ring.size); + } + else { + int ret; + if((ret = (int)speex_buffer_resize(consumer->ring.buffer, (int)consumer->ring.size)) < 0){ + TSK_DEBUG_ERROR("speex_buffer_resize(%d) failed with error code=%d", (int)consumer->ring.size, ret); + return ret; + } + } + if(!consumer->ring.buffer){ + TSK_DEBUG_ERROR("Failed to create a new ring buffer with size = %d", (int)consumer->ring.size); + return -8; + } + if(!consumer->ring.mutex && !(consumer->ring.mutex = tsk_mutex_create_2(tsk_false))){ + TSK_DEBUG_ERROR("Failed to create mutex"); + return -9; + } + + // set maximum frames per slice as buffer size + //UInt32 numFrames = (UInt32)consumer->ring.chunck.size; + //status = AudioUnitSetProperty(tdav_audiounit_handle_get_instance(consumer->audioUnitHandle), + // kAudioUnitProperty_MaximumFramesPerSlice, + // kAudioUnitScope_Global, + // 0, + // &numFrames, + // sizeof(numFrames)); + //if(status){ + // TSK_DEBUG_ERROR("AudioUnitSetProperty(kAudioUnitProperty_MaximumFramesPerSlice, %u) failed with status=%d", (unsigned)numFrames, (int32_t)status); + // return -6; + //} + + TSK_DEBUG_INFO("AudioUnit consumer prepared"); + return tdav_audiounit_handle_signal_consumer_prepared(consumer->audioUnitHandle); +} + +static int tdav_consumer_audiounit_start(tmedia_consumer_t* self) +{ + tdav_consumer_audiounit_t* consumer = (tdav_consumer_audiounit_t*)self; + + if(!consumer){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + if(consumer->paused){ + consumer->paused = tsk_false; + } + if(consumer->started){ + TSK_DEBUG_WARN("Already started"); + return 0; + } + else { + int ret = tdav_audiounit_handle_start(consumer->audioUnitHandle); + if(ret){ + TSK_DEBUG_ERROR("tdav_audiounit_handle_start failed with error code=%d", ret); + return ret; + } + } + consumer->started = tsk_true; + TSK_DEBUG_INFO("AudioUnit consumer started"); + return 0; +} + +static int tdav_consumer_audiounit_consume(tmedia_consumer_t* self, const void* buffer, tsk_size_t size, const tsk_object_t* proto_hdr) +{ + tdav_consumer_audiounit_t* consumer = (tdav_consumer_audiounit_t*)self; + if(!consumer || !buffer || !size){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } +#if DISABLE_JITTER_BUFFER + { + if(consumer->ring.buffer){ + tsk_mutex_lock(consumer->ring.mutex); + speex_buffer_write(consumer->ring.buffer, (void*)buffer, size); + tsk_mutex_unlock(consumer->ring.mutex); + return 0; + } + return -2; + } +#else + { + return tdav_consumer_audio_put(TDAV_CONSUMER_AUDIO(consumer), buffer, size, proto_hdr); + } +#endif +} + +static int tdav_consumer_audiounit_pause(tmedia_consumer_t* self) +{ + tdav_consumer_audiounit_t* consumer = (tdav_consumer_audiounit_t*)self; + if(!consumer){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + consumer->paused = tsk_true; + TSK_DEBUG_INFO("AudioUnit consumer paused"); + return 0; +} + +static int tdav_consumer_audiounit_stop(tmedia_consumer_t* self) +{ + tdav_consumer_audiounit_t* consumer = (tdav_consumer_audiounit_t*)self; + + if(!consumer){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + if(!consumer->started){ + TSK_DEBUG_INFO("Not started"); + return 0; + } + else { + int ret = tdav_audiounit_handle_stop(consumer->audioUnitHandle); + if(ret){ + TSK_DEBUG_ERROR("tdav_audiounit_handle_stop failed with error code=%d", ret); + return ret; + } + } +#if TARGET_OS_IPHONE + //https://devforums.apple.com/thread/118595 + if(consumer->audioUnitHandle){ + tdav_audiounit_handle_destroy(&consumer->audioUnitHandle); + } +#endif + + consumer->started = tsk_false; + TSK_DEBUG_INFO("AudioUnit consumer stoppped"); + return 0; + +} + +// +// coreaudio consumer (AudioUnit) object definition +// +/* constructor */ +static tsk_object_t* tdav_consumer_audiounit_ctor(tsk_object_t * self, va_list * app) +{ + tdav_consumer_audiounit_t *consumer = self; + if(consumer){ + /* init base */ + tdav_consumer_audio_init(TDAV_CONSUMER_AUDIO(consumer)); + /* init self */ + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_consumer_audiounit_dtor(tsk_object_t * self) +{ + tdav_consumer_audiounit_t *consumer = self; + if(consumer){ + /* deinit self */ + // Stop the consumer if not done + if(consumer->started){ + tdav_consumer_audiounit_stop(self); + } + // destroy handle + if(consumer->audioUnitHandle){ + tdav_audiounit_handle_destroy(&consumer->audioUnitHandle); + } + TSK_FREE(consumer->ring.chunck.buffer); + if(consumer->ring.buffer){ + speex_buffer_destroy(consumer->ring.buffer); + } + if(consumer->ring.mutex){ + tsk_mutex_destroy(&consumer->ring.mutex); + } + + /* deinit base */ + tdav_consumer_audio_deinit(TDAV_CONSUMER_AUDIO(consumer)); + TSK_DEBUG_INFO("*** AudioUnit Consumer destroyed ***"); + } + + return self; +} + +/* object definition */ +static const tsk_object_def_t tdav_consumer_audiounit_def_s = +{ + sizeof(tdav_consumer_audiounit_t), + tdav_consumer_audiounit_ctor, + tdav_consumer_audiounit_dtor, + tdav_consumer_audio_cmp, +}; + +/* plugin definition*/ +static const tmedia_consumer_plugin_def_t tdav_consumer_audiounit_plugin_def_s = +{ + &tdav_consumer_audiounit_def_s, + + tmedia_audio, + "Apple CoreAudio consumer(AudioUnit)", + + tdav_consumer_audiounit_set, + tdav_consumer_audiounit_prepare, + tdav_consumer_audiounit_start, + tdav_consumer_audiounit_consume, + tdav_consumer_audiounit_pause, + tdav_consumer_audiounit_stop +}; + +const tmedia_consumer_plugin_def_t *tdav_consumer_audiounit_plugin_def_t = &tdav_consumer_audiounit_plugin_def_s; + +#endif /* HAVE_COREAUDIO_AUDIO_UNIT */ diff --git a/tinyDAV/src/audio/coreaudio/tdav_producer_audioqueue.c b/tinyDAV/src/audio/coreaudio/tdav_producer_audioqueue.c new file mode 100644 index 0000000..d96fd67 --- /dev/null +++ b/tinyDAV/src/audio/coreaudio/tdav_producer_audioqueue.c @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2010-2011 Mamadou Diop. + * + * Contact: Mamadou Diop <diopmamadou(at)doubango.org> + * + * This file is part of Open Source Doubango Framework. + * + * DOUBANGO is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DOUBANGO is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DOUBANGO. + * + */ + +/**@file tdav_producer_audioqueue.c + * @brief Audio Producer for MacOSX and iOS platforms using AudioQueue. + * + * @authors + * - Laurent Etiemble <laurent.etiemble(at)gmail.com> + * - Mamadou Diop <diopmamadou(at)doubango(dot)org> + * + * @date Created: Sat Nov 8 16:54:58 2009 letiemble + */ +#include "tinydav/audio/coreaudio/tdav_producer_audioqueue.h" + + +// http://developer.apple.com/library/mac/#documentation/MusicAudio/Reference/AudioQueueReference/Reference/reference.html + +#if HAVE_COREAUDIO_AUDIO_QUEUE + +#include "tsk_string.h" +#include "tsk_thread.h" +#include "tsk_memory.h" +#include "tsk_debug.h" + +static void __handle_input_buffer (void *userdata, AudioQueueRef queue, AudioQueueBufferRef buffer, const AudioTimeStamp *start_time, UInt32 number_packet_descriptions, const AudioStreamPacketDescription *packet_descriptions ) { + tdav_producer_audioqueue_t* producer = (tdav_producer_audioqueue_t*)userdata; + + if (!producer->started) { + return; + } + + // Alert the session that there is new data to send + if(TMEDIA_PRODUCER(producer)->enc_cb.callback) { + TMEDIA_PRODUCER(producer)->enc_cb.callback(TMEDIA_PRODUCER(producer)->enc_cb.callback_data, buffer->mAudioData, buffer->mAudioDataByteSize); + } + + // Re-enqueue the buffer + AudioQueueEnqueueBuffer(producer->queue, buffer, 0, NULL); +} + +/* ============ Media Producer Interface ================= */ +#define tdav_producer_audioqueue_set tsk_null + +static int tdav_producer_audioqueue_prepare(tmedia_producer_t* self, const tmedia_codec_t* codec) +{ + OSStatus ret; + tsk_size_t i; + tdav_producer_audioqueue_t* producer = (tdav_producer_audioqueue_t*)self; + + if(!producer || !codec && codec->plugin){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + TMEDIA_PRODUCER(producer)->audio.channels = TMEDIA_CODEC_CHANNELS_AUDIO_ENCODING(codec); + TMEDIA_PRODUCER(producer)->audio.rate = TMEDIA_CODEC_RATE_ENCODING(codec); + TMEDIA_PRODUCER(producer)->audio.ptime = TMEDIA_CODEC_PTIME_AUDIO_ENCODING(codec); + /* codec should have ptime */ + + + // Set audio category +#if TARGET_OS_IPHONE + UInt32 category = kAudioSessionCategory_PlayAndRecord; + AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(category), &category); +#endif + // Create the audio stream description + AudioStreamBasicDescription *description = &(producer->description); + description->mSampleRate = TMEDIA_PRODUCER(producer)->audio.rate; + description->mFormatID = kAudioFormatLinearPCM; + description->mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; + description->mChannelsPerFrame = TMEDIA_PRODUCER(producer)->audio.channels; + description->mFramesPerPacket = 1; + description->mBitsPerChannel = TMEDIA_PRODUCER(producer)->audio.bits_per_sample; + description->mBytesPerPacket = description->mBitsPerChannel / 8 * description->mChannelsPerFrame; + description->mBytesPerFrame = description->mBytesPerPacket; + description->mReserved = 0; + + int packetperbuffer = 1000 / TMEDIA_PRODUCER(producer)->audio.ptime; + producer->buffer_size = description->mSampleRate * description->mBytesPerFrame / packetperbuffer; + + // Create the record audio queue + ret = AudioQueueNewInput(&(producer->description), + __handle_input_buffer, + producer, + NULL, + kCFRunLoopCommonModes, + 0, + &(producer->queue)); + + for(i = 0; i < CoreAudioRecordBuffers; i++) { + // Create the buffer for the queue + ret = AudioQueueAllocateBuffer(producer->queue, producer->buffer_size, &(producer->buffers[i])); + if (ret) { + break; + } + + // Clear the data + memset(producer->buffers[i]->mAudioData, 0, producer->buffer_size); + producer->buffers[i]->mAudioDataByteSize = producer->buffer_size; + + // Enqueue the buffer + ret = AudioQueueEnqueueBuffer(producer->queue, producer->buffers[i], 0, NULL); + if (ret) { + break; + } + } + + return 0; +} + +static int tdav_producer_audioqueue_start(tmedia_producer_t* self) +{ + OSStatus ret; + tdav_producer_audioqueue_t* producer = (tdav_producer_audioqueue_t*)self; + + if(!producer){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + if(producer->started){ + TSK_DEBUG_WARN("Producer already started"); + return 0; + } + + producer->started = tsk_true; + ret = AudioQueueStart(producer->queue, NULL); + + return ret; +} + +static int tdav_producer_audioqueue_pause(tmedia_producer_t* self) +{ + OSStatus ret; + tdav_producer_audioqueue_t* producer = (tdav_producer_audioqueue_t*)self; + + if(!producer){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + ret = AudioQueuePause(producer->queue); + + return ret; +} + +static int tdav_producer_audioqueue_stop(tmedia_producer_t* self) +{ + OSStatus ret; + tdav_producer_audioqueue_t* producer = (tdav_producer_audioqueue_t*)self; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + if(!producer->started){ + TSK_DEBUG_WARN("Producer not started"); + return 0; + } + + producer->started = tsk_false; + ret = AudioQueueStop(producer->queue, false); + + return ret; +} + + +// +// CoreAudio producer object definition +// +/* constructor */ +static tsk_object_t* tdav_producer_audioqueue_ctor(tsk_object_t * self, va_list * app) +{ + tdav_producer_audioqueue_t *producer = self; + if(producer){ + /* init base */ + tdav_producer_audio_init(TDAV_PRODUCER_AUDIO(producer)); + /* init self */ + // TODO + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_producer_audioqueue_dtor(tsk_object_t * self) +{ + tdav_producer_audioqueue_t *producer = self; + if(producer){ + // Stop the producer if not done + if(producer->started){ + tdav_producer_audioqueue_stop(self); + } + + // Free all buffers and dispose the queue + if (producer->queue) { + tsk_size_t i; + + for(i=0; i<CoreAudioRecordBuffers; i++){ + AudioQueueFreeBuffer(producer->queue, producer->buffers[i]); + } + AudioQueueDispose(producer->queue, true); + } + + /* deinit base */ + tdav_producer_audio_deinit(TDAV_PRODUCER_AUDIO(producer)); + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_producer_audioqueue_def_s = +{ + sizeof(tdav_producer_audioqueue_t), + tdav_producer_audioqueue_ctor, + tdav_producer_audioqueue_dtor, + tdav_producer_audio_cmp, +}; +/* plugin definition*/ +static const tmedia_producer_plugin_def_t tdav_producer_audioqueue_plugin_def_s = +{ + &tdav_producer_audioqueue_def_s, + + tmedia_audio, + "Apple CoreAudio producer (AudioQueue)", + + tdav_producer_audioqueue_set, + tdav_producer_audioqueue_prepare, + tdav_producer_audioqueue_start, + tdav_producer_audioqueue_pause, + tdav_producer_audioqueue_stop +}; +const tmedia_producer_plugin_def_t *tdav_producer_audioqueue_plugin_def_t = &tdav_producer_audioqueue_plugin_def_s; + +#endif /* HAVE_COREAUDIO_AUDIO_QUEUE */ diff --git a/tinyDAV/src/audio/coreaudio/tdav_producer_audiounit.c b/tinyDAV/src/audio/coreaudio/tdav_producer_audiounit.c new file mode 100644 index 0000000..a88261e --- /dev/null +++ b/tinyDAV/src/audio/coreaudio/tdav_producer_audiounit.c @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2010-2011 Mamadou Diop. + * + * Contact: Mamadou Diop <diopmamadou(at)doubango.org> + * + * This file is part of Open Source Doubango Framework. + * + * DOUBANGO is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DOUBANGO is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DOUBANGO. + * + */ +#include "tinydav/audio/coreaudio/tdav_producer_audiounit.h" + +// http://developer.apple.com/library/ios/#documentation/MusicAudio/Conceptual/AudioUnitHostingGuide_iOS/Introduction/Introduction.html%23//apple_ref/doc/uid/TP40009492-CH1-SW1 + +#if HAVE_COREAUDIO_AUDIO_UNIT + +#include <mach/mach.h> +#import <sys/sysctl.h> + +#include "tsk_string.h" +#include "tsk_memory.h" +#include "tsk_thread.h" +#include "tsk_debug.h" + +#define kRingPacketCount 10 + +static OSStatus __handle_input_buffer(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData) { + OSStatus status = noErr; + tdav_producer_audiounit_t* producer = (tdav_producer_audiounit_t*)inRefCon; + + // holder + AudioBuffer buffer; + buffer.mData = tsk_null; + buffer.mDataByteSize = 0; + buffer.mNumberChannels = TMEDIA_PRODUCER(producer)->audio.channels; + + // list of holders + AudioBufferList buffers; + buffers.mNumberBuffers = 1; + buffers.mBuffers[0] = buffer; + + // render to get frames from the system + status = AudioUnitRender(tdav_audiounit_handle_get_instance(producer->audioUnitHandle), + ioActionFlags, + inTimeStamp, + inBusNumber, + inNumberFrames, + &buffers); + if(status == 0){ + // must not be done on async thread: doing it gives bad audio quality when audio+video call is done with CPU consuming codec (e.g. speex or g729) + speex_buffer_write(producer->ring.buffer, buffers.mBuffers[0].mData, buffers.mBuffers[0].mDataByteSize); + int avail = speex_buffer_get_available(producer->ring.buffer); + while (producer->started && avail >= producer->ring.chunck.size) { + avail -= speex_buffer_read(producer->ring.buffer, (void*)producer->ring.chunck.buffer, (int)producer->ring.chunck.size); + TMEDIA_PRODUCER(producer)->enc_cb.callback(TMEDIA_PRODUCER(producer)->enc_cb.callback_data, + producer->ring.chunck.buffer, producer->ring.chunck.size); + } + } + + return status; +} + +/* ============ Media Producer Interface ================= */ +int tdav_producer_audiounit_set(tmedia_producer_t* self, const tmedia_param_t* param) +{ + tdav_producer_audiounit_t* producer = (tdav_producer_audiounit_t*)self; + if(param->plugin_type == tmedia_ppt_producer){ + if(param->value_type == tmedia_pvt_int32){ + if (tsk_striequals(param->key, "mute")) { + producer->muted = TSK_TO_INT32((uint8_t*)param->value); + return tdav_audiounit_handle_mute(((tdav_producer_audiounit_t*)self)->audioUnitHandle, producer->muted); + } + else if (tsk_striequals(param->key, "interrupt")) { + int32_t interrupt = *((uint8_t*)param->value) ? 1 : 0; + return tdav_audiounit_handle_interrupt(producer->audioUnitHandle, interrupt); + } + } + } + return tdav_producer_audio_set(TDAV_PRODUCER_AUDIO(self), param); +} + +static int tdav_producer_audiounit_prepare(tmedia_producer_t* self, const tmedia_codec_t* codec) +{ + static UInt32 flagOne = 1; + UInt32 param; + // static UInt32 flagZero = 0; +#define kInputBus 1 + + tdav_producer_audiounit_t* producer = (tdav_producer_audiounit_t*)self; + OSStatus status = noErr; + AudioStreamBasicDescription audioFormat; + AudioStreamBasicDescription deviceFormat; + + if(!producer || !codec || !codec->plugin){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + if(!producer->audioUnitHandle){ + if(!(producer->audioUnitHandle = tdav_audiounit_handle_create(TMEDIA_PRODUCER(producer)->session_id))){ + TSK_DEBUG_ERROR("Failed to get audio unit instance for session with id=%lld", TMEDIA_PRODUCER(producer)->session_id); + return -3; + } + } + + // enable + status = AudioUnitSetProperty(tdav_audiounit_handle_get_instance(producer->audioUnitHandle), + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Input, + kInputBus, + &flagOne, + sizeof(flagOne)); + if(status != noErr){ + TSK_DEBUG_ERROR("AudioUnitSetProperty(kAudioOutputUnitProperty_EnableIO) failed with status=%ld", (signed long)status); + return -4; + } + else { +#if !TARGET_OS_IPHONE // strange: TARGET_OS_MAC is equal to '1' on Smulator + // disable output + param = 0; + status = AudioUnitSetProperty(tdav_audiounit_handle_get_instance(producer->audioUnitHandle), + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Output, + 0, + ¶m, + sizeof(UInt32)); + if(status != noErr){ + TSK_DEBUG_ERROR("AudioUnitSetProperty(kAudioOutputUnitProperty_EnableIO) failed with status=%ld", (signed long)status); + return -4; + } + + // set default audio device + param = sizeof(AudioDeviceID); + AudioDeviceID inputDeviceID; + status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, ¶m, &inputDeviceID); + if(status != noErr){ + TSK_DEBUG_ERROR("AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice) failed with status=%ld", (signed long)status); + return -4; + } + + // set the current device to the default input unit + status = AudioUnitSetProperty(tdav_audiounit_handle_get_instance(producer->audioUnitHandle), + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Output, + 0, + &inputDeviceID, + sizeof(AudioDeviceID)); + if(status != noErr){ + TSK_DEBUG_ERROR("AudioUnitSetProperty(kAudioOutputUnitProperty_CurrentDevice) failed with status=%ld", (signed long)status); + return -4; + } +#endif /* TARGET_OS_MAC */ + + /* codec should have ptime */ + TMEDIA_PRODUCER(producer)->audio.channels = TMEDIA_CODEC_CHANNELS_AUDIO_ENCODING(codec); + TMEDIA_PRODUCER(producer)->audio.rate = TMEDIA_CODEC_RATE_ENCODING(codec); + TMEDIA_PRODUCER(producer)->audio.ptime = TMEDIA_CODEC_PTIME_AUDIO_ENCODING(codec); + + TSK_DEBUG_INFO("AudioUnit producer: channels=%d, rate=%d, ptime=%d", + TMEDIA_PRODUCER(producer)->audio.channels, + TMEDIA_PRODUCER(producer)->audio.rate, + TMEDIA_PRODUCER(producer)->audio.ptime); + + // get device format + param = sizeof(AudioStreamBasicDescription); + status = AudioUnitGetProperty(tdav_audiounit_handle_get_instance(producer->audioUnitHandle), + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + kInputBus, + &deviceFormat, ¶m); + if(status == noErr && deviceFormat.mSampleRate){ +#if TARGET_OS_IPHONE + // iOS support 8Khz, 16kHz and 32kHz => do not override the sampleRate +#elif TARGET_OS_MAC + // For example, iSight supports only 48kHz + TMEDIA_PRODUCER(producer)->audio.rate = deviceFormat.mSampleRate; +#endif + } + + // set format + audioFormat.mSampleRate = TMEDIA_PRODUCER(producer)->audio.rate; + audioFormat.mFormatID = kAudioFormatLinearPCM; + audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagIsNonInterleaved; + audioFormat.mChannelsPerFrame = TMEDIA_PRODUCER(producer)->audio.channels; + audioFormat.mFramesPerPacket = 1; + audioFormat.mBitsPerChannel = TMEDIA_PRODUCER(producer)->audio.bits_per_sample; + audioFormat.mBytesPerPacket = audioFormat.mBitsPerChannel / 8 * audioFormat.mChannelsPerFrame; + audioFormat.mBytesPerFrame = audioFormat.mBytesPerPacket; + audioFormat.mReserved = 0; + if(audioFormat.mFormatID == kAudioFormatLinearPCM && audioFormat.mChannelsPerFrame == 1){ + audioFormat.mFormatFlags &= ~kLinearPCMFormatFlagIsNonInterleaved; + } + status = AudioUnitSetProperty(tdav_audiounit_handle_get_instance(producer->audioUnitHandle), + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + kInputBus, + &audioFormat, + sizeof(audioFormat)); + if(status){ + TSK_DEBUG_ERROR("AudioUnitSetProperty(kAudioUnitProperty_StreamFormat) failed with status=%ld", (signed long)status); + return -5; + } + else { + + // configure + if(tdav_audiounit_handle_configure(producer->audioUnitHandle, tsk_false, TMEDIA_PRODUCER(producer)->audio.ptime, &audioFormat)){ + TSK_DEBUG_ERROR("tdav_audiounit_handle_set_rate(%d) failed", TMEDIA_PRODUCER(producer)->audio.rate); + return -4; + } + + // set callback function + AURenderCallbackStruct callback; + callback.inputProc = __handle_input_buffer; + callback.inputProcRefCon = producer; + status = AudioUnitSetProperty(tdav_audiounit_handle_get_instance(producer->audioUnitHandle), + kAudioOutputUnitProperty_SetInputCallback, + kAudioUnitScope_Output, + kInputBus, + &callback, + sizeof(callback)); + if(status){ + TSK_DEBUG_ERROR("AudioUnitSetProperty(kAudioOutputUnitProperty_SetInputCallback) failed with status=%ld", (signed long)status); + return -6; + } + else { + // disbale buffer allocation as we will provide ours + //status = AudioUnitSetProperty(tdav_audiounit_handle_get_instance(producer->audioUnitHandle), + // kAudioUnitProperty_ShouldAllocateBuffer, + // kAudioUnitScope_Output, + // kInputBus, + // &flagZero, + // sizeof(flagZero)); + + producer->ring.chunck.size = (TMEDIA_PRODUCER(producer)->audio.ptime * audioFormat.mSampleRate * audioFormat.mBytesPerFrame) / 1000; + // allocate our chunck buffer + if(!(producer->ring.chunck.buffer = tsk_realloc(producer->ring.chunck.buffer, producer->ring.chunck.size))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + return -7; + } + // create ringbuffer + producer->ring.size = kRingPacketCount * producer->ring.chunck.size; + if(!producer->ring.buffer){ + producer->ring.buffer = speex_buffer_init((int)producer->ring.size); + } + else { + int ret; + if((ret = speex_buffer_resize(producer->ring.buffer, producer->ring.size)) < 0){ + TSK_DEBUG_ERROR("speex_buffer_resize(%d) failed with error code=%d", (int)producer->ring.size, ret); + return ret; + } + } + if(!producer->ring.buffer){ + TSK_DEBUG_ERROR("Failed to create a new ring buffer with size = %d", (int)producer->ring.size); + return -9; + } + } + + } + } + + TSK_DEBUG_INFO("AudioUnit producer prepared"); + return tdav_audiounit_handle_signal_producer_prepared(producer->audioUnitHandle);; +} + +static int tdav_producer_audiounit_start(tmedia_producer_t* self) +{ + tdav_producer_audiounit_t* producer = (tdav_producer_audiounit_t*)self; + + if(!producer){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + if(producer->paused){ + producer->paused = tsk_false; + return tsk_false; + } + + int ret; + if(producer->started){ + TSK_DEBUG_WARN("Already started"); + return 0; + } + else { + ret = tdav_audiounit_handle_start(producer->audioUnitHandle); + if(ret){ + TSK_DEBUG_ERROR("tdav_audiounit_handle_start failed with error code=%d", ret); + return ret; + } + } + producer->started = tsk_true; + + // apply parameters (because could be lost when the producer is restarted -handle recreated-) + ret = tdav_audiounit_handle_mute(producer->audioUnitHandle, producer->muted); + + TSK_DEBUG_INFO("AudioUnit producer started"); + return 0; +} + +static int tdav_producer_audiounit_pause(tmedia_producer_t* self) +{ + tdav_producer_audiounit_t* producer = (tdav_producer_audiounit_t*)self; + if(!producer){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + producer->paused = tsk_true; + TSK_DEBUG_INFO("AudioUnit producer paused"); + return 0; +} + +static int tdav_producer_audiounit_stop(tmedia_producer_t* self) +{ + tdav_producer_audiounit_t* producer = (tdav_producer_audiounit_t*)self; + + if(!producer){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + if(!producer->started){ + TSK_DEBUG_INFO("Not started"); + return 0; + } + else { + int ret = tdav_audiounit_handle_stop(producer->audioUnitHandle); + if(ret){ + TSK_DEBUG_ERROR("tdav_audiounit_handle_stop failed with error code=%d", ret); + // do not return even if failed => we MUST stop the thread! + } +#if TARGET_OS_IPHONE + //https://devforums.apple.com/thread/118595 + if(producer->audioUnitHandle){ + tdav_audiounit_handle_destroy(&producer->audioUnitHandle); + } +#endif + } + producer->started = tsk_false; + TSK_DEBUG_INFO("AudioUnit producer stoppped"); + return 0; +} + + +// +// CoreAudio producer object definition +// +/* constructor */ +static tsk_object_t* tdav_producer_audiounit_ctor(tsk_object_t * self, va_list * app) +{ + tdav_producer_audiounit_t *producer = self; + if(producer){ + /* init base */ + tdav_producer_audio_init(TDAV_PRODUCER_AUDIO(producer)); + /* init self */ + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_producer_audiounit_dtor(tsk_object_t * self) +{ + tdav_producer_audiounit_t *producer = self; + if(producer){ + // Stop the producer if not done + if(producer->started){ + tdav_producer_audiounit_stop(self); + } + + // Free all buffers and dispose the queue + if (producer->audioUnitHandle) { + tdav_audiounit_handle_destroy(&producer->audioUnitHandle); + } + TSK_FREE(producer->ring.chunck.buffer); + if(producer->ring.buffer){ + speex_buffer_destroy(producer->ring.buffer); + } + /* deinit base */ + tdav_producer_audio_deinit(TDAV_PRODUCER_AUDIO(producer)); + + TSK_DEBUG_INFO("*** AudioUnit Producer destroyed ***"); + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_producer_audiounit_def_s = +{ + sizeof(tdav_producer_audiounit_t), + tdav_producer_audiounit_ctor, + tdav_producer_audiounit_dtor, + tdav_producer_audio_cmp, +}; +/* plugin definition*/ +static const tmedia_producer_plugin_def_t tdav_producer_audiounit_plugin_def_s = +{ + &tdav_producer_audiounit_def_s, + + tmedia_audio, + "Apple CoreAudio producer (AudioUnit)", + + tdav_producer_audiounit_set, + tdav_producer_audiounit_prepare, + tdav_producer_audiounit_start, + tdav_producer_audiounit_pause, + tdav_producer_audiounit_stop +}; +const tmedia_producer_plugin_def_t *tdav_producer_audiounit_plugin_def_t = &tdav_producer_audiounit_plugin_def_s; + + +#endif /* HAVE_COREAUDIO_AUDIO_UNIT */ |