1 /** 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 * SPDX-License-Identifier: Apache-2.0. 4 */ 5 6 #include <aws/text-to-speech/apple/CoreAudioPCMOutputDriver.h> 7 #include <aws/core/utils/logging/LogMacros.h> 8 #include <iostream> 9 10 namespace Aws 11 { 12 namespace TextToSpeech 13 { 14 static const char* CLASS_TAG = "CoreAudioPCMOutputDriver"; 15 CoreAudioPCMOutputDriver()16 CoreAudioPCMOutputDriver::CoreAudioPCMOutputDriver() : m_audioQueue(nullptr), m_maxBufferSize(4096), m_bufferCount(3) 17 { 18 } 19 ~CoreAudioPCMOutputDriver()20 CoreAudioPCMOutputDriver::~CoreAudioPCMOutputDriver() 21 { 22 CleanUp(); 23 } 24 WriteBufferToDevice(const unsigned char * buffer,size_t size)25 bool CoreAudioPCMOutputDriver::WriteBufferToDevice(const unsigned char* buffer , size_t size) 26 { 27 InitDevice(); 28 bool success(true); 29 30 if(m_audioQueue) 31 { 32 for(size_t i = 0; i < size && success; i += m_maxBufferSize) 33 { 34 std::unique_lock<std::mutex> m(m_queueBufferLock); 35 while(m_bufferQueue.size() == 0) 36 { 37 AWS_LOGSTREAM_DEBUG(CLASS_TAG, " waiting on audio buffers to become available."); 38 m_queueReadySemaphore.wait(m, [this](){ return m_bufferQueue.size() > 0;}); 39 AWS_LOGSTREAM_TRACE(CLASS_TAG, " an audio buffer has been released, waking up."); 40 } 41 42 if (!m_audioQueue) 43 { 44 AWS_LOGSTREAM_ERROR(CLASS_TAG, " audio queue has been cleaned up."); 45 return false; 46 } 47 48 if(m_bufferQueue.size() > 0) 49 { 50 AudioQueueBufferRef audioBuffer = m_bufferQueue.front(); 51 m_bufferQueue.pop(); 52 53 auto toCpy = (std::min)(m_maxBufferSize, size - i); 54 AWS_LOGSTREAM_TRACE(CLASS_TAG, " Writing " << toCpy << " bytes to audio device."); 55 memcpy(audioBuffer->mAudioData, buffer + i, toCpy); 56 audioBuffer->mAudioDataByteSize = static_cast<UInt32>(toCpy); 57 auto errorCode = AudioQueueEnqueueBuffer(m_audioQueue, audioBuffer, 0, nullptr); 58 success = !errorCode; 59 60 if(!success) 61 { 62 AWS_LOGSTREAM_ERROR(CLASS_TAG, " error while queueing audio output. error code " << errorCode); 63 } 64 } 65 } 66 } 67 else 68 { 69 AWS_LOGSTREAM_ERROR(CLASS_TAG, " audio queue has not been initialized."); 70 return false; 71 } 72 73 return success; 74 } 75 EnumerateDevices() const76 Aws::Vector<DeviceInfo> CoreAudioPCMOutputDriver::EnumerateDevices() const 77 { 78 DeviceInfo devInfo; 79 devInfo.deviceId = "default"; 80 devInfo.deviceName = "Default Audio Output Queue"; 81 82 CapabilityInfo caps; 83 caps.sampleWidthBits = BIT_WIDTH_16; 84 caps.channels = MONO; 85 caps.sampleRate = KHZ_16; 86 87 devInfo.capabilities.push_back(caps); 88 caps.sampleRate = KHZ_8; 89 devInfo.capabilities.push_back(caps); 90 91 92 return Aws::Vector<DeviceInfo>({devInfo}); 93 } 94 SetActiveDevice(const DeviceInfo &,const CapabilityInfo & caps)95 void CoreAudioPCMOutputDriver::SetActiveDevice(const DeviceInfo&, const CapabilityInfo& caps) 96 { 97 m_selectedCaps.mSampleRate = caps.sampleRate; 98 m_selectedCaps.mFormatID = kAudioFormatLinearPCM; 99 m_selectedCaps.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; 100 m_selectedCaps.mBitsPerChannel = caps.sampleWidthBits; 101 m_selectedCaps.mChannelsPerFrame = caps.channels; 102 m_selectedCaps.mBytesPerFrame = caps.channels * (caps.sampleWidthBits / 8); 103 m_selectedCaps.mFramesPerPacket = 1; 104 m_selectedCaps.mBytesPerPacket = m_selectedCaps.mBytesPerFrame * m_selectedCaps.mFramesPerPacket; 105 m_selectedCaps.mReserved = 0; 106 107 CleanUp(); 108 109 InitDevice(); 110 } 111 GetName() const112 const char* CoreAudioPCMOutputDriver::GetName() const 113 { 114 return "CoreAudio (Apple Platform)"; 115 } 116 Prime()117 void CoreAudioPCMOutputDriver::Prime() 118 { 119 AudioQueueStart(m_audioQueue, nullptr); 120 } 121 Flush()122 void CoreAudioPCMOutputDriver::Flush() 123 { 124 AudioQueueFlush(m_audioQueue); 125 AudioQueueStop(m_audioQueue, false); 126 } 127 InitDevice()128 void CoreAudioPCMOutputDriver::InitDevice() 129 { 130 if(!m_audioQueue) 131 { 132 AWS_LOGSTREAM_INFO(CLASS_TAG, " Initializing audio queue for sample rate: " << m_selectedCaps.mSampleRate); 133 134 AudioQueueNewOutput(&m_selectedCaps, &OnBufferReady, this, nullptr, kCFRunLoopCommonModes, 0, &m_audioQueue); 135 136 for (size_t i = 0; i < m_bufferCount; i++) 137 { 138 AWS_LOGSTREAM_TRACE(CLASS_TAG, " Allocating buffer of size: " << m_maxBufferSize); 139 AudioQueueBufferRef buf; 140 AudioQueueAllocateBuffer(m_audioQueue, static_cast<UInt32>(m_maxBufferSize), &buf); 141 m_bufferQueue.push(buf); 142 } 143 } 144 } 145 CleanUp()146 void CoreAudioPCMOutputDriver::CleanUp() 147 { 148 if(m_audioQueue) 149 { 150 AWS_LOGSTREAM_INFO(CLASS_TAG, " Cleaning up audio queue"); 151 //make sure all buffers finish processing so we can delete them. 152 AudioQueueStop(m_audioQueue, false); 153 154 std::lock_guard<std::mutex> m(m_queueBufferLock); 155 while(m_bufferQueue.size() > 0) 156 { 157 AWS_LOGSTREAM_DEBUG(CLASS_TAG, " Cleaning up audio buffer"); 158 AudioQueueFreeBuffer(m_audioQueue, m_bufferQueue.front()); 159 m_bufferQueue.pop(); 160 } 161 162 //force The audio queue to cleanup the buffers. 163 AudioQueueDispose(m_audioQueue, true); 164 m_audioQueue = nullptr; 165 } 166 } 167 OnBufferReady(void * custom_data,AudioQueueRef,AudioQueueBufferRef buffer)168 void CoreAudioPCMOutputDriver::OnBufferReady(void *custom_data, AudioQueueRef, AudioQueueBufferRef buffer) 169 { 170 CoreAudioPCMOutputDriver* driver = (CoreAudioPCMOutputDriver*)custom_data; 171 172 { 173 std::unique_lock<std::mutex> m(driver->m_queueBufferLock); 174 driver->m_bufferQueue.push(buffer); 175 } 176 AWS_LOGSTREAM_DEBUG(CLASS_TAG, "Buffer free, notifying waiting threads."); 177 driver->m_queueReadySemaphore.notify_one(); 178 } 179 } 180 } 181