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