/* This file is part of Darling. Copyright (C) 2020 Lubos Dolezel Darling 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. Darling 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 Darling. If not, see . */ #include "AUHAL.h" #include #pragma GCC visibility push(default) AUDIOCOMPONENT_ENTRY(AUOutputBaseFactory, AUHAL); #pragma GCC visibility pop enum { kOutputBus = 0, kInputBus }; AUHAL::AUHAL(AudioComponentInstance inInstance, bool supportRecording) : AUBase(inInstance, 1, supportRecording ? 1 : 0) { UInt32 propSize = sizeof(AudioDeviceID); AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &propSize, &m_outputDevice); AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &propSize, &m_inputDevice); } AUHAL::~AUHAL() { Stop(); } bool AUHAL::CanScheduleParameters() const { return true; } bool AUHAL::StreamFormatWritable(AudioUnitScope scope, AudioUnitElement element) { return !m_running && IsInitialized(); } OSStatus AUHAL::Version() { return 1; } // Provide data from the microphone OSStatus AUHAL::Render(AudioUnitRenderActionFlags& ioActionFlags, const AudioTimeStamp& inTimeStamp, UInt32 inNumberFrames) { std::unique_lock lk(m_dataAvailableMutex); m_dataAvailableCV.wait(lk, [=]{ return m_dataAvailable; }); AudioBufferList& abl = GetOutput(kInputBus)->GetBufferList(); // TODO: Prepare for non-interleaved audio UInt32 howMuch = std::min(abl.mBuffers[0].mDataByteSize, m_bufferUsed); memcpy(abl.mBuffers[0].mData, m_buffer.get(), howMuch); abl.mBuffers[0].mDataByteSize = howMuch; return noErr; } OSStatus AUHAL::Start() { if (m_running) return noErr; if (m_enableOutput) { // std::cout << "Output is enabled, starting playback\n"; AudioDeviceCreateIOProcID(m_outputDevice, playbackCallback, this, &m_outputProcID); // m_auhalData.open("/tmp/auhal.raw", std::ios_base::binary | std::ios_base::out); const CAStreamBasicDescription& desc = GetStreamFormat(kAudioUnitScope_Input, kOutputBus); AudioDeviceSetProperty(m_outputDevice, nullptr, 0, false, kAudioDevicePropertyStreamFormat, sizeof(AudioStreamBasicDescription), &desc); AudioDeviceStart(m_outputDevice, m_outputProcID); } if (m_enableInput) { AudioDeviceCreateIOProcID(m_inputDevice, recordCallback, this, &m_inputProcID); const CAStreamBasicDescription& desc = GetStreamFormat(kAudioUnitScope_Output, kInputBus); AudioDeviceSetProperty(m_inputDevice, nullptr, 0, true, kAudioDevicePropertyStreamFormat, sizeof(AudioStreamBasicDescription), &desc); AudioDeviceStart(m_inputDevice, m_inputProcID); } m_running = m_enableOutput || m_enableInput; return noErr; } OSStatus AUHAL::Stop() { if (!m_running) return noErr; if (m_outputProcID) { AudioDeviceStop(m_outputDevice, m_outputProcID); AudioDeviceDestroyIOProcID(m_outputDevice, m_outputProcID); } if (m_inputProcID) { AudioDeviceStop(m_inputDevice, m_inputProcID); AudioDeviceDestroyIOProcID(m_inputDevice, m_inputProcID); } return noErr; } OSStatus AUHAL::SetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void* inData, UInt32 inDataSize) { switch (inID) { case kAudioOutputUnitProperty_SetInputCallback: { ca_require(inDataSize == sizeof(AURenderCallbackStruct), InvalidPropertyValue); const AURenderCallbackStruct* cb = static_cast(inData); m_outputAvailableCb = *cb; PropertyChanged(inID, inScope, inElement); return noErr; } case kAudioOutputUnitProperty_EnableIO: { ca_require(inDataSize == sizeof(UInt32), InvalidPropertyValue); const bool enable = *((const UInt32*) inData); if (inElement == kOutputBus) { m_enableOutput = enable; PropertyChanged(inID, inScope, inElement); } else if (inElement == kInputBus) { m_enableInput = enable; PropertyChanged(inID, inScope, inElement); } else return kAudioUnitErr_InvalidElement; return noErr; } case kAudioOutputUnitProperty_CurrentDevice: { ca_require(inDataSize == sizeof(AudioDeviceID), InvalidPropertyValue); const AudioDeviceID* dev = static_cast(inData); if (inElement == kOutputBus) { m_outputDevice = *dev; PropertyChanged(inID, inScope, inElement); } else if (inElement == kInputBus) { m_inputDevice = *dev; PropertyChanged(inID, inScope, inElement); } else return kAudioUnitErr_InvalidElement; return noErr; } } return AUBase::SetProperty(inID, inScope, inElement, inData, inDataSize); InvalidPropertyValue: return kAudioUnitErr_InvalidPropertyValue; } OSStatus AUHAL::GetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32& outDataSize, Boolean& outWritable) { switch (inID) { case kAudioOutputUnitProperty_SetInputCallback: { outDataSize = sizeof(AURenderCallbackStruct); outWritable = true; break; } case kAudioOutputUnitProperty_EnableIO: { outDataSize = sizeof(UInt32); outWritable = true; return noErr; } case kAudioOutputUnitProperty_HasIO: { outDataSize = sizeof(UInt32); outWritable = false; return noErr; } case kAudioOutputUnitProperty_CurrentDevice: { outDataSize = sizeof(AudioDeviceID); outWritable = true; return noErr; } } return AUBase::GetPropertyInfo(inID, inScope, inElement, outDataSize, outWritable); } OSStatus AUHAL::GetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void* outData) { switch (inID) { case kAudioOutputUnitProperty_SetInputCallback: { memcpy(outData, &m_outputAvailableCb, sizeof(m_outputAvailableCb)); return noErr; } case kAudioOutputUnitProperty_EnableIO: case kAudioOutputUnitProperty_HasIO: { if (inElement == kOutputBus) { UInt32 value = m_enableOutput; memcpy(&outData, &value, sizeof(value)); } else if (inElement == kInputBus) { UInt32 value = m_enableInput; memcpy(&outData, &value, sizeof(value)); } else return kAudioUnitErr_InvalidElement; return noErr; } case kAudioOutputUnitProperty_CurrentDevice: { if (inElement == kOutputBus) { memcpy(outData, &m_outputDevice, sizeof(m_outputDevice)); } else if (inElement == kInputBus) { memcpy(outData, &m_inputDevice, sizeof(m_inputDevice)); } else return kAudioUnitErr_InvalidElement; return noErr; } } return AUBase::GetProperty(inID, inScope, inElement, outData); } OSStatus AUHAL::playbackCallback(AudioObjectID inObjectID, const AudioTimeStamp* inNow, const AudioBufferList* inInputData, const AudioTimeStamp* inInputTime, AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime, void* inClientData) { AUHAL* This = static_cast(inClientData); return This->doPlayback(inNow, outOutputData, inOutputTime); } OSStatus AUHAL::recordCallback(AudioObjectID inObjectID, const AudioTimeStamp* inNow, const AudioBufferList* inInputData, const AudioTimeStamp* inInputTime, AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime, void* inClientData) { AUHAL* This = static_cast(inClientData); return This->doRecord(inNow, inInputData, inInputTime); } OSStatus AUHAL::doPlayback(const AudioTimeStamp* inNow, AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime) { // std::cout << "AUHAL::DoPlayback()\n"; if (!HasInput(0)) { // std::cerr << "No connection\n"; return kAudioUnitErr_NoConnection; } OSStatus result = noErr; AudioUnitRenderActionFlags flags = kAudioUnitRenderAction_PreRender; const CAStreamBasicDescription& desc = GetStreamFormat(kAudioUnitScope_Input, kOutputBus); UInt32 nFrames = outOutputData->mBuffers[0].mDataByteSize / (desc.mBytesPerFrame / outOutputData->mBuffers[0].mNumberChannels); result = GetInput(kOutputBus)->PullInputWithBufferList(flags, *inNow, kOutputBus, nFrames, outOutputData); // std::cout << "Pull result: " << result << std::endl; // std::cout << "Bytes: " << outOutputData->mBuffers[0].mDataByteSize << std::endl; // m_auhalData.write((char*) outOutputData->mBuffers[0].mData, outOutputData->mBuffers[0].mDataByteSize); // m_auhalData.flush(); return result; } OSStatus AUHAL::doRecord(const AudioTimeStamp* inNow, const AudioBufferList* inInputData, const AudioTimeStamp* inInputTime) { // TODO: Prepare for non-interleaved audio std::unique_lock lk(m_dataAvailableMutex); if (m_bufferSize < inInputData->mBuffers[0].mDataByteSize) { m_buffer.reset(new uint8_t[inInputData->mBuffers[0].mDataByteSize]); m_bufferSize = inInputData->mBuffers[0].mDataByteSize; } m_bufferUsed = inInputData->mBuffers[0].mDataByteSize; memcpy(m_buffer.get(), inInputData->mBuffers[0].mData, m_bufferUsed); m_dataAvailable = true; lk.unlock(); m_dataAvailableCV.notify_one(); if (m_outputAvailableCb.inputProc) { AudioUnitRenderActionFlags flags = kAudioUnitRenderAction_PostRender; const CAStreamBasicDescription& desc = GetStreamFormat(kAudioUnitScope_Output, kInputBus); UInt32 numFrames = m_bufferUsed / desc.mBytesPerFrame; m_outputAvailableCb.inputProc(m_outputAvailableCb.inputProcRefCon, &flags, inInputTime, kInputBus, numFrames, nullptr); } return noErr; } // AUDispatch.cpp doesn't implement dispatch code for AudioOutputUnits OSStatus AUHAL::ComponentEntryDispatch(ComponentParameters *params, AUHAL *This) { if (This == NULL) return kAudio_ParamError; OSStatus result = noErr; switch (params->what) { case kComponentCanDoSelect: switch (GetSelectorForCanDo(params)) { case kAudioOutputUnitStartSelect: case kAudioOutputUnitStopSelect: return 1; } break; case kAudioOutputUnitStartSelect: { CAMutex::Locker lock(This->GetMutex()); return This->Start(); } case kAudioOutputUnitStopSelect: { CAMutex::Locker lock(This->GetMutex()); return This->Stop(); } } return AUBase::ComponentEntryDispatch(params, This); }