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