]> icculus.org git repositories - divverent/darkplaces.git/blob - snd_coreaudio.c
oops, I didn't see this warning
[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 <limits.h>
24 #include <pthread.h>
25
26 #include <CoreAudio/AudioHardware.h>
27
28 #include "quakedef.h"
29 #include "snd_main.h"
30
31
32 #define CHUNK_SIZE 1024
33
34 static unsigned int submissionChunk = 0;  // in sample frames
35 static unsigned int coreaudiotime = 0;  // based on the number of chunks submitted so far
36 static qboolean s_isRunning = false;
37 static pthread_mutex_t coreaudio_mutex;
38 static AudioDeviceID outputDeviceID = kAudioDeviceUnknown;
39
40
41 /*
42 ====================
43 audioDeviceIOProc
44 ====================
45 */
46 static OSStatus audioDeviceIOProc(AudioDeviceID inDevice,
47                                                                   const AudioTimeStamp *inNow,
48                                                                   const AudioBufferList *inInputData,
49                                                                   const AudioTimeStamp *inInputTime,
50                                                                   AudioBufferList *outOutputData,
51                                                                   const AudioTimeStamp *inOutputTime,
52                                                                   void *inClientData)
53 {
54         float *outBuffer;
55         unsigned int frameCount, factor;
56
57         outBuffer = (float*)outOutputData->mBuffers[0].mData;
58         factor = snd_renderbuffer->format.channels * snd_renderbuffer->format.width;
59         frameCount = 0;
60
61         // Lock the snd_renderbuffer
62         if (SndSys_LockRenderBuffer())
63         {
64                 unsigned int maxFrames, sampleIndex, sampleCount;
65                 unsigned int startOffset, endOffset;
66                 const short *samples;
67                 const float scale = 1.0f / SHRT_MAX;
68
69                 // Transfert up to a chunk of sample frames from snd_renderbuffer to outBuffer
70                 maxFrames = snd_renderbuffer->endframe - snd_renderbuffer->startframe;
71                 if (maxFrames >= submissionChunk)
72                         frameCount = submissionChunk;
73                 else
74                         frameCount = maxFrames;
75
76                 // Convert the samples from shorts to floats.  Scale the floats to be [-1..1].
77                 startOffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes;
78                 endOffset = (snd_renderbuffer->startframe + frameCount) % snd_renderbuffer->maxframes;
79                 if (startOffset > endOffset)  // if the buffer wraps
80                 {
81                         sampleCount = (snd_renderbuffer->maxframes - startOffset) * snd_renderbuffer->format.channels;
82                         samples = (const short*)(&snd_renderbuffer->ring[startOffset * factor]);
83                         for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
84                                 outBuffer[sampleIndex] = samples[sampleIndex] * scale;
85
86                         outBuffer = &outBuffer[sampleCount];
87                         sampleCount = frameCount * snd_renderbuffer->format.channels - sampleCount;
88                         samples = (const short*)(&snd_renderbuffer->ring[0]);
89                         for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
90                                 outBuffer[sampleIndex] = samples[sampleIndex] * scale;
91                 }
92                 else
93                 {
94                         sampleCount = frameCount * snd_renderbuffer->format.channels;
95                         samples = (const short*)(&snd_renderbuffer->ring[startOffset * factor]);
96                         for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
97                                 outBuffer[sampleIndex] = samples[sampleIndex] * scale;
98                 }
99
100                 snd_renderbuffer->startframe += frameCount;
101
102                 // Unlock the snd_renderbuffer
103                 SndSys_UnlockRenderBuffer();
104         }
105
106         // If there was not enough samples, complete with silence samples
107         if (frameCount < submissionChunk)
108         {
109                 unsigned int missingFrames;
110
111                 missingFrames = submissionChunk - frameCount;
112                 if (developer.integer >= 1000 && vid_activewindow)
113                         Con_Printf("audioDeviceIOProc: %u sample frames missing\n", missingFrames);
114                 memset(&outBuffer[frameCount * snd_renderbuffer->format.channels], 0, missingFrames * sizeof(outBuffer[0]));
115         }
116
117         coreaudiotime += submissionChunk;
118         return 0;
119 }
120
121
122 /*
123 ====================
124 SndSys_Init
125
126 Create "snd_renderbuffer" with the proper sound format if the call is successful
127 May return a suggested format if the requested format isn't available
128 ====================
129 */
130 qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested)
131 {
132         OSStatus status;
133         UInt32 propertySize, bufferByteCount;
134         AudioStreamBasicDescription streamDesc;
135
136         if (s_isRunning)
137                 return true;
138
139         Con_Printf("Initializing CoreAudio...\n");
140
141         if(requested->width != 2)
142         {
143                 // we can only do 16bit per sample for now
144                 if(suggested != NULL)
145                 {
146                         memcpy (suggested, requested, sizeof (*suggested));
147                         suggested->width = 2;
148                 }
149                 return false;
150         }
151
152         // Get the output device
153         propertySize = sizeof(outputDeviceID);
154         status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &propertySize, &outputDeviceID);
155         if (status)
156         {
157                 Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when getting kAudioHardwarePropertyDefaultOutputDevice\n", status);
158                 return false;
159         }
160         if (outputDeviceID == kAudioDeviceUnknown)
161         {
162                 Con_Printf("CoreAudio: outputDeviceID is kAudioDeviceUnknown\n");
163                 return false;
164         }
165
166         // Configure the output device
167         propertySize = sizeof(bufferByteCount);
168         bufferByteCount = CHUNK_SIZE * sizeof(float) * requested->channels;
169         status = AudioDeviceSetProperty(outputDeviceID, NULL, 0, false, kAudioDevicePropertyBufferSize, propertySize, &bufferByteCount);
170         if (status)
171         {
172                 Con_Printf("CoreAudio: AudioDeviceSetProperty() returned %d when setting kAudioDevicePropertyBufferSize to %d\n", status, CHUNK_SIZE);
173                 return false;
174         }
175
176         propertySize = sizeof(bufferByteCount);
177         status = AudioDeviceGetProperty(outputDeviceID, 0, false, kAudioDevicePropertyBufferSize, &propertySize, &bufferByteCount);
178         if (status)
179         {
180                 Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when setting kAudioDevicePropertyBufferSize\n", status);
181                 return false;
182         }
183
184         submissionChunk = bufferByteCount / sizeof(float);
185         if (submissionChunk % requested->channels != 0)
186         {
187                 Con_Print("CoreAudio: chunk size is NOT a multiple of the number of channels\n");
188                 return false;
189         }
190         submissionChunk /= requested->channels;
191         Con_DPrintf("   Chunk size = %d sample frames\n", submissionChunk);
192
193         // Print out the device status
194         propertySize = sizeof(streamDesc);
195         status = AudioDeviceGetProperty(outputDeviceID, 0, false, kAudioDevicePropertyStreamFormat, &propertySize, &streamDesc);
196         if (status)
197         {
198                 Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when getting kAudioDevicePropertyStreamFormat\n", status);
199                 return false;
200         }
201
202         Con_DPrint ("   Hardware format:\n");
203         Con_DPrintf("    %5d mSampleRate\n", (unsigned int)streamDesc.mSampleRate);
204         Con_DPrintf("     %c%c%c%c mFormatID\n",
205                                 (streamDesc.mFormatID & 0xff000000) >> 24,
206                                 (streamDesc.mFormatID & 0x00ff0000) >> 16,
207                                 (streamDesc.mFormatID & 0x0000ff00) >>  8,
208                                 (streamDesc.mFormatID & 0x000000ff) >>  0);
209         Con_DPrintf("    %5d mBytesPerPacket\n", streamDesc.mBytesPerPacket);
210         Con_DPrintf("    %5d mFramesPerPacket\n", streamDesc.mFramesPerPacket);
211         Con_DPrintf("    %5d mBytesPerFrame\n", streamDesc.mBytesPerFrame);
212         Con_DPrintf("    %5d mChannelsPerFrame\n", streamDesc.mChannelsPerFrame);
213         Con_DPrintf("    %5d mBitsPerChannel\n", streamDesc.mBitsPerChannel);
214
215         // Suggest proper settings if they differ
216         if (requested->channels != streamDesc.mChannelsPerFrame || requested->speed != streamDesc.mSampleRate)
217         {
218                 if (suggested != NULL)
219                 {
220                         memcpy (suggested, requested, sizeof (*suggested));
221                         suggested->channels = streamDesc.mChannelsPerFrame;
222                         suggested->speed = streamDesc.mSampleRate;
223                 }
224                 return false;
225         }
226
227         if(streamDesc.mFormatID != kAudioFormatLinearPCM)
228         {
229                 Con_Print("CoreAudio: Default audio device doesn't support linear PCM!\n");
230                 return false;
231         }
232
233         // Add the callback function
234         status = AudioDeviceAddIOProc(outputDeviceID, audioDeviceIOProc, NULL);
235         if (status)
236         {
237                 Con_Printf("CoreAudio: AudioDeviceAddIOProc() returned %d\n", status);
238                 return false;
239         }
240
241         // We haven't sent any sample frames yet
242         coreaudiotime = 0;
243
244         if (pthread_mutex_init(&coreaudio_mutex, NULL) != 0)
245         {
246                 Con_Print("CoreAudio: can't create pthread mutex\n");
247                 AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc);
248                 return false;
249         }
250
251         snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL);
252
253         // Start sound running
254         status = AudioDeviceStart(outputDeviceID, audioDeviceIOProc);
255         if (status)
256         {
257                 Con_Printf("CoreAudio: AudioDeviceStart() returned %d\n", status);
258                 pthread_mutex_destroy(&coreaudio_mutex);
259                 AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc);
260                 return false;
261         }
262         s_isRunning = true;
263
264         Con_Print("   Initialization successful\n");
265         return true;
266 }
267
268
269 /*
270 ====================
271 SndSys_Shutdown
272
273 Stop the sound card, delete "snd_renderbuffer" and free its other resources
274 ====================
275 */
276 void SndSys_Shutdown(void)
277 {
278         OSStatus status;
279
280         if (!s_isRunning)
281                 return;
282
283         status = AudioDeviceStop(outputDeviceID, audioDeviceIOProc);
284         if (status)
285         {
286                 Con_Printf("AudioDeviceStop: returned %d\n", status);
287                 return;
288         }
289         s_isRunning = false;
290
291         pthread_mutex_destroy(&coreaudio_mutex);
292
293         status = AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc);
294         if (status)
295         {
296                 Con_Printf("AudioDeviceRemoveIOProc: returned %d\n", status);
297                 return;
298         }
299
300         if (snd_renderbuffer != NULL)
301         {
302                 Mem_Free(snd_renderbuffer->ring);
303                 Mem_Free(snd_renderbuffer);
304                 snd_renderbuffer = NULL;
305         }
306 }
307
308
309 /*
310 ====================
311 SndSys_Submit
312
313 Submit the contents of "snd_renderbuffer" to the sound card
314 ====================
315 */
316 void SndSys_Submit (void)
317 {
318         // Nothing to do here (this sound module is callback-based)
319 }
320
321
322 /*
323 ====================
324 SndSys_GetSoundTime
325
326 Returns the number of sample frames consumed since the sound started
327 ====================
328 */
329 unsigned int SndSys_GetSoundTime (void)
330 {
331         return coreaudiotime;
332 }
333
334
335 /*
336 ====================
337 SndSys_LockRenderBuffer
338
339 Get the exclusive lock on "snd_renderbuffer"
340 ====================
341 */
342 qboolean SndSys_LockRenderBuffer (void)
343 {
344         return (pthread_mutex_lock(&coreaudio_mutex) == 0);
345 }
346
347
348 /*
349 ====================
350 SndSys_UnlockRenderBuffer
351
352 Release the exclusive lock on "snd_renderbuffer"
353 ====================
354 */
355 void SndSys_UnlockRenderBuffer (void)
356 {
357     pthread_mutex_unlock(&coreaudio_mutex);
358 }