1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
11
|
|
12
|
|
13
|
|
14
|
|
15
|
|
16
|
|
17
|
|
18
|
|
19
|
|
20
|
#include <math.h>
|
21
|
#include <pthread.h>
|
22
|
#include <string.h>
|
23
|
#include <sys/time.h>
|
24
|
|
25
|
#include <SDL.h>
|
26
|
#include <SDL_audio.h>
|
27
|
|
28
|
#include <libaudcore/audstrings.h>
|
29
|
#include <libaudcore/i18n.h>
|
30
|
#include <libaudcore/plugin.h>
|
31
|
#include <libaudcore/ringbuf.h>
|
32
|
#include <libaudcore/runtime.h>
|
33
|
|
34
|
#define VOLUME_RANGE 40
|
35
|
|
36
|
class SDLOutput : public OutputPlugin
|
37
|
{
|
38
|
public:
|
39
|
static const char about[];
|
40
|
static const char * const defaults[];
|
41
|
|
42
|
static constexpr PluginInfo info = {
|
43
|
N_("SDL Output"),
|
44
|
PACKAGE,
|
45
|
about
|
46
|
};
|
47
|
|
48
|
constexpr SDLOutput () : OutputPlugin (info, 1) {}
|
49
|
|
50
|
bool init ();
|
51
|
void cleanup ();
|
52
|
|
53
|
StereoVolume get_volume ();
|
54
|
void set_volume (StereoVolume v);
|
55
|
|
56
|
bool open_audio (int aud_format, int rate, int chans, String & error);
|
57
|
void close_audio ();
|
58
|
|
59
|
void period_wait ();
|
60
|
int write_audio (const void * data, int size);
|
61
|
void drain ();
|
62
|
|
63
|
int get_delay ();
|
64
|
|
65
|
void pause (bool pause);
|
66
|
void flush ();
|
67
|
};
|
68
|
|
69
|
EXPORT SDLOutput aud_plugin_instance;
|
70
|
|
71
|
const char SDLOutput::about[] =
|
72
|
N_("SDL Output Plugin for Audacious\n"
|
73
|
"Copyright 2010 John Lindgren");
|
74
|
|
75
|
const char * const SDLOutput::defaults[] = {
|
76
|
"vol_left", "100",
|
77
|
"vol_right", "100",
|
78
|
nullptr};
|
79
|
|
80
|
static pthread_mutex_t sdlout_mutex = PTHREAD_MUTEX_INITIALIZER;
|
81
|
static pthread_cond_t sdlout_cond = PTHREAD_COND_INITIALIZER;
|
82
|
|
83
|
static volatile int vol_left, vol_right;
|
84
|
|
85
|
static int sdlout_chan, sdlout_rate;
|
86
|
|
87
|
static RingBuf<unsigned char> buffer;
|
88
|
|
89
|
static bool prebuffer_flag, paused_flag;
|
90
|
|
91
|
static int block_delay;
|
92
|
static struct timeval block_time;
|
93
|
static SDL_AudioDeviceID ouraudiodevice;
|
94
|
|
95
|
bool SDLOutput::init ()
|
96
|
{
|
97
|
aud_config_set_defaults ("sdlout", defaults);
|
98
|
|
99
|
vol_left = aud_get_int ("sdlout", "vol_left");
|
100
|
vol_right = aud_get_int ("sdlout", "vol_right");
|
101
|
|
102
|
if (SDL_Init (SDL_INIT_AUDIO) < 0)
|
103
|
{
|
104
|
AUDERR ("Failed to init SDL: %s.\n", SDL_GetError ());
|
105
|
return false;
|
106
|
}
|
107
|
|
108
|
return true;
|
109
|
}
|
110
|
|
111
|
void SDLOutput::cleanup ()
|
112
|
{
|
113
|
SDL_Quit ();
|
114
|
}
|
115
|
|
116
|
StereoVolume SDLOutput::get_volume ()
|
117
|
{
|
118
|
return {vol_left, vol_right};
|
119
|
}
|
120
|
|
121
|
void SDLOutput::set_volume (StereoVolume v)
|
122
|
{
|
123
|
vol_left = v.left;
|
124
|
vol_right = v.right;
|
125
|
|
126
|
aud_set_int ("sdlout", "vol_left", v.left);
|
127
|
aud_set_int ("sdlout", "vol_right", v.right);
|
128
|
}
|
129
|
|
130
|
static void apply_mono_volume (unsigned char * data, int len)
|
131
|
{
|
132
|
int vol = aud::max (vol_left, vol_right);
|
133
|
int factor = (vol == 0) ? 0 : powf (10, (float) VOLUME_RANGE * (vol - 100)
|
134
|
/ 100 / 20) * 65536;
|
135
|
|
136
|
int16_t * i = (int16_t *) data;
|
137
|
int16_t * end = (int16_t *) (data + len);
|
138
|
|
139
|
while (i < end)
|
140
|
{
|
141
|
* i = ((int) * i * factor) >> 16;
|
142
|
i ++;
|
143
|
}
|
144
|
}
|
145
|
|
146
|
static void apply_stereo_volume (unsigned char * data, int len)
|
147
|
{
|
148
|
int factor_left = (vol_left == 0) ? 0 : powf (10, (float) VOLUME_RANGE *
|
149
|
(vol_left - 100) / 100 / 20) * 65536;
|
150
|
int factor_right = (vol_right == 0) ? 0 : powf (10, (float) VOLUME_RANGE *
|
151
|
(vol_right - 100) / 100 / 20) * 65536;
|
152
|
|
153
|
int16_t * i = (int16_t *) data;
|
154
|
int16_t * end = (int16_t *) (data + len);
|
155
|
|
156
|
while (i < end)
|
157
|
{
|
158
|
* i = ((int) * i * factor_left) >> 16;
|
159
|
i ++;
|
160
|
* i = ((int) * i * factor_right) >> 16;
|
161
|
i ++;
|
162
|
}
|
163
|
}
|
164
|
|
165
|
static void callback (void * user, unsigned char * buf, int len)
|
166
|
{
|
167
|
pthread_mutex_lock (& sdlout_mutex);
|
168
|
|
169
|
int copy = aud::min (len, buffer.len ());
|
170
|
buffer.move_out (buf, copy);
|
171
|
|
172
|
if (sdlout_chan == 2)
|
173
|
apply_stereo_volume (buf, copy);
|
174
|
else
|
175
|
apply_mono_volume (buf, copy);
|
176
|
|
177
|
if (copy < len)
|
178
|
memset (buf + copy, 0, len - copy);
|
179
|
|
180
|
|
181
|
|
182
|
|
183
|
block_delay = aud::rescale (copy / (2 * sdlout_chan), sdlout_rate, 1000);
|
184
|
gettimeofday (& block_time, nullptr);
|
185
|
|
186
|
pthread_cond_broadcast (& sdlout_cond);
|
187
|
pthread_mutex_unlock (& sdlout_mutex);
|
188
|
}
|
189
|
|
190
|
bool SDLOutput::open_audio (int format, int rate, int chan, String & error)
|
191
|
{
|
192
|
if (format != FMT_S16_NE)
|
193
|
{
|
194
|
error = String ("SDL error: Only signed 16-bit, native endian audio is supported.");
|
195
|
return false;
|
196
|
}
|
197
|
|
198
|
AUDDBG ("Opening audio for %d channels, %d Hz.\n", chan, rate);
|
199
|
|
200
|
sdlout_chan = chan;
|
201
|
sdlout_rate = rate;
|
202
|
|
203
|
int buffer_ms = aud_get_int (nullptr, "output_buffer_size");
|
204
|
buffer.alloc (2 * chan * aud::rescale (buffer_ms, 1000, rate));
|
205
|
|
206
|
prebuffer_flag = true;
|
207
|
paused_flag = false;
|
208
|
|
209
|
SDL_AudioSpec spec = {0};
|
210
|
SDL_AudioSpec gotspec;
|
211
|
|
212
|
spec.freq = rate;
|
213
|
spec.format = AUDIO_S16;
|
214
|
spec.channels = chan;
|
215
|
spec.samples = 4096;
|
216
|
spec.callback = callback;
|
217
|
|
218
|
if (! (ouraudiodevice = SDL_OpenAudioDevice (NULL, 0, & spec, & gotspec, SDL_AUDIO_ALLOW_FORMAT_CHANGE)))
|
219
|
{
|
220
|
error = String (str_printf
|
221
|
("SDL error: Failed to open audio stream: %s.", SDL_GetError ()));
|
222
|
buffer.destroy ();
|
223
|
return false;
|
224
|
}
|
225
|
else if (spec.format != gotspec.format)
|
226
|
AUDINFO ("We didn't get desired audio format, but playing anyway.");
|
227
|
|
228
|
return true;
|
229
|
}
|
230
|
|
231
|
void SDLOutput::close_audio ()
|
232
|
{
|
233
|
AUDDBG ("Closing audio.\n");
|
234
|
if (ouraudiodevice)
|
235
|
SDL_CloseAudioDevice (ouraudiodevice);
|
236
|
buffer.destroy ();
|
237
|
}
|
238
|
|
239
|
static void check_started ()
|
240
|
{
|
241
|
if (! prebuffer_flag)
|
242
|
return;
|
243
|
|
244
|
AUDDBG ("Starting playback.\n");
|
245
|
prebuffer_flag = false;
|
246
|
block_delay = 0;
|
247
|
if (ouraudiodevice)
|
248
|
SDL_PauseAudioDevice (ouraudiodevice, 0);
|
249
|
}
|
250
|
|
251
|
void SDLOutput::period_wait ()
|
252
|
{
|
253
|
pthread_mutex_lock (& sdlout_mutex);
|
254
|
|
255
|
while (! buffer.space ())
|
256
|
{
|
257
|
if (! paused_flag)
|
258
|
check_started ();
|
259
|
|
260
|
pthread_cond_wait (& sdlout_cond, & sdlout_mutex);
|
261
|
}
|
262
|
|
263
|
pthread_mutex_unlock (& sdlout_mutex);
|
264
|
}
|
265
|
|
266
|
int SDLOutput::write_audio (const void * data, int len)
|
267
|
{
|
268
|
pthread_mutex_lock (& sdlout_mutex);
|
269
|
|
270
|
len = aud::min (len, buffer.space ());
|
271
|
buffer.copy_in ((const unsigned char *) data, len);
|
272
|
|
273
|
pthread_mutex_unlock (& sdlout_mutex);
|
274
|
return len;
|
275
|
}
|
276
|
|
277
|
void SDLOutput::drain ()
|
278
|
{
|
279
|
AUDDBG ("Draining.\n");
|
280
|
pthread_mutex_lock (& sdlout_mutex);
|
281
|
|
282
|
check_started ();
|
283
|
|
284
|
while (buffer.len ())
|
285
|
pthread_cond_wait (& sdlout_cond, & sdlout_mutex);
|
286
|
|
287
|
pthread_mutex_unlock (& sdlout_mutex);
|
288
|
}
|
289
|
|
290
|
int SDLOutput::get_delay ()
|
291
|
{
|
292
|
auto timediff = [] (const timeval & a, const timeval & b) -> int64_t
|
293
|
{ return 1000 * (int64_t) (b.tv_sec - a.tv_sec) + (b.tv_usec - a.tv_usec) / 1000; };
|
294
|
|
295
|
pthread_mutex_lock (& sdlout_mutex);
|
296
|
|
297
|
int delay = aud::rescale (buffer.len (), 2 * sdlout_chan * sdlout_rate, 1000);
|
298
|
|
299
|
|
300
|
if (! prebuffer_flag && ! paused_flag && block_delay)
|
301
|
{
|
302
|
struct timeval cur;
|
303
|
gettimeofday (& cur, nullptr);
|
304
|
|
305
|
delay += aud::max (block_delay - timediff (block_time, cur), (int64_t) 0);
|
306
|
}
|
307
|
|
308
|
pthread_mutex_unlock (& sdlout_mutex);
|
309
|
return delay;
|
310
|
}
|
311
|
|
312
|
void SDLOutput::pause (bool pause)
|
313
|
{
|
314
|
AUDDBG ("%sause.\n", pause ? "P" : "Unp");
|
315
|
pthread_mutex_lock (& sdlout_mutex);
|
316
|
|
317
|
paused_flag = pause;
|
318
|
|
319
|
if (! prebuffer_flag && ouraudiodevice)
|
320
|
SDL_PauseAudioDevice (ouraudiodevice, pause);
|
321
|
|
322
|
pthread_cond_broadcast (& sdlout_cond);
|
323
|
pthread_mutex_unlock (& sdlout_mutex);
|
324
|
}
|
325
|
|
326
|
void SDLOutput::flush ()
|
327
|
{
|
328
|
AUDDBG ("Seek requested; discarding buffer.\n");
|
329
|
pthread_mutex_lock (& sdlout_mutex);
|
330
|
|
331
|
buffer.discard ();
|
332
|
|
333
|
prebuffer_flag = true;
|
334
|
|
335
|
pthread_cond_broadcast (& sdlout_cond);
|
336
|
pthread_mutex_unlock (& sdlout_mutex);
|
337
|
}
|