threaded audio mixing for OSX
[divverent/darkplaces.git] / snd_coreaudio.c
1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4
5 This file is part of Quake III Arena source code.
6
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Foobar; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 ===========================================================================
21 */
22
23 #include "quakedef.h"
24
25 #include <limits.h>
26 #include <pthread.h>
27
28 #include <CoreAudio/AudioHardware.h>
29
30 #include "snd_main.h"
31
32
33 #define CHUNK_SIZE 1024
34
35 static unsigned int submissionChunk = 0;  // in sample frames
36 static unsigned int coreaudiotime = 0;  // based on the number of chunks submitted so far
37 static qboolean s_isRunning = false;
38 static pthread_mutex_t coreaudio_mutex;
39 static AudioDeviceID outputDeviceID = kAudioDeviceUnknown;
40 static short *mixbuffer = NULL;
41
42
43 /*
44 ====================
45 audioDeviceIOProc
46 ====================
47 */
48 static OSStatus audioDeviceIOProc(AudioDeviceID inDevice,
49                                                                   const AudioTimeStamp *inNow,
50                                                                   const AudioBufferList *inInputData,
51                                                                   const AudioTimeStamp *inInputTime,
52                                                                   AudioBufferList *outOutputData,
53                                                                   const AudioTimeStamp *inOutputTime,
54                                                                   void *inClientData)
55 {
56         float *outBuffer;
57         unsigned int frameCount, factor, sampleIndex;
58         const float scale = 1.0f / SHRT_MAX;
59
60         outBuffer = (float*)outOutputData->mBuffers[0].mData;
61         factor = snd_renderbuffer->format.channels * snd_renderbuffer->format.width;
62         frameCount = 0;
63
64         // Lock the snd_renderbuffer
65         if (SndSys_LockRenderBuffer())
66         {
67                 unsigned int maxFrames, sampleCount;
68                 unsigned int startOffset, endOffset;
69                 const short *samples;
70
71                 if (snd_usethreadedmixing)
72                 {
73                         S_MixToBuffer(mixbuffer, submissionChunk);
74                         for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
75                                 outBuffer[sampleIndex] = mixbuffer[sampleIndex] * scale;
76                         // unlock the mutex now
77                         SndSys_UnlockRenderBuffer();
78                         return 0;
79                 }
80
81                 // Transfert up to a chunk of sample frames from snd_renderbuffer to outBuffer
82                 maxFrames = snd_renderbuffer->endframe - snd_renderbuffer->startframe;
83                 if (maxFrames >= submissionChunk)
84                         frameCount = submissionChunk;
85                 else
86                         frameCount = maxFrames;
87
88                 // Convert the samples from shorts to floats.  Scale the floats to be [-1..1].
89                 startOffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes;
90                 endOffset = (snd_renderbuffer->startframe + frameCount) % snd_renderbuffer->maxframes;
91                 if (startOffset > endOffset)  // if the buffer wraps
92                 {
93                         sampleCount = (snd_renderbuffer->maxframes - startOffset) * snd_renderbuffer->format.channels;
94                         samples = (const short*)(&snd_renderbuffer->ring[startOffset * factor]);
95                         for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
96                                 outBuffer[sampleIndex] = samples[sampleIndex] * scale;
97
98                         outBuffer = &outBuffer[sampleCount];
99                         sampleCount = frameCount * snd_renderbuffer->format.channels - sampleCount;
100                         samples = (const short*)(&snd_renderbuffer->ring[0]);
101                         for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
102                                 outBuffer[sampleIndex] = samples[sampleIndex] * scale;
103                 }
104                 else
105                 {
106                         sampleCount = frameCount * snd_renderbuffer->format.channels;
107                         samples = (const short*)(&snd_renderbuffer->ring[startOffset * factor]);
108                         for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
109                                 outBuffer[sampleIndex] = samples[sampleIndex] * scale;
110                 }
111
112                 snd_renderbuffer->startframe += frameCount;
113
114                 // unlock the mutex now
115                 SndSys_UnlockRenderBuffer();
116         }
117
118         // If there was not enough samples, complete with silence samples
119         if (frameCount < submissionChunk)
120         {
121                 unsigned int missingFrames;
122
123                 missingFrames = submissionChunk - frameCount;
124                 if (developer.integer >= 1000 && vid_activewindow)
125                         Con_Printf("audioDeviceIOProc: %u sample frames missing\n", missingFrames);
126                 memset(&outBuffer[frameCount * snd_renderbuffer->format.channels], 0, missingFrames * sizeof(outBuffer[0]));
127         }
128
129         coreaudiotime += submissionChunk;
130         return 0;
131 }
132
133
134 /*
135 ====================
136 SndSys_Init
137
138 Create "snd_renderbuffer" with the proper sound format if the call is successful
139 May return a suggested format if the requested format isn't available
140 ====================
141 */
142 qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested)
143 {
144         OSStatus status;
145         UInt32 propertySize, bufferByteCount;
146         AudioStreamBasicDescription streamDesc;
147
148         if (s_isRunning)
149                 return true;
150
151         Con_Printf("Initializing CoreAudio...\n");
152         snd_threaded = false;
153
154         if(requested->width != 2)
155         {
156                 // we can only do 16bit per sample for now
157                 if(suggested != NULL)
158                 {
159                         memcpy (suggested, requested, sizeof (*suggested));
160                         suggested->width = 2;
161                 }
162                 return false;
163         }
164
165         // Get the output device
166         propertySize = sizeof(outputDeviceID);
167         status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &propertySize, &outputDeviceID);
168         if (status)
169         {
170                 Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when getting kAudioHardwarePropertyDefaultOutputDevice\n", (int)status);
171                 return false;
172         }
173         if (outputDeviceID == kAudioDeviceUnknown)
174         {
175                 Con_Printf("CoreAudio: outputDeviceID is kAudioDeviceUnknown\n");
176                 return false;
177         }
178
179         // Configure the output device
180         propertySize = sizeof(bufferByteCount);
181         bufferByteCount = CHUNK_SIZE * sizeof(float) * requested->channels;
182         status = AudioDeviceSetProperty(outputDeviceID, NULL, 0, false, kAudioDevicePropertyBufferSize, propertySize, &bufferByteCount);
183         if (status)
184         {
185                 Con_Printf("CoreAudio: AudioDeviceSetProperty() returned %d when setting kAudioDevicePropertyBufferSize to %d\n", (int)status, CHUNK_SIZE);
186                 return false;
187         }
188
189         propertySize = sizeof(bufferByteCount);
190         status = AudioDeviceGetProperty(outputDeviceID, 0, false, kAudioDevicePropertyBufferSize, &propertySize, &bufferByteCount);
191         if (status)
192         {
193                 Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when setting kAudioDevicePropertyBufferSize\n", (int)status);
194                 return false;
195         }
196
197         submissionChunk = bufferByteCount / sizeof(float);
198         if (submissionChunk % requested->channels != 0)
199         {
200                 Con_Print("CoreAudio: chunk size is NOT a multiple of the number of channels\n");
201                 return false;
202         }
203         submissionChunk /= requested->channels;
204         Con_Printf("   Chunk size = %d sample frames\n", submissionChunk);
205
206         // Print out the device status
207         propertySize = sizeof(streamDesc);
208         status = AudioDeviceGetProperty(outputDeviceID, 0, false, kAudioDevicePropertyStreamFormat, &propertySize, &streamDesc);
209         if (status)
210         {
211                 Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when getting kAudioDevicePropertyStreamFormat\n", (int)status);
212                 return false;
213         }
214
215         Con_Print ("   Hardware format:\n");
216         Con_Printf("    %5d mSampleRate\n", (unsigned int)streamDesc.mSampleRate);
217         Con_Printf("     %c%c%c%c mFormatID\n",
218                                 (char)(streamDesc.mFormatID >> 24),
219                                 (char)(streamDesc.mFormatID >> 16),
220                                 (char)(streamDesc.mFormatID >>  8),
221                                 (char)(streamDesc.mFormatID >>  0));
222         Con_Printf("    %5u mBytesPerPacket\n", (unsigned int)streamDesc.mBytesPerPacket);
223         Con_Printf("    %5u mFramesPerPacket\n", (unsigned int)streamDesc.mFramesPerPacket);
224         Con_Printf("    %5u mBytesPerFrame\n", (unsigned int)streamDesc.mBytesPerFrame);
225         Con_Printf("    %5u mChannelsPerFrame\n", (unsigned int)streamDesc.mChannelsPerFrame);
226         Con_Printf("    %5u mBitsPerChannel\n", (unsigned int)streamDesc.mBitsPerChannel);
227
228         // Suggest proper settings if they differ
229         if (requested->channels != streamDesc.mChannelsPerFrame || requested->speed != streamDesc.mSampleRate)
230         {
231                 if (suggested != NULL)
232                 {
233                         memcpy (suggested, requested, sizeof (*suggested));
234                         suggested->channels = streamDesc.mChannelsPerFrame;
235                         suggested->speed = streamDesc.mSampleRate;
236                 }
237                 return false;
238         }
239
240         if(streamDesc.mFormatID != kAudioFormatLinearPCM)
241         {
242                 // Add the callback function
243                 status = AudioDeviceAddIOProc(outputDeviceID, audioDeviceIOProc, NULL);
244                 if (!status)
245                 {
246                         // We haven't sent any sample frames yet
247                         coreaudiotime = 0;
248                         if (pthread_mutex_init(&coreaudio_mutex, NULL) == 0)
249                         {
250                                 if ((snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL)))
251                                 {
252                                         if ((mixbuffer = Mem_Alloc(snd_mempool, CHUNK_SIZE * sizeof(*mixbuffer) * requested->channels)))
253                                         {
254                                                 // Start sound running
255                                                 status = AudioDeviceStart(outputDeviceID, audioDeviceIOProc);
256                                                 if (!status)
257                                                 {
258                                                         s_isRunning = true;
259                                                         snd_threaded = true;
260                                                         Con_Print("   Initialization successful\n");
261                                                         return true;
262                                                 }
263                                                 else
264                                                         Con_Printf("CoreAudio: AudioDeviceStart() returned %d\n", (int)status);
265                                                 Mem_Free(mixbuffer);
266                                                 mixbuffer = NULL;
267                                         }
268                                         else
269                                                 Con_Print("CoreAudio: can't allocate memory for mixbuffer\n");
270                                         Mem_Free(snd_renderbuffer->ring);
271                                         Mem_Free(snd_renderbuffer);
272                                         snd_renderbuffer = NULL;
273                                 }
274                                 else
275                                         Con_Print("CoreAudio: can't allocate memory for ringbuffer\n");
276                                 pthread_mutex_destroy(&coreaudio_mutex);
277                         }
278                         else
279                                 Con_Print("CoreAudio: can't create pthread mutex\n");
280                         AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc);
281                 }
282                 else
283                         Con_Printf("CoreAudio: AudioDeviceAddIOProc() returned %d\n", (int)status);
284         }
285         else
286                 Con_Print("CoreAudio: Default audio device doesn't support linear PCM!\n");
287         return false;
288 }
289
290
291 /*
292 ====================
293 SndSys_Shutdown
294
295 Stop the sound card, delete "snd_renderbuffer" and free its other resources
296 ====================
297 */
298 void SndSys_Shutdown(void)
299 {
300         OSStatus status;
301
302         if (!s_isRunning)
303                 return;
304
305         status = AudioDeviceStop(outputDeviceID, audioDeviceIOProc);
306         if (status)
307         {
308                 Con_Printf("AudioDeviceStop: returned %d\n", (int)status);
309                 return;
310         }
311         s_isRunning = false;
312
313         pthread_mutex_destroy(&coreaudio_mutex);
314
315         status = AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc);
316         if (status)
317         {
318                 Con_Printf("AudioDeviceRemoveIOProc: returned %d\n", (int)status);
319                 return;
320         }
321
322         if (snd_renderbuffer != NULL)
323         {
324                 Mem_Free(snd_renderbuffer->ring);
325                 Mem_Free(snd_renderbuffer);
326                 snd_renderbuffer = NULL;
327         }
328
329         if (mixbuffer != NULL)
330                 Mem_Free(mixbuffer);
331         mixbuffer = NULL;
332 }
333
334
335 /*
336 ====================
337 SndSys_Submit
338
339 Submit the contents of "snd_renderbuffer" to the sound card
340 ====================
341 */
342 void SndSys_Submit (void)
343 {
344         // Nothing to do here (this sound module is callback-based)
345 }
346
347
348 /*
349 ====================
350 SndSys_GetSoundTime
351
352 Returns the number of sample frames consumed since the sound started
353 ====================
354 */
355 unsigned int SndSys_GetSoundTime (void)
356 {
357         return coreaudiotime;
358 }
359
360
361 /*
362 ====================
363 SndSys_LockRenderBuffer
364
365 Get the exclusive lock on "snd_renderbuffer"
366 ====================
367 */
368 qboolean SndSys_LockRenderBuffer (void)
369 {
370         return (pthread_mutex_lock(&coreaudio_mutex) == 0);
371 }
372
373
374 /*
375 ====================
376 SndSys_UnlockRenderBuffer
377
378 Release the exclusive lock on "snd_renderbuffer"
379 ====================
380 */
381 void SndSys_UnlockRenderBuffer (void)
382 {
383         pthread_mutex_unlock(&coreaudio_mutex);
384 }