1 //////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (c) 2007-2016 musikcube team
4 //
5 // All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions are met:
9 //
10 //    * Redistributions of source code must retain the above copyright notice,
11 //      this list of conditions and the following disclaimer.
12 //
13 //    * Redistributions in binary form must reproduce the above copyright
14 //      notice, this list of conditions and the following disclaimer in the
15 //      documentation and/or other materials provided with the distribution.
16 //
17 //    * Neither the name of the author nor the names of other contributors may
18 //      be used to endorse or promote products derived from this software
19 //      without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
32 //
33 //////////////////////////////////////////////////////////////////////////////
34 
35 #include "CoreAudioOut.h"
36 #include <musikcore/sdk/constants.h>
37 #include <musikcore/sdk/IPreferences.h>
38 #include <iostream>
39 #include <vector>
40 
41 #define BUFFER_COUNT 24
42 #define PREF_DEVICE_ID "device_id"
43 
44 using namespace musik::core::sdk;
45 
46 class CoreAudioDevice : public IDevice {
47     public:
CoreAudioDevice(const std::string & id,const std::string & name)48         CoreAudioDevice(const std::string& id, const std::string& name) {
49             this->id = id;
50             this->name = name;
51         }
52 
Release()53         virtual void Release() { delete this; }
Name() const54         virtual const char* Name() const { return name.c_str(); }
Id() const55         virtual const char* Id() const { return id.c_str(); }
56 
57     private:
58         std::string name, id;
59 };
60 
61 class CoreAudioDeviceList : public IDeviceList {
62     public:
Release()63         virtual void Release() { delete this; }
Count() const64         virtual size_t Count() const { return devices.size(); }
At(size_t index) const65         virtual const IDevice* At(size_t index) const { return &devices.at(index); }
66 
Add(const std::string & id,const std::string & name)67         void Add(const std::string& id, const std::string& name) {
68             devices.push_back(CoreAudioDevice(id, name));
69         }
70 
71     private:
72         std::vector<CoreAudioDevice> devices;
73 };
74 
75 static musik::core::sdk::IPreferences* prefs = nullptr;
76 
SetPreferences(musik::core::sdk::IPreferences * prefs)77 extern "C" void SetPreferences(musik::core::sdk::IPreferences* prefs) {
78     ::prefs = prefs;
79     prefs->GetString(PREF_DEVICE_ID, nullptr, 0, "");
80     prefs->Save();
81 }
82 
getDeviceId()83 static std::string getDeviceId() {
84     return getPreferenceString<std::string>(prefs, PREF_DEVICE_ID, "");
85 }
86 
audioCallback(void * customData,AudioQueueRef queue,AudioQueueBufferRef buffer)87 void audioCallback(void *customData, AudioQueueRef queue, AudioQueueBufferRef buffer) {
88     CoreAudioOut* output = (CoreAudioOut *) customData;
89     CoreAudioOut::BufferContext* context = (CoreAudioOut::BufferContext *) buffer->mUserData;
90 
91     OSStatus result = AudioQueueFreeBuffer(queue, buffer);
92 
93     if (result != 0) {
94         std::cerr << "AudioQueueFreeBuffer failed: " << result << "\n";
95     }
96 
97     output->NotifyBufferCompleted(context);
98 }
99 
NotifyBufferCompleted(BufferContext * context)100 void CoreAudioOut::NotifyBufferCompleted(BufferContext *context) {
101     {
102         std::unique_lock<std::recursive_mutex> lock(this->mutex);
103 
104         if (bufferCount > 0) {
105             --bufferCount;
106         }
107     }
108 
109     context->provider->OnBufferProcessed(context->buffer);
110     delete context;
111 }
112 
CoreAudioOut()113 CoreAudioOut::CoreAudioOut() {
114     this->quit = false;
115     this->state = StateStopped;
116     this->volume = 1.0f;
117 
118     this->audioFormat = (AudioStreamBasicDescription) { 0 };
119 
120     this->audioFormat.mFormatID = kAudioFormatLinearPCM;
121     this->audioFormat.mFormatFlags = kAudioFormatFlagIsFloat;
122     this->audioFormat.mFramesPerPacket = 1;
123     this->audioFormat.mBitsPerChannel = 32;
124     this->audioFormat.mReserved = 0;
125 
126     /* these get filled in later */
127     this->audioFormat.mChannelsPerFrame = -1;
128     this->audioFormat.mSampleRate = -1;
129     this->audioFormat.mBytesPerFrame = -1;
130     this->audioFormat.mBytesPerPacket = -1;
131 
132     this->audioQueue = nullptr;
133     this->bufferCount = 0;
134 }
135 
Play(IBuffer * buffer,IBufferProvider * provider)136 OutputState CoreAudioOut::Play(IBuffer *buffer, IBufferProvider *provider) {
137     std::unique_lock<std::recursive_mutex> lock(this->mutex);
138 
139     if (this->state != StatePlaying) {
140         return OutputState::InvalidState;
141     }
142 
143     if (this->bufferCount >= BUFFER_COUNT) {
144         /* enough buffers are already in the queue. bail, we'll notify the
145         caller when there's more data available */
146         return OutputState::BufferFull;
147     }
148 
149     OSStatus result;
150 
151     if (buffer->SampleRate() != this->audioFormat.mSampleRate ||
152         buffer->Channels() != this->audioFormat.mChannelsPerFrame ||
153         this->audioQueue == nullptr)
154     {
155         this->audioFormat.mSampleRate = buffer->SampleRate();
156         this->audioFormat.mChannelsPerFrame = buffer->Channels();
157         this->audioFormat.mBytesPerFrame = (this->audioFormat.mBitsPerChannel / 8) * this->audioFormat.mChannelsPerFrame;
158         this->audioFormat.mBytesPerPacket = this->audioFormat.mBytesPerFrame * this->audioFormat.mFramesPerPacket;
159 
160         lock.unlock();
161         this->Stop();
162         lock.lock();
163 
164         /* create the queue */
165         result = AudioQueueNewOutput(
166             &this->audioFormat,
167             audioCallback,
168             this,
169             nullptr,
170             kCFRunLoopCommonModes,
171             0,
172             &this->audioQueue);
173 
174         if (result != 0) {
175             std::cerr << "AudioQueueNewOutput failed: " << result << "\n";
176             return OutputState::InvalidState;
177         }
178 
179         /* after the queue is created, but before it's started, let's make
180         sure the correct output device is selected */
181         auto device = this->GetDefaultDevice();
182         if (device) {
183             std::string deviceId = device->Id();
184             if (deviceId.c_str()) {
185                 CFStringRef deviceUid = CFStringCreateWithBytes(
186                     kCFAllocatorDefault,
187                     (const UInt8*) deviceId.c_str(),
188                     deviceId.size(),
189                     kCFStringEncodingUTF8,
190                     false);
191 
192                 AudioQueueSetProperty(
193                     this->audioQueue,
194                     kAudioQueueProperty_CurrentDevice,
195                     &deviceUid,
196                     sizeof(deviceUid));
197 
198                 CFRelease(deviceUid);
199             }
200             device->Release();
201         }
202 
203         /* get it running! */
204         result = AudioQueueStart(this->audioQueue, nullptr);
205 
206         this->SetVolume(volume);
207 
208         if (result != 0) {
209             std::cerr << "AudioQueueStart failed: " << result << "\n";
210             return OutputState::InvalidState;
211         }
212 
213         this->Resume();
214     }
215 
216     AudioQueueBufferRef audioQueueBuffer = nullptr;
217 
218     size_t bytes = buffer->Bytes();
219 
220     result = AudioQueueAllocateBuffer(this->audioQueue, bytes, &audioQueueBuffer);
221 
222     BufferContext* context = new BufferContext();
223     context->provider = provider;
224     context->buffer = buffer;
225 
226     if (result != 0) {
227         std::cerr << "AudioQueueAllocateBuffer failed: " << result << "\n";
228         return OutputState::InvalidState;
229     }
230 
231     audioQueueBuffer->mUserData = (void *) context;
232     audioQueueBuffer->mAudioDataByteSize = bytes;
233 
234     memcpy(
235         audioQueueBuffer->mAudioData,
236         (void *) buffer->BufferPointer(),
237         bytes);
238 
239     result = AudioQueueEnqueueBuffer(
240         this->audioQueue, audioQueueBuffer, 0, nullptr);
241 
242     if (result != 0) {
243         std::cerr << "AudioQueueEnqueueBuffer failed: " << result << "\n";
244         delete context;
245         return OutputState::InvalidState;
246     }
247 
248     ++bufferCount;
249 
250     return OutputState::BufferWritten;
251 }
252 
~CoreAudioOut()253 CoreAudioOut::~CoreAudioOut() {
254     this->Stop();
255 }
256 
Release()257 void CoreAudioOut::Release() {
258     delete this;
259 }
260 
Drain()261 void CoreAudioOut::Drain() {
262     std::unique_lock<std::recursive_mutex> lock(this->mutex);
263 
264     if (this->state != StateStopped && this->audioQueue) {
265         std::cerr << "CoreAudioOut: draining...\n";
266         AudioQueueFlush(this->audioQueue);
267         std::cerr << "CoreAudioOut: drained.\n";
268     }
269 }
270 
Pause()271 void CoreAudioOut::Pause() {
272     std::unique_lock<std::recursive_mutex> lock(this->mutex);
273 
274     if (this->audioQueue) {
275         AudioQueuePause(this->audioQueue);
276         this->state = StatePaused;
277     }
278 }
279 
Resume()280 void CoreAudioOut::Resume() {
281     std::unique_lock<std::recursive_mutex> lock(this->mutex);
282 
283     if (this->audioQueue) {
284         AudioQueueStart(this->audioQueue, nullptr);
285     }
286 
287     this->state = StatePlaying;
288 }
289 
SetVolume(double volume)290 void CoreAudioOut::SetVolume(double volume) {
291     std::unique_lock<std::recursive_mutex> lock(this->mutex);
292 
293     this->volume = volume;
294 
295     if (this->audioQueue) {
296         OSStatus result = AudioQueueSetParameter(
297             this->audioQueue,
298             kAudioQueueParam_Volume,
299             volume);
300 
301         if (result != 0) {
302             std::cerr << "AudioQueueSetParameter(volume) failed: " << result << "\n";
303         }
304     }
305 }
306 
GetVolume()307 double CoreAudioOut::GetVolume() {
308     return this->volume;
309 }
310 
Stop()311 void CoreAudioOut::Stop() {
312     AudioQueueRef queue = nullptr;
313 
314     {
315         /* AudioQueueStop/AudioQueueDispose will trigger the callback, which
316         will try to dispose of the samples on a separate thread and deadlock.
317         cache the queue and reset outside of the critical section */
318         std::unique_lock<std::recursive_mutex> lock(this->mutex);
319         queue = this->audioQueue;
320         this->audioQueue = nullptr;
321         this->state = StateStopped;
322         this->bufferCount = 0;
323     }
324 
325     if (queue) {
326         AudioQueueStop(queue, true);
327         AudioQueueDispose(queue, true);
328     }
329 }
330 
GetDeviceList()331 IDeviceList* CoreAudioOut::GetDeviceList() {
332     CoreAudioDeviceList* result = new CoreAudioDeviceList();
333 
334     AudioObjectPropertyAddress address = {
335         kAudioHardwarePropertyDevices,
336         kAudioObjectPropertyScopeGlobal,
337         kAudioObjectPropertyElementMaster
338     };
339 
340     UInt32 propsize;
341     if (AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &address, 0, NULL, &propsize) == 0) {
342         int deviceCount = propsize / sizeof(AudioDeviceID);
343         AudioDeviceID *deviceIds = new AudioDeviceID[deviceCount];
344 
345         char buffer[2048];
346         if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &address, 0, NULL, &propsize, deviceIds) == 0) {
347             for (int i = 0; i < deviceCount; ++i) {
348                 auto deviceId = deviceIds[i];
349 
350                 AudioObjectPropertyAddress outputAddress = {
351                     kAudioDevicePropertyStreamConfiguration,
352                     kAudioDevicePropertyScopeOutput,
353                     0
354                 };
355 
356                 /* see if this device has output channels. if it doesn't, it's a device
357                 that can only do input */
358                 size_t outputChannels = 0;
359                 if (AudioObjectGetPropertyDataSize(deviceId, &outputAddress, 0, NULL, &propsize) == 0) {
360                     AudioBufferList *bufferList = (AudioBufferList *) malloc(propsize);
361                     if (AudioObjectGetPropertyData(deviceId, &outputAddress, 0, NULL, &propsize, bufferList) == 0) {
362                         for (UInt32 j = 0; j < bufferList->mNumberBuffers; ++j) {
363                             outputChannels += bufferList->mBuffers[j].mNumberChannels;
364                         }
365                     }
366                     free(bufferList);
367                 }
368 
369                 if (outputChannels > 0) { /* device has an output! */
370                     std::string deviceNameStr, deviceIdStr;
371 
372                     /* get the device name */
373 
374                     AudioObjectPropertyAddress nameAddress = {
375                         kAudioDevicePropertyDeviceName,
376                         kAudioDevicePropertyScopeOutput,
377                         0
378                     };
379 
380                     UInt32 maxLength = 2048;
381                     if (AudioObjectGetPropertyData(deviceId, &nameAddress, 0, NULL, &maxLength, buffer) == 0) {
382                         deviceNameStr = buffer;
383                     }
384 
385                     /* get the device id */
386 
387                     AudioObjectPropertyAddress uidAddress = {
388                         kAudioDevicePropertyDeviceUID,
389                         kAudioObjectPropertyScopeGlobal,
390                         kAudioObjectPropertyElementMaster
391                     };
392 
393                     CFStringRef deviceUid;
394                     if (AudioObjectGetPropertyData(deviceId, &uidAddress, 0, NULL, &propsize, &deviceUid) == 0) {
395                         const char* cstr = CFStringGetCStringPtr(deviceUid, kCFStringEncodingUTF8);
396                         if (cstr) {
397                             deviceIdStr = cstr;
398                         }
399                     }
400 
401                     if (deviceNameStr.size() && deviceIdStr.size()) {
402                         result->Add(deviceIdStr, deviceNameStr);
403                     }
404                 }
405             }
406         }
407 
408         delete[] deviceIds;
409     }
410 
411     return result;
412 }
413 
SetDefaultDevice(const char * deviceId)414 bool CoreAudioOut::SetDefaultDevice(const char* deviceId) {
415     return setDefaultDevice<IPreferences, CoreAudioDevice, IOutput>(prefs, this, PREF_DEVICE_ID, deviceId);
416 }
417 
GetDefaultDevice()418 IDevice* CoreAudioOut::GetDefaultDevice() {
419     return findDeviceById<CoreAudioDevice, IOutput>(this, getDeviceId());
420 }