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