From 631fffee8a28b1bec5ed1f1d26a20e0135967f99 Mon Sep 17 00:00:00 2001 From: Mamadou DIOP Date: Mon, 17 Aug 2015 01:56:35 +0200 Subject: - --- .../src/audio/coreaudio/tdav_producer_audiounit.c | 422 +++++++++++++++++++++ 1 file changed, 422 insertions(+) create mode 100644 tinyDAV/src/audio/coreaudio/tdav_producer_audiounit.c (limited to 'tinyDAV/src/audio/coreaudio/tdav_producer_audiounit.c') 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 + * + * 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 +#import + +#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 */ -- cgit v1.1