check vid_activewindow in several spammy prints that occur when the client is running...
[xonotic/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         Con_DPrint ("   Hardware format:\n");
204         Con_DPrintf("    %5d mSampleRate\n", (unsigned int)streamDesc.mSampleRate);
205         Con_DPrintf("     %c%c%c%c mFormatID\n",
206                                 (streamDesc.mFormatID & 0xff000000) >> 24,
207                                 (streamDesc.mFormatID & 0x00ff0000) >> 16,
208                                 (streamDesc.mFormatID & 0x0000ff00) >>  8,
209                                 (streamDesc.mFormatID & 0x000000ff) >>  0);
210         Con_DPrintf("    %5d mBytesPerPacket\n", streamDesc.mBytesPerPacket);
211         Con_DPrintf("    %5d mFramesPerPacket\n", streamDesc.mFramesPerPacket);
212         Con_DPrintf("    %5d mBytesPerFrame\n", streamDesc.mBytesPerFrame);
213         Con_DPrintf("    %5d mChannelsPerFrame\n", streamDesc.mChannelsPerFrame);
214         Con_DPrintf("    %5d mBitsPerChannel\n", streamDesc.mBitsPerChannel);
215
216         if(streamDesc.mFormatID != kAudioFormatLinearPCM)
217         {
218                 Con_Print("CoreAudio: Default audio device doesn't support linear PCM!\n");
219                 return false;
220         }
221
222         // Add the callback function
223         status = AudioDeviceAddIOProc(outputDeviceID, audioDeviceIOProc, NULL);
224         if (status)
225         {
226                 Con_Printf("CoreAudio: AudioDeviceAddIOProc() returned %d\n", status);
227                 return false;
228         }
229
230         // We haven't sent any sample frames yet
231         coreaudiotime = 0;
232
233         if (pthread_mutex_init(&coreaudio_mutex, NULL) != 0)
234         {
235                 Con_Print("CoreAudio: can't create pthread mutex\n");
236                 AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc);
237                 return false;
238         }
239
240         snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL);
241
242         // Start sound running
243         status = AudioDeviceStart(outputDeviceID, audioDeviceIOProc);
244         if (status)
245         {
246                 Con_Printf("CoreAudio: AudioDeviceStart() returned %d\n", status);
247                 pthread_mutex_destroy(&coreaudio_mutex);
248                 AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc);
249                 return false;
250         }
251         s_isRunning = true;
252
253         Con_Print("   Initialization successful\n");
254         return true;
255 }
256
257
258 /*
259 ====================
260 SndSys_Shutdown
261
262 Stop the sound card, delete "snd_renderbuffer" and free its other resources
263 ====================
264 */
265 void SndSys_Shutdown(void)
266 {
267         OSStatus status;
268
269         if (!s_isRunning)
270                 return;
271
272         status = AudioDeviceStop(outputDeviceID, audioDeviceIOProc);
273         if (status)
274         {
275                 Con_Printf("AudioDeviceStop: returned %d\n", status);
276                 return;
277         }
278         s_isRunning = false;
279
280         pthread_mutex_destroy(&coreaudio_mutex);
281
282         status = AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc);
283         if (status)
284         {
285                 Con_Printf("AudioDeviceRemoveIOProc: returned %d\n", status);
286                 return;
287         }
288
289         if (snd_renderbuffer != NULL)
290         {
291                 Mem_Free(snd_renderbuffer->ring);
292                 Mem_Free(snd_renderbuffer);
293                 snd_renderbuffer = NULL;
294         }
295 }
296
297
298 /*
299 ====================
300 SndSys_Submit
301
302 Submit the contents of "snd_renderbuffer" to the sound card
303 ====================
304 */
305 void SndSys_Submit (void)
306 {
307         // Nothing to do here (this sound module is callback-based)
308 }
309
310
311 /*
312 ====================
313 SndSys_GetSoundTime
314
315 Returns the number of sample frames consumed since the sound started
316 ====================
317 */
318 unsigned int SndSys_GetSoundTime (void)
319 {
320         return coreaudiotime;
321 }
322
323
324 /*
325 ====================
326 SndSys_LockRenderBuffer
327
328 Get the exclusive lock on "snd_renderbuffer"
329 ====================
330 */
331 qboolean SndSys_LockRenderBuffer (void)
332 {
333         return (pthread_mutex_lock(&coreaudio_mutex) == 0);
334 }
335
336
337 /*
338 ====================
339 SndSys_UnlockRenderBuffer
340
341 Release the exclusive lock on "snd_renderbuffer"
342 ====================
343 */
344 void SndSys_UnlockRenderBuffer (void)
345 {
346     pthread_mutex_unlock(&coreaudio_mutex);
347 }