]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - snd_sdl.c
e93d302113f31e5cb509faa5f5824c584ae21a1f
[xonotic/darkplaces.git] / snd_sdl.c
1 /*
2 Copyright (C) 2004 Andreas Kirsch
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 */
19 #include <math.h>
20 #include <SDL.h>
21
22 #include "darkplaces.h"
23 #include "vid.h"
24
25 #include "snd_main.h"
26
27
28 static unsigned int sdlaudiotime = 0;
29 static int audio_device = 0;
30
31
32 // Note: SDL calls SDL_LockAudio() right before this function, so no need to lock the audio data here
33 static void Buffer_Callback (void *userdata, Uint8 *stream, int len)
34 {
35         unsigned int factor, RequestedFrames, MaxFrames, FrameCount;
36         unsigned int StartOffset, EndOffset;
37
38         factor = snd_renderbuffer->format.channels * snd_renderbuffer->format.width;
39         if ((unsigned int)len % factor != 0)
40                 Sys_Error("SDL sound: invalid buffer length passed to Buffer_Callback (%d bytes)\n", len);
41
42         RequestedFrames = (unsigned int)len / factor;
43
44         if (SndSys_LockRenderBuffer())
45         {
46                 if (snd_usethreadedmixing)
47                 {
48                         S_MixToBuffer(stream, RequestedFrames);
49                         if (snd_blocked)
50                                 memset(stream, snd_renderbuffer->format.width == 1 ? 0x80 : 0, len);
51                         SndSys_UnlockRenderBuffer();
52                         return;
53                 }
54
55                 // Transfert up to a chunk of samples from snd_renderbuffer to stream
56                 MaxFrames = snd_renderbuffer->endframe - snd_renderbuffer->startframe;
57                 if (MaxFrames > RequestedFrames)
58                         FrameCount = RequestedFrames;
59                 else
60                         FrameCount = MaxFrames;
61                 StartOffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes;
62                 EndOffset = (snd_renderbuffer->startframe + FrameCount) % snd_renderbuffer->maxframes;
63                 if (StartOffset > EndOffset)  // if the buffer wraps
64                 {
65                         unsigned int PartialLength1, PartialLength2;
66
67                         PartialLength1 = (snd_renderbuffer->maxframes - StartOffset) * factor;
68                         memcpy(stream, &snd_renderbuffer->ring[StartOffset * factor], PartialLength1);
69
70                         PartialLength2 = FrameCount * factor - PartialLength1;
71                         memcpy(&stream[PartialLength1], &snd_renderbuffer->ring[0], PartialLength2);
72
73                         // As of SDL 2.0 buffer needs to be fully initialized, so fill leftover part with silence
74                         // FIXME this is another place that assumes 8bit is always unsigned and others always signed
75                         memset(&stream[PartialLength1 + PartialLength2], snd_renderbuffer->format.width == 1 ? 0x80 : 0, len - (PartialLength1 + PartialLength2));
76                 }
77                 else
78                 {
79                         memcpy(stream, &snd_renderbuffer->ring[StartOffset * factor], FrameCount * factor);
80
81                         // As of SDL 2.0 buffer needs to be fully initialized, so fill leftover part with silence
82                         // FIXME this is another place that assumes 8bit is always unsigned and others always signed
83                         memset(&stream[FrameCount * factor], snd_renderbuffer->format.width == 1 ? 0x80 : 0, len - (FrameCount * factor));
84                 }
85
86                 snd_renderbuffer->startframe += FrameCount;
87
88                 if (FrameCount < RequestedFrames && developer_insane.integer && vid_activewindow)
89                         Con_DPrintf("SDL sound: %u sample frames missing\n", RequestedFrames - FrameCount);
90
91                 sdlaudiotime += RequestedFrames;
92
93                 SndSys_UnlockRenderBuffer();
94         }
95 }
96
97
98 /*
99 ====================
100 SndSys_Init
101
102 Create "snd_renderbuffer" with the proper sound format if the call is successful
103 May return a suggested format if the requested format isn't available
104 ====================
105 */
106 qbool SndSys_Init (snd_format_t* fmt)
107 {
108         unsigned int buffersize;
109         SDL_AudioSpec wantspec;
110         SDL_AudioSpec obtainspec;
111
112         snd_threaded = false;
113
114         Con_DPrint ("SndSys_Init: using the SDL module\n");
115
116         // Init the SDL Audio subsystem
117         if( SDL_InitSubSystem( SDL_INIT_AUDIO ) ) {
118                 Con_Print( "Initializing the SDL Audio subsystem failed!\n" );
119                 return false;
120         }
121
122         buffersize = (unsigned int)ceil((double)fmt->speed / 25.0); // 2048 bytes on 24kHz to 48kHz
123
124         // Init the SDL Audio subsystem
125         memset(&wantspec, 0, sizeof(wantspec));
126         wantspec.callback = Buffer_Callback;
127         wantspec.userdata = NULL;
128         wantspec.freq = fmt->speed;
129         wantspec.format = fmt->width == 1 ? AUDIO_U8 : (fmt->width == 2 ? AUDIO_S16SYS : AUDIO_F32);
130         wantspec.channels = fmt->channels;
131         wantspec.samples = CeilPowerOf2(buffersize);  // needs to be a power of 2 on some platforms.
132
133         Con_Printf("Wanted audio Specification:\n"
134                                 "    Channels  : %i\n"
135                                 "    Format    : 0x%X\n"
136                                 "    Frequency : %i\n"
137                                 "    Samples   : %i\n",
138                                 wantspec.channels, wantspec.format, wantspec.freq, wantspec.samples);
139
140         if ((audio_device = SDL_OpenAudioDevice(NULL, 0, &wantspec, &obtainspec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE)) == 0)
141         {
142                 Con_Printf(CON_ERROR "Failed to open the audio device! (%s)\n", SDL_GetError() );
143                 return false;
144         }
145
146         Con_Printf("Obtained audio specification:\n"
147                                 "    Channels  : %i\n"
148                                 "    Format    : 0x%X\n"
149                                 "    Frequency : %i\n"
150                                 "    Samples   : %i\n",
151                                 obtainspec.channels, obtainspec.format, obtainspec.freq, obtainspec.samples);
152
153         fmt->speed = obtainspec.freq;
154         fmt->channels = obtainspec.channels;
155
156         snd_threaded = true;
157
158         snd_renderbuffer = Snd_CreateRingBuffer(fmt, 0, NULL);
159         if (snd_channellayout.integer == SND_CHANNELLAYOUT_AUTO)
160                 Cvar_SetValueQuick (&snd_channellayout, SND_CHANNELLAYOUT_STANDARD);
161
162         sdlaudiotime = 0;
163         SDL_PauseAudioDevice(audio_device, 0);
164
165         return true;
166 }
167
168
169 /*
170 ====================
171 SndSys_Shutdown
172
173 Stop the sound card, delete "snd_renderbuffer" and free its other resources
174 ====================
175 */
176 void SndSys_Shutdown(void)
177 {
178         if (audio_device > 0) {
179                 SDL_CloseAudioDevice(audio_device);
180                 audio_device = 0;
181         }
182         if (snd_renderbuffer != NULL)
183         {
184                 Mem_Free(snd_renderbuffer->ring);
185                 Mem_Free(snd_renderbuffer);
186                 snd_renderbuffer = NULL;
187         }
188 }
189
190
191 /*
192 ====================
193 SndSys_Submit
194
195 Submit the contents of "snd_renderbuffer" to the sound card
196 ====================
197 */
198 void SndSys_Submit (void)
199 {
200         // Nothing to do here (this sound module is callback-based)
201 }
202
203
204 /*
205 ====================
206 SndSys_GetSoundTime
207
208 Returns the number of sample frames consumed since the sound started
209 ====================
210 */
211 unsigned int SndSys_GetSoundTime (void)
212 {
213         return sdlaudiotime;
214 }
215
216
217 /*
218 ====================
219 SndSys_LockRenderBuffer
220
221 Get the exclusive lock on "snd_renderbuffer"
222 ====================
223 */
224 qbool SndSys_LockRenderBuffer (void)
225 {
226         SDL_LockAudioDevice(audio_device);
227         return true;
228 }
229
230
231 /*
232 ====================
233 SndSys_UnlockRenderBuffer
234
235 Release the exclusive lock on "snd_renderbuffer"
236 ====================
237 */
238 void SndSys_UnlockRenderBuffer (void)
239 {
240         SDL_UnlockAudioDevice(audio_device);
241 }
242
243 /*
244 ====================
245 SndSys_SendKeyEvents
246
247 Send keyboard events originating from the sound system (e.g. MIDI)
248 ====================
249 */
250 void SndSys_SendKeyEvents(void)
251 {
252         // not supported
253 }