-
Notifications
You must be signed in to change notification settings - Fork 1
/
jack.c
359 lines (307 loc) · 10.9 KB
/
jack.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
/*
JACK output plugin for DeaDBeeF
Copyright (C) 2010 Steven McDonald <steven.mcdonald@libremail.me>
See COPYING file for modification and redistribution conditions.
*/
#define _GNU_SOURCE
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#define JACK_CLIENT_NAME "deadbeef"
#include <unistd.h>
#include <jack/jack.h>
#include <deadbeef/deadbeef.h>
#include <signal.h>
#include <limits.h>
//#define trace(...) { fprintf(stderr, __VA_ARGS__); }
#define trace(fmt,...)
static DB_output_t plugin;
DB_functions_t *deadbeef;
static jack_client_t *ch; // client handle
static jack_status_t jack_status;
static jack_port_t *jack_ports[2]; // FIXME: this magic number is a hack to
// get it to build. must replace
// this with number of channels!
static unsigned short state;
static short errcode; // used to store error codes
static short jack_connected = 0;
static short DidWeStartJack = 0;
static int rate;
static int
jack_stop (void);
static int
jack_init (void);
static int
jack_proc_callback (jack_nframes_t nframes, void *arg) {
trace ("jack_proc_callback\n");
if (!jack_connected) return -1;
trace ("jack_connected was true\n");
// FIXME: This function copies from the streamer to a local buffer,
// and then to JACK's buffer. This is wasteful.
// Update 2011-01-01:
// The streamer can now use floating point samples, but there
// is still no easy solution to this because the streamer
// outputs both channels multiplexed, whereas JACK expects
// each channel to be written to a separate buffer.
switch (state) {
case OUTPUT_STATE_PLAYING: {
char buf[nframes * plugin.fmt.channels * (plugin.fmt.bps / CHAR_BIT)];
unsigned bytesread = deadbeef->streamer_read (buf, sizeof(buf));
// this avoids a crash if we are playing and change to a plugin
// with no valid output and then switch back
if (bytesread == -1) {
state = OUTPUT_STATE_STOPPED;
return 0;
}
// this is intended to make playback less jittery in case of
// inadequate read from streamer
/* while (bytesread < sizeof(buf)) {
//usleep (100);
unsigned morebytesread = deadbeef->streamer_read (buf+bytesread, sizeof(buf)-bytesread);
if (morebytesread != -1) bytesread += morebytesread;
} */
jack_nframes_t framesread = bytesread / (plugin.fmt.channels * (plugin.fmt.bps / CHAR_BIT));
float *jack_port_buffer[plugin.fmt.channels];
for (unsigned short i = 0; i < plugin.fmt.channels; i++) {
jack_port_buffer[i] = jack_port_get_buffer(jack_ports[i], framesread);//nframes);
}
float vol = deadbeef->volume_get_amp ();
for (unsigned i = 0; i < framesread; i++) {
for (unsigned short j = 0; j < plugin.fmt.channels; j++) {
// JACK expects floating point samples, so we need to convert from integer
*jack_port_buffer[j]++ = ((float*)buf)[(plugin.fmt.channels*i) + j] * vol; // / 32768;
}
}
return 0;
}
// this is necessary to stop JACK going berserk when we pause/stop
default: {
float *jack_port_buffer[plugin.fmt.channels];
for (unsigned short i = 0; i < plugin.fmt.channels; i++) {
jack_port_buffer[i] = jack_port_get_buffer(jack_ports[i], nframes);
}
for (unsigned i = 0; i < nframes; i++) {
for (unsigned short j = 0; j < plugin.fmt.channels; j++) {
*jack_port_buffer[j]++ = 0;
}
}
return 0;
}
}
}
static int
jack_rate_callback (void *arg) {
if (!jack_connected) return -1;
plugin.fmt.samplerate = (int)jack_get_sample_rate(ch);
return 0;
}
static int
jack_shutdown_callback (void *arg) {
if (!jack_connected) return -1;
jack_connected = 0;
// if JACK crashes or is shut down, start a new server instance
if (deadbeef->conf_get_int ("jack.autorestart", 0)) {
fprintf (stderr, "jack: JACK server shut down unexpectedly, restarting...\n");
sleep (1);
jack_init ();
}
else {
//fprintf (stderr, "jack: JACK server shut down unexpectedly, stopping playback\n");
//deadbeef->playback_stop ();
}
return 0;
}
static int
jack_init (void) {
trace ("jack_init\n");
jack_connected = 1;
// create new client on JACK server
if ((ch = jack_client_open (JACK_CLIENT_NAME, JackNullOption | (JackNoStartServer && !deadbeef->conf_get_int ("jack.autostart", 1)), &jack_status)) == 0) {
fprintf (stderr, "jack: could not connect to JACK server\n");
plugin.free();
return -1;
}
rate = (int)jack_get_sample_rate(ch);
// Did we start JACK, or was it already running?
if (jack_status & JackServerStarted)
DidWeStartJack = 1;
else
DidWeStartJack = 0;
// set process callback
if ((errcode = jack_set_process_callback(ch, &jack_proc_callback, NULL)) != 0) {
fprintf (stderr, "jack: could not set process callback, error %d\n", errcode);
plugin.free();
return -1;
}
// set sample rate callback
if ((errcode = jack_set_sample_rate_callback(ch, (JackSampleRateCallback)&jack_rate_callback, NULL)) != 0) {
fprintf (stderr, "jack: could not set sample rate callback, error %d\n", errcode);
plugin.free();
return -1;
}
// set shutdown callback
jack_on_shutdown (ch, (JackShutdownCallback)&jack_shutdown_callback, NULL);
// register ports
for (unsigned short i=0; i < plugin.fmt.channels; i++) {
char port_name[11];
// i+1 used to adhere to JACK convention of counting ports from 1, not 0
sprintf (port_name, "deadbeef_%d", i+1);
if (!(jack_ports[i] = jack_port_register(ch, (const char*)&port_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput|JackPortIsTerminal, 0))) {
fprintf (stderr, "jack: could not register port number %d\n", i+1);
plugin.free();
return -1;
}
}
// tell JACK we are ready to roll
if ((errcode = jack_activate(ch)) != 0) {
fprintf (stderr, "jack: could not activate client, error %d\n", errcode);
plugin.free();
return -1;
}
// connect ports to hardware output
if (deadbeef->conf_get_int ("jack.autoconnect", 1)) {
const char **playback_ports;
if (!(playback_ports = jack_get_ports (ch, 0, 0, JackPortIsPhysical|JackPortIsInput))) {
fprintf (stderr, "jack: warning: could not find any playback ports to connect to\n");
}
else {
for (unsigned short i=0; i < plugin.fmt.channels; i++) {
// error code 17 means port connection exists. We do not want to return an error in this case, simply proceed.
if ((errcode = jack_connect(ch, jack_port_name (jack_ports[i]), playback_ports[i])) && (errcode != 17)) {
fprintf (stderr, "jack: could not create connection from %s to %s, error %d\n", jack_port_name (jack_ports[i]), playback_ports[i], errcode);
plugin.free();
return -1;
}
}
}
}
return 0;
}
static int
jack_setformat (ddb_waveformat_t *rate) {
// FIXME: If (and ONLY IF) we started JACK (i.e. DidWeStartJack == TRUE),
// allow this to work by stopping and restarting JACK.
return 0;
}
static int
jack_play (void) {
trace ("jack_play\n");
if (!jack_connected) {
if (jack_init() != 0) {
trace("jack_init failed\n");
plugin.free();
return -1;
}
}
state = OUTPUT_STATE_PLAYING;
return 0;
}
static int
jack_stop (void) {
trace ("jack_stop\n");
state = OUTPUT_STATE_STOPPED;
deadbeef->streamer_reset (1);
return 0;
}
static int
jack_pause (void) {
trace ("jack_pause\n");
if (state == OUTPUT_STATE_STOPPED) {
return -1;
}
// set pause state
state = OUTPUT_STATE_PAUSED;
return 0;
}
static int
jack_plugin_start (void) {
trace ("jack_plugin_start\n");
sigset_t set;
sigemptyset (&set);
sigaddset (&set, SIGPIPE);
sigprocmask (SIG_BLOCK, &set, 0);
return 0;
}
static int
jack_plugin_stop (void) {
trace ("jack_plugin_stop\n");
return 0;
}
static int
jack_unpause (void) {
trace ("jack_unpause\n");
jack_play ();
return 0;
}
static int
jack_get_state (void) {
trace ("jack_get_state\n");
return state;
}
static int
jack_free_deadbeef (void) {
trace ("jack_free_deadbeef\n");
jack_connected = 0;
// stop playback if we didn't start jack
// this prevents problems with not disconnecting gracefully
if (!DidWeStartJack) {
jack_stop ();
sleep (1);
}
if (ch) {
if (jack_client_close (ch)) {
fprintf (stderr, "jack: could not disconnect from JACK server\n");
return -1;
}
ch = NULL;
}
// sleeping here is necessary to give JACK time to disconnect from the backend
// if we are switching to another backend, it will fail without this
if (DidWeStartJack)
sleep (1);
return 0;
}
DB_plugin_t *
jack_load (DB_functions_t *api) {
deadbeef = api;
return DB_PLUGIN (&plugin);
}
static const char settings_dlg[] =
"property \"Start JACK server automatically, if not already running\" checkbox jack.autostart 1;\n"
"property \"Automatically connect to system playback ports\" checkbox jack.autoconnect 1;\n"
"property \"Automatically restart JACK server if shut down\" checkbox jack.autorestart 0;\n"
;
// define plugin interface
static DB_output_t plugin = {
DB_PLUGIN_SET_API_VERSION
.plugin.version_major = 0,
.plugin.version_minor = 2,
//.plugin.nostop = 0,
.plugin.type = DB_PLUGIN_OUTPUT,
.plugin.id = "jack",
.plugin.name = "JACK output plugin",
.plugin.descr = "plays sound via JACK API",
.plugin.copyright = "Copyright (C) 2010-2011 Steven McDonald <steven.mcdonald@ libremail.me>",
// .plugin.author = "Steven McDonald",
// .plugin.email = "steven.mcdonald@libremail.me",
.plugin.website = "http://gitorious.org/deadbeef-sm-plugins/pages/Home",
.plugin.start = jack_plugin_start,
.plugin.stop = jack_plugin_stop,
.plugin.configdialog = settings_dlg,
.init = jack_init,
.free = jack_free_deadbeef,
.setformat = jack_setformat,
.play = jack_play,
.stop = jack_stop,
.pause = jack_pause,
.unpause = jack_unpause,
.state = jack_get_state,
.fmt = {
.bps = 32,
.is_float = 1,
.channels = 2,
.channelmask = DDB_SPEAKER_FRONT_LEFT | DDB_SPEAKER_FRONT_RIGHT,
.is_bigendian = 0,
},
.has_volume = 1,
};