diff options
Diffstat (limited to 'tinyDAV/src/audio/coreaudio/tdav_consumer_audiounit.c')
-rw-r--r-- | tinyDAV/src/audio/coreaudio/tdav_consumer_audiounit.c | 447 |
1 files changed, 447 insertions, 0 deletions
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 */ |