/******************************************************************************* * Copyright 2009-2021 Jörg Müller * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ #include "CoreAudioDevice.h" #include "devices/DeviceManager.h" #include "devices/IDeviceFactory.h" #include "Exception.h" #include "IReader.h" AUD_NAMESPACE_BEGIN OSStatus CoreAudioDevice::CoreAudio_mix(void* data, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time_stamp, UInt32 bus_number, UInt32 number_frames, AudioBufferList* buffer_list) { CoreAudioDevice* device = (CoreAudioDevice*)data; size_t sample_size = AUD_DEVICE_SAMPLE_SIZE(device->m_specs); for(int i = 0; i < buffer_list->mNumberBuffers; i++) { auto& buffer = buffer_list->mBuffers[i]; size_t readsamples = device->getRingBuffer().getReadSize(); size_t num_bytes = size_t(buffer.mDataByteSize); readsamples = std::min(readsamples, num_bytes) / sample_size; device->getRingBuffer().read((data_t*) buffer.mData, readsamples * sample_size); if(readsamples * sample_size < num_bytes) std::memset((data_t*) buffer.mData + readsamples * sample_size, 0, num_bytes - readsamples * sample_size); device->notifyMixingThread(); } if (!device->m_audio_clock_ready) { // Workaround CoreAudio quirk that corrupts the clock time data when the first mix callback occurs. // Both the start time and current time will be invalid. We need to reset them. if(device->isSynchronizerPlaying()) CAClockStop(device->m_clock_ref); CAClockTime clock_time; clock_time.format = kCAClockTimeFormat_Seconds; clock_time.time.seconds = device->m_synchronizerStartTime; CAClockSetCurrentTime(device->m_clock_ref, &clock_time); if(device->isSynchronizerPlaying()) CAClockStart(device->m_clock_ref); device->m_audio_clock_ready = true; } return noErr; } void CoreAudioDevice::preMixingWork(bool playing) { if(!playing) { if((getRingBuffer().getReadSize() == 0) && m_active) { AudioOutputUnitStop(m_audio_unit); m_active = false; } } } void CoreAudioDevice::playing(bool playing) { MixingThreadDevice::playing(playing); if(m_playback != playing) { if(playing) { AudioOutputUnitStart(m_audio_unit); m_active = true; } } m_playback = playing; } CoreAudioDevice::CoreAudioDevice(DeviceSpecs specs, int buffersize) : m_buffersize(uint32_t(buffersize)), m_playback(false), m_audio_unit(nullptr) { if(specs.channels == CHANNELS_INVALID) specs.channels = CHANNELS_STEREO; if(specs.format == FORMAT_INVALID) specs.format = FORMAT_FLOAT32; if(specs.rate == RATE_INVALID) specs.rate = RATE_48000; m_specs = specs; AudioComponentDescription component_description = {}; component_description.componentType = kAudioUnitType_Output; component_description.componentSubType = kAudioUnitSubType_DefaultOutput; component_description.componentManufacturer = kAudioUnitManufacturer_Apple; AudioComponent component = AudioComponentFindNext(nullptr, &component_description); if(!component) AUD_THROW(DeviceException, "The audio device couldn't be opened with CoreAudio."); OSStatus status = AudioComponentInstanceNew(component, &m_audio_unit); if(status != noErr) AUD_THROW(DeviceException, "The audio device couldn't be opened with CoreAudio."); AudioStreamBasicDescription stream_basic_description = {}; switch(m_specs.format) { case FORMAT_U8: stream_basic_description.mFormatFlags = 0; stream_basic_description.mBitsPerChannel = 8; break; case FORMAT_S16: stream_basic_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; stream_basic_description.mBitsPerChannel = 16; break; case FORMAT_S24: stream_basic_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; stream_basic_description.mBitsPerChannel = 24; break; case FORMAT_S32: stream_basic_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; stream_basic_description.mBitsPerChannel = 32; break; case FORMAT_FLOAT32: stream_basic_description.mFormatFlags = kLinearPCMFormatFlagIsFloat; stream_basic_description.mBitsPerChannel = 32; break; case FORMAT_FLOAT64: stream_basic_description.mFormatFlags = kLinearPCMFormatFlagIsFloat; stream_basic_description.mBitsPerChannel = 64; break; default: m_specs.format = FORMAT_FLOAT32; stream_basic_description.mFormatFlags = kLinearPCMFormatFlagIsFloat; stream_basic_description.mBitsPerChannel = 32; break; } stream_basic_description.mSampleRate = m_specs.rate; stream_basic_description.mFormatID = kAudioFormatLinearPCM; stream_basic_description.mFormatFlags |= kAudioFormatFlagsNativeEndian | kLinearPCMFormatFlagIsPacked; stream_basic_description.mBytesPerPacket = stream_basic_description.mBytesPerFrame = AUD_DEVICE_SAMPLE_SIZE(m_specs); stream_basic_description.mFramesPerPacket = 1; stream_basic_description.mChannelsPerFrame = m_specs.channels; status = AudioUnitSetProperty(m_audio_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &stream_basic_description, sizeof(stream_basic_description)); if(status != noErr) { AudioComponentInstanceDispose(m_audio_unit); AUD_THROW(DeviceException, "The audio device couldn't be opened with CoreAudio."); } AURenderCallbackStruct render_callback_struct; render_callback_struct.inputProc = CoreAudioDevice::CoreAudio_mix; render_callback_struct.inputProcRefCon = this; status = AudioUnitSetProperty(m_audio_unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &render_callback_struct, sizeof(render_callback_struct)); if(status != noErr) { AudioComponentInstanceDispose(m_audio_unit); AUD_THROW(DeviceException, "The audio device couldn't be opened with CoreAudio."); } status = AudioUnitSetProperty(m_audio_unit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Input, 0, &m_buffersize, sizeof(m_buffersize)); if(status != noErr) { AudioComponentInstanceDispose(m_audio_unit); AUD_THROW(DeviceException, "Could not set the buffer size for the audio device."); } status = AudioUnitInitialize(m_audio_unit); if(status != noErr) { AudioComponentInstanceDispose(m_audio_unit); AUD_THROW(DeviceException, "The audio device couldn't be opened with CoreAudio."); } try { OSStatus status = CAClockNew(0, &m_clock_ref); if(status != noErr) AUD_THROW(DeviceException, "Could not create a CoreAudio clock."); CAClockTimebase timebase = kCAClockTimebase_AudioOutputUnit; status = CAClockSetProperty(m_clock_ref, kCAClockProperty_InternalTimebase, sizeof(timebase), &timebase); if(status != noErr) { CAClockDispose(m_clock_ref); AUD_THROW(DeviceException, "Could not create a CoreAudio clock."); } status = CAClockSetProperty(m_clock_ref, kCAClockProperty_TimebaseSource, sizeof(m_audio_unit), &m_audio_unit); if(status != noErr) { CAClockDispose(m_clock_ref); AUD_THROW(DeviceException, "Could not create a CoreAudio clock."); } CAClockSyncMode sync_mode = kCAClockSyncMode_Internal; status = CAClockSetProperty(m_clock_ref, kCAClockProperty_SyncMode, sizeof(sync_mode), &sync_mode); if(status != noErr) { CAClockDispose(m_clock_ref); AUD_THROW(DeviceException, "Could not create a CoreAudio clock."); } } catch(Exception&) { AudioComponentInstanceDispose(m_audio_unit); throw; } create(); startMixingThread(buffersize * 2 * AUD_DEVICE_SAMPLE_SIZE(specs)); } CoreAudioDevice::~CoreAudioDevice() { stopMixingThread(); destroy(); CAClockDispose(m_clock_ref); AudioOutputUnitStop(m_audio_unit); AudioUnitUninitialize(m_audio_unit); AudioComponentInstanceDispose(m_audio_unit); } void CoreAudioDevice::seekSynchronizer(double time) { if(isSynchronizerPlaying()) CAClockStop(m_clock_ref); CAClockTime clock_time; clock_time.format = kCAClockTimeFormat_Seconds; clock_time.time.seconds = time; CAClockSetCurrentTime(m_clock_ref, &clock_time); m_synchronizerStartTime = time; if(isSynchronizerPlaying()) CAClockStart(m_clock_ref); SoftwareDevice::seekSynchronizer(time); } double CoreAudioDevice::getSynchronizerPosition() { CAClockTime clock_time; OSStatus status; if(isSynchronizerPlaying() && m_audio_clock_ready) status = CAClockGetCurrentTime(m_clock_ref, kCAClockTimeFormat_Seconds, &clock_time); else status = CAClockGetStartTime(m_clock_ref, kCAClockTimeFormat_Seconds, &clock_time); if(status != noErr) return 0; return clock_time.time.seconds; } void CoreAudioDevice::playSynchronizer() { if(isSynchronizerPlaying()) return; CAClockStart(m_clock_ref); SoftwareDevice::playSynchronizer(); } void CoreAudioDevice::stopSynchronizer() { if(!isSynchronizerPlaying()) return; CAClockStop(m_clock_ref); SoftwareDevice::stopSynchronizer(); } class CoreAudioDeviceFactory : public IDeviceFactory { private: DeviceSpecs m_specs; int m_buffersize; public: CoreAudioDeviceFactory() : m_buffersize(AUD_DEFAULT_BUFFER_SIZE) { m_specs.format = FORMAT_FLOAT32; m_specs.channels = CHANNELS_STEREO; m_specs.rate = RATE_48000; } virtual std::shared_ptr openDevice() { return std::shared_ptr(new CoreAudioDevice(m_specs, m_buffersize)); } virtual int getPriority() { return 1 << 15; } virtual void setSpecs(DeviceSpecs specs) { m_specs = specs; } virtual void setBufferSize(int buffersize) { m_buffersize = buffersize; } virtual void setName(const std::string &name) { } }; void CoreAudioDevice::registerPlugin() { DeviceManager::registerDevice("CoreAudio", std::shared_ptr(new CoreAudioDeviceFactory)); } #ifdef COREAUDIO_PLUGIN extern "C" AUD_PLUGIN_API void registerPlugin() { CoreAudioDevice::registerPlugin(); } extern "C" AUD_PLUGIN_API const char* getName() { return "CoreAudio"; } #endif AUD_NAMESPACE_END