/* Copyright(C) 2007-2017 VoIP objects (voipobjects.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifdef TARGET_OSX #include "Audio_CoreAudio.h" #include "../Helper/HL_Log.h" //#include #include #include using namespace Audio; #define LOG_SUBSYSTEM "CoreAudio" enum { Bus_Speaker = 0, Bus_Microphone = 1 }; static inline short Float32ToInt16(Float32 v) { //assert (v <= 1.0); int r = int(v * 32768); if (r >= 32768) return (short)32767; else if (r < -32768) return (short)-32768; else return (short)r; } static inline Float32 Int16ToFloat32(short v) { Float32 r = Float32(v) / 32768.0; return r; } static inline Float32 StereoToMono(bool interleaved, Float32* buffer, int samples, int index, int channels) { Float32 sum = 0; for (int i = 0; i(inRefCon); if (!d) return noErr; if (!ioData->mNumberBuffers) return noErr; // Here deinterleaving is expected; it means coreaudio will request multiple buffers - one buffer for one channel // So only single buffer is filled from MacDevice; other are copies AudioBuffer& ab = ioData->mBuffers[0]; if (ab.mNumberChannels == 1) { ICELogMedia(<< "CoreAudio output callback for mono " << (int)ab.mDataByteSize << " bytes"); memset(ab.mData, 0, ab.mDataByteSize); d->provideAudioToSpeaker(ab.mNumberChannels, ab.mData, ab.mDataByteSize); for (int i=1; imNumberBuffers; i++) memcpy(ioData->mBuffers[i].mData, ab.mData, ab.mDataByteSize); } else { ICELogMedia(<< "CoreAudio output callback for stereo " << (int)ab.mDataByteSize << " bytes") // Iterate requested buffers for (unsigned i=0; imNumberBuffers; i++) { unsigned channels = ioData->mBuffers[i].mNumberChannels; short* dataPtr = (short*)ioData->mBuffers[i].mData; unsigned dataSize = ioData->mBuffers[i].mDataByteSize; // Preset with silence memset(dataPtr, 0, dataSize); // Find how much PCM16 samples are required to fill the requested space d->provideAudioToSpeaker(channels, dataPtr, dataSize); } } return noErr; } static char GlobalInputBuffer[AUDIO_MIC_BUFFER_COUNT * AUDIO_MIC_BUFFER_SIZE]; OSStatus MacDevice::inputCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) { //ICELogDebug(<< "CoreAudio input callback"); MacDevice* d = reinterpret_cast(inRefCon); if (!d) return 0; OSStatus ostatus; AudioBuffer& b = d->mInputBufferList->mBuffers[0]; //b.mDataByteSize = 65536; b.mNumberChannels = d->mStreamFormat.mChannelsPerFrame; b.mData = NULL; b.mDataByteSize = inNumberFrames * d->mStreamFormat.mChannelsPerFrame; // Render the unit to get input data ostatus = AudioUnitRender(d->mAudioUnit.getHandle(), ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, d->mInputBufferList); if (ostatus != noErr) { ICELogError(<< "Cannot render input audio data, error " << int(ostatus)); } else { d->obtainAudioFromMic(b.mNumberChannels, b.mData, b.mDataByteSize); } return noErr; } #ifdef TARGET_IOS void MacDevice::propListener(void *inClientData, AudioSessionPropertyID inID, UInt32 inDataSize, const void * inData) { MacDevice* d = reinterpret_cast(inClientData); CFDictionaryRef routeDictionary; CFNumberRef reason; SInt32 reasonVal; if (!d || inID != kAudioSessionProperty_AudioRouteChange) return; routeDictionary = (CFDictionaryRef)inData; reason = (CFNumberRef)CFDictionaryGetValue(routeDictionary, CFSTR(kAudioSession_AudioRouteChangeKey_Reason)); CFNumberGetValue(reason, kCFNumberSInt32Type, &reasonVal); if (reasonVal != kAudioSessionRouteChangeReason_OldDeviceUnavailable) { return; } // Audio route changed. Nothing to do in this implementation. } void MacDevice::interruptionListener(void *inClientData, UInt32 inInterruption) { MacDevice* d = reinterpret_cast(inClientData); if (inInterruption == kAudioSessionEndInterruption) { UInt32 audioCategory; OSStatus ostatus; /* Make sure that your application can receive remote control * events by adding the code: * [[UIApplication sharedApplication] * beginReceivingRemoteControlEvents]; * Otherwise audio unit will fail to restart while your * application is in the background mode. */ /* Make sure we set the correct audio category before restarting */ audioCategory = kAudioSessionCategory_PlayAndRecord; ostatus = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(audioCategory), &audioCategory); if (ostatus != kAudioSessionNoError) { ICELogError(<<"Cannot set the audio session category, error " << ostatus); } // Start stream d->startStream(); } else if (inInterruption == kAudioSessionBeginInterruption) { d->stopStream(); } } #endif MacDevice::MacDevice(int devId) :mDeviceId(devId), mCapture(false), mRender(false), mActive(false), mConnection(nullptr), mUsageCount(0) { mInputBuffer.setCapacity(AUDIO_MIC_BUFFER_COUNT * AUDIO_MIC_BUFFER_SIZE); mOutputBuffer.setCapacity(AUDIO_SPK_BUFFER_SIZE); } MacDevice::~MacDevice() { } DataConnection* MacDevice::connection() { return mConnection; } void MacDevice::setConnection(DataConnection *c) { mConnection = c; } void MacDevice::provideAudioToSpeaker(int channels, void *buffer, int length) { if (!mConnection) return; mConnection->onSpkData(getFormat(), buffer, length); return; } void MacDevice::obtainAudioFromMic(int channels, const void *buffer, int length) { if (!mConnection) return; // Put audio into internal buffer and see if there are ready AUDIO_BUFFER_SIZE chunks mConnection->onMicData(getFormat(), buffer, length); return; } bool MacDevice::open() { Lock l(mGuard); mUsageCount++; if (mUsageCount == 1) { if (!createUnit(true)) { ICELogError(<< "Unable to create&adjust AudioUnit"); return false; // TODO - enable stub for iOS } startStream(); } return true; } void MacDevice::close() { Lock l(mGuard); if (mUsageCount == 1) { stopStream(); destroyUnit(); } if (mUsageCount > 0) mUsageCount--; } void MacDevice::setRender(bool render) { mRender = render; } void MacDevice::setCapture(bool capture) { mCapture = capture; } int MacDevice::getId() { return this->mDeviceId; } Format MacDevice::getFormat() { Format result; result.mChannels = mStreamFormat.mChannelsPerFrame; result.mRate = mStreamFormat.mSampleRate; return result; } void MacDevice::setupStreamFormat() { mStreamFormat.mSampleRate = AUDIO_SAMPLERATE; mStreamFormat.mFormatID = kAudioFormatLinearPCM; mStreamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked; mStreamFormat.mBitsPerChannel = AUDIO_SAMPLE_WIDTH; mStreamFormat.mChannelsPerFrame = AUDIO_CHANNELS; mStreamFormat.mBytesPerFrame = mStreamFormat.mChannelsPerFrame * mStreamFormat.mBitsPerChannel >> 3; mStreamFormat.mFramesPerPacket = 1; mStreamFormat.mBytesPerPacket = mStreamFormat.mFramesPerPacket * mStreamFormat.mBytesPerFrame; } bool MacDevice::createUnit(bool voice) { OSStatus ostatus; mAudioUnit.open(voice); //if (mCapture != mAudioUnit.getEnabled(kAudioUnitScope_Input, Bus_Microphone)) mAudioUnit.setEnabled(mCapture, kAudioUnitScope_Input, Bus_Microphone); //mAudioUnit.setEnabled(mCapture, kAudioUnitScope_Output, Bus_Microphone); //if (mRender != mAudioUnit.getEnabled(kAudioUnitScope_Output, Bus_Speaker)) mAudioUnit.setEnabled(mRender, kAudioUnitScope_Output, Bus_Speaker); //mAudioUnit.setEnabled(mRender, kAudioUnitScope_Input, Bus_Speaker); #ifdef TARGET_OSX // Open device if (mCapture) mAudioUnit.makeCurrent(mDeviceId, kAudioUnitScope_Global, Bus_Microphone); if (mRender) mAudioUnit.makeCurrent(mDeviceId, kAudioUnitScope_Global, Bus_Speaker); #endif setupStreamFormat(); if (mCapture) { mCaptureInputFormat = mAudioUnit.getFormat(kAudioUnitScope_Input, Bus_Microphone); mCaptureOutputFormat = mAudioUnit.getFormat(kAudioUnitScope_Output, Bus_Microphone); mStreamFormat.mSampleRate = mCaptureInputFormat.mSampleRate; if (mCaptureOutputFormat.mFormatFlags & kAudioFormatFlagIsNonInterleaved) ICELogDebug(<< "Input device produces noninterleaved data"); mAudioUnit.setFormat(mStreamFormat, kAudioUnitScope_Output, Bus_Microphone); // Start resampler mCaptureResampler.start(mStreamFormat.mChannelsPerFrame, mStreamFormat.mSampleRate, AUDIO_SAMPLERATE); } if (mRender) { // Init resample mRenderOutputFormat = mAudioUnit.getFormat(kAudioUnitScope_Output, Bus_Speaker); mRenderInputFormat = mAudioUnit.getFormat(kAudioUnitScope_Input, Bus_Speaker); mAudioUnit.setFormat(mStreamFormat, kAudioUnitScope_Input, Bus_Speaker); // Start resample mRenderResampler.start(mStreamFormat.mChannelsPerFrame, AUDIO_SAMPLERATE, mStreamFormat.mSampleRate); // Set current render format - it is format required by unit from application; scope is Input and bus is 0 (speaker) //mAudioUnit.setFormat(mRenderInputFormat, kAudioUnitScope_Input, Bus_Speaker); // Configure callback AURenderCallbackStruct cb; cb.inputProc = outputCallback; cb.inputProcRefCon = this; mAudioUnit.setCallback(cb, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, Bus_Speaker); } if (mCapture) { AURenderCallbackStruct cb; cb.inputProc = inputCallback; cb.inputProcRefCon = this; mAudioUnit.setCallback(cb, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, Bus_Microphone); //!!! #ifdef TARGET_OSX AudioBuffer *ab; UInt32 size, bufsize; mAudioUnit.setBufferFrameSizeInMilliseconds(COREAUDIO_BUFFER_TIME); // Get device's buffer frame size bufsize = mAudioUnit.getBufferFrameSize(); mInputBufferList = (AudioBufferList*)malloc(sizeof(AudioBufferList) + sizeof(AudioBuffer)); if (!mInputBufferList) { ICELogError(<< "No memory for buffer list"); return false; } mInputBufferList->mNumberBuffers = 1; ab = &mInputBufferList->mBuffers[0]; ab->mNumberChannels = mStreamFormat.mChannelsPerFrame; ab->mDataByteSize = 0;//bufsize * ab->mNumberChannels * mCaptureOutputFormat.mBitsPerChannel / 8 * 8; ab->mData = NULL;//malloc(ab->mDataByteSize); if (!ab->mData) { //ICELogError(<< "No memory for capture buffer"); } #endif #ifdef TARGET_IOS /* We will let AudioUnitRender() to allocate the buffer * for us later */ mInputBufferList = (AudioBufferList*)malloc(sizeof(AudioBufferList) + sizeof(AudioBuffer)); if (!mInputBufferList) { ICELogError(<< "No memory for buffer list"); return false; } mInputBufferList->mNumberBuffers = 1; mInputBufferList->mBuffers[0].mNumberChannels = 2; #endif } // Finally bring it online mAudioUnit.initialize(); return true; } void MacDevice::destroyUnit() { mAudioUnit.close(); mCaptureResampler.stop(); mRenderResampler.stop(); } void MacDevice::startStream() { if (mActive) return; #ifdef TARGET_IOS if (iosVersion() > 4) { UInt32 sessionMode = kAudioSessionMode_VoiceChat; AudioSessionSetProperty(kAudioSessionProperty_Mode, sizeof(sessionMode), &sessionMode); } // Share audio chain //UInt32 allowMixing = YES; //AudioSessionSetProperty (kAudioSessionProperty_OverrideCategoryMixWithOthers, sizeof (allowMixing), &allowMixing); // Activate audio chain AudioSessionSetActive(true); #endif OSStatus ostatus; if (mAudioUnit.getHandle()) { ostatus = AudioOutputUnitStart(mAudioUnit.getHandle()); if (ostatus != noErr) { ICELogError(<< "Failed to start audio unit, error " << int(ostatus)); return; } } mActive = true; } void MacDevice::stopStream() { if (!mActive || !mAudioUnit.getHandle()) return; OSStatus ostatus; ostatus = AudioOutputUnitStop(mAudioUnit.getHandle()); if (ostatus != noErr) { ICELogError(<< "Failed to stop audio unit, error " << int(ostatus)); } #ifdef TARGET_IOS AudioSessionSetActive(false); #endif mActive = false; } bool MacDevice::createResampleUnit(AudioStreamBasicDescription format) { return true; } class MacDeviceList { public: MacDeviceList(); ~MacDeviceList(); PMacDevice findDevice(int devId); static MacDeviceList& instance(); protected: Mutex mGuard; std::vector mDeviceList; static MacDeviceList* mInstance; }; MacDeviceList* MacDeviceList::mInstance = NULL; MacDeviceList::MacDeviceList() { } MacDeviceList::~MacDeviceList() { } MacDeviceList& MacDeviceList::instance() { if (!mInstance) mInstance = new MacDeviceList(); return *mInstance; } PMacDevice MacDeviceList::findDevice(int devId) { Lock l(mGuard); for (unsigned i=0; igetId() == devId) return d; } PMacDevice d(new MacDevice(devId)); mDeviceList.push_back(d); return d; } // Share list of opened devices MacInputDevice::MacInputDevice(int devId) :InputDevice() { // Look for MacDevice mDevice = MacDeviceList::instance().findDevice(devId); mDevice->setCapture(true); mDevice->setConnection(mConnection); } MacInputDevice::~MacInputDevice() { mDevice.reset(); } bool MacInputDevice::open() { mDevice->setConnection(mConnection); return mDevice->open(); } void MacInputDevice::close() { mDevice->close(); } Format MacInputDevice::getFormat() { return mDevice->getFormat(); } MacOutputDevice::MacOutputDevice(int devId) :OutputDevice() { // Look for MacDevice mDevice = MacDeviceList::instance().findDevice(devId); mDevice->setRender(true); mDevice->setConnection(mConnection); } MacOutputDevice::~MacOutputDevice() { mDevice.reset(); } bool MacOutputDevice::open() { mDevice->setConnection(mConnection); return mDevice->open(); } void MacOutputDevice::close() { mDevice->close(); } Format MacOutputDevice::getFormat() { return mDevice->getFormat(); } MacEnumerator::MacEnumerator() :mDefaultInput(0), mDefaultOutput(0) { } MacEnumerator::~MacEnumerator() { } void MacEnumerator::open(int direction) { mDirection = direction; #ifdef TARGET_OSX mDefaultInput = 0; mDefaultOutput = 0; mDeviceList.clear(); AudioObjectPropertyAddress addr; OSStatus osstatus; UInt32 devSize, size; // Get audio list size addr.mSelector = kAudioHardwarePropertyDevices; addr.mScope = kAudioObjectPropertyScopeGlobal; addr.mElement = kAudioObjectPropertyElementMaster; osstatus = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr, 0, NULL, &devSize); if (osstatus != noErr) { devSize = 0; return; } UInt32 devCount = devSize / sizeof(AudioDeviceID); std::vector deviceIds; deviceIds.resize(devCount); // Get actual list osstatus = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0, NULL, &devSize, (void *)&deviceIds.front()); if (osstatus != noErr) return; for (unsigned i=0; i::iterator r = std::find_if(mDeviceList.begin(), mDeviceList.end(), [devId] (const DeviceInfo& di) { return di.mId == devId;}); if (r != mDeviceList.end()) mDefaultInput = r - mDeviceList.begin(); } } /* Find default audio output device */ if (direction == mySpeaker) { addr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; osstatus = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0, NULL, &size, (void *)&devId); if (osstatus == noErr) { std::vector::iterator r = std::find_if(mDeviceList.begin(), mDeviceList.end(), [devId](const DeviceInfo& di) { return di.mId == devId;}); if (r != mDeviceList.end()) mDefaultOutput = r - mDeviceList.begin(); } } #endif } void MacEnumerator::getInfo(DeviceInfo &di) { UInt32 size; OSStatus osstatus; // Name AudioObjectPropertyAddress addr; addr.mSelector = kAudioDevicePropertyDeviceName; addr.mScope = kAudioObjectPropertyScopeGlobal; addr.mElement = kAudioObjectPropertyElementMaster; char name[256]; size = sizeof(name); AudioObjectGetPropertyData(di.mId, &addr, 0, NULL, &size, (void *)name); di.mName = name; // Get the number of input channels addr.mSelector = kAudioDevicePropertyStreamConfiguration; addr.mScope = kAudioDevicePropertyScopeInput; size = 0; osstatus = AudioObjectGetPropertyDataSize(di.mId, &addr, 0, NULL, &size); AudioBufferList* buf = NULL; if (osstatus == noErr && size > 0) buf = (AudioBufferList*)malloc(size); if (buf) { UInt32 idx; /* Get the input stream configuration */ osstatus = AudioObjectGetPropertyData(di.mId, &addr, 0, NULL, &size, buf); if (osstatus == noErr) { /* Count the total number of input channels in * the stream */ for (idx = 0; idx < buf->mNumberBuffers; idx++) { di.mInputCount += buf->mBuffers[idx].mNumberChannels; } } free(buf); buf = NULL; } // Get the number of output channels addr.mScope = kAudioDevicePropertyScopeOutput; size = 0; osstatus = AudioObjectGetPropertyDataSize(di.mId, &addr, 0, NULL, &size); if (osstatus == noErr && size > 0) buf = (AudioBufferList*)malloc(size); if (buf) { UInt32 idx; /* Get the output stream configuration */ osstatus = AudioObjectGetPropertyData(di.mId, &addr, 0, NULL, &size, buf); if (osstatus == noErr) { /* Count the total number of output channels in * the stream */ for (idx = 0; idx < buf->mNumberBuffers; idx++) { di.mOutputCount += buf->mBuffers[idx].mNumberChannels; } } free(buf); buf = NULL; } /* Get default sample rate */ addr.mSelector = kAudioDevicePropertyNominalSampleRate; addr.mScope = kAudioObjectPropertyScopeGlobal; size = sizeof(Float64); Float64 sampleRate; osstatus = AudioObjectGetPropertyData (di.mId, &addr, 0, NULL, &size, &sampleRate); if (osstatus == noErr) di.mDefaultRate = int(sampleRate); /* Set device capabilities here */ if (di.mOutputCount > 0) { addr.mSelector = kAudioDevicePropertyVolumeScalar; addr.mScope = kAudioDevicePropertyScopeOutput; osstatus = AudioObjectHasProperty(di.mId, &addr); if (osstatus == noErr) { di.mCanChangeOutputVolume = true; } } } void MacEnumerator::close() { } int MacEnumerator::count() { return mDeviceList.size(); } std::tstring MacEnumerator::nameAt(int index) { return mDeviceList[index].mName; } int MacEnumerator::idAt(int index) { return mDeviceList[index].mId; } int MacEnumerator::indexOfDefaultDevice() { if (mDirection == myMicrophone) return mDefaultInput; else return mDefaultOutput; } #endif