b98534f8055dfea647b8d0fbd788dada1adf0ca6
[xonotic/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->samples = maxMixedSamples;
187         shm->buffer = (qbyte *)s_mixedSamples;
188         shm->samplepos = 0;
189
190         // We haven't enqueued anything yet
191         s_chunkCount = 0;
192
193         status = AudioDeviceStart(outputDeviceID, audioDeviceIOProc);
194         if (status)
195         {
196                 Con_Printf("AudioDeviceStart: returned %d\n", status);
197                 return false;
198         }
199
200         s_isRunning = true;
201
202         Con_Printf("   Initialization successful\n");
203
204         return true;
205 }
206
207 /*
208 ===============
209 SNDDMA_GetDMAPos
210
211 return the current sample position (in mono samples read)
212 inside the recirculating dma buffer, so the mixing code will know
213 how many sample are required to fill it up.
214 ===============
215 */
216 int SNDDMA_GetDMAPos(void)
217 {
218         return (s_chunkCount * submissionChunk) % shm->samples;
219 }
220
221 /*
222 ===============
223 SNDDMA_Shutdown
224
225 Reset the sound device for exiting
226 ===============
227 */
228 void SNDDMA_Shutdown(void)
229 {
230         OSStatus status;
231
232         if (!s_isRunning)
233                 return;
234
235         status = AudioDeviceStop(outputDeviceID, audioDeviceIOProc);
236         if (status)
237         {
238                 Con_Printf("AudioDeviceStop: returned %d\n", status);
239                 return;
240         }
241
242         s_isRunning = false;
243
244         status = AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc);
245         if (status)
246         {
247                 Con_Printf("AudioDeviceRemoveIOProc: returned %d\n", status);
248                 return;
249         }
250
251         Mem_Free(s_mixedSamples);
252         s_mixedSamples = NULL;
253         shm->buffer = NULL;
254 }
255
256 /*
257 ===============
258 SNDDMA_Submit
259 ===============
260 */
261 void SNDDMA_Submit(void)
262 {
263         // nothing to do (CoreAudio is callback-based)
264 }
265
266 /*
267 ===============
268 S_LockBuffer
269 ===============
270 */
271 void *S_LockBuffer(void)
272 {
273         // not necessary (just return the buffer)
274         return shm->buffer;
275 }
276
277 /*
278 ===============
279 S_UnlockBuffer
280 ===============
281 */
282 void S_UnlockBuffer(void)
283 {
284         // not necessary
285 }