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