fix really stupid typo in coreaudio init (!= was supposed to be ==)
[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         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         if (snd_blocked)
64                 scale = 0;
65
66         // Lock the snd_renderbuffer
67         if (SndSys_LockRenderBuffer())
68         {
69                 unsigned int maxFrames, sampleCount;
70                 unsigned int startOffset, endOffset;
71                 const short *samples;
72
73                 if (snd_usethreadedmixing)
74                 {
75                         S_MixToBuffer(mixbuffer, submissionChunk);
76                         for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
77                                 outBuffer[sampleIndex] = mixbuffer[sampleIndex] * scale;
78                         // unlock the mutex now
79                         SndSys_UnlockRenderBuffer();
80                         return 0;
81                 }
82
83                 // Transfert up to a chunk of sample frames from snd_renderbuffer to outBuffer
84                 maxFrames = snd_renderbuffer->endframe - snd_renderbuffer->startframe;
85                 if (maxFrames >= submissionChunk)
86                         frameCount = submissionChunk;
87                 else
88                         frameCount = maxFrames;
89
90                 // Convert the samples from shorts to floats.  Scale the floats to be [-1..1].
91                 startOffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes;
92                 endOffset = (snd_renderbuffer->startframe + frameCount) % snd_renderbuffer->maxframes;
93                 if (startOffset > endOffset)  // if the buffer wraps
94                 {
95                         sampleCount = (snd_renderbuffer->maxframes - startOffset) * snd_renderbuffer->format.channels;
96                         samples = (const short*)(&snd_renderbuffer->ring[startOffset * factor]);
97                         for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
98                                 outBuffer[sampleIndex] = samples[sampleIndex] * scale;
99
100                         outBuffer = &outBuffer[sampleCount];
101                         sampleCount = frameCount * snd_renderbuffer->format.channels - sampleCount;
102                         samples = (const short*)(&snd_renderbuffer->ring[0]);
103                         for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
104                                 outBuffer[sampleIndex] = samples[sampleIndex] * scale;
105                 }
106                 else
107                 {
108                         sampleCount = frameCount * snd_renderbuffer->format.channels;
109                         samples = (const short*)(&snd_renderbuffer->ring[startOffset * factor]);
110                         for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
111                                 outBuffer[sampleIndex] = samples[sampleIndex] * scale;
112                 }
113
114                 snd_renderbuffer->startframe += frameCount;
115
116                 // unlock the mutex now
117                 SndSys_UnlockRenderBuffer();
118         }
119
120         // If there was not enough samples, complete with silence samples
121         if (frameCount < submissionChunk)
122         {
123                 unsigned int missingFrames;
124
125                 missingFrames = submissionChunk - frameCount;
126                 if (developer.integer >= 1000 && vid_activewindow)
127                         Con_Printf("audioDeviceIOProc: %u sample frames missing\n", missingFrames);
128                 memset(&outBuffer[frameCount * snd_renderbuffer->format.channels], 0, missingFrames * sizeof(outBuffer[0]));
129         }
130
131         coreaudiotime += submissionChunk;
132         return 0;
133 }
134
135
136 /*
137 ====================
138 SndSys_Init
139
140 Create "snd_renderbuffer" with the proper sound format if the call is successful
141 May return a suggested format if the requested format isn't available
142 ====================
143 */
144 qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested)
145 {
146         OSStatus status;
147         UInt32 propertySize, bufferByteCount;
148         AudioStreamBasicDescription streamDesc;
149
150         if (s_isRunning)
151                 return true;
152
153         Con_Printf("Initializing CoreAudio...\n");
154         snd_threaded = false;
155
156         if(requested->width != 2)
157         {
158                 // we can only do 16bit per sample for now
159                 if(suggested != NULL)
160                 {
161                         memcpy (suggested, requested, sizeof (*suggested));
162                         suggested->width = 2;
163                 }
164                 return false;
165         }
166
167         // Get the output device
168         propertySize = sizeof(outputDeviceID);
169         status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &propertySize, &outputDeviceID);
170         if (status)
171         {
172                 Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when getting kAudioHardwarePropertyDefaultOutputDevice\n", (int)status);
173                 return false;
174         }
175         if (outputDeviceID == kAudioDeviceUnknown)
176         {
177                 Con_Printf("CoreAudio: outputDeviceID is kAudioDeviceUnknown\n");
178                 return false;
179         }
180
181         // Configure the output device
182         propertySize = sizeof(bufferByteCount);
183         bufferByteCount = CHUNK_SIZE * sizeof(float) * requested->channels;
184         status = AudioDeviceSetProperty(outputDeviceID, NULL, 0, false, kAudioDevicePropertyBufferSize, propertySize, &bufferByteCount);
185         if (status)
186         {
187                 Con_Printf("CoreAudio: AudioDeviceSetProperty() returned %d when setting kAudioDevicePropertyBufferSize to %d\n", (int)status, CHUNK_SIZE);
188                 return false;
189         }
190
191         propertySize = sizeof(bufferByteCount);
192         status = AudioDeviceGetProperty(outputDeviceID, 0, false, kAudioDevicePropertyBufferSize, &propertySize, &bufferByteCount);
193         if (status)
194         {
195                 Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when setting kAudioDevicePropertyBufferSize\n", (int)status);
196                 return false;
197         }
198
199         submissionChunk = bufferByteCount / sizeof(float);
200         if (submissionChunk % requested->channels != 0)
201         {
202                 Con_Print("CoreAudio: chunk size is NOT a multiple of the number of channels\n");
203                 return false;
204         }
205         submissionChunk /= requested->channels;
206         Con_Printf("   Chunk size = %d sample frames\n", submissionChunk);
207
208         // Print out the device status
209         propertySize = sizeof(streamDesc);
210         status = AudioDeviceGetProperty(outputDeviceID, 0, false, kAudioDevicePropertyStreamFormat, &propertySize, &streamDesc);
211         if (status)
212         {
213                 Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when getting kAudioDevicePropertyStreamFormat\n", (int)status);
214                 return false;
215         }
216
217         Con_Print ("   Hardware format:\n");
218         Con_Printf("    %5d mSampleRate\n", (unsigned int)streamDesc.mSampleRate);
219         Con_Printf("     %c%c%c%c mFormatID\n",
220                                 (char)(streamDesc.mFormatID >> 24),
221                                 (char)(streamDesc.mFormatID >> 16),
222                                 (char)(streamDesc.mFormatID >>  8),
223                                 (char)(streamDesc.mFormatID >>  0));
224         Con_Printf("    %5u mBytesPerPacket\n", (unsigned int)streamDesc.mBytesPerPacket);
225         Con_Printf("    %5u mFramesPerPacket\n", (unsigned int)streamDesc.mFramesPerPacket);
226         Con_Printf("    %5u mBytesPerFrame\n", (unsigned int)streamDesc.mBytesPerFrame);
227         Con_Printf("    %5u mChannelsPerFrame\n", (unsigned int)streamDesc.mChannelsPerFrame);
228         Con_Printf("    %5u mBitsPerChannel\n", (unsigned int)streamDesc.mBitsPerChannel);
229
230         // Suggest proper settings if they differ
231         if (requested->channels != streamDesc.mChannelsPerFrame || requested->speed != streamDesc.mSampleRate)
232         {
233                 if (suggested != NULL)
234                 {
235                         memcpy (suggested, requested, sizeof (*suggested));
236                         suggested->channels = streamDesc.mChannelsPerFrame;
237                         suggested->speed = streamDesc.mSampleRate;
238                 }
239                 return false;
240         }
241
242         if(streamDesc.mFormatID == kAudioFormatLinearPCM)
243         {
244                 // Add the callback function
245                 status = AudioDeviceAddIOProc(outputDeviceID, audioDeviceIOProc, NULL);
246                 if (!status)
247                 {
248                         // We haven't sent any sample frames yet
249                         coreaudiotime = 0;
250                         if (pthread_mutex_init(&coreaudio_mutex, NULL) == 0)
251                         {
252                                 if ((snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL)))
253                                 {
254                                         if ((mixbuffer = Mem_Alloc(snd_mempool, CHUNK_SIZE * sizeof(*mixbuffer) * requested->channels)))
255                                         {
256                                                 // Start sound running
257                                                 status = AudioDeviceStart(outputDeviceID, audioDeviceIOProc);
258                                                 if (!status)
259                                                 {
260                                                         s_isRunning = true;
261                                                         snd_threaded = true;
262                                                         Con_Print("   Initialization successful\n");
263                                                         return true;
264                                                 }
265                                                 else
266                                                         Con_Printf("CoreAudio: AudioDeviceStart() returned %d\n", (int)status);
267                                                 Mem_Free(mixbuffer);
268                                                 mixbuffer = NULL;
269                                         }
270                                         else
271                                                 Con_Print("CoreAudio: can't allocate memory for mixbuffer\n");
272                                         Mem_Free(snd_renderbuffer->ring);
273                                         Mem_Free(snd_renderbuffer);
274                                         snd_renderbuffer = NULL;
275                                 }
276                                 else
277                                         Con_Print("CoreAudio: can't allocate memory for ringbuffer\n");
278                                 pthread_mutex_destroy(&coreaudio_mutex);
279                         }
280                         else
281                                 Con_Print("CoreAudio: can't create pthread mutex\n");
282                         AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc);
283                 }
284                 else
285                         Con_Printf("CoreAudio: AudioDeviceAddIOProc() returned %d\n", (int)status);
286         }
287         else
288                 Con_Print("CoreAudio: Default audio device doesn't support linear PCM!\n");
289         return false;
290 }
291
292
293 /*
294 ====================
295 SndSys_Shutdown
296
297 Stop the sound card, delete "snd_renderbuffer" and free its other resources
298 ====================
299 */
300 void SndSys_Shutdown(void)
301 {
302         OSStatus status;
303
304         if (!s_isRunning)
305                 return;
306
307         status = AudioDeviceStop(outputDeviceID, audioDeviceIOProc);
308         if (status)
309         {
310                 Con_Printf("AudioDeviceStop: returned %d\n", (int)status);
311                 return;
312         }
313         s_isRunning = false;
314
315         pthread_mutex_destroy(&coreaudio_mutex);
316
317         status = AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc);
318         if (status)
319         {
320                 Con_Printf("AudioDeviceRemoveIOProc: returned %d\n", (int)status);
321                 return;
322         }
323
324         if (snd_renderbuffer != NULL)
325         {
326                 Mem_Free(snd_renderbuffer->ring);
327                 Mem_Free(snd_renderbuffer);
328                 snd_renderbuffer = NULL;
329         }
330
331         if (mixbuffer != NULL)
332                 Mem_Free(mixbuffer);
333         mixbuffer = NULL;
334 }
335
336
337 /*
338 ====================
339 SndSys_Submit
340
341 Submit the contents of "snd_renderbuffer" to the sound card
342 ====================
343 */
344 void SndSys_Submit (void)
345 {
346         // Nothing to do here (this sound module is callback-based)
347 }
348
349
350 /*
351 ====================
352 SndSys_GetSoundTime
353
354 Returns the number of sample frames consumed since the sound started
355 ====================
356 */
357 unsigned int SndSys_GetSoundTime (void)
358 {
359         return coreaudiotime;
360 }
361
362
363 /*
364 ====================
365 SndSys_LockRenderBuffer
366
367 Get the exclusive lock on "snd_renderbuffer"
368 ====================
369 */
370 qboolean SndSys_LockRenderBuffer (void)
371 {
372         return (pthread_mutex_lock(&coreaudio_mutex) == 0);
373 }
374
375
376 /*
377 ====================
378 SndSys_UnlockRenderBuffer
379
380 Release the exclusive lock on "snd_renderbuffer"
381 ====================
382 */
383 void SndSys_UnlockRenderBuffer (void)
384 {
385         pthread_mutex_unlock(&coreaudio_mutex);
386 }