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 static short *mixbuffer = NULL;
41
42
43 /*
44 ====================
45 audioDeviceIOProc
46 ====================
47 */
audioDeviceIOProc(AudioDeviceID inDevice,const AudioTimeStamp * inNow,const AudioBufferList * inInputData,const AudioTimeStamp * inInputTime,AudioBufferList * outOutputData,const AudioTimeStamp * inOutputTime,void * inClientData)48 static OSStatus audioDeviceIOProc(AudioDeviceID inDevice,
49 const AudioTimeStamp *inNow,
50 const AudioBufferList *inInputData,
51 const AudioTimeStamp *inInputTime,
52 AudioBufferList *outOutputData,
53 const AudioTimeStamp *inOutputTime,
54 void *inClientData)
55 {
56 float *outBuffer;
57 unsigned int frameCount, factor, sampleIndex;
58 float scale = 1.0f / SHRT_MAX;
59
60 outBuffer = (float*)outOutputData->mBuffers[0].mData;
61 factor = snd_renderbuffer->format.channels * snd_renderbuffer->format.width;
62 frameCount = 0;
63 if (snd_blocked)
64 scale = 0;
65
66 // Lock the snd_renderbuffer
67 if (SndSys_LockRenderBuffer())
68 {
69 unsigned int maxFrames, sampleCount;
70 unsigned int startOffset, endOffset;
71 const short *samples;
72
73 if (snd_usethreadedmixing)
74 {
75 S_MixToBuffer(mixbuffer, submissionChunk);
76 sampleCount = submissionChunk * snd_renderbuffer->format.channels;
77 for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
78 outBuffer[sampleIndex] = mixbuffer[sampleIndex] * scale;
79 // unlock the mutex now
80 SndSys_UnlockRenderBuffer();
81 return 0;
82 }
83
84 // Transfert up to a chunk of sample frames from snd_renderbuffer to outBuffer
85 maxFrames = snd_renderbuffer->endframe - snd_renderbuffer->startframe;
86 if (maxFrames >= submissionChunk)
87 frameCount = submissionChunk;
88 else
89 frameCount = maxFrames;
90
91 // Convert the samples from shorts to floats. Scale the floats to be [-1..1].
92 startOffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes;
93 endOffset = (snd_renderbuffer->startframe + frameCount) % snd_renderbuffer->maxframes;
94 if (startOffset > endOffset) // if the buffer wraps
95 {
96 sampleCount = (snd_renderbuffer->maxframes - startOffset) * snd_renderbuffer->format.channels;
97 samples = (const short*)(&snd_renderbuffer->ring[startOffset * factor]);
98 for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
99 outBuffer[sampleIndex] = samples[sampleIndex] * scale;
100
101 outBuffer = &outBuffer[sampleCount];
102 sampleCount = frameCount * snd_renderbuffer->format.channels - sampleCount;
103 samples = (const short*)(&snd_renderbuffer->ring[0]);
104 for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
105 outBuffer[sampleIndex] = samples[sampleIndex] * scale;
106 }
107 else
108 {
109 sampleCount = frameCount * snd_renderbuffer->format.channels;
110 samples = (const short*)(&snd_renderbuffer->ring[startOffset * factor]);
111 for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
112 outBuffer[sampleIndex] = samples[sampleIndex] * scale;
113 }
114
115 snd_renderbuffer->startframe += frameCount;
116
117 // unlock the mutex now
118 SndSys_UnlockRenderBuffer();
119 }
120
121 // If there was not enough samples, complete with silence samples
122 if (frameCount < submissionChunk)
123 {
124 unsigned int missingFrames;
125
126 missingFrames = submissionChunk - frameCount;
127 if (developer_insane.integer && vid_activewindow)
128 Con_DPrintf("audioDeviceIOProc: %u sample frames missing\n", missingFrames);
129 memset(&outBuffer[frameCount * snd_renderbuffer->format.channels], 0, missingFrames * sizeof(outBuffer[0]));
130 }
131
132 coreaudiotime += submissionChunk;
133 return 0;
134 }
135
136
137 /*
138 ====================
139 SndSys_Init
140
141 Create "snd_renderbuffer" with the proper sound format if the call is successful
142 May return a suggested format if the requested format isn't available
143 ====================
144 */
SndSys_Init(const snd_format_t * requested,snd_format_t * suggested)145 qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested)
146 {
147 OSStatus status;
148 UInt32 propertySize, bufferByteCount;
149 AudioStreamBasicDescription streamDesc;
150
151 if (s_isRunning)
152 return true;
153
154 Con_Printf("Initializing CoreAudio...\n");
155 snd_threaded = false;
156
157 if(requested->width != 2)
158 {
159 // we can only do 16bit per sample for now
160 if(suggested != NULL)
161 {
162 memcpy (suggested, requested, sizeof (*suggested));
163 suggested->width = 2;
164 }
165 return false;
166 }
167
168 // Get the output device
169 propertySize = sizeof(outputDeviceID);
170 status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &propertySize, &outputDeviceID);
171 if (status)
172 {
173 Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when getting kAudioHardwarePropertyDefaultOutputDevice\n", (int)status);
174 return false;
175 }
176 if (outputDeviceID == kAudioDeviceUnknown)
177 {
178 Con_Printf("CoreAudio: outputDeviceID is kAudioDeviceUnknown\n");
179 return false;
180 }
181
182 // Configure the output device
183 propertySize = sizeof(bufferByteCount);
184 bufferByteCount = CHUNK_SIZE * sizeof(float) * requested->channels;
185 status = AudioDeviceSetProperty(outputDeviceID, NULL, 0, false, kAudioDevicePropertyBufferSize, propertySize, &bufferByteCount);
186 if (status)
187 {
188 Con_Printf("CoreAudio: AudioDeviceSetProperty() returned %d when setting kAudioDevicePropertyBufferSize to %d\n", (int)status, CHUNK_SIZE);
189 return false;
190 }
191
192 propertySize = sizeof(bufferByteCount);
193 status = AudioDeviceGetProperty(outputDeviceID, 0, false, kAudioDevicePropertyBufferSize, &propertySize, &bufferByteCount);
194 if (status)
195 {
196 Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when setting kAudioDevicePropertyBufferSize\n", (int)status);
197 return false;
198 }
199
200 submissionChunk = bufferByteCount / sizeof(float);
201 if (submissionChunk % requested->channels != 0)
202 {
203 Con_Print("CoreAudio: chunk size is NOT a multiple of the number of channels\n");
204 return false;
205 }
206 submissionChunk /= requested->channels;
207 Con_Printf(" Chunk size = %d sample frames\n", submissionChunk);
208
209 // Print out the device status
210 propertySize = sizeof(streamDesc);
211 status = AudioDeviceGetProperty(outputDeviceID, 0, false, kAudioDevicePropertyStreamFormat, &propertySize, &streamDesc);
212 if (status)
213 {
214 Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when getting kAudioDevicePropertyStreamFormat\n", (int)status);
215 return false;
216 }
217
218 Con_Print (" Hardware format:\n");
219 Con_Printf(" %5d mSampleRate\n", (unsigned int)streamDesc.mSampleRate);
220 Con_Printf(" %c%c%c%c mFormatID\n",
221 (char)(streamDesc.mFormatID >> 24),
222 (char)(streamDesc.mFormatID >> 16),
223 (char)(streamDesc.mFormatID >> 8),
224 (char)(streamDesc.mFormatID >> 0));
225 Con_Printf(" %5u mBytesPerPacket\n", (unsigned int)streamDesc.mBytesPerPacket);
226 Con_Printf(" %5u mFramesPerPacket\n", (unsigned int)streamDesc.mFramesPerPacket);
227 Con_Printf(" %5u mBytesPerFrame\n", (unsigned int)streamDesc.mBytesPerFrame);
228 Con_Printf(" %5u mChannelsPerFrame\n", (unsigned int)streamDesc.mChannelsPerFrame);
229 Con_Printf(" %5u mBitsPerChannel\n", (unsigned int)streamDesc.mBitsPerChannel);
230
231 // Suggest proper settings if they differ
232 if (requested->channels != streamDesc.mChannelsPerFrame || requested->speed != streamDesc.mSampleRate)
233 {
234 if (suggested != NULL)
235 {
236 memcpy (suggested, requested, sizeof (*suggested));
237 suggested->channels = streamDesc.mChannelsPerFrame;
238 suggested->speed = streamDesc.mSampleRate;
239 }
240 return false;
241 }
242
243 if(streamDesc.mFormatID == kAudioFormatLinearPCM)
244 {
245 // Add the callback function
246 status = AudioDeviceAddIOProc(outputDeviceID, audioDeviceIOProc, NULL);
247 if (!status)
248 {
249 // We haven't sent any sample frames yet
250 coreaudiotime = 0;
251 if (pthread_mutex_init(&coreaudio_mutex, NULL) == 0)
252 {
253 if ((snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL)))
254 {
255 if ((mixbuffer = Mem_Alloc(snd_mempool, CHUNK_SIZE * sizeof(*mixbuffer) * requested->channels)))
256 {
257 // Start sound running
258 status = AudioDeviceStart(outputDeviceID, audioDeviceIOProc);
259 if (!status)
260 {
261 s_isRunning = true;
262 snd_threaded = true;
263 Con_Print(" Initialization successful\n");
264 return true;
265 }
266 else
267 Con_Printf("CoreAudio: AudioDeviceStart() returned %d\n", (int)status);
268 Mem_Free(mixbuffer);
269 mixbuffer = NULL;
270 }
271 else
272 Con_Print("CoreAudio: can't allocate memory for mixbuffer\n");
273 Mem_Free(snd_renderbuffer->ring);
274 Mem_Free(snd_renderbuffer);
275 snd_renderbuffer = NULL;
276 }
277 else
278 Con_Print("CoreAudio: can't allocate memory for ringbuffer\n");
279 pthread_mutex_destroy(&coreaudio_mutex);
280 }
281 else
282 Con_Print("CoreAudio: can't create pthread mutex\n");
283 AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc);
284 }
285 else
286 Con_Printf("CoreAudio: AudioDeviceAddIOProc() returned %d\n", (int)status);
287 }
288 else
289 Con_Print("CoreAudio: Default audio device doesn't support linear PCM!\n");
290 return false;
291 }
292
293
294 /*
295 ====================
296 SndSys_Shutdown
297
298 Stop the sound card, delete "snd_renderbuffer" and free its other resources
299 ====================
300 */
SndSys_Shutdown(void)301 void SndSys_Shutdown(void)
302 {
303 OSStatus status;
304
305 if (!s_isRunning)
306 return;
307
308 status = AudioDeviceStop(outputDeviceID, audioDeviceIOProc);
309 if (status)
310 {
311 Con_Printf("AudioDeviceStop: returned %d\n", (int)status);
312 return;
313 }
314 s_isRunning = false;
315
316 pthread_mutex_destroy(&coreaudio_mutex);
317
318 status = AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc);
319 if (status)
320 {
321 Con_Printf("AudioDeviceRemoveIOProc: returned %d\n", (int)status);
322 return;
323 }
324
325 if (snd_renderbuffer != NULL)
326 {
327 Mem_Free(snd_renderbuffer->ring);
328 Mem_Free(snd_renderbuffer);
329 snd_renderbuffer = NULL;
330 }
331
332 if (mixbuffer != NULL)
333 Mem_Free(mixbuffer);
334 mixbuffer = NULL;
335 }
336
337
338 /*
339 ====================
340 SndSys_Submit
341
342 Submit the contents of "snd_renderbuffer" to the sound card
343 ====================
344 */
SndSys_Submit(void)345 void SndSys_Submit (void)
346 {
347 // Nothing to do here (this sound module is callback-based)
348 }
349
350
351 /*
352 ====================
353 SndSys_GetSoundTime
354
355 Returns the number of sample frames consumed since the sound started
356 ====================
357 */
SndSys_GetSoundTime(void)358 unsigned int SndSys_GetSoundTime (void)
359 {
360 return coreaudiotime;
361 }
362
363
364 /*
365 ====================
366 SndSys_LockRenderBuffer
367
368 Get the exclusive lock on "snd_renderbuffer"
369 ====================
370 */
SndSys_LockRenderBuffer(void)371 qboolean SndSys_LockRenderBuffer (void)
372 {
373 return (pthread_mutex_lock(&coreaudio_mutex) == 0);
374 }
375
376
377 /*
378 ====================
379 SndSys_UnlockRenderBuffer
380
381 Release the exclusive lock on "snd_renderbuffer"
382 ====================
383 */
SndSys_UnlockRenderBuffer(void)384 void SndSys_UnlockRenderBuffer (void)
385 {
386 pthread_mutex_unlock(&coreaudio_mutex);
387 }
388
389 /*
390 ====================
391 SndSys_SendKeyEvents
392
393 Send keyboard events originating from the sound system (e.g. MIDI)
394 ====================
395 */
SndSys_SendKeyEvents(void)396 void SndSys_SendKeyEvents(void)
397 {
398 // not supported
399 }
400