Added CoreAudio (Mac OS X) sound driver. Changed / fixed a couple of Mac OS X related...
[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 // snd_coreaudio.c
24 // all other sound mixing is portable
25
26 #include <limits.h>
27
28 #include <CoreAudio/AudioHardware.h>
29
30 #include "quakedef.h"
31 #include "snd_main.h"
32
33 // BUFFER_SIZE must be an even multiple of CHUNK_SIZE
34 #define CHUNK_SIZE 2048
35 #define BUFFER_SIZE 16384
36
37 static unsigned int submissionChunk;
38 static unsigned int maxMixedSamples;
39 static short *s_mixedSamples;
40 static int s_chunkCount;  // number of chunks submitted
41 static qboolean s_isRunning;
42
43 static AudioDeviceID outputDeviceID;
44 static AudioStreamBasicDescription outputStreamBasicDescription;
45
46 extern mempool_t *snd_mempool;
47
48
49 /*
50 ===============
51 audioDeviceIOProc
52 ===============
53 */
54
55 OSStatus audioDeviceIOProc(AudioDeviceID inDevice,
56                                                    const AudioTimeStamp *inNow,
57                                                    const AudioBufferList *inInputData,
58                                                    const AudioTimeStamp *inInputTime,
59                                                    AudioBufferList *outOutputData,
60                                                    const AudioTimeStamp *inOutputTime,
61                                                    void *inClientData)
62 {
63         int     offset;
64         short *samples;
65         unsigned int sampleIndex;
66         float *outBuffer;
67         float scale;
68         
69         offset = (s_chunkCount * submissionChunk) % maxMixedSamples;
70         samples = s_mixedSamples + offset;
71         
72         outBuffer = (float *)outOutputData->mBuffers[0].mData;
73         
74         // If we have run out of samples, return silence
75         if (s_chunkCount * submissionChunk > shm->format.channels * paintedtime)
76         {
77                 memset(outBuffer, 0, sizeof(*outBuffer) * submissionChunk);
78         }
79         else
80         {
81                 // Convert the samples from shorts to floats.  Scale the floats to be [-1..1].
82                 scale = (1.0f / SHRT_MAX);
83                 for (sampleIndex = 0; sampleIndex < submissionChunk; sampleIndex++)
84                         outBuffer[sampleIndex] = samples[sampleIndex] * scale;
85
86                 s_chunkCount++; // this is the next buffer we will submit
87         }
88         
89         return 0;
90 }
91
92 /*
93 ===============
94 SNDDMA_Init
95 ===============
96 */
97 qboolean SNDDMA_Init(void)
98 {
99         OSStatus status;
100         UInt32 propertySize, bufferByteCount;
101
102         if (s_isRunning)
103                 return true;
104
105         Con_Printf("Initializing CoreAudio...\n");
106
107         // Get the output device
108         propertySize = sizeof(outputDeviceID);
109         status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &propertySize, &outputDeviceID);
110         if (status)
111         {
112                 Con_Printf("AudioHardwareGetProperty returned %d\n", status);
113                 return false;
114         }
115         
116         if (outputDeviceID == kAudioDeviceUnknown)
117         {
118                 Con_Printf("AudioHardwareGetProperty: outputDeviceID is kAudioDeviceUnknown\n");
119                 return false;
120         }
121
122         // Configure the output device  
123         // TODO: support "-sndspeed", "-sndmono" and "-sndstereo"
124         propertySize = sizeof(bufferByteCount);
125         bufferByteCount = CHUNK_SIZE * sizeof(float);
126         status = AudioDeviceSetProperty(outputDeviceID, NULL, 0, false, kAudioDevicePropertyBufferSize, propertySize, &bufferByteCount);
127         if (status)
128         {
129                 Con_Printf("AudioDeviceSetProperty: returned %d when setting kAudioDevicePropertyBufferSize to %d\n", status, CHUNK_SIZE);
130                 return false;
131         }
132         
133         propertySize = sizeof(bufferByteCount);
134         status = AudioDeviceGetProperty(outputDeviceID, 0, false, kAudioDevicePropertyBufferSize, &propertySize, &bufferByteCount);
135         if (status)
136         {
137                 Con_Printf("AudioDeviceGetProperty: returned %d when setting kAudioDevicePropertyBufferSize\n", status);
138                 return false;
139         }
140         submissionChunk = bufferByteCount / sizeof(float);
141         Con_DPrintf("   Chunk size = %d samples\n", submissionChunk);
142
143         // Print out the device status
144         propertySize = sizeof(outputStreamBasicDescription);
145         status = AudioDeviceGetProperty(outputDeviceID, 0, false, kAudioDevicePropertyStreamFormat, &propertySize, &outputStreamBasicDescription);
146         if (status)
147         {
148                 Con_Printf("AudioDeviceGetProperty: returned %d when getting kAudioDevicePropertyStreamFormat\n", status);
149                 return false;
150         }
151         Con_DPrintf("   Hardware format:\n");
152         Con_DPrintf("    %5d mSampleRate\n", (unsigned int)outputStreamBasicDescription.mSampleRate);
153         Con_DPrintf("     %c%c%c%c mFormatID\n",
154                                 (outputStreamBasicDescription.mFormatID & 0xff000000) >> 24,
155                                 (outputStreamBasicDescription.mFormatID & 0x00ff0000) >> 16,
156                                 (outputStreamBasicDescription.mFormatID & 0x0000ff00) >>  8,
157                                 (outputStreamBasicDescription.mFormatID & 0x000000ff) >>  0);
158         Con_DPrintf("    %5d mBytesPerPacket\n", outputStreamBasicDescription.mBytesPerPacket);
159         Con_DPrintf("    %5d mFramesPerPacket\n", outputStreamBasicDescription.mFramesPerPacket);
160         Con_DPrintf("    %5d mBytesPerFrame\n", outputStreamBasicDescription.mBytesPerFrame);
161         Con_DPrintf("    %5d mChannelsPerFrame\n", outputStreamBasicDescription.mChannelsPerFrame);
162         Con_DPrintf("    %5d mBitsPerChannel\n", outputStreamBasicDescription.mBitsPerChannel);
163
164         if(outputStreamBasicDescription.mFormatID != kAudioFormatLinearPCM)
165         {
166                 Con_Printf("Default Audio Device doesn't support Linear PCM!\n");
167                 return false;
168         }
169         
170         // Start sound running
171         status = AudioDeviceAddIOProc(outputDeviceID, audioDeviceIOProc, NULL);
172         if (status)
173         {
174                 Con_Printf("AudioDeviceAddIOProc: returned %d\n", status);
175                 return false;
176         }
177
178         maxMixedSamples = BUFFER_SIZE;
179         s_mixedSamples = Mem_Alloc (snd_mempool, sizeof(*s_mixedSamples) * maxMixedSamples);
180         Con_DPrintf("   Buffer size = %d samples (%d chunks)\n",
181                                 maxMixedSamples, (maxMixedSamples / submissionChunk));
182
183         // Tell the main app what we expect from it
184         memset ((void*)shm, 0, sizeof (*shm));
185         shm->format.speed = outputStreamBasicDescription.mSampleRate;
186         shm->format.channels = outputStreamBasicDescription.mChannelsPerFrame;
187         shm->format.width = 2;
188         shm->samples = maxMixedSamples;
189         shm->buffer = (qbyte *)s_mixedSamples;
190         shm->samplepos = 0;
191
192         // We haven't enqueued anything yet
193         s_chunkCount = 0;
194
195         status = AudioDeviceStart(outputDeviceID, audioDeviceIOProc);
196         if (status)
197         {
198                 Con_Printf("AudioDeviceStart: returned %d\n", status);
199                 return false;
200         }
201
202         s_isRunning = true;
203
204         Con_Printf("   Initialization successful\n");
205         
206         return true;
207 }
208
209 /*
210 ===============
211 SNDDMA_GetDMAPos
212
213 return the current sample position (in mono samples read)
214 inside the recirculating dma buffer, so the mixing code will know
215 how many sample are required to fill it up.
216 ===============
217 */
218 int SNDDMA_GetDMAPos(void)
219 {
220         return (s_chunkCount * submissionChunk) % shm->samples;
221 }
222
223 /*
224 ===============
225 SNDDMA_Shutdown
226
227 Reset the sound device for exiting
228 ===============
229 */
230 void SNDDMA_Shutdown(void)
231 {
232         OSStatus status;
233         
234         if (!s_isRunning)
235                 return;
236                 
237         status = AudioDeviceStop(outputDeviceID, audioDeviceIOProc);
238         if (status)
239         {
240                 Con_Printf("AudioDeviceStop: returned %d\n", status);
241                 return;
242         }
243         
244         s_isRunning = false;
245         
246         status = AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc);
247         if (status)
248         {
249                 Con_Printf("AudioDeviceRemoveIOProc: returned %d\n", status);
250                 return;
251         }
252         
253         Mem_Free(s_mixedSamples);
254         s_mixedSamples = NULL;
255         shm->samples = NULL;
256 }
257
258 /*
259 ===============
260 SNDDMA_Submit
261 ===============
262 */
263 void SNDDMA_Submit(void)
264 {
265         // nothing to do (CoreAudio is callback-based)
266 }
267
268 /*
269 ===============
270 S_LockBuffer
271 ===============
272 */
273 void *S_LockBuffer(void)
274 {
275         // not necessary (just return the buffer)
276         return shm->buffer;
277 }
278
279 /*
280 ===============
281 S_UnlockBuffer
282 ===============
283 */
284 void S_UnlockBuffer(void)
285 {
286         // not necessary
287 }