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