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