diff options
author | Poommetee Ketson <poommetee@protonmail.com> | 2017-10-02 23:37:55 +0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-10-02 23:37:55 +0700 |
commit | 3f4e973a60e20051bf4a51a93048d84d2c83a0c9 (patch) | |
tree | ab216bf42a21b9f1bc51dec677c8760bd77d8549 /drivers | |
parent | fcc209590977f486243860c48957a518bdd37564 (diff) | |
parent | 12d40fa03f95cccfb6af994b03576f78af314891 (diff) |
Merge pull request #11716 from marcelofg55/coreaudio_driver
Merged iphone and osx audio drivers into drivers/coreaudio
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/SCsub | 1 | ||||
-rw-r--r-- | drivers/coreaudio/SCsub | 8 | ||||
-rw-r--r-- | drivers/coreaudio/audio_driver_coreaudio.cpp | 315 | ||||
-rw-r--r-- | drivers/coreaudio/audio_driver_coreaudio.h | 89 |
4 files changed, 413 insertions, 0 deletions
diff --git a/drivers/SCsub b/drivers/SCsub index 195f7ec438..34d6254578 100644 --- a/drivers/SCsub +++ b/drivers/SCsub @@ -13,6 +13,7 @@ SConscript('windows/SCsub') # Sounds drivers SConscript('alsa/SCsub') +SConscript('coreaudio/SCsub') SConscript('pulseaudio/SCsub') if (env["platform"] == "windows"): SConscript("rtaudio/SCsub") diff --git a/drivers/coreaudio/SCsub b/drivers/coreaudio/SCsub new file mode 100644 index 0000000000..233593b0f9 --- /dev/null +++ b/drivers/coreaudio/SCsub @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +Import('env') + +# Driver source files +env.add_source_files(env.drivers_sources, "*.cpp") + +Export('env') diff --git a/drivers/coreaudio/audio_driver_coreaudio.cpp b/drivers/coreaudio/audio_driver_coreaudio.cpp new file mode 100644 index 0000000000..c531d6af9d --- /dev/null +++ b/drivers/coreaudio/audio_driver_coreaudio.cpp @@ -0,0 +1,315 @@ +/*************************************************************************/ +/* audio_driver_coreaudio.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifdef COREAUDIO_ENABLED + +#include "audio_driver_coreaudio.h" +#include "core/project_settings.h" +#include "os/os.h" + +#define kOutputBus 0 + +#ifdef OSX_ENABLED +static OSStatus outputDeviceAddressCB(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *__nullable inClientData) { + AudioDriverCoreAudio *driver = (AudioDriverCoreAudio *)inClientData; + + driver->reopen(); + + return noErr; +} +#endif + +Error AudioDriverCoreAudio::initDevice() { + AudioComponentDescription desc; + zeromem(&desc, sizeof(desc)); + desc.componentType = kAudioUnitType_Output; +#ifdef OSX_ENABLED + desc.componentSubType = kAudioUnitSubType_HALOutput; +#else + desc.componentSubType = kAudioUnitSubType_RemoteIO; +#endif + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + + AudioComponent comp = AudioComponentFindNext(NULL, &desc); + ERR_FAIL_COND_V(comp == NULL, FAILED); + + OSStatus result = AudioComponentInstanceNew(comp, &audio_unit); + ERR_FAIL_COND_V(result != noErr, FAILED); + + AudioStreamBasicDescription strdesc; + + zeromem(&strdesc, sizeof(strdesc)); + UInt32 size = sizeof(strdesc); + result = AudioUnitGetProperty(audio_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kOutputBus, &strdesc, &size); + ERR_FAIL_COND_V(result != noErr, FAILED); + + switch (strdesc.mChannelsPerFrame) { + case 2: // Stereo + case 4: // Surround 3.1 + case 6: // Surround 5.1 + case 8: // Surround 7.1 + channels = strdesc.mChannelsPerFrame; + break; + + default: + // Unknown number of channels, default to stereo + channels = 2; + break; + } + + mix_rate = GLOBAL_DEF("audio/mix_rate", DEFAULT_MIX_RATE); + + zeromem(&strdesc, sizeof(strdesc)); + strdesc.mFormatID = kAudioFormatLinearPCM; + strdesc.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked; + strdesc.mChannelsPerFrame = channels; + strdesc.mSampleRate = mix_rate; + strdesc.mFramesPerPacket = 1; + strdesc.mBitsPerChannel = 16; + strdesc.mBytesPerFrame = strdesc.mBitsPerChannel * strdesc.mChannelsPerFrame / 8; + strdesc.mBytesPerPacket = strdesc.mBytesPerFrame * strdesc.mFramesPerPacket; + + result = AudioUnitSetProperty(audio_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &strdesc, sizeof(strdesc)); + ERR_FAIL_COND_V(result != noErr, FAILED); + + int latency = GLOBAL_DEF("audio/output_latency", DEFAULT_OUTPUT_LATENCY); + // Sample rate is independent of channels (ref: https://stackoverflow.com/questions/11048825/audio-sample-frequency-rely-on-channels) + buffer_frames = closest_power_of_2(latency * mix_rate / 1000); + +#ifdef OSX_ENABLED + result = AudioUnitSetProperty(audio_unit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global, kOutputBus, &buffer_frames, sizeof(UInt32)); + ERR_FAIL_COND_V(result != noErr, FAILED); +#endif + + buffer_size = buffer_frames * channels; + samples_in.resize(buffer_size); + + if (OS::get_singleton()->is_stdout_verbose()) { + print_line("CoreAudio: detected " + itos(channels) + " channels"); + print_line("CoreAudio: audio buffer frames: " + itos(buffer_frames) + " calculated latency: " + itos(buffer_frames * 1000 / mix_rate) + "ms"); + } + + AURenderCallbackStruct callback; + zeromem(&callback, sizeof(AURenderCallbackStruct)); + callback.inputProc = &AudioDriverCoreAudio::output_callback; + callback.inputProcRefCon = this; + result = AudioUnitSetProperty(audio_unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, kOutputBus, &callback, sizeof(callback)); + ERR_FAIL_COND_V(result != noErr, FAILED); + + result = AudioUnitInitialize(audio_unit); + ERR_FAIL_COND_V(result != noErr, FAILED); + + return OK; +} + +Error AudioDriverCoreAudio::finishDevice() { + OSStatus result; + + if (active) { + result = AudioOutputUnitStop(audio_unit); + ERR_FAIL_COND_V(result != noErr, FAILED); + + active = false; + } + + result = AudioUnitUninitialize(audio_unit); + ERR_FAIL_COND_V(result != noErr, FAILED); + + return OK; +} + +Error AudioDriverCoreAudio::init() { + OSStatus result; + + mutex = Mutex::create(); + active = false; + channels = 2; + +#ifdef OSX_ENABLED + outputDeviceAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + outputDeviceAddress.mScope = kAudioObjectPropertyScopeGlobal; + outputDeviceAddress.mElement = kAudioObjectPropertyElementMaster; + + result = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &outputDeviceAddress, &outputDeviceAddressCB, this); + ERR_FAIL_COND_V(result != noErr, FAILED); +#endif + + return initDevice(); +}; + +Error AudioDriverCoreAudio::reopen() { + bool restart = false; + + lock(); + + if (active) { + restart = true; + } + + Error err = finishDevice(); + if (err != OK) { + ERR_PRINT("finishDevice failed"); + unlock(); + return err; + } + + err = initDevice(); + if (err != OK) { + ERR_PRINT("initDevice failed"); + unlock(); + return err; + } + + if (restart) { + start(); + } + + unlock(); + + return OK; +} + +OSStatus AudioDriverCoreAudio::output_callback(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, UInt32 inNumberFrames, + AudioBufferList *ioData) { + + AudioDriverCoreAudio *ad = (AudioDriverCoreAudio *)inRefCon; + + if (!ad->active || !ad->try_lock()) { + for (unsigned int i = 0; i < ioData->mNumberBuffers; i++) { + AudioBuffer *abuf = &ioData->mBuffers[i]; + zeromem(abuf->mData, abuf->mDataByteSize); + }; + return 0; + }; + + for (unsigned int i = 0; i < ioData->mNumberBuffers; i++) { + + AudioBuffer *abuf = &ioData->mBuffers[i]; + int frames_left = inNumberFrames; + int16_t *out = (int16_t *)abuf->mData; + + while (frames_left) { + + int frames = MIN(frames_left, ad->buffer_frames); + ad->audio_server_process(frames, ad->samples_in.ptr()); + + for (int j = 0; j < frames * ad->channels; j++) { + + out[j] = ad->samples_in[j] >> 16; + } + + frames_left -= frames; + out += frames * ad->channels; + }; + }; + + ad->unlock(); + + return 0; +}; + +void AudioDriverCoreAudio::start() { + if (!active) { + OSStatus result = AudioOutputUnitStart(audio_unit); + if (result != noErr) { + ERR_PRINT("AudioOutputUnitStart failed"); + } else { + active = true; + } + } +}; + +int AudioDriverCoreAudio::get_mix_rate() const { + return mix_rate; +}; + +AudioDriver::SpeakerMode AudioDriverCoreAudio::get_speaker_mode() const { + return get_speaker_mode_by_total_channels(channels); +}; + +void AudioDriverCoreAudio::lock() { + if (mutex) + mutex->lock(); +}; + +void AudioDriverCoreAudio::unlock() { + if (mutex) + mutex->unlock(); +}; + +bool AudioDriverCoreAudio::try_lock() { + if (mutex) + return mutex->try_lock() == OK; + return true; +} + +void AudioDriverCoreAudio::finish() { + OSStatus result; + + finishDevice(); + +#ifdef OSX_ENABLED + result = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &outputDeviceAddress, &outputDeviceAddressCB, this); + if (result != noErr) { + ERR_PRINT("AudioObjectRemovePropertyListener failed"); + } +#endif + + AURenderCallbackStruct callback; + zeromem(&callback, sizeof(AURenderCallbackStruct)); + result = AudioUnitSetProperty(audio_unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, kOutputBus, &callback, sizeof(callback)); + if (result != noErr) { + ERR_PRINT("AudioUnitSetProperty failed"); + } + + if (mutex) { + memdelete(mutex); + mutex = NULL; + } +}; + +AudioDriverCoreAudio::AudioDriverCoreAudio() { + active = false; + mutex = NULL; + + mix_rate = 0; + channels = 2; + + buffer_size = 0; + buffer_frames = 0; + + samples_in.clear(); +}; + +AudioDriverCoreAudio::~AudioDriverCoreAudio(){}; + +#endif diff --git a/drivers/coreaudio/audio_driver_coreaudio.h b/drivers/coreaudio/audio_driver_coreaudio.h new file mode 100644 index 0000000000..33b3ba93ec --- /dev/null +++ b/drivers/coreaudio/audio_driver_coreaudio.h @@ -0,0 +1,89 @@ +/*************************************************************************/ +/* audio_driver_coreaudio.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifdef COREAUDIO_ENABLED + +#ifndef AUDIO_DRIVER_COREAUDIO_H +#define AUDIO_DRIVER_COREAUDIO_H + +#include "servers/audio_server.h" + +#include <AudioUnit/AudioUnit.h> +#ifdef OSX_ENABLED +#include <CoreAudio/AudioHardware.h> +#endif + +class AudioDriverCoreAudio : public AudioDriver { + + AudioComponentInstance audio_unit; +#ifdef OSX_ENABLED + AudioObjectPropertyAddress outputDeviceAddress; +#endif + bool active; + Mutex *mutex; + + int mix_rate; + unsigned int channels; + unsigned int buffer_frames; + unsigned int buffer_size; + + Vector<int32_t> samples_in; + + static OSStatus output_callback(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, UInt32 inNumberFrames, + AudioBufferList *ioData); + + Error initDevice(); + Error finishDevice(); + +public: + const char *get_name() const { + return "CoreAudio"; + }; + + virtual Error init(); + virtual void start(); + virtual int get_mix_rate() const; + virtual SpeakerMode get_speaker_mode() const; + virtual void lock(); + virtual void unlock(); + virtual void finish(); + + bool try_lock(); + Error reopen(); + + AudioDriverCoreAudio(); + ~AudioDriverCoreAudio(); +}; + +#endif + +#endif |