removed NET_MAXRATE (as it served no useful purpose), changed description of rate...
[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                 Con_DPrintf("audioDeviceIOProc: %u sample frames missing\n", missingFrames);
113                 memset(&outBuffer[frameCount * snd_renderbuffer->format.channels], 0, missingFrames * sizeof(outBuffer[0]));
114         }
115
116         coreaudiotime += submissionChunk;
117         return 0;
118 }
119
120
121 /*
122 ====================
123 SndSys_Init
124
125 Create "snd_renderbuffer" with the proper sound format if the call is successful
126 May return a suggested format if the requested format isn't available
127 ====================
128 */
129 qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested)
130 {
131         OSStatus status;
132         UInt32 propertySize, bufferByteCount;
133         AudioStreamBasicDescription streamDesc;
134
135         if (s_isRunning)
136                 return true;
137
138         Con_Printf("Initializing CoreAudio...\n");
139         
140         // We only accept 16-bit samples for the moment
141         if (requested->width != 2)
142         {
143                 // Suggest a 16-bit format instead
144                 if (suggested != NULL)
145                 {
146                         memcpy (suggested, requested, sizeof (suggested));
147                         suggested->width = 2;
148                 }
149
150                 return false;
151         }
152         
153         // Get the output device
154         propertySize = sizeof(outputDeviceID);
155         status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &propertySize, &outputDeviceID);
156         if (status)
157         {
158                 Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when getting kAudioHardwarePropertyDefaultOutputDevice\n", status);
159                 return false;
160         }
161         if (outputDeviceID == kAudioDeviceUnknown)
162         {
163                 Con_Printf("CoreAudio: outputDeviceID is kAudioDeviceUnknown\n");
164                 return false;
165         }
166
167         // Configure the output device
168         propertySize = sizeof(bufferByteCount);
169         bufferByteCount = CHUNK_SIZE * sizeof(float) * requested->channels;
170         status = AudioDeviceSetProperty(outputDeviceID, NULL, 0, false, kAudioDevicePropertyBufferSize, propertySize, &bufferByteCount);
171         if (status)
172         {
173                 Con_Printf("CoreAudio: AudioDeviceSetProperty() returned %d when setting kAudioDevicePropertyBufferSize to %d\n", status, CHUNK_SIZE);
174                 return false;
175         }
176
177         propertySize = sizeof(bufferByteCount);
178         status = AudioDeviceGetProperty(outputDeviceID, 0, false, kAudioDevicePropertyBufferSize, &propertySize, &bufferByteCount);
179         if (status)
180         {
181                 Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when setting kAudioDevicePropertyBufferSize\n", status);
182                 return false;
183         }
184
185         submissionChunk = bufferByteCount / sizeof(float);
186         if (submissionChunk % requested->channels != 0)
187         {
188                 Con_Print("CoreAudio: chunk size is NOT a multiple of the number of channels\n");
189                 return false;
190         }
191         submissionChunk /= requested->channels;
192         Con_DPrintf("   Chunk size = %d sample frames\n", submissionChunk);
193
194         // Print out the device status
195         propertySize = sizeof(streamDesc);
196         status = AudioDeviceGetProperty(outputDeviceID, 0, false, kAudioDevicePropertyStreamFormat, &propertySize, &streamDesc);
197         if (status)
198         {
199                 Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when getting kAudioDevicePropertyStreamFormat\n", status);
200                 return false;
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         if(streamDesc.mFormatID != kAudioFormatLinearPCM)
216         {
217                 Con_Print("CoreAudio: Default audio device doesn't support linear PCM!\n");
218                 return false;
219         }
220
221         // Add the callback function
222         status = AudioDeviceAddIOProc(outputDeviceID, audioDeviceIOProc, NULL);
223         if (status)
224         {
225                 Con_Printf("CoreAudio: AudioDeviceAddIOProc() returned %d\n", status);
226                 return false;
227         }
228
229         // We haven't sent any sample frames yet
230         coreaudiotime = 0;
231
232         if (pthread_mutex_init(&coreaudio_mutex, NULL) != 0)
233         {
234                 Con_Print("CoreAudio: can't create pthread mutex\n");
235                 AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc);
236                 return false;
237         }
238
239         snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL);
240
241         // Start sound running
242         status = AudioDeviceStart(outputDeviceID, audioDeviceIOProc);
243         if (status)
244         {
245                 Con_Printf("CoreAudio: AudioDeviceStart() returned %d\n", status);
246                 pthread_mutex_destroy(&coreaudio_mutex);
247                 AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc);
248                 return false;
249         }
250         s_isRunning = true;
251
252         Con_Print("   Initialization successful\n");
253         return true;
254 }
255
256
257 /*
258 ====================
259 SndSys_Shutdown
260
261 Stop the sound card, delete "snd_renderbuffer" and free its other resources
262 ====================
263 */
264 void SndSys_Shutdown(void)
265 {
266         OSStatus status;
267
268         if (!s_isRunning)
269                 return;
270
271         status = AudioDeviceStop(outputDeviceID, audioDeviceIOProc);
272         if (status)
273         {
274                 Con_Printf("AudioDeviceStop: returned %d\n", status);
275                 return;
276         }
277         s_isRunning = false;
278         
279         pthread_mutex_destroy(&coreaudio_mutex);
280
281         status = AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc);
282         if (status)
283         {
284                 Con_Printf("AudioDeviceRemoveIOProc: returned %d\n", status);
285                 return;
286         }
287
288         if (snd_renderbuffer != NULL)
289         {
290                 Mem_Free(snd_renderbuffer->ring);
291                 Mem_Free(snd_renderbuffer);
292                 snd_renderbuffer = NULL;
293         }
294 }
295
296
297 /*
298 ====================
299 SndSys_Submit
300
301 Submit the contents of "snd_renderbuffer" to the sound card
302 ====================
303 */
304 void SndSys_Submit (void)
305 {
306         // Nothing to do here (this sound module is callback-based)
307 }
308
309
310 /*
311 ====================
312 SndSys_GetSoundTime
313
314 Returns the number of sample frames consumed since the sound started
315 ====================
316 */
317 unsigned int SndSys_GetSoundTime (void)
318 {
319         return coreaudiotime;
320 }
321
322
323 /*
324 ====================
325 SndSys_LockRenderBuffer
326
327 Get the exclusive lock on "snd_renderbuffer"
328 ====================
329 */
330 qboolean SndSys_LockRenderBuffer (void)
331 {
332         return (pthread_mutex_lock(&coreaudio_mutex) == 0);
333 }
334
335
336 /*
337 ====================
338 SndSys_UnlockRenderBuffer
339
340 Release the exclusive lock on "snd_renderbuffer"
341 ====================
342 */
343 void SndSys_UnlockRenderBuffer (void)
344 {
345     pthread_mutex_unlock(&coreaudio_mutex);
346 }