Something went wrong. Try again.
Cider Isn't Darwin Emulation, Really
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771#include "darling-config.h"#include "AudioUnitALSA.h"#include "AudioUnitProperties.h"#include <CoreServices/MacErrors.h>#include <util/debug.h>#include <sstream>#include <stdexcept>#include <memory>#include <cstdio>#include <dispatch/dispatch.h>
#define SAMPLE_PERIOD 4096
static dispatch_queue_t g_audioQueue;
AudioUnitALSA::AudioUnitALSA(int cardIndex, char* cardName): m_cardIndex(cardIndex), m_cardName(cardName), m_pcmOutput(nullptr), m_pcmInput(nullptr){ static dispatch_once_t pred; dispatch_once(&pred, ^{ g_audioQueue = dispatch_queue_create("org.darlinghw.audiounit", nullptr); });}
static void throwAlsaError(const std::string& msg, int errCode){ std::stringstream ss; ss << msg << ": " << snd_strerror(errCode); throw std::runtime_error(ss.str());}
static snd_pcm_format_t alsaFormatForASBD(const AudioStreamBasicDescription& asbd){ bool isFloat = asbd.mFormatFlags & kAudioFormatFlagIsFloat; bool isSigned = asbd.mFormatFlags & kAudioFormatFlagIsSignedInteger; bool isBE = asbd.mFormatFlags & kAudioFormatFlagIsBigEndian; if (asbd.mFormatID == kAudioFormatLinearPCM) { if (isFloat) return isBE ? SND_PCM_FORMAT_FLOAT_BE : SND_PCM_FORMAT_FLOAT_LE; switch (asbd.mBitsPerChannel) { case 8: if (isSigned) return SND_PCM_FORMAT_S8; else return SND_PCM_FORMAT_U8; case 16: if (isSigned) return isBE ? SND_PCM_FORMAT_S16_BE : SND_PCM_FORMAT_S16_LE; else return isBE ? SND_PCM_FORMAT_U16_BE : SND_PCM_FORMAT_U16_LE; case 24: if (isSigned) return isBE ? SND_PCM_FORMAT_S24_BE : SND_PCM_FORMAT_S24_LE; else return isBE ? SND_PCM_FORMAT_U24_BE : SND_PCM_FORMAT_U24_LE; case 32: if (isSigned) return isBE ? SND_PCM_FORMAT_S32_BE : SND_PCM_FORMAT_S32_LE; else return isBE ? SND_PCM_FORMAT_U32_BE : SND_PCM_FORMAT_U32_LE; default: throw std::runtime_error("Invalid mBitsPerChannel value"); } } else throw std::runtime_error("Unsupported mFormatID value");}
AudioUnitComponent* AudioUnitALSA::create(int cardIndex){ char* name; if (cardIndex > 0) { if (snd_card_get_name(cardIndex, &name)) return nullptr; } else { name = strdup("default" /*"plughw:0"*/); } return new AudioUnitALSA(cardIndex, name);}
AudioUnitALSA::~AudioUnitALSA(){ deinit(); free(m_cardName);}
OSStatus AudioUnitALSA::reset(AudioUnitScope inScope, AudioUnitElement inElement){ STUB(); return unimpErr;}
void AudioUnitALSA::initOutput(){ snd_pcm_hw_params_t* hw_params = nullptr; snd_pcm_sw_params_t* sw_params = nullptr; try { // Configure ALSA based on m_configOutputPlayback int err; const AudioStreamBasicDescription& alsaConfig = m_config[kOutputBus].second; unsigned int rate = alsaConfig.mSampleRate; //if (memcmp(&m_config[kOutputBus].second, &m_config[kOutputBus].first, sizeof(AudioStreamBasicDescription)) != 0) // throw std::runtime_error("Different input and HW config not implemented yet"); // Let ALSA do the conversion on its own m_config[kOutputBus].second = m_config[kOutputBus].first; err = snd_pcm_open(&m_pcmOutput, m_cardName, SND_PCM_STREAM_PLAYBACK, 0); if (err < 0) throwAlsaError("Failed to initialize playback PCM", err); err = snd_pcm_hw_params_malloc(&hw_params); if (err < 0) throwAlsaError("Failed to alloc hw params", err); err = snd_pcm_hw_params_any(m_pcmOutput, hw_params); if (err < 0) throwAlsaError("Failed to init hw params", err); if (isOutputPlanar()) err = snd_pcm_hw_params_set_access(m_pcmOutput, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED); else err = snd_pcm_hw_params_set_access(m_pcmOutput, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); if (err < 0) throwAlsaError("Failed to set interleaved access", err); err = snd_pcm_hw_params_set_format(m_pcmOutput, hw_params, alsaFormatForASBD(alsaConfig)); if (err < 0) throwAlsaError("Failed to set format", err); err = snd_pcm_hw_params_set_rate_near(m_pcmOutput, hw_params, &rate, 0); if (err < 0) throwAlsaError("Failed to set sample rate", err); LOG << "Channel count: " << int(alsaConfig.mChannelsPerFrame) << std::endl; err = snd_pcm_hw_params_set_channels(m_pcmOutput, hw_params, alsaConfig.mChannelsPerFrame); if (err < 0) throwAlsaError("Failed to set channel count", err); err = snd_pcm_hw_params(m_pcmOutput, hw_params); if (err < 0) throwAlsaError("Failed to set HW parameters", err); snd_pcm_hw_params_free(hw_params); hw_params = nullptr; err = snd_pcm_sw_params_malloc(&sw_params); if (err < 0) throwAlsaError("Failed to alloc sw params", err); err = snd_pcm_sw_params_current(m_pcmOutput, sw_params); if (err < 0) throwAlsaError("Failed to init sw params", err); err = snd_pcm_sw_params_set_avail_min(m_pcmOutput, sw_params, SAMPLE_PERIOD); if (err < 0) throwAlsaError("snd_pcm_sw_params_set_avail_min() failed", err); err = snd_pcm_sw_params_set_start_threshold(m_pcmOutput, sw_params, 0U); if (err < 0) throwAlsaError("snd_pcm_sw_params_set_start_threshold() failed", err); err = snd_pcm_sw_params_set_tstamp_mode(m_pcmOutput, sw_params, SND_PCM_TSTAMP_ENABLE); if (err < 0) throwAlsaError("snd_pcm_sw_params_set_tstamp_mode() failed", err); err = snd_pcm_sw_params(m_pcmOutput, sw_params); if (err < 0) throwAlsaError("Failed to set SW parameters", err); snd_pcm_sw_params_free(sw_params); sw_params = nullptr; } catch (...) { if (hw_params != nullptr) snd_pcm_hw_params_free(hw_params); if (sw_params != nullptr) snd_pcm_sw_params_free(sw_params); throw; }}
void AudioUnitALSA::initInput(){ STUB(); try { //if (memcmp(&m_config[kInputBus].second, &m_config[kInputBus].first, sizeof(AudioStreamBasicDescription)) != 0) // throw std::runtime_error("Different input and HW config not implemented yet"); // Let ALSA do the conversion on its own m_config[kInputBus].first = m_config[kInputBus].second; // TODO: support recording } catch (...) { throw; }}
OSStatus AudioUnitALSA::init(){ try { if (m_pcmOutput || m_pcmInput) return kAudioUnitErr_Initialized;
/*if (m_enableInput && snd_pcm_open(&m_pcmInput, m_cardName, SND_PCM_STREAM_CAPTURE, 0)) { deinit();
LOG << "Failed to initialize capture PCM " << m_cardName << std::endl; return kAudioUnitErr_FailedInitialization; }*/ if (m_enableOutput) initOutput(); if (m_enableInput) initInput(); } catch (const std::exception& e) { ERROR() << e.what(); deinit(); return kAudioUnitErr_FailedInitialization; } return noErr;}
OSStatus AudioUnitALSA::deinit(){ if (m_pcmOutput) { snd_pcm_close(m_pcmOutput); m_pcmOutput = nullptr; } if (m_pcmInput) { snd_pcm_close(m_pcmInput); m_pcmInput = nullptr; } return noErr;}
void AudioUnitALSA::processAudioEvent(struct pollfd origPoll, int event){ struct pollfd pfd = origPoll; unsigned short revents; int err; TRACE1(event);
pfd.revents = event;
err = snd_pcm_poll_descriptors_revents(m_pcmOutput, &pfd, 1, &revents); if (err < 0) ERROR() << "snd_pcm_poll_descriptors_revents() failed: " << snd_strerror(err); if (revents & POLLIN) pushDataFromInput(); if (revents & POLLOUT) requestDataForPlayback();}
void AudioUnitALSA::requestDataForPlayback(){ AudioUnitRenderActionFlags flags; AudioTimeStamp ts; AudioBufferList* bufs; OSStatus err; std::unique_ptr<uint8_t[]> data; snd_pcm_uframes_t availFrames; snd_htimestamp_t alsaTimestamp; const AudioStreamBasicDescription& config = m_config[kOutputBus].first; UInt32 cc = config.mChannelsPerFrame; TRACE(); memset(&ts, 0, sizeof(ts)); if (isOutputPlanar()) { bufs = (AudioBufferList*) operator new(sizeof(AudioBufferList) + (cc-1)*sizeof(AudioBuffer)); bufs->mNumberBuffers = cc; } else { bufs = (AudioBufferList*) operator new(sizeof(AudioBufferList)); bufs->mNumberBuffers = 1; } if (m_shouldAllocateBuffer) { if (isOutputPlanar()) { UInt32 bytesPerChannel = config.mBytesPerFrame * SAMPLE_PERIOD; data.reset(new uint8_t[cc*bytesPerChannel]); for (UInt32 i = 0; i < cc; i++) { bufs->mBuffers[i].mNumberChannels = 1; bufs->mBuffers[i].mDataByteSize = bytesPerChannel; bufs->mBuffers[i].mData = data.get() + i*bytesPerChannel; } } else { bufs->mBuffers[0].mNumberChannels = cc; bufs->mBuffers[0].mDataByteSize = config.mBytesPerFrame * SAMPLE_PERIOD; data.reset(new uint8_t[bufs->mBuffers[0].mDataByteSize]); bufs->mBuffers[0].mData = data.get(); } } else { if (isOutputPlanar()) { for (UInt32 i = 0; i < cc; i++) { bufs->mBuffers[i].mDataByteSize = 0; bufs->mBuffers[i].mData = nullptr; } } else { bufs->mBuffers[0].mDataByteSize = 0; bufs->mBuffers[0].mData = nullptr; } } snd_pcm_htimestamp(m_pcmOutput, &availFrames, &alsaTimestamp); // TODO: fill in AudioTimeStamp based on alsaTimestamp err = AudioUnitRender(m_inputUnit.sourceAudioUnit, &flags, &ts, kOutputBus, SAMPLE_PERIOD, bufs); if (err != noErr) { ERROR() << "Render callback failed with error " << err; // Fill with silence, the error may be temporary UInt32 bytes = config.mBytesPerFrame * SAMPLE_PERIOD; if (!m_shouldAllocateBuffer) { if (isOutputPlanar()) { data.reset(new uint8_t[bytes*cc]); memset(data.get(), 0, bytes*cc); } else { data.reset(new uint8_t[bytes]); memset(data.get(), 0, bytes); } } if (isOutputPlanar()) { for (UInt32 i = 0; i < cc; i++) { bufs->mBuffers[i].mData = data.get() + bytes*i; bufs->mBuffers[i].mDataByteSize = bytes; } } else { bufs->mBuffers[0].mData = data.get(); bufs->mBuffers[0].mDataByteSize = bytes; } } m_lastRenderError = AudioUnitRender(this, &flags, &ts, kOutputBus, SAMPLE_PERIOD, bufs); operator delete(bufs);}
void AudioUnitALSA::pushDataFromInput(){ AudioUnitRenderActionFlags flags; AudioTimeStamp ts; AudioBufferList* bufs; OSStatus err; std::unique_ptr<uint8_t[]> data; snd_pcm_uframes_t availFrames; snd_htimestamp_t alsaTimestamp; const AudioStreamBasicDescription& config = m_config[kInputBus].second; TRACE(); memset(&ts, 0, sizeof(ts)); if (m_shouldAllocateBuffer) { if (isInputPlanar()) { UInt32 cc = config.mChannelsPerFrame; bufs = (AudioBufferList*) operator new(sizeof(AudioBufferList) + (cc-1)*sizeof(AudioBuffer)); bufs->mNumberBuffers = cc; } else { bufs = (AudioBufferList*) operator new(sizeof(AudioBufferList)); bufs->mNumberBuffers = 1; } if (isInputPlanar()) { UInt32 cc = config.mChannelsPerFrame; UInt32 bytesPerChannel = config.mBytesPerFrame * SAMPLE_PERIOD; data.reset(new uint8_t[cc*bytesPerChannel]); for (UInt32 i = 0; i < cc; i++) { bufs->mBuffers[i].mNumberChannels = 1; bufs->mBuffers[i].mDataByteSize = bytesPerChannel; bufs->mBuffers[i].mData = data.get() + i*bytesPerChannel; } } else { bufs->mBuffers[0].mNumberChannels = config.mChannelsPerFrame; bufs->mBuffers[0].mDataByteSize = config.mBytesPerFrame * SAMPLE_PERIOD; data.reset(new uint8_t[bufs->mBuffers[0].mDataByteSize]); bufs->mBuffers[0].mData = data.get(); } //render(&flags, &ts, kInputBus, SAMPLE_PERIOD, bufs); } else bufs = nullptr; // TODO: fill in AudioTimeStamp based on alsaTimestamp snd_pcm_htimestamp(m_pcmOutput, &availFrames, &alsaTimestamp); m_outputCallback.inputProc(m_outputCallback.inputProcRefCon, &flags, &ts, kInputBus, SAMPLE_PERIOD, bufs); operator delete(bufs);}
OSStatus AudioUnitALSA::render(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData){ if (inBusNumber == kOutputBus) return renderOutput(ioActionFlags, inTimeStamp, inNumberFrames, ioData); else if (inBusNumber == kInputBus) return renderInput(ioActionFlags, inTimeStamp, inNumberFrames, ioData); else return paramErr;}
OSStatus AudioUnitALSA::renderOutput(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inNumberFrames, AudioBufferList *ioData){ if (!isOutputPlanar()) return renderInterleavedOutput(ioActionFlags, inTimeStamp, inNumberFrames, ioData); else return renderPlanarOutput(ioActionFlags, inTimeStamp, inNumberFrames, ioData);}
OSStatus AudioUnitALSA::renderInterleavedOutput(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inNumberFrames, AudioBufferList *ioData){ int wr, sampleCount; const AudioStreamBasicDescription& config = m_config[kOutputBus].first; UInt32 framesSoFar = 0; for (UInt32 i = 0; i < ioData->mNumberBuffers; i++) { LOG << "Writing " << ioData->mBuffers[i].mDataByteSize << " bytes into sound card\n"; sampleCount = std::min<UInt32>(ioData->mBuffers[i].mDataByteSize / config.mBytesPerFrame, inNumberFrames - framesSoFar); framesSoFar += sampleCount; if (!sampleCount) break;
do_write: wr = snd_pcm_writei(m_pcmOutput, ioData->mBuffers[i].mData, sampleCount); if (wr < 0) { if (wr == -EINTR || wr == -EPIPE) { LOG << "Recovering PCM\n"; snd_pcm_recover(m_pcmOutput, wr, false); goto do_write; } else { ERROR() << "snd_pcm_writei() failed: " << snd_strerror(wr); return kAudioUnitErr_NoConnection; } } else if (wr < sampleCount) ERROR() << "snd_pcm_writei(): not all data written?"; } return noErr;}
OSStatus AudioUnitALSA::renderPlanarOutput(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inNumberFrames, AudioBufferList *ioData){ std::unique_ptr<void*[]> bufferPointers (new void*[ioData->mNumberBuffers]); int size, sampleCount, wr; const AudioStreamBasicDescription& config = m_config[kOutputBus].first; if (ioData->mNumberBuffers != config.mChannelsPerFrame) { ERROR() << "Incorrect buffer count for planar audio, only " << ioData->mNumberBuffers; return paramErr; } size = ioData->mBuffers[0].mDataByteSize; for (UInt32 i = 1; i < ioData->mNumberBuffers; i++) { if (size != ioData->mBuffers[i].mDataByteSize) { ERROR() << "Bad buffer size in buffer " << i; return paramErr; } } for (UInt32 i = 0; i < ioData->mNumberBuffers; i++) bufferPointers[i] = ioData->mBuffers[i].mData; sampleCount = size / config.mBytesPerFrame; do_write: wr = snd_pcm_writen(m_pcmOutput, bufferPointers.get(), sampleCount); if (wr < 0) { if (wr == -EINTR || wr == -EPIPE) { LOG << "Recovering PCM\n"; snd_pcm_recover(m_pcmOutput, wr, false); goto do_write; } else { ERROR() << "snd_pcm_writen() failed: " << snd_strerror(wr); return kAudioUnitErr_NoConnection; } } else if (wr < sampleCount) ERROR() << "snd_pcm_writen(): not all data written?"; return noErr;}
OSStatus AudioUnitALSA::renderInput(AudioUnitRenderActionFlags *ioActionFlags,const AudioTimeStamp *inTimeStamp, UInt32 inNumberFrames, AudioBufferList *ioData){ if (!m_outputCallback.inputProc) return noErr; // We don't push, we should be polled if (!isInputPlanar()) return renderInterleavedInput(ioActionFlags, inTimeStamp, inNumberFrames, ioData); else return renderPlanarInput(ioActionFlags, inTimeStamp, inNumberFrames, ioData);}
OSStatus AudioUnitALSA::renderInterleavedInput(AudioUnitRenderActionFlags *ioActionFlags,const AudioTimeStamp *inTimeStamp, UInt32 inNumberFrames, AudioBufferList *ioData){ int wr, sampleCount; const AudioStreamBasicDescription& config = m_config[kInputBus].second; for (UInt32 i = 0; i < ioData->mNumberBuffers; i++) { LOG << "Reading up to " << ioData->mBuffers[i].mDataByteSize << " bytes from sound card\n"; sampleCount = ioData->mBuffers[i].mDataByteSize / config.mBytesPerFrame;
do_write: wr = snd_pcm_writei(m_pcmOutput, ioData->mBuffers[i].mData, sampleCount); if (wr < 0) { if (wr == -EINTR || wr == -EPIPE) { snd_pcm_recover(m_pcmOutput, wr, false); goto do_write; } else { ERROR() << "snd_pcm_writei() failed: " << snd_strerror(wr); return kAudioUnitErr_NoConnection; } } ioData->mBuffers[i].mDataByteSize = wr * config.mBytesPerFrame; } return noErr;}
OSStatus AudioUnitALSA::renderPlanarInput(AudioUnitRenderActionFlags *ioActionFlags,const AudioTimeStamp *inTimeStamp, UInt32 inNumberFrames, AudioBufferList *ioData){ std::unique_ptr<void*[]> bufferPointers (new void*[ioData->mNumberBuffers]); int size, sampleCount, wr; const AudioStreamBasicDescription& config = m_config[kInputBus].second; if (ioData->mNumberBuffers != config.mChannelsPerFrame) { ERROR() << "Incorrect buffer count for planar audio, only " << ioData->mNumberBuffers; return paramErr; } size = ioData->mBuffers[0].mDataByteSize; for (UInt32 i = 1; i < ioData->mNumberBuffers; i++) { if (size != ioData->mBuffers[i].mDataByteSize) { ERROR() << "Bad buffer size in buffer " << i; return paramErr; } } for (UInt32 i = 0; i < ioData->mNumberBuffers; i++) bufferPointers[i] = ioData->mBuffers[i].mData; sampleCount = size / config.mBytesPerFrame; do_write: wr = snd_pcm_readn(m_pcmInput, bufferPointers.get(), sampleCount); if (wr < 0) { if (wr == -EINTR || wr == -EPIPE) { snd_pcm_recover(m_pcmInput, wr, false); goto do_write; } else { ERROR() << "snd_pcm_writen() failed: " << snd_strerror(wr); return kAudioUnitErr_NoConnection; } } for (UInt32 i = 0; i < ioData->mNumberBuffers; i++) ioData->mBuffers[i].mDataByteSize = sampleCount * config.mBytesPerFrame; return noErr;}
void AudioUnitALSA::startOutput(){ std::unique_ptr<struct pollfd[]> pollfds; int count, err; if (!m_inputUnit.sourceAudioUnit) throw std::runtime_error("No input unit set"); err = snd_pcm_prepare(m_pcmOutput); if (err != 0) throwAlsaError("snd_pcm_prepare() failed", err); count = snd_pcm_poll_descriptors_count(m_pcmOutput); pollfds.reset(new struct pollfd[count]); if (snd_pcm_poll_descriptors(m_pcmOutput, pollfds.get(), count) != count) throw std::runtime_error("snd_pcm_poll_descriptors() failed"); LOG << "ALSA descriptor count: " << count << std::endl; startDescriptors(pollfds.get(), count);}
void AudioUnitALSA::startDescriptors(const struct pollfd* pollfds, int count){ for (int i = 0; i < count; i++) { dispatch_source_t source; const struct pollfd& cur = pollfds[i]; if (cur.events & POLLIN) { source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, cur.fd, 0, g_audioQueue); dispatch_source_set_event_handler(source, ^{ processAudioEvent(cur, POLLIN); }); dispatch_resume(source); m_sources.push_back(source); } if (cur.events & POLLOUT) { source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, cur.fd, 0, g_audioQueue); dispatch_source_set_event_handler(source, ^{ processAudioEvent(cur, POLLOUT); }); dispatch_resume(source); m_sources.push_back(source); } }}
void AudioUnitALSA::startInput(){ int err; STUB(); err = snd_pcm_prepare(m_pcmInput); if (err != 0) throwAlsaError("snd_pcm_prepare() failed", err);}
OSStatus AudioUnitALSA::start(){ int err; TRACE(); try { if (m_pcmInput) startInput();
if (m_pcmOutput) startOutput();
} catch (const std::exception& e) { if (m_pcmInput) snd_pcm_drop(m_pcmInput); if (m_pcmOutput) snd_pcm_drop(m_pcmOutput); ERROR() << e.what(); return kAudioUnitErr_FailedInitialization; } return noErr;}
OSStatus AudioUnitALSA::stop(){ TRACE(); for (dispatch_source_t src : m_sources) dispatch_source_cancel(src); m_sources.clear(); if (m_pcmInput) snd_pcm_drop(m_pcmInput); if (m_pcmOutput) snd_pcm_drop(m_pcmOutput); return noErr;}