this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at vchroot 544 lines 13 kB view raw
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}