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