|
1 |
/*
|
|
2 |
* Copyright (c) 2008, 2009 Thomas Pfaff <tpfaff@tp76.info>
|
|
3 |
* Copyright (c) 2012 Alexandre Ratchov <alex@caoua.org>
|
|
4 |
*
|
|
5 |
* Permission to use, copy, modify, and distribute this software for any
|
|
6 |
* purpose with or without fee is hereby granted, provided that the above
|
|
7 |
* copyright notice and this permission notice appear in all copies.
|
|
8 |
*
|
|
9 |
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10 |
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11 |
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12 |
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13 |
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14 |
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15 |
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
16 |
*/
|
|
17 |
|
|
18 |
#include <errno.h>
|
|
19 |
#include <poll.h>
|
|
20 |
#include <pthread.h>
|
|
21 |
#include <sndio.h>
|
|
22 |
#include <stdio.h>
|
|
23 |
#include <stdlib.h>
|
|
24 |
#include <string.h>
|
|
25 |
#include <gtk/gtk.h>
|
|
26 |
#include <audacious/plugin.h>
|
|
27 |
#include <audacious/misc.h>
|
|
28 |
#include <audacious/i18n.h>
|
|
29 |
#include <audacious/plugin.h>
|
|
30 |
#include <libaudgui/libaudgui.h>
|
|
31 |
#include <libaudgui/libaudgui-gtk.h>
|
|
32 |
|
|
33 |
#include "config.h"
|
|
34 |
|
|
35 |
/*
|
|
36 |
* minimum output buffer size in milliseconds
|
|
37 |
*/
|
|
38 |
#define BUFFER_SIZE_MIN 250
|
|
39 |
|
|
40 |
bool_t sndio_init(void);
|
|
41 |
void sndio_cleanup(void);
|
|
42 |
void sndio_about(void);
|
|
43 |
int sndio_take_message(const char *, const void *, int);
|
|
44 |
void sndio_configure(void);
|
|
45 |
void sndio_get_volume(int *, int *);
|
|
46 |
void sndio_set_volume(int, int);
|
|
47 |
bool_t sndio_open(int, int, int);
|
|
48 |
void sndio_close(void);
|
|
49 |
int sndio_buffer_free(void);
|
|
50 |
void sndio_period_wait(void);
|
|
51 |
void sndio_write(void *, int);
|
|
52 |
void sndio_pause(bool_t);
|
|
53 |
void sndio_flush(int);
|
|
54 |
int sndio_output_time(void);
|
|
55 |
int sndio_written_time(void);
|
|
56 |
void sndio_drain(void);
|
|
57 |
void sndio_set_written_time(int);
|
|
58 |
|
|
59 |
void onmove_cb(void *, int);
|
|
60 |
void onvol_cb(void *, unsigned);
|
|
61 |
|
|
62 |
void configure_win_ok_cb(GtkWidget *, gpointer);
|
|
63 |
|
|
64 |
static struct sio_par par;
|
|
65 |
static struct sio_hdl *hdl;
|
|
66 |
static long long rdpos;
|
|
67 |
static long long wrpos;
|
|
68 |
static int paused, restarted, volume;
|
|
69 |
static int pause_pending, flush_pending, volume_pending;
|
|
70 |
static int bytes_per_sec;
|
|
71 |
static pthread_mutex_t mtx;
|
|
72 |
|
|
73 |
static GtkWidget *configure_win;
|
|
74 |
static GtkWidget *adevice_entry;
|
|
75 |
static gchar *audiodev;
|
|
76 |
|
|
77 |
AUD_OUTPUT_PLUGIN
|
|
78 |
(
|
|
79 |
.name = "sndio",
|
|
80 |
.init = sndio_init,
|
|
81 |
.cleanup = sndio_cleanup,
|
|
82 |
.about = sndio_about,
|
|
83 |
.configure = sndio_configure,
|
|
84 |
.probe_priority = 2,
|
|
85 |
.get_volume = sndio_get_volume,
|
|
86 |
.set_volume = sndio_set_volume,
|
|
87 |
.open_audio = sndio_open,
|
|
88 |
.write_audio = sndio_write,
|
|
89 |
.close_audio = sndio_close,
|
|
90 |
.buffer_free = sndio_buffer_free,
|
|
91 |
.period_wait = sndio_period_wait,
|
|
92 |
.flush = sndio_flush,
|
|
93 |
.pause = sndio_pause,
|
|
94 |
.output_time = sndio_output_time,
|
|
95 |
.written_time = sndio_written_time,
|
|
96 |
.set_written_time = sndio_set_written_time,
|
|
97 |
.drain = sndio_drain
|
|
98 |
)
|
|
99 |
|
|
100 |
static struct fmt_to_par {
|
|
101 |
int fmt, bits, sig, le;
|
|
102 |
} fmt_to_par[] = {
|
|
103 |
{FMT_S8, 8, 1, 0}, {FMT_U8, 8, 1, 0},
|
|
104 |
{FMT_S16_LE, 16, 1, 1}, {FMT_S16_BE, 16, 1, 0},
|
|
105 |
{FMT_U16_LE, 16, 0, 1}, {FMT_U16_BE, 16, 0, 0},
|
|
106 |
{FMT_S24_LE, 24, 1, 1}, {FMT_S24_BE, 24, 1, 0},
|
|
107 |
{FMT_U24_LE, 24, 0, 1}, {FMT_U24_BE, 24, 0, 0},
|
|
108 |
{FMT_S32_LE, 32, 1, 1}, {FMT_S32_BE, 32, 1, 0},
|
|
109 |
{FMT_U32_LE, 32, 0, 1}, {FMT_U32_BE, 32, 0, 0}
|
|
110 |
};
|
|
111 |
|
|
112 |
static const gchar * const sndio_defaults[] = {
|
|
113 |
"volume", "100",
|
|
114 |
"audiodev", "",
|
|
115 |
NULL,
|
|
116 |
};
|
|
117 |
|
|
118 |
static void
|
|
119 |
reset(void)
|
|
120 |
{
|
|
121 |
if (!restarted) {
|
|
122 |
restarted = 1;
|
|
123 |
sio_stop(hdl);
|
|
124 |
sio_start(hdl);
|
|
125 |
rdpos = wrpos;
|
|
126 |
}
|
|
127 |
}
|
|
128 |
|
|
129 |
static void
|
|
130 |
wait_ready(void)
|
|
131 |
{
|
|
132 |
int n;
|
|
133 |
struct pollfd pfds[16];
|
|
134 |
|
|
135 |
if (volume_pending) {
|
|
136 |
sio_setvol(hdl, volume * SIO_MAXVOL / 100);
|
|
137 |
volume_pending = 0;
|
|
138 |
}
|
|
139 |
if (flush_pending) {
|
|
140 |
reset();
|
|
141 |
flush_pending = 0;
|
|
142 |
}
|
|
143 |
if (pause_pending) {
|
|
144 |
if (paused)
|
|
145 |
reset();
|
|
146 |
pause_pending = 0;
|
|
147 |
}
|
|
148 |
if (paused) {
|
|
149 |
pthread_mutex_unlock(&mtx);
|
|
150 |
usleep(20000);
|
|
151 |
pthread_mutex_lock(&mtx);
|
|
152 |
return;
|
|
153 |
}
|
|
154 |
n = sio_pollfd(hdl, pfds, POLLOUT);
|
|
155 |
if (n != 0) {
|
|
156 |
pthread_mutex_unlock(&mtx);
|
|
157 |
while (poll(pfds, n, -1) < 0) {
|
|
158 |
if (errno != EINTR) {
|
|
159 |
perror("poll");
|
|
160 |
exit(1);
|
|
161 |
}
|
|
162 |
}
|
|
163 |
pthread_mutex_lock(&mtx);
|
|
164 |
}
|
|
165 |
(void)sio_revents(hdl, pfds);
|
|
166 |
}
|
|
167 |
|
|
168 |
bool_t
|
|
169 |
sndio_init(void)
|
|
170 |
{
|
|
171 |
pthread_mutex_init(&mtx, NULL);
|
|
172 |
|
|
173 |
aud_config_set_defaults("sndio", sndio_defaults);
|
|
174 |
volume = aud_get_int("sndio", "volume");
|
|
175 |
audiodev = aud_get_string("sndio", "audiodev");
|
|
176 |
|
|
177 |
return (1);
|
|
178 |
}
|
|
179 |
|
|
180 |
void
|
|
181 |
sndio_cleanup(void)
|
|
182 |
{
|
|
183 |
aud_set_int("sndio", "volume", volume);
|
|
184 |
aud_set_string("sndio", "audiodev", audiodev);
|
|
185 |
pthread_mutex_destroy(&mtx);
|
|
186 |
}
|
|
187 |
|
|
188 |
void
|
|
189 |
sndio_about(void)
|
|
190 |
{
|
|
191 |
static GtkWidget *about = NULL;
|
|
192 |
|
|
193 |
audgui_simple_message(&about, GTK_MESSAGE_INFO,
|
|
194 |
_("About Sndio Output Plugin"),
|
|
195 |
_("Sndio Output Plugin\n\n"
|
|
196 |
"Written by Thomas Pfaff <tpfaff@tp76.info>\n"));
|
|
197 |
}
|
|
198 |
|
|
199 |
void
|
|
200 |
sndio_get_volume(int *l, int *r)
|
|
201 |
{
|
|
202 |
pthread_mutex_lock(&mtx);
|
|
203 |
*l = *r = volume;
|
|
204 |
pthread_mutex_unlock(&mtx);
|
|
205 |
}
|
|
206 |
|
|
207 |
void
|
|
208 |
sndio_set_volume(int l, int r)
|
|
209 |
{
|
|
210 |
/* Ignore balance control, so use unattenuated channel. */
|
|
211 |
pthread_mutex_lock(&mtx);
|
|
212 |
volume = l > r ? l : r;
|
|
213 |
volume_pending = 1;
|
|
214 |
pthread_mutex_unlock(&mtx);
|
|
215 |
}
|
|
216 |
|
|
217 |
bool_t
|
|
218 |
sndio_open(int fmt, int rate, int nch)
|
|
219 |
{
|
|
220 |
int i;
|
|
221 |
struct sio_par askpar;
|
|
222 |
GtkWidget *dialog = NULL;
|
|
223 |
unsigned buffer_size;
|
|
224 |
|
|
225 |
hdl = sio_open(strlen(audiodev) > 0 ? audiodev : NULL, SIO_PLAY, 1);
|
|
226 |
if (!hdl) {
|
|
227 |
g_warning("failed to open audio device %s", audiodev);
|
|
228 |
return (0);
|
|
229 |
}
|
|
230 |
sio_initpar(&askpar);
|
|
231 |
for (i = 0; ; i++) {
|
|
232 |
if (i == sizeof(fmt_to_par) / sizeof(struct fmt_to_par)) {
|
|
233 |
g_warning("unknown format %d requested", fmt);
|
|
234 |
sndio_close();
|
|
235 |
return 0;
|
|
236 |
}
|
|
237 |
if (fmt_to_par[i].fmt == fmt)
|
|
238 |
break;
|
|
239 |
}
|
|
240 |
askpar.bits = fmt_to_par[i].bits;
|
|
241 |
askpar.bps = SIO_BPS(askpar.bits);
|
|
242 |
askpar.sig = fmt_to_par[i].sig;
|
|
243 |
if (askpar.bits > 8)
|
|
244 |
askpar.le = fmt_to_par[i].le;
|
|
245 |
askpar.pchan = nch;
|
|
246 |
askpar.rate = rate;
|
|
247 |
buffer_size = aud_get_int(NULL, "output_buffer_size");
|
|
248 |
if (buffer_size < BUFFER_SIZE_MIN)
|
|
249 |
buffer_size = BUFFER_SIZE_MIN;
|
|
250 |
askpar.appbufsz = buffer_size * rate / 1000;
|
|
251 |
if (!sio_setpar(hdl, &askpar) || !sio_getpar(hdl, &par)) {
|
|
252 |
g_warning("failed to set parameters");
|
|
253 |
sndio_close();
|
|
254 |
return (0);
|
|
255 |
}
|
|
256 |
if ((par.bps > 1 && par.le != askpar.le) ||
|
|
257 |
(par.bits < par.bps * 8 && !par.msb) ||
|
|
258 |
par.bps != askpar.bps ||
|
|
259 |
par.sig != askpar.sig ||
|
|
260 |
par.pchan != askpar.pchan ||
|
|
261 |
par.rate != askpar.rate) {
|
|
262 |
g_warning("parameters not supported by the audio device");
|
|
263 |
audgui_simple_message(&dialog, GTK_MESSAGE_INFO,
|
|
264 |
_("Unsupported format"),
|
|
265 |
_("A format not supported by the audio device "
|
|
266 |
"was requested.\n\n"
|
|
267 |
"Please try again with the sndiod(1) server running."));
|
|
268 |
sndio_close();
|
|
269 |
return (0);
|
|
270 |
}
|
|
271 |
rdpos = 0;
|
|
272 |
wrpos = 0;
|
|
273 |
sio_onmove(hdl, onmove_cb, NULL);
|
|
274 |
sio_onvol(hdl, onvol_cb, NULL);
|
|
275 |
sio_setvol(hdl, volume * SIO_MAXVOL / 100);
|
|
276 |
if (!sio_start(hdl)) {
|
|
277 |
g_warning("failed to start audio device");
|
|
278 |
sndio_close();
|
|
279 |
return (0);
|
|
280 |
}
|
|
281 |
pause_pending = flush_pending = volume_pending = 0;
|
|
282 |
bytes_per_sec = par.bps * par.pchan * par.rate;
|
|
283 |
restarted = 1;
|
|
284 |
paused = 0;
|
|
285 |
return (1);
|
|
286 |
}
|
|
287 |
|
|
288 |
void
|
|
289 |
sndio_write(void *ptr, int length)
|
|
290 |
{
|
|
291 |
unsigned n;
|
|
292 |
|
|
293 |
pthread_mutex_lock(&mtx);
|
|
294 |
for (;;) {
|
|
295 |
if (paused)
|
|
296 |
break;
|
|
297 |
restarted = 0;
|
|
298 |
n = sio_write(hdl, ptr, length);
|
|
299 |
if (n == 0 && sio_eof(hdl))
|
|
300 |
return;
|
|
301 |
wrpos += n;
|
|
302 |
length -= n;
|
|
303 |
ptr = (char *)ptr + n;
|
|
304 |
if (length == 0)
|
|
305 |
break;
|
|
306 |
wait_ready();
|
|
307 |
}
|
|
308 |
pthread_mutex_unlock(&mtx);
|
|
309 |
}
|
|
310 |
|
|
311 |
void
|
|
312 |
sndio_close(void)
|
|
313 |
{
|
|
314 |
if (!hdl)
|
|
315 |
return;
|
|
316 |
sio_close(hdl);
|
|
317 |
hdl = NULL;
|
|
318 |
}
|
|
319 |
|
|
320 |
int
|
|
321 |
sndio_buffer_free(void)
|
|
322 |
{
|
|
323 |
return paused ? 0 : par.round * par.pchan * par.bps;
|
|
324 |
}
|
|
325 |
|
|
326 |
void
|
|
327 |
sndio_period_wait(void)
|
|
328 |
{
|
|
329 |
pthread_mutex_lock(&mtx);
|
|
330 |
wait_ready();
|
|
331 |
pthread_mutex_unlock(&mtx);
|
|
332 |
}
|
|
333 |
|
|
334 |
void
|
|
335 |
sndio_flush(int time)
|
|
336 |
{
|
|
337 |
pthread_mutex_lock(&mtx);
|
|
338 |
rdpos = wrpos = (long long)time * bytes_per_sec / 1000;
|
|
339 |
flush_pending = 1;
|
|
340 |
pthread_mutex_unlock(&mtx);
|
|
341 |
}
|
|
342 |
|
|
343 |
void
|
|
344 |
sndio_pause(bool_t flag)
|
|
345 |
{
|
|
346 |
pthread_mutex_lock(&mtx);
|
|
347 |
paused = flag;
|
|
348 |
pause_pending = 1;
|
|
349 |
pthread_mutex_unlock(&mtx);
|
|
350 |
}
|
|
351 |
|
|
352 |
void
|
|
353 |
sndio_drain(void)
|
|
354 |
{
|
|
355 |
/* sndio always drains */
|
|
356 |
}
|
|
357 |
|
|
358 |
int
|
|
359 |
sndio_output_time(void)
|
|
360 |
{
|
|
361 |
int time;
|
|
362 |
|
|
363 |
pthread_mutex_lock(&mtx);
|
|
364 |
time = rdpos * 1000 / bytes_per_sec;
|
|
365 |
pthread_mutex_unlock(&mtx);
|
|
366 |
return time;
|
|
367 |
}
|
|
368 |
|
|
369 |
int
|
|
370 |
sndio_written_time(void)
|
|
371 |
{
|
|
372 |
int time;
|
|
373 |
|
|
374 |
pthread_mutex_lock(&mtx);
|
|
375 |
time = wrpos * 1000 / bytes_per_sec;
|
|
376 |
pthread_mutex_unlock(&mtx);
|
|
377 |
return time;
|
|
378 |
}
|
|
379 |
|
|
380 |
void
|
|
381 |
sndio_set_written_time(int time)
|
|
382 |
{
|
|
383 |
int used;
|
|
384 |
|
|
385 |
pthread_mutex_lock(&mtx);
|
|
386 |
wrpos = time * bytes_per_sec / 1000;
|
|
387 |
used = wrpos - rdpos;
|
|
388 |
rdpos = time * bytes_per_sec / 1000;
|
|
389 |
wrpos = rdpos + used;
|
|
390 |
pthread_mutex_unlock(&mtx);
|
|
391 |
}
|
|
392 |
|
|
393 |
void
|
|
394 |
onmove_cb(void *addr, int delta)
|
|
395 |
{
|
|
396 |
rdpos += delta * (int)(par.bps * par.pchan);
|
|
397 |
}
|
|
398 |
|
|
399 |
void
|
|
400 |
onvol_cb(void *addr, unsigned ctl)
|
|
401 |
{
|
|
402 |
/* Update volume only if it actually changed */
|
|
403 |
if (ctl != volume * SIO_MAXVOL / 100)
|
|
404 |
volume = ctl * 100 / SIO_MAXVOL;
|
|
405 |
}
|
|
406 |
|
|
407 |
void
|
|
408 |
configure_win_ok_cb(GtkWidget *w, gpointer data)
|
|
409 |
{
|
|
410 |
strlcpy(audiodev, gtk_entry_get_text(GTK_ENTRY(adevice_entry)),
|
|
411 |
PATH_MAX);
|
|
412 |
aud_set_string("sndio", "audiodev", audiodev);
|
|
413 |
gtk_widget_destroy(configure_win);
|
|
414 |
}
|
|
415 |
|
|
416 |
void
|
|
417 |
sndio_configure(void)
|
|
418 |
{
|
|
419 |
GtkWidget *vbox;
|
|
420 |
GtkWidget *adevice_frame, *adevice_text, *adevice_vbox;
|
|
421 |
GtkWidget *bbox, *ok, *cancel;
|
|
422 |
|
|
423 |
if (configure_win) {
|
|
424 |
gtk_window_present(GTK_WINDOW(configure_win));
|
|
425 |
return;
|
|
426 |
}
|
|
427 |
|
|
428 |
configure_win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|
429 |
g_signal_connect(configure_win, "destroy",
|
|
430 |
G_CALLBACK(gtk_widget_destroyed), &configure_win);
|
|
431 |
|
|
432 |
gtk_window_set_title(GTK_WINDOW(configure_win), _("sndio device"));
|
|
433 |
gtk_window_set_resizable(GTK_WINDOW(configure_win), FALSE);
|
|
434 |
gtk_window_set_position(GTK_WINDOW(configure_win), GTK_WIN_POS_MOUSE);
|
|
435 |
gtk_container_set_border_width(GTK_CONTAINER(configure_win), 10);
|
|
436 |
|
|
437 |
vbox = gtk_vbox_new(FALSE, 5);
|
|
438 |
gtk_container_add(GTK_CONTAINER(configure_win), vbox);
|
|
439 |
gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);
|
|
440 |
|
|
441 |
adevice_frame = gtk_frame_new(_("Audio device:"));
|
|
442 |
gtk_box_pack_start(GTK_BOX(vbox), adevice_frame, FALSE, FALSE, 0);
|
|
443 |
|
|
444 |
adevice_vbox = gtk_vbox_new(FALSE, 5);
|
|
445 |
gtk_container_set_border_width(GTK_CONTAINER(adevice_vbox), 5);
|
|
446 |
gtk_container_add(GTK_CONTAINER(adevice_frame), adevice_vbox);
|
|
447 |
|
|
448 |
adevice_text = gtk_label_new(_("(empty means default)"));
|
|
449 |
gtk_box_pack_start(GTK_BOX(adevice_vbox), adevice_text, TRUE, TRUE, 0);
|
|
450 |
|
|
451 |
adevice_entry = gtk_entry_new();
|
|
452 |
gtk_entry_set_text(GTK_ENTRY(adevice_entry), audiodev);
|
|
453 |
gtk_box_pack_start(GTK_BOX(adevice_vbox), adevice_entry, TRUE, TRUE, 0);
|
|
454 |
|
|
455 |
bbox = gtk_hbutton_box_new();
|
|
456 |
gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
|
|
457 |
gtk_box_set_spacing(GTK_BOX(bbox), 5);
|
|
458 |
gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0);
|
|
459 |
|
|
460 |
ok = gtk_button_new_with_label(_("OK"));
|
|
461 |
g_signal_connect(ok, "clicked",
|
|
462 |
G_CALLBACK(configure_win_ok_cb), NULL);
|
|
463 |
|
|
464 |
gtk_widget_set_can_default(ok, TRUE);
|
|
465 |
gtk_box_pack_start(GTK_BOX(bbox), ok, TRUE, TRUE, 0);
|
|
466 |
gtk_widget_grab_default(ok);
|
|
467 |
|
|
468 |
cancel = gtk_button_new_with_label(_("Cancel"));
|
|
469 |
g_signal_connect(cancel, "clicked",
|
|
470 |
G_CALLBACK(gtk_widget_destroy), &configure_win);
|
|
471 |
|
|
472 |
gtk_widget_set_can_default(cancel, TRUE);
|
|
473 |
gtk_box_pack_start(GTK_BOX(bbox), cancel, TRUE, TRUE, 0);
|
|
474 |
|
|
475 |
gtk_widget_show_all(configure_win);
|
|
476 |
}
|
0 |
|
-
|