this repo has no description
1/*
2This file is part of Darling.
3
4Copyright (C) 2020 Lubos Dolezel
5
6Darling is free software: you can redistribute it and/or modify
7it under the terms of the GNU General Public License as published by
8the Free Software Foundation, either version 3 of the License, or
9(at your option) any later version.
10
11Darling is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16You should have received a copy of the GNU General Public License
17along with Darling. If not, see <http://www.gnu.org/licenses/>.
18*/
19
20#include "AUHAL.h"
21#include <iostream>
22
23#pragma GCC visibility push(default)
24AUDIOCOMPONENT_ENTRY(AUOutputBaseFactory, AUHAL);
25#pragma GCC visibility pop
26
27enum {
28 kOutputBus = 0,
29 kInputBus
30};
31
32AUHAL::AUHAL(AudioComponentInstance inInstance, bool supportRecording)
33: AUBase(inInstance, 1, supportRecording ? 1 : 0)
34{
35 UInt32 propSize = sizeof(AudioDeviceID);
36 AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &propSize, &m_outputDevice);
37 AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &propSize, &m_inputDevice);
38}
39
40AUHAL::~AUHAL()
41{
42 Stop();
43}
44
45bool AUHAL::CanScheduleParameters() const
46{
47 return true;
48}
49
50bool AUHAL::StreamFormatWritable(AudioUnitScope scope, AudioUnitElement element)
51{
52 return !m_running && IsInitialized();
53}
54
55OSStatus AUHAL::Version()
56{
57 return 1;
58}
59
60// Provide data from the microphone
61OSStatus AUHAL::Render(AudioUnitRenderActionFlags& ioActionFlags, const AudioTimeStamp& inTimeStamp, UInt32 inNumberFrames)
62{
63 std::unique_lock<std::mutex> lk(m_dataAvailableMutex);
64
65 m_dataAvailableCV.wait(lk, [=]{ return m_dataAvailable; });
66
67 AudioBufferList& abl = GetOutput(kInputBus)->GetBufferList();
68
69 // TODO: Prepare for non-interleaved audio
70 UInt32 howMuch = std::min<UInt32>(abl.mBuffers[0].mDataByteSize, m_bufferUsed);
71 memcpy(abl.mBuffers[0].mData, m_buffer.get(), howMuch);
72 abl.mBuffers[0].mDataByteSize = howMuch;
73
74 return noErr;
75}
76
77OSStatus AUHAL::Start()
78{
79 if (m_running)
80 return noErr;
81
82 if (m_enableOutput)
83 {
84 // std::cout << "Output is enabled, starting playback\n";
85 AudioDeviceCreateIOProcID(m_outputDevice, playbackCallback, this, &m_outputProcID);
86
87 // m_auhalData.open("/tmp/auhal.raw", std::ios_base::binary | std::ios_base::out);
88
89 const CAStreamBasicDescription& desc = GetStreamFormat(kAudioUnitScope_Input, kOutputBus);
90 AudioDeviceSetProperty(m_outputDevice, nullptr, 0, false, kAudioDevicePropertyStreamFormat, sizeof(AudioStreamBasicDescription), &desc);
91 AudioDeviceStart(m_outputDevice, m_outputProcID);
92 }
93 if (m_enableInput)
94 {
95 AudioDeviceCreateIOProcID(m_inputDevice, recordCallback, this, &m_inputProcID);
96 const CAStreamBasicDescription& desc = GetStreamFormat(kAudioUnitScope_Output, kInputBus);
97 AudioDeviceSetProperty(m_inputDevice, nullptr, 0, true, kAudioDevicePropertyStreamFormat, sizeof(AudioStreamBasicDescription), &desc);
98 AudioDeviceStart(m_inputDevice, m_inputProcID);
99 }
100
101 m_running = m_enableOutput || m_enableInput;
102 return noErr;
103}
104
105OSStatus AUHAL::Stop()
106{
107 if (!m_running)
108 return noErr;
109
110 if (m_outputProcID)
111 {
112 AudioDeviceStop(m_outputDevice, m_outputProcID);
113 AudioDeviceDestroyIOProcID(m_outputDevice, m_outputProcID);
114 }
115 if (m_inputProcID)
116 {
117 AudioDeviceStop(m_inputDevice, m_inputProcID);
118 AudioDeviceDestroyIOProcID(m_inputDevice, m_inputProcID);
119 }
120
121 return noErr;
122}
123
124OSStatus AUHAL::SetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void* inData, UInt32 inDataSize)
125{
126 switch (inID)
127 {
128 case kAudioOutputUnitProperty_SetInputCallback:
129 {
130 ca_require(inDataSize == sizeof(AURenderCallbackStruct), InvalidPropertyValue);
131 const AURenderCallbackStruct* cb = static_cast<const AURenderCallbackStruct*>(inData);
132
133 m_outputAvailableCb = *cb;
134
135 PropertyChanged(inID, inScope, inElement);
136 return noErr;
137 }
138 case kAudioOutputUnitProperty_EnableIO:
139 {
140 ca_require(inDataSize == sizeof(UInt32), InvalidPropertyValue);
141
142 const bool enable = *((const UInt32*) inData);
143
144 if (inElement == kOutputBus)
145 {
146 m_enableOutput = enable;
147 PropertyChanged(inID, inScope, inElement);
148 }
149 else if (inElement == kInputBus)
150 {
151 m_enableInput = enable;
152 PropertyChanged(inID, inScope, inElement);
153 }
154 else
155 return kAudioUnitErr_InvalidElement;
156
157 return noErr;
158 }
159 case kAudioOutputUnitProperty_CurrentDevice:
160 {
161 ca_require(inDataSize == sizeof(AudioDeviceID), InvalidPropertyValue);
162 const AudioDeviceID* dev = static_cast<const AudioDeviceID*>(inData);
163
164 if (inElement == kOutputBus)
165 {
166 m_outputDevice = *dev;
167 PropertyChanged(inID, inScope, inElement);
168 }
169 else if (inElement == kInputBus)
170 {
171 m_inputDevice = *dev;
172 PropertyChanged(inID, inScope, inElement);
173 }
174 else
175 return kAudioUnitErr_InvalidElement;
176 return noErr;
177 }
178 }
179 return AUBase::SetProperty(inID, inScope, inElement, inData, inDataSize);
180InvalidPropertyValue:
181 return kAudioUnitErr_InvalidPropertyValue;
182}
183
184OSStatus AUHAL::GetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32& outDataSize, Boolean& outWritable)
185{
186 switch (inID)
187 {
188 case kAudioOutputUnitProperty_SetInputCallback:
189 {
190 outDataSize = sizeof(AURenderCallbackStruct);
191 outWritable = true;
192 break;
193 }
194 case kAudioOutputUnitProperty_EnableIO:
195 {
196 outDataSize = sizeof(UInt32);
197 outWritable = true;
198 return noErr;
199 }
200 case kAudioOutputUnitProperty_HasIO:
201 {
202 outDataSize = sizeof(UInt32);
203 outWritable = false;
204 return noErr;
205 }
206 case kAudioOutputUnitProperty_CurrentDevice:
207 {
208 outDataSize = sizeof(AudioDeviceID);
209 outWritable = true;
210 return noErr;
211 }
212 }
213 return AUBase::GetPropertyInfo(inID, inScope, inElement, outDataSize, outWritable);
214}
215
216OSStatus AUHAL::GetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void* outData)
217{
218 switch (inID)
219 {
220 case kAudioOutputUnitProperty_SetInputCallback:
221 {
222 memcpy(outData, &m_outputAvailableCb, sizeof(m_outputAvailableCb));
223 return noErr;
224 }
225 case kAudioOutputUnitProperty_EnableIO:
226 case kAudioOutputUnitProperty_HasIO:
227 {
228 if (inElement == kOutputBus)
229 {
230 UInt32 value = m_enableOutput;
231 memcpy(&outData, &value, sizeof(value));
232 }
233 else if (inElement == kInputBus)
234 {
235 UInt32 value = m_enableInput;
236 memcpy(&outData, &value, sizeof(value));
237 }
238 else
239 return kAudioUnitErr_InvalidElement;
240 return noErr;
241 }
242 case kAudioOutputUnitProperty_CurrentDevice:
243 {
244 if (inElement == kOutputBus)
245 {
246 memcpy(outData, &m_outputDevice, sizeof(m_outputDevice));
247 }
248 else if (inElement == kInputBus)
249 {
250 memcpy(outData, &m_inputDevice, sizeof(m_inputDevice));
251 }
252 else
253 return kAudioUnitErr_InvalidElement;
254 return noErr;
255 }
256 }
257 return AUBase::GetProperty(inID, inScope, inElement, outData);
258}
259
260OSStatus AUHAL::playbackCallback(AudioObjectID inObjectID,
261 const AudioTimeStamp* inNow, const AudioBufferList* inInputData,
262 const AudioTimeStamp* inInputTime,
263 AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime,
264 void* inClientData)
265{
266 AUHAL* This = static_cast<AUHAL*>(inClientData);
267 return This->doPlayback(inNow, outOutputData, inOutputTime);
268}
269
270OSStatus AUHAL::recordCallback(AudioObjectID inObjectID,
271 const AudioTimeStamp* inNow, const AudioBufferList* inInputData,
272 const AudioTimeStamp* inInputTime,
273 AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime,
274 void* inClientData)
275{
276 AUHAL* This = static_cast<AUHAL*>(inClientData);
277 return This->doRecord(inNow, inInputData, inInputTime);
278}
279
280OSStatus AUHAL::doPlayback(const AudioTimeStamp* inNow, AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime)
281{
282 // std::cout << "AUHAL::DoPlayback()\n";
283 if (!HasInput(0))
284 {
285 // std::cerr << "No connection\n";
286 return kAudioUnitErr_NoConnection;
287 }
288
289 OSStatus result = noErr;
290 AudioUnitRenderActionFlags flags = kAudioUnitRenderAction_PreRender;
291 const CAStreamBasicDescription& desc = GetStreamFormat(kAudioUnitScope_Input, kOutputBus);
292
293 UInt32 nFrames = outOutputData->mBuffers[0].mDataByteSize / (desc.mBytesPerFrame / outOutputData->mBuffers[0].mNumberChannels);
294 result = GetInput(kOutputBus)->PullInputWithBufferList(flags, *inNow, kOutputBus, nFrames, outOutputData);
295
296 // std::cout << "Pull result: " << result << std::endl;
297 // std::cout << "Bytes: " << outOutputData->mBuffers[0].mDataByteSize << std::endl;
298 // m_auhalData.write((char*) outOutputData->mBuffers[0].mData, outOutputData->mBuffers[0].mDataByteSize);
299 // m_auhalData.flush();
300
301 return result;
302}
303
304OSStatus AUHAL::doRecord(const AudioTimeStamp* inNow, const AudioBufferList* inInputData, const AudioTimeStamp* inInputTime)
305{
306 // TODO: Prepare for non-interleaved audio
307 std::unique_lock<std::mutex> lk(m_dataAvailableMutex);
308
309 if (m_bufferSize < inInputData->mBuffers[0].mDataByteSize)
310 {
311 m_buffer.reset(new uint8_t[inInputData->mBuffers[0].mDataByteSize]);
312 m_bufferSize = inInputData->mBuffers[0].mDataByteSize;
313 }
314
315 m_bufferUsed = inInputData->mBuffers[0].mDataByteSize;
316 memcpy(m_buffer.get(), inInputData->mBuffers[0].mData, m_bufferUsed);
317
318 m_dataAvailable = true;
319
320 lk.unlock();
321 m_dataAvailableCV.notify_one();
322
323 if (m_outputAvailableCb.inputProc)
324 {
325 AudioUnitRenderActionFlags flags = kAudioUnitRenderAction_PostRender;
326 const CAStreamBasicDescription& desc = GetStreamFormat(kAudioUnitScope_Output, kInputBus);
327 UInt32 numFrames = m_bufferUsed / desc.mBytesPerFrame;
328
329 m_outputAvailableCb.inputProc(m_outputAvailableCb.inputProcRefCon, &flags, inInputTime, kInputBus, numFrames, nullptr);
330 }
331
332 return noErr;
333}
334
335// AUDispatch.cpp doesn't implement dispatch code for AudioOutputUnits
336OSStatus AUHAL::ComponentEntryDispatch(ComponentParameters *params, AUHAL *This)
337{
338 if (This == NULL) return kAudio_ParamError;
339
340 OSStatus result = noErr;
341
342 switch (params->what)
343 {
344 case kComponentCanDoSelect:
345 switch (GetSelectorForCanDo(params))
346 {
347 case kAudioOutputUnitStartSelect:
348 case kAudioOutputUnitStopSelect:
349 return 1;
350 }
351 break;
352 case kAudioOutputUnitStartSelect:
353 {
354 CAMutex::Locker lock(This->GetMutex());
355 return This->Start();
356 }
357 case kAudioOutputUnitStopSelect:
358 {
359 CAMutex::Locker lock(This->GetMutex());
360 return This->Stop();
361 }
362 }
363
364 return AUBase::ComponentEntryDispatch(params, This);
365}