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