always send a packet when an impulse is used, regardless of the
[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 #if 0
262 // FIXME: This causes crashes and weird problems, why doesn't it work?
263                                                         snd_threaded = true;
264 #endif
265                                                         Con_Print("   Initialization successful\n");
266                                                         return true;
267                                                 }
268                                                 else
269                                                         Con_Printf("CoreAudio: AudioDeviceStart() returned %d\n", (int)status);
270                                                 Mem_Free(mixbuffer);
271                                                 mixbuffer = NULL;
272                                         }
273                                         else
274                                                 Con_Print("CoreAudio: can't allocate memory for mixbuffer\n");
275                                         Mem_Free(snd_renderbuffer->ring);
276                                         Mem_Free(snd_renderbuffer);
277                                         snd_renderbuffer = NULL;
278                                 }
279                                 else
280                                         Con_Print("CoreAudio: can't allocate memory for ringbuffer\n");
281                                 pthread_mutex_destroy(&coreaudio_mutex);
282                         }
283                         else
284                                 Con_Print("CoreAudio: can't create pthread mutex\n");
285                         AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc);
286                 }
287                 else
288                         Con_Printf("CoreAudio: AudioDeviceAddIOProc() returned %d\n", (int)status);
289         }
290         else
291                 Con_Print("CoreAudio: Default audio device doesn't support linear PCM!\n");
292         return false;
293 }
294
295
296 /*
297 ====================
298 SndSys_Shutdown
299
300 Stop the sound card, delete "snd_renderbuffer" and free its other resources
301 ====================
302 */
303 void SndSys_Shutdown(void)
304 {
305         OSStatus status;
306
307         if (!s_isRunning)
308                 return;
309
310         status = AudioDeviceStop(outputDeviceID, audioDeviceIOProc);
311         if (status)
312         {
313                 Con_Printf("AudioDeviceStop: returned %d\n", (int)status);
314                 return;
315         }
316         s_isRunning = false;
317
318         pthread_mutex_destroy(&coreaudio_mutex);
319
320         status = AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc);
321         if (status)
322         {
323                 Con_Printf("AudioDeviceRemoveIOProc: returned %d\n", (int)status);
324                 return;
325         }
326
327         if (snd_renderbuffer != NULL)
328         {
329                 Mem_Free(snd_renderbuffer->ring);
330                 Mem_Free(snd_renderbuffer);
331                 snd_renderbuffer = NULL;
332         }
333
334         if (mixbuffer != NULL)
335                 Mem_Free(mixbuffer);
336         mixbuffer = NULL;
337 }
338
339
340 /*
341 ====================
342 SndSys_Submit
343
344 Submit the contents of "snd_renderbuffer" to the sound card
345 ====================
346 */
347 void SndSys_Submit (void)
348 {
349         // Nothing to do here (this sound module is callback-based)
350 }
351
352
353 /*
354 ====================
355 SndSys_GetSoundTime
356
357 Returns the number of sample frames consumed since the sound started
358 ====================
359 */
360 unsigned int SndSys_GetSoundTime (void)
361 {
362         return coreaudiotime;
363 }
364
365
366 /*
367 ====================
368 SndSys_LockRenderBuffer
369
370 Get the exclusive lock on "snd_renderbuffer"
371 ====================
372 */
373 qboolean SndSys_LockRenderBuffer (void)
374 {
375         return (pthread_mutex_lock(&coreaudio_mutex) == 0);
376 }
377
378
379 /*
380 ====================
381 SndSys_UnlockRenderBuffer
382
383 Release the exclusive lock on "snd_renderbuffer"
384 ====================
385 */
386 void SndSys_UnlockRenderBuffer (void)
387 {
388         pthread_mutex_unlock(&coreaudio_mutex);
389 }