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 }