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