]> icculus.org git repositories - divverent/darkplaces.git/blob - snd_coreaudio.c
9d9381253fdc42aa52f5d69023722a391ec4876d
[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 (suggested != NULL)
142                 memcpy (suggested, requested, sizeof (suggested));
143
144         // Get the device status and suggest any appropriate changes to format
145         propertySize = sizeof(streamDesc);
146         status = AudioDeviceGetProperty(outputDeviceID, 0, false, kAudioDevicePropertyStreamFormat, &propertySize, &streamDesc);
147         if (status)
148         {
149                 Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when getting kAudioDevicePropertyStreamFormat\n", status);
150                 return false;
151         }
152         // Suggest proper settings if they differ
153         if (requested->channels != streamDesc.mChannelsPerFrame || requested->speed != streamDesc.mSampleRate || requested->width != streamDesc.mBitsPerChannel/8)
154         {
155                 if (suggested != NULL)
156                 {
157                         suggested->channels = streamDesc.mChannelsPerFrame;
158                         suggested->speed = streamDesc.mSampleRate;
159                         suggested->width = streamDesc.mBitsPerChannel/8;
160                 }
161                 return false;
162         }
163
164         // Get the output device
165         propertySize = sizeof(outputDeviceID);
166         status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &propertySize, &outputDeviceID);
167         if (status)
168         {
169                 Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when getting kAudioHardwarePropertyDefaultOutputDevice\n", status);
170                 return false;
171         }
172         if (outputDeviceID == kAudioDeviceUnknown)
173         {
174                 Con_Printf("CoreAudio: outputDeviceID is kAudioDeviceUnknown\n");
175                 return false;
176         }
177
178         // Configure the output device
179         propertySize = sizeof(bufferByteCount);
180         bufferByteCount = CHUNK_SIZE * sizeof(float) * requested->channels;
181         status = AudioDeviceSetProperty(outputDeviceID, NULL, 0, false, kAudioDevicePropertyBufferSize, propertySize, &bufferByteCount);
182         if (status)
183         {
184                 Con_Printf("CoreAudio: AudioDeviceSetProperty() returned %d when setting kAudioDevicePropertyBufferSize to %d\n", status, CHUNK_SIZE);
185                 return false;
186         }
187
188         propertySize = sizeof(bufferByteCount);
189         status = AudioDeviceGetProperty(outputDeviceID, 0, false, kAudioDevicePropertyBufferSize, &propertySize, &bufferByteCount);
190         if (status)
191         {
192                 Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when setting kAudioDevicePropertyBufferSize\n", status);
193                 return false;
194         }
195
196         submissionChunk = bufferByteCount / sizeof(float);
197         if (submissionChunk % requested->channels != 0)
198         {
199                 Con_Print("CoreAudio: chunk size is NOT a multiple of the number of channels\n");
200                 return false;
201         }
202         submissionChunk /= requested->channels;
203         Con_DPrintf("   Chunk size = %d sample frames\n", submissionChunk);
204
205         // Print out the device status
206         propertySize = sizeof(streamDesc);
207         status = AudioDeviceGetProperty(outputDeviceID, 0, false, kAudioDevicePropertyStreamFormat, &propertySize, &streamDesc);
208         if (status)
209         {
210                 Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when getting kAudioDevicePropertyStreamFormat\n", status);
211                 return false;
212         }
213         Con_DPrint ("   Hardware format:\n");
214         Con_DPrintf("    %5d mSampleRate\n", (unsigned int)streamDesc.mSampleRate);
215         Con_DPrintf("     %c%c%c%c mFormatID\n",
216                                 (streamDesc.mFormatID & 0xff000000) >> 24,
217                                 (streamDesc.mFormatID & 0x00ff0000) >> 16,
218                                 (streamDesc.mFormatID & 0x0000ff00) >>  8,
219                                 (streamDesc.mFormatID & 0x000000ff) >>  0);
220         Con_DPrintf("    %5d mBytesPerPacket\n", streamDesc.mBytesPerPacket);
221         Con_DPrintf("    %5d mFramesPerPacket\n", streamDesc.mFramesPerPacket);
222         Con_DPrintf("    %5d mBytesPerFrame\n", streamDesc.mBytesPerFrame);
223         Con_DPrintf("    %5d mChannelsPerFrame\n", streamDesc.mChannelsPerFrame);
224         Con_DPrintf("    %5d mBitsPerChannel\n", streamDesc.mBitsPerChannel);
225
226         if(streamDesc.mFormatID != kAudioFormatLinearPCM)
227         {
228                 Con_Print("CoreAudio: Default audio device doesn't support linear PCM!\n");
229                 return false;
230         }
231
232         // Add the callback function
233         status = AudioDeviceAddIOProc(outputDeviceID, audioDeviceIOProc, NULL);
234         if (status)
235         {
236                 Con_Printf("CoreAudio: AudioDeviceAddIOProc() returned %d\n", status);
237                 return false;
238         }
239
240         // We haven't sent any sample frames yet
241         coreaudiotime = 0;
242
243         if (pthread_mutex_init(&coreaudio_mutex, NULL) != 0)
244         {
245                 Con_Print("CoreAudio: can't create pthread mutex\n");
246                 AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc);
247                 return false;
248         }
249
250         snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL);
251
252         // Start sound running
253         status = AudioDeviceStart(outputDeviceID, audioDeviceIOProc);
254         if (status)
255         {
256                 Con_Printf("CoreAudio: AudioDeviceStart() returned %d\n", status);
257                 pthread_mutex_destroy(&coreaudio_mutex);
258                 AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc);
259                 return false;
260         }
261         s_isRunning = true;
262
263         Con_Print("   Initialization successful\n");
264         return true;
265 }
266
267
268 /*
269 ====================
270 SndSys_Shutdown
271
272 Stop the sound card, delete "snd_renderbuffer" and free its other resources
273 ====================
274 */
275 void SndSys_Shutdown(void)
276 {
277         OSStatus status;
278
279         if (!s_isRunning)
280                 return;
281
282         status = AudioDeviceStop(outputDeviceID, audioDeviceIOProc);
283         if (status)
284         {
285                 Con_Printf("AudioDeviceStop: returned %d\n", status);
286                 return;
287         }
288         s_isRunning = false;
289
290         pthread_mutex_destroy(&coreaudio_mutex);
291
292         status = AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc);
293         if (status)
294         {
295                 Con_Printf("AudioDeviceRemoveIOProc: returned %d\n", status);
296                 return;
297         }
298
299         if (snd_renderbuffer != NULL)
300         {
301                 Mem_Free(snd_renderbuffer->ring);
302                 Mem_Free(snd_renderbuffer);
303                 snd_renderbuffer = NULL;
304         }
305 }
306
307
308 /*
309 ====================
310 SndSys_Submit
311
312 Submit the contents of "snd_renderbuffer" to the sound card
313 ====================
314 */
315 void SndSys_Submit (void)
316 {
317         // Nothing to do here (this sound module is callback-based)
318 }
319
320
321 /*
322 ====================
323 SndSys_GetSoundTime
324
325 Returns the number of sample frames consumed since the sound started
326 ====================
327 */
328 unsigned int SndSys_GetSoundTime (void)
329 {
330         return coreaudiotime;
331 }
332
333
334 /*
335 ====================
336 SndSys_LockRenderBuffer
337
338 Get the exclusive lock on "snd_renderbuffer"
339 ====================
340 */
341 qboolean SndSys_LockRenderBuffer (void)
342 {
343         return (pthread_mutex_lock(&coreaudio_mutex) == 0);
344 }
345
346
347 /*
348 ====================
349 SndSys_UnlockRenderBuffer
350
351 Release the exclusive lock on "snd_renderbuffer"
352 ====================
353 */
354 void SndSys_UnlockRenderBuffer (void)
355 {
356     pthread_mutex_unlock(&coreaudio_mutex);
357 }