#include "AudioVoiceEngine.hpp" #include "logvisor/logvisor.hpp" #include #include #include namespace boo { static logvisor::Module Log("boo::AQS"); static AudioChannel AQSChannelToBooChannel(AudioChannelLabel ch) { switch (ch) { case kAudioChannelLabel_Left: return AudioChannel::FrontLeft; case kAudioChannelLabel_Right: return AudioChannel::FrontRight; case kAudioChannelLabel_LeftSurround: return AudioChannel::RearLeft; case kAudioChannelLabel_RightSurround: return AudioChannel::RearRight; case kAudioChannelLabel_Center: return AudioChannel::FrontCenter; case kAudioChannelLabel_LFEScreen: return AudioChannel::LFE; case kAudioChannelLabel_LeftSurroundDirect: return AudioChannel::RearLeft; case kAudioChannelLabel_RightSurroundDirect: return AudioChannel::SideRight; } return AudioChannel::Unknown; } struct AQSAudioVoiceEngine : BaseAudioVoiceEngine { AudioQueueRef m_queue = nullptr; AudioQueueBufferRef m_buffers[3]; size_t m_frameBytes; std::mutex m_engineMutex; std::condition_variable m_engineCv; static void Callback(AQSAudioVoiceEngine* engine, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) { std::unique_lock lk(engine->m_engineMutex); engine->m_engineCv.wait(lk); engine->_pumpAndMixVoices(engine->m_mixInfo.m_periodFrames, reinterpret_cast(inBuffer->mAudioData)); inBuffer->mAudioDataByteSize = engine->m_frameBytes; AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, nullptr); } static void DummyCallback(AQSAudioVoiceEngine* engine, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) {} AudioChannelSet _getAvailableSet() { const unsigned chCount = 8; AudioStreamBasicDescription desc = {}; desc.mSampleRate = 96000; desc.mFormatID = kAudioFormatLinearPCM; desc.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; desc.mBytesPerPacket = chCount * 4; desc.mFramesPerPacket = 1; desc.mBytesPerFrame = chCount * 4; desc.mChannelsPerFrame = chCount; desc.mBitsPerChannel = 32; AudioQueueRef queue; if (AudioQueueNewOutput(&desc, AudioQueueOutputCallback(DummyCallback), this, nullptr, nullptr, 0, &queue)) { Log.report(logvisor::Error, "unable to create output audio queue"); return AudioChannelSet::Unknown; } UInt32 hwChannels; UInt32 channelsSz = sizeof(UInt32); if (AudioQueueGetProperty(queue, kAudioQueueDeviceProperty_NumberChannels, &hwChannels, &channelsSz)) { Log.report(logvisor::Error, "unable to get channel count from audio queue"); AudioQueueDispose(queue, true); return AudioChannelSet::Unknown; } AudioQueueDispose(queue, true); switch (hwChannels) { case 2: return AudioChannelSet::Stereo; case 4: return AudioChannelSet::Quad; case 6: return AudioChannelSet::Surround51; case 8: return AudioChannelSet::Surround71; default: break; } return AudioChannelSet::Unknown; } AQSAudioVoiceEngine() { m_mixInfo.m_channels = _getAvailableSet(); unsigned chCount = ChannelCount(m_mixInfo.m_channels); AudioStreamBasicDescription desc = {}; desc.mSampleRate = 96000; desc.mFormatID = kAudioFormatLinearPCM; desc.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; desc.mBytesPerPacket = chCount * 4; desc.mFramesPerPacket = 1; desc.mBytesPerFrame = chCount * 4; desc.mChannelsPerFrame = chCount; desc.mBitsPerChannel = 32; OSStatus err; if ((err = AudioQueueNewOutput(&desc, AudioQueueOutputCallback(Callback), this, nullptr, nullptr, 0, &m_queue))) { Log.report(logvisor::Fatal, "unable to create output audio queue"); return; } m_mixInfo.m_sampleRate = 96000.0; m_mixInfo.m_sampleFormat = SOXR_INT32_I; m_mixInfo.m_bitsPerSample = 32; ChannelMap& chMapOut = m_mixInfo.m_channelMap; if (chCount > 2) { AudioChannelLayout layout; UInt32 layoutSz = sizeof(layout); if (AudioQueueGetProperty(m_queue, kAudioQueueProperty_ChannelLayout, &layout, &layoutSz)) { Log.report(logvisor::Fatal, "unable to get channel layout from audio queue"); return; } switch (layout.mChannelLayoutTag) { case kAudioChannelLayoutTag_UseChannelDescriptions: chMapOut.m_channelCount = layout.mNumberChannelDescriptions; for (int i=0 ; i(m_buffers[i]->mAudioData)); m_buffers[i]->mAudioDataByteSize = m_frameBytes; AudioQueueEnqueueBuffer(m_queue, m_buffers[i], 0, nullptr); } AudioQueuePrime(m_queue, 0, nullptr); AudioQueueStart(m_queue, nullptr); } ~AQSAudioVoiceEngine() { AudioQueueDispose(m_queue, false); } void pumpAndMixVoices() { std::unique_lock lk(m_engineMutex); m_engineCv.notify_one(); lk.unlock(); lk.lock(); } }; std::unique_ptr NewAudioVoiceEngine() { std::unique_ptr ret = std::make_unique(); if (!static_cast(*ret).m_queue) return {}; return ret; } }