this repo has no description
1#include "AudioUnitPA.h"
2#include <CoreServices/MacErrors.h>
3#include <CoreFoundation/CFString.h>
4#include <mutex>
5#include <condition_variable>
6#include <stdexcept>
7#include <sstream>
8#include <memory>
9#include <cstring>
10#include <util/debug.h>
11#include <objc/runtime.h>
12
13extern "C" char*** _NSGetArgv(void);
14
15pa_mainloop* AudioUnitPA::m_mainloop = nullptr;
16pa_context* AudioUnitPA::m_context = nullptr;
17std::thread* AudioUnitPA::m_mainloopThread;
18
19AudioUnitPA::AudioUnitPA()
20{
21}
22
23AudioUnitPA::~AudioUnitPA()
24{
25 deinit();
26}
27
28static std::string CFStringToStdString(CFStringRef str)
29{
30 CFIndex length, maxSize;
31 std::unique_ptr<char[]> buffer;
32
33 length = CFStringGetLength(str);
34 maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
35 buffer.reset(new char[maxSize]);
36
37 if (CFStringGetCString(str, buffer.get(), maxSize, kCFStringEncodingUTF8))
38 return std::string(buffer.get());
39 else
40 return std::string();
41}
42
43OSStatus AudioUnitPA::init()
44{
45 static std::once_flag once;
46 OSStatus ret;
47
48 std::call_once(once, initializePA);
49 if (m_context == nullptr)
50 return kAudioUnitErr_FailedInitialization;
51
52 if (m_enableOutput)
53 ret = initOutput();
54
55 // TODO: support recording
56 return ret;
57}
58
59OSStatus AudioUnitPA::initOutput()
60{
61 std::string streamName;
62 pa_sample_spec spec;
63
64 if (m_contextName != nullptr)
65 streamName = CFStringToStdString(m_contextName);
66 else
67 streamName = "AudioUnit";
68
69 spec = paSampleSpecForASBD(m_config[kOutputBus].second);
70 m_stream = pa_stream_new(m_context, streamName.c_str(), &spec, nullptr);
71
72 if (m_stream == nullptr)
73 {
74 ERROR() << "pa_stream_new() failed";
75 return kAudioUnitErr_FailedInitialization;
76 }
77
78 // TODO: support recording
79 pa_stream_set_state_callback(m_stream, paStreamStateCB, this);
80 pa_stream_set_write_callback(m_stream, paStreamWriteCB, this);
81
82 pa_stream_connect_playback(m_stream, nullptr, nullptr, PA_STREAM_START_CORKED /*(pa_stream_flags_t) 0*/,
83 nullptr, nullptr);
84
85 return noErr;
86}
87
88int AudioUnitPA::cardIndex() const
89{
90 return 0;
91}
92
93void AudioUnitPA::paStreamStateCB(pa_stream* s, void*)
94{
95 int state = pa_stream_get_state(s);
96 TRACE() << "state=" << state;
97
98 switch (state)
99 {
100 case PA_STREAM_FAILED:
101 ERROR() << "PA stream error: " << pa_strerror(pa_context_errno(pa_stream_get_context(s)));
102 break;
103 case PA_STREAM_READY:
104 LOG << "PA stream is ready\n";
105 break;
106 }
107}
108
109void AudioUnitPA::paStreamWriteCB(pa_stream* s, size_t length, void* self)
110{
111 AudioUnitPA* This = static_cast<AudioUnitPA*>(self);
112 This->requestDataForPlayback(length);
113}
114
115void AudioUnitPA::requestDataForPlayback(size_t length)
116{
117 AudioUnitRenderActionFlags flags;
118 AudioTimeStamp ts;
119 AudioBufferList* bufs;
120 OSStatus err;
121 std::unique_ptr<uint8_t[]> data;
122 const AudioStreamBasicDescription& config = m_config[kOutputBus].first;
123 const UInt32 cc = config.mChannelsPerFrame;
124
125 if (!m_stream)
126 {
127 std::cerr << "No stream?!\n";
128 return;
129 }
130
131 TRACE() << "m_started=" << m_started;
132
133 if (!m_started)
134 {
135 std::unique_ptr<char[]> empty;
136 size_t len;
137 int ret;
138
139 len = config.mBytesPerFrame * (config.mSampleRate / 10);
140 empty.reset(new char[len]);
141
142 memset(empty.get(), 0, len);
143
144 ret = pa_stream_write(m_stream, empty.get(), len,
145 nullptr, 0, PA_SEEK_RELATIVE);
146
147 // pa_stream_cork(m_stream, true, [](pa_stream*, int, void*) {}, nullptr);
148 return;
149 }
150
151 memset(&ts, 0, sizeof(ts));
152
153 if (isOutputPlanar())
154 {
155 bufs = (AudioBufferList*) operator new(sizeof(AudioBufferList) + (cc-1)*sizeof(AudioBuffer));
156 bufs->mNumberBuffers = cc;
157 }
158 else
159 {
160 bufs = (AudioBufferList*) operator new(sizeof(AudioBufferList));
161 bufs->mNumberBuffers = 1;
162 }
163
164 if (m_shouldAllocateBuffer)
165 {
166 if (isOutputPlanar())
167 {
168 UInt32 bytesPerChannel = length / cc;
169
170 data.reset(new uint8_t[cc*bytesPerChannel]);
171
172 for (UInt32 i = 0; i < cc; i++)
173 {
174 bufs->mBuffers[i].mNumberChannels = 1;
175 bufs->mBuffers[i].mDataByteSize = bytesPerChannel;
176 bufs->mBuffers[i].mData = data.get() + i*bytesPerChannel;
177 }
178 }
179 else
180 {
181 bufs->mBuffers[0].mNumberChannels = cc;
182 bufs->mBuffers[0].mDataByteSize = length;
183
184 data.reset(new uint8_t[bufs->mBuffers[0].mDataByteSize]);
185 bufs->mBuffers[0].mData = data.get();
186 }
187 }
188 else
189 {
190 if (isOutputPlanar())
191 {
192 for (UInt32 i = 0; i < cc; i++)
193 {
194 bufs->mBuffers[i].mDataByteSize = 0;
195 bufs->mBuffers[i].mData = nullptr;
196 }
197 }
198 else
199 {
200 bufs->mBuffers[0].mDataByteSize = 0;
201 bufs->mBuffers[0].mData = nullptr;
202 }
203 }
204
205 // snd_pcm_htimestamp(m_pcmOutput, &availFrames, &alsaTimestamp);
206
207 // TODO: fill in AudioTimeStamp based on some PA timestamp
208
209 err = AudioUnitRender(m_inputUnit.sourceAudioUnit, &flags, &ts, kOutputBus, length / config.mBytesPerFrame, bufs);
210
211 if (err != noErr || bufs->mBuffers[0].mDataByteSize == 0)
212 {
213 ERROR() << "Render callback failed with error " << err;
214
215 // Fill with silence, the error may be temporary
216 UInt32 bytes = length;
217
218 if (!m_shouldAllocateBuffer)
219 {
220 if (isOutputPlanar())
221 {
222 data.reset(new uint8_t[bytes*cc]);
223 memset(data.get(), 0, bytes*cc);
224 }
225 else
226 {
227 data.reset(new uint8_t[bytes]);
228 memset(data.get(), 0, bytes);
229 }
230 }
231
232 if (isOutputPlanar())
233 {
234 for (UInt32 i = 0; i < cc; i++)
235 {
236 bufs->mBuffers[i].mData = data.get() + bytes*i;
237 bufs->mBuffers[i].mDataByteSize = bytes;
238 }
239 }
240 else
241 {
242 bufs->mBuffers[0].mData = data.get();
243 bufs->mBuffers[0].mDataByteSize = bytes;
244 }
245 }
246
247 LOG << "Rendering...\n";
248 m_lastRenderError = AudioUnitRender(this, &flags, &ts, kOutputBus, length / config.mBytesPerFrame, bufs);
249
250 operator delete(bufs);
251}
252
253OSStatus AudioUnitPA::render(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData)
254{
255 if (inBusNumber == kOutputBus)
256 return renderOutput(ioActionFlags, inTimeStamp, inNumberFrames, ioData);
257 else if (inBusNumber == kInputBus)
258 return renderInput(ioActionFlags, inTimeStamp, inNumberFrames, ioData);
259 else
260 return paramErr;
261}
262
263OSStatus AudioUnitPA::renderOutput(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inNumberFrames, AudioBufferList *ioData)
264{
265 if (!isOutputPlanar())
266 return renderInterleavedOutput(ioActionFlags, inTimeStamp, inNumberFrames, ioData);
267 else
268 return renderPlanarOutput(ioActionFlags, inTimeStamp, inNumberFrames, ioData);
269}
270
271OSStatus AudioUnitPA::renderInterleavedOutput(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inNumberFrames, AudioBufferList *ioData)
272{
273 const AudioStreamBasicDescription& config = m_config[kOutputBus].first;
274 UInt32 framesSoFar = 0;
275
276 for (UInt32 i = 0; i < ioData->mNumberBuffers; i++)
277 {
278 size_t bytes = std::min<size_t>((inNumberFrames - framesSoFar) * config.mBytesPerFrame, ioData->mBuffers[i].mDataByteSize);
279
280 if (!bytes)
281 break;
282
283 LOG << "AudioUnitPA::renderInterleavedOutput(): data=" << ioData->mBuffers[i].mData << ", bytes=" << bytes << std::endl;
284 pa_stream_write(m_stream, ((char*) ioData->mBuffers[i].mData) + framesSoFar * config.mBytesPerFrame, bytes,
285 nullptr, 0, PA_SEEK_RELATIVE);
286
287 framesSoFar += ioData->mBuffers[i].mDataByteSize / config.mBytesPerFrame;
288 }
289
290 return noErr;
291}
292
293OSStatus AudioUnitPA::renderPlanarOutput(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inNumberFrames, AudioBufferList *ioData)
294{
295 // PulseAudio doesn't support planar audio, we have to perform conversion
296 return unimpErr;
297}
298
299OSStatus AudioUnitPA::renderInput(AudioUnitRenderActionFlags *ioActionFlags,const AudioTimeStamp *inTimeStamp, UInt32 inNumberFrames, AudioBufferList *ioData)
300{
301 if (!m_outputCallback.inputProc)
302 return noErr; // We don't push, we should be polled
303
304 if (!isInputPlanar())
305 return renderInterleavedInput(ioActionFlags, inTimeStamp, inNumberFrames, ioData);
306 else
307 return renderPlanarInput(ioActionFlags, inTimeStamp, inNumberFrames, ioData);
308}
309
310OSStatus AudioUnitPA::renderInterleavedInput(AudioUnitRenderActionFlags *ioActionFlags,const AudioTimeStamp *inTimeStamp, UInt32 inNumberFrames, AudioBufferList *ioData)
311{
312 STUB();
313 return unimpErr;
314}
315
316OSStatus AudioUnitPA::renderPlanarInput(AudioUnitRenderActionFlags *ioActionFlags,const AudioTimeStamp *inTimeStamp, UInt32 inNumberFrames, AudioBufferList *ioData)
317{
318 STUB();
319 return unimpErr;
320}
321
322pa_sample_spec AudioUnitPA::paSampleSpecForASBD(const AudioStreamBasicDescription& asbd)
323{
324 const bool isFloat = asbd.mFormatFlags & kAudioFormatFlagIsFloat;
325 const bool isSigned = asbd.mFormatFlags & kAudioFormatFlagIsSignedInteger;
326 const bool isBE = asbd.mFormatFlags & kAudioFormatFlagIsBigEndian;
327
328 pa_sample_spec spec;
329
330 spec.rate = asbd.mSampleRate;
331 spec.channels = asbd.mChannelsPerFrame;
332
333 if (asbd.mFormatID == kAudioFormatLinearPCM)
334 {
335 if (isFloat)
336 spec.format = isBE ? PA_SAMPLE_FLOAT32BE : PA_SAMPLE_FLOAT32LE;
337 else
338 {
339 switch (asbd.mBitsPerChannel)
340 {
341 case 8:
342 m_convertUnsignedSigned = isSigned;
343 spec.format = PA_SAMPLE_U8;
344 break;
345 case 16:
346 m_convertUnsignedSigned = !isSigned;
347
348 spec.format = isBE ? PA_SAMPLE_S16BE : PA_SAMPLE_S16LE;
349 break;
350 case 24:
351 m_convertUnsignedSigned = !isSigned;
352
353 spec.format = isBE ? PA_SAMPLE_S24BE : PA_SAMPLE_S24LE;
354 break;
355 case 32:
356 m_convertUnsignedSigned = !isSigned;
357
358 spec.format = isBE ? PA_SAMPLE_S32BE : PA_SAMPLE_S32LE;
359 break;
360 }
361 }
362 }
363 else
364 throw std::runtime_error("Unsupported mFormatID value");
365
366 if (!pa_sample_spec_valid(&spec))
367 throw std::logic_error("Invalid pa_sample_spec constructed");
368
369 return spec;
370}
371
372OSStatus AudioUnitPA::reset(AudioUnitScope inScope, AudioUnitElement inElement)
373{
374 STUB();
375 return unimpErr;
376}
377
378OSStatus AudioUnitPA::deinit()
379{
380 TRACE();
381 if (!m_stream)
382 return kAudioUnitErr_Uninitialized;
383
384 pa_stream_disconnect(m_stream);
385 pa_stream_unref(m_stream);
386 m_stream = nullptr;
387
388 return noErr;
389}
390
391OSStatus AudioUnitPA::start()
392{
393 TRACE();
394 if (!m_stream)
395 return kAudioUnitErr_Uninitialized;
396
397 if (pa_stream_is_corked(m_stream))
398 {
399 m_started = true;
400
401 // const AudioStreamBasicDescription& config = m_config[kOutputBus].first;
402 pa_stream_cork(m_stream, 0, [](pa_stream*, int, void*) {}, nullptr);
403 // requestDataForPlayback(config.mSampleRate / 10 * config.mBytesPerFrame);
404 }
405
406 return noErr;
407}
408
409OSStatus AudioUnitPA::stop()
410{
411 TRACE();
412 if (!m_stream)
413 return kAudioUnitErr_Uninitialized;
414
415 if (pa_stream_is_corked(m_stream))
416 pa_stream_cork(m_stream, 1, [](pa_stream*, int, void*) {}, nullptr);
417
418 m_started = false;
419 return noErr;
420}
421
422struct Completion
423{
424 std::condition_variable cond;
425 std::mutex mutex;
426 bool complete = false;
427};
428
429void AudioUnitPA::initializePA()
430{
431 try
432 {
433 pa_mainloop_api* mainloopApi;
434 const char* appName;
435 Completion comp;
436 static int retval;
437
438 if ((m_mainloop = pa_mainloop_new()) == nullptr)
439 throw std::runtime_error("Failed to create PA mainloop");
440
441 mainloopApi = pa_mainloop_get_api(m_mainloop);
442
443 // TODO: Detect if this is an app in a bundle and get a nice app name
444 appName = (*_NSGetArgv())[0];
445
446 if ((m_context = pa_context_new(mainloopApi, appName)) == nullptr)
447 throw std::runtime_error("Failed to create PA context");
448
449 pa_context_set_state_callback(m_context, paContextStateCB, &comp);
450
451 if (pa_context_connect(m_context, nullptr, pa_context_flags_t(0), nullptr) < 0)
452 {
453 std::stringstream ss;
454 ss << "pa_context_connect() failed: " << pa_strerror(pa_context_errno(m_context));
455 throw std::runtime_error(ss.str());
456 }
457
458 m_mainloopThread = new std::thread(pa_mainloop_run, m_mainloop, &retval);
459
460 std::unique_lock<std::mutex> lk(comp.mutex);
461 comp.cond.wait(lk, [&]{ return comp.complete; });
462
463 if (m_context == nullptr)
464 throw std::runtime_error("Failed to connect context");
465 }
466 catch (const std::exception& e)
467 {
468 pa_mainloop_free(m_mainloop);
469 m_mainloop = nullptr;
470
471 ERROR() << e.what();
472 }
473}
474
475std::string AudioUnitPA::getAppName()
476{
477 Class classBundle;
478 std::string name;
479 size_t pos;
480
481 classBundle = (Class) objc_getClass("NSBundle");
482
483 if (classBundle != nullptr)
484 {
485 // TODO: get a nice app name
486 }
487
488 name = (*_NSGetArgv())[0];
489 pos = name.rfind('/');
490
491 if (pos != std::string::npos)
492 name = name.substr(pos+1);
493
494 return name;
495}
496
497void AudioUnitPA::deinitializePA()
498{
499 if (m_context != nullptr)
500 {
501 pa_context_unref(m_context);
502 m_context = nullptr;
503 }
504 if (m_mainloop != nullptr)
505 {
506 pa_mainloop_api* mainloopApi;
507
508 mainloopApi = pa_mainloop_get_api(m_mainloop);
509 mainloopApi->quit(mainloopApi, 0);
510
511 m_mainloopThread->join();
512 delete m_mainloopThread;
513
514 pa_mainloop_free(m_mainloop);
515 }
516}
517
518void AudioUnitPA::paContextStateCB(pa_context* c, void* priv)
519{
520 Completion* comp = static_cast<Completion*>(priv);
521
522 TRACE() << pa_context_get_state(c);
523 switch (pa_context_get_state(c))
524 {
525 case PA_CONTEXT_READY:
526 {
527 std::unique_lock<std::mutex> lk(comp->mutex);
528 comp->complete = true;
529 comp->cond.notify_one();
530 break;
531 }
532
533 case PA_CONTEXT_FAILED:
534 // Indicate failure
535 pa_context_unref(m_context);
536 m_context = nullptr;
537
538 comp->complete = true;
539 comp->cond.notify_one();
540 break;
541 default:
542 break;
543 }
544}