]> icculus.org git repositories - divverent/darkplaces.git/blob - snd_coreaudio.c
make "stopdownload" also stop curl downloads
[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 "quakedef.h"
24
25 #include <limits.h>
26 #include <pthread.h>
27
28 #include <CoreAudio/AudioHardware.h>
29
30 #include "snd_main.h"
31
32
33 #define CHUNK_SIZE 1024
34
35 static unsigned int submissionChunk = 0;  // in sample frames
36 static unsigned int coreaudiotime = 0;  // based on the number of chunks submitted so far
37 static qboolean s_isRunning = false;
38 static pthread_mutex_t coreaudio_mutex;
39 static AudioDeviceID outputDeviceID = kAudioDeviceUnknown;
40
41
42 /*
43 ====================
44 audioDeviceIOProc
45 ====================
46 */
47 static OSStatus audioDeviceIOProc(AudioDeviceID inDevice,
48                                                                   const AudioTimeStamp *inNow,
49                                                                   const AudioBufferList *inInputData,
50                                                                   const AudioTimeStamp *inInputTime,
51                                                                   AudioBufferList *outOutputData,
52                                                                   const AudioTimeStamp *inOutputTime,
53                                                                   void *inClientData)
54 {
55         float *outBuffer;
56         unsigned int frameCount, factor;
57
58         outBuffer = (float*)outOutputData->mBuffers[0].mData;
59         factor = snd_renderbuffer->format.channels * snd_renderbuffer->format.width;
60         frameCount = 0;
61
62         // Lock the snd_renderbuffer
63         if (SndSys_LockRenderBuffer())
64         {
65                 unsigned int maxFrames, sampleIndex, sampleCount;
66                 unsigned int startOffset, endOffset;
67                 const short *samples;
68                 const float scale = 1.0f / SHRT_MAX;
69
70                 // Transfert up to a chunk of sample frames from snd_renderbuffer to outBuffer
71                 maxFrames = snd_renderbuffer->endframe - snd_renderbuffer->startframe;
72                 if (maxFrames >= submissionChunk)
73                         frameCount = submissionChunk;
74                 else
75                         frameCount = maxFrames;
76
77                 // Convert the samples from shorts to floats.  Scale the floats to be [-1..1].
78                 startOffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes;
79                 endOffset = (snd_renderbuffer->startframe + frameCount) % snd_renderbuffer->maxframes;
80                 if (startOffset > endOffset)  // if the buffer wraps
81                 {
82                         sampleCount = (snd_renderbuffer->maxframes - startOffset) * snd_renderbuffer->format.channels;
83                         samples = (const short*)(&snd_renderbuffer->ring[startOffset * factor]);
84                         for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
85                                 outBuffer[sampleIndex] = samples[sampleIndex] * scale;
86
87                         outBuffer = &outBuffer[sampleCount];
88                         sampleCount = frameCount * snd_renderbuffer->format.channels - sampleCount;
89                         samples = (const short*)(&snd_renderbuffer->ring[0]);
90                         for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
91                                 outBuffer[sampleIndex] = samples[sampleIndex] * scale;
92                 }
93                 else
94                 {
95                         sampleCount = frameCount * snd_renderbuffer->format.channels;
96                         samples = (const short*)(&snd_renderbuffer->ring[startOffset * factor]);
97                         for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
98                                 outBuffer[sampleIndex] = samples[sampleIndex] * scale;
99                 }
100
101                 snd_renderbuffer->startframe += frameCount;
102
103                 // Unlock the snd_renderbuffer
104                 SndSys_UnlockRenderBuffer();
105         }
106
107         // If there was not enough samples, complete with silence samples
108         if (frameCount < submissionChunk)
109         {
110                 unsigned int missingFrames;
111
112                 missingFrames = submissionChunk - frameCount;
113                 if (developer.integer >= 1000 && vid_activewindow)
114                         Con_Printf("audioDeviceIOProc: %u sample frames missing\n", missingFrames);
115                 memset(&outBuffer[frameCount * snd_renderbuffer->format.channels], 0, missingFrames * sizeof(outBuffer[0]));
116         }
117
118         coreaudiotime += submissionChunk;
119         return 0;
120 }
121
122
123 /*
124 ====================
125 SndSys_Init
126
127 Create "snd_renderbuffer" with the proper sound format if the call is successful
128 May return a suggested format if the requested format isn't available
129 ====================
130 */
131 qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested)
132 {
133         OSStatus status;
134         UInt32 propertySize, bufferByteCount;
135         AudioStreamBasicDescription streamDesc;
136
137         if (s_isRunning)
138                 return true;
139
140         Con_Printf("Initializing CoreAudio...\n");
141
142         if(requested->width != 2)
143         {
144                 // we can only do 16bit per sample for now
145                 if(suggested != NULL)
146                 {
147                         memcpy (suggested, requested, sizeof (*suggested));
148                         suggested->width = 2;
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", (int)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", (int)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", (int)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", (int)status);
200                 return false;
201         }
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                                 (char)(streamDesc.mFormatID >> 24),
207                                 (char)(streamDesc.mFormatID >> 16),
208                                 (char)(streamDesc.mFormatID >>  8),
209                                 (char)(streamDesc.mFormatID >>  0));
210         Con_DPrintf("    %5u mBytesPerPacket\n", (unsigned int)streamDesc.mBytesPerPacket);
211         Con_DPrintf("    %5u mFramesPerPacket\n", (unsigned int)streamDesc.mFramesPerPacket);
212         Con_DPrintf("    %5u mBytesPerFrame\n", (unsigned int)streamDesc.mBytesPerFrame);
213         Con_DPrintf("    %5u mChannelsPerFrame\n", (unsigned int)streamDesc.mChannelsPerFrame);
214         Con_DPrintf("    %5u mBitsPerChannel\n", (unsigned int)streamDesc.mBitsPerChannel);
215
216         // Suggest proper settings if they differ
217         if (requested->channels != streamDesc.mChannelsPerFrame || requested->speed != streamDesc.mSampleRate)
218         {
219                 if (suggested != NULL)
220                 {
221                         memcpy (suggested, requested, sizeof (*suggested));
222                         suggested->channels = streamDesc.mChannelsPerFrame;
223                         suggested->speed = streamDesc.mSampleRate;
224                 }
225                 return false;
226         }
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", (int)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", (int)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", (int)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", (int)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 }