©  Jan Newmarch 2017

Jan Newmarch, Linux Sound Programming, 10.1007/978-1-4842-2496-0_7

7. Jack

Jan Newmarch

(1)Oakleigh, Victoria, Australia

The role of a sound server in Linux is to take inputs from a number of sources and route them to a number of sinks. Several audio servers are available in Linux, with the primary ones being PulseAudio and Jack. They are designed for different roles: PulseAudio is intended for consumer audio systems, while Jack is designed for professional audio. Lennart Poettering at http://0pointer.de/blog/projects/when-pa-and-when-not.html draws up a table of differences. The main one is that Jack is intended for environments in which low latency is critical, with Jack introducing less than 5ms latency into an audio chain, while PulseAudio can introduce up to 2-second delays. Other differences are that PulseAudio can run on low-quality systems including mobile phones, while Jack is usually run on high-quality audio equipment. The article “Knowing Jack” gives a gentle introduction to Jack. This chapter looks at tools built specifically for Jack, how applications use Jack, and finally programming with Jack.

Starting Jack

Jack is available in the repositories of most distros. You want to install Jack2 rather than Jack1. For programming, you will also need the libjack2 dev package, which may get installed along with the Jack2 package.

The Jack server is jackd. It has one required parameter, which is a sound back end such as ALSA. The minimal command is as follows:

jackd -dalsa

Following the option -dalsa ALSA options can appear. On one of my computers aplay -l shows card 0 has devices 3, 7, and 8, and I needed to specify one of these:

jackd -dalsa -d hw:0,3

If you are using a normal Linux distro such as Fedora or Ubuntu, this will quite likely fail if the PulseAudio system is running. This may need to be stopped, or at least paused, while you run Jack. See the previous chapter for stopping PulseAudio. To pause it, I usually run this in a terminal window:

pasuspender cat

This will pause PulseAudio until cat terminates, which it will do when you enter Ctrl-D.

jackd will try to start using the Linux real-time scheduler. If you want to run without it, use the following option:

jackd --no-realtime -dalsa

If you want to run with the real-time scheduler, there are several ways.

  • Run the server from the root user.

sudo jackd -dalsa
  • Add a user to the audio and jackuser groups, as follows:

useradd -G audio newmarch
useradd -G jackuser newmarch

(You will need to log out and back in before this takes effect.)

Note that if you run the server as the root user, then you will not be able to connect to it from clients that are not in the jackuser group.

No apparent systemd or upstart scripts exist for Jack, but there are instructions for starting Jack at boot time at http://gentoo-en.vfose.ru/wiki/JACK#Starting_JACK_at_boot_time . The following instructions are excerpted from that, which is under a GPL license (last modified in 2012):

#!/sbin/runscript
 # This programm will be used by init in order to launch jackd with the privileges
 # and id of the user defined into /etc/conf.d/jackd


 depend() {
        need alsasound
 }


 start() {
        if ! test -f "${JACKDHOME}/.jackdrc"; then
                eerror "You must start and configure jackd before launch it. Sorry."
                eerror "You can use qjackctl for that."
                return 1
        else JACKDOPTS=$(cat "${JACKDHOME}/.jackdrc"|sed -e 's/usr/bin/jackd ')
        fi


        if [ -e /var/run/jackd.pid ]; then
                 rm /var/run/jackd.pid
        fi


        ebegin "Starting JACK Daemon"
        env HOME="${JACKDHOME}" start-stop-daemon --start
                --quiet --background
                --make-pidfile --pidfile /var/run/jackd.pid
                -c ${JACKDUSER}
                -x /usr/bin/jackd -- ${JACKDOPTS} >${LOG}


        sleep 2
        if ! pgrep -u ${JACKDUSER} jackd > /dev/null; then
                eerror "JACK daemon can't be started! Check logfile: ${LOG}"
        fi
        eend $?
 }


 stop() {
        ebegin "Stopping JACK daemon -- please wait"
        start-stop-daemon --stop --pidfile /var/run/jackd.pid &>/dev/null
        eend $?
 }


 restart() {
        svc_stop
        while `pgrep -u ${JACKDUSER} jackd >/dev/null`; do
                sleep 1
        done
        svc_start
 }

File: /etc/conf.d/jackd:

 # owner of jackd process (Must be an existing user.)
 JACKDUSER="dom"


 # .jackdrc location for that user (Must be existing, JACKDUSER can use
 # qjackctl in order to create it.)
 JACKDHOME="/home/${JACKDUSER}"


 # logfile (/dev/null for nowhere)
 LOG=/var/log/jackd.log

Create and save those 2 files. Don’t forget to adjust JACKDUSER to the wanted user name (the same as yours I guess; [Author: Yes, that is what the Gentoo instructions say!]). We need to make /etc/init.d/jackd executable:

# chmod +x /etc/init.d/jackd

Adding the script into the default run-level:

# rc-update add jackd default

Before restarting your system or starting this script, you must be sure that jackd is configured for $JACKUSER or jackd will fail. This is because the script will read /home/${USER}/.jackdrc. If this file doesn’t exist, the easiest way to create it is to run QJackCtl as explained above.

Note on Realtime: Due to a limitation in the implementation of start-stop-daemon, it is not possible to start jackd in realtime mode as a non-root user by this method if using pam_limits. start-stop-daemon does not implement support for pam_sessions, meaning that changes to limits.conf have no effect in this context.

User Tools

There is really only one tool that you need to use with Jack: qjackctl. This gives a graphical view of which Jack applications are playing and allows you to link inputs and outputs.

A simple tutorial on using qjackctl is HowToQjackCtlConnections ( https://help.ubuntu.com/community/HowToQjackCtlConnections ). It is actually amazingly simple to use: click a source and link it to a destination by clicking the destination. A line will be shown linking them. That’s all you have to do. Many Jack applications will do this for you, so you just observe the results. Illustrations of this are give n later in the chapter.

Applications Using Jack

There are many pieces of software using Jack, described in “Applications using JACK” ( http://jackaudio.org/applications ).

mplayer

To run mplayer using Jack, add the option -ao jack.

mplayer -ao jack 54154.mp3

mplayerused in this way will connect to the Jack system output device. To output to another Jack application such as jack-rack, append the output application to the audio output command.

mplayer -ao jack:port=jack_rack 54154.mp3

VLC

VLC w ill play to Jack output if the Jack module ( https://wiki.videolan.org/Documentation:Modules/jack/ ) is included. This is available as a downloadable Debian package called vlc-plugin-jack. You can check whether you have it by seeing if jack is listed as a module in vlc --list shows ALSA but not Jack.

Play a file using Jack by doing the following:

vlc --aout jack 54154.mp3

You should be able to connect to a particular Jack application using the option --jack-connect-regex <string>.

TiMidity

TiMidity is a MIDI player discussed in Chapter 21. It can play to Jack output devices with this:

timidity -Oj 54154.mid

Jack-Supplied Programs

Jack comes with a large number of clients.

jack_alias                  jack_midisine
jack_bufsize                jack_monitor_client
jack_connect                jack_multiple_metro
jack_control                jack_net_master
jack_cpu                    jack_net_slave
jack_cpu_load               jack_netsource
jackd                       jack_rec
jackdbus                    jack_samplerate
jack_disconnect             jack_server_control
jack_evmon                  jack_session_notify
jack_freewheel              jack_showtime
jack_iodelay                jack_simple_client
jack_latent_client          jack_simple_session_client
jack_load                   jack_test
jack_lsp                    jack_thru
jack_metro                  jack_transport
jack_midi_dump              jack_unload
jack_midi_latency_test      jack_wait
jack_midiseq                jack_zombie

For many of these, the source code is available in the Jack source code distribution, and there is a man page for each one.

Running, say, jack_thru connects the system capture ports to the jack_thru input ports and the jack_thru output ports to the system playback ports. You can then do things such as disconnect ports using client:port for the port name as follows:

jack_disconnect jack_thru:output_1 system:playback_1

These command-line tools allow you to do the same kind of actions as qjackctl.

Other Jack Programs

The page Applications using JACK ( http://jackaudio.org/applications ) lists many applications using Jack.

The page Jack MIDI Apps ( http://apps.linuxaudio.org/apps/categories/jack_midi ) at linuxaudio.org lists many MIDI applications using Jack.

Using a Different Sound Card

The default ALSA device for Jack will be hw:0. If you want to use a different sound card, then you can specify this when starting Jack, as follows:

jackd -dalsa -dhw:0

I have a USB Sound Blaster card , which requires some extra parameters.

jackd -dalsa -dhw:2 -r 48000 -S

This doesn’t work great; I get a regular “ticking” sound.

Without the -S (16-bit) flag, I just get this cryptic line:

ALSA: cannot set hardware parameters for playback

Alternatively, I can run this:

jackd -dalsa -dplughw:2 -r 48000

When I start it this way, Jack advises against using ALSA plug devices, but it works best so far.

How Can I Use Multiple Sound Cards with Jack?

Jack is intended for professional audio use. In such a system there will generally be only a single digital sample “clock.” In this “ideal” Jack world, there would not be multiple independent sound cards each with their own clock. I’m just going to talk about this ideal world. If you need to run Jack in a situation where there is more than one sound card, then see “How can I use multiple soundcards with JACK?” ( http://jackaudio.org/multiple_devices ).

Mixing Audio

If two output ports from two different sources are connected to the same input port, then Jack will mix them for you. This allows you to sing along to your favorite MP3 file with no effort.

  1. Connect the microphone capture ports to the playback ports. Avoid setting up a feedback loop between your laptop’s microphone and speakers by, for example, plugging in headphones.

  2. Start a player such as mplayer,which will also connect to the playback ports with something like the following:

    mplayer -ao jack <MP3 file >
  3. Start singing.

Of course, there is no volume control on each source. You can insert a mixer such as jack_mixer ( http://home.gna.org/jackmixer/ ), maybe in your distro too, and then use that to control the volume of each source, as shown in the qjackctl screen in Figure 7-1.

A435426_1_En_7_Fig1_HTML.jpg
Figure 7-1. qjackctl showing a mixer of mplayer and system

Writing Audio Applications with Jack

The design of Jack is discussed at the JACK Audio Connection Kit ( http://lac.linuxaudio.org/2003/zkm/slides/paul_davis-jack/title.html ) by its primary author Paul Davis. The goals are as follows:

  • Jack should allow streaming of low-latency, high-bandwidth data between independent applications.

  • Although not a requirement, Jack should support any streaming data type, not just audio.

  • In an active Jack setup, there will be one server and one or more Jack plug-ins. It will be possible to run multiple Jack servers, but each server will form an independent Jack setup. Jack will not define any interfaces between Jack servers.

  • Applications connected using Jack may have their own graphical interfaces. Jack will not make any specifications as to different GUI toolkits or libraries. As a consequence of this requirement, different parts of a running Jack setup may be spread across multiple processes.

  • Jack should provide full, sample-accurate synchronization (in other words, totally synchronous execution of all client plug-ins).

  • To represent audio data, Jack should use 32-bit IEEE floats, normalized to value range [-1,1].

  • Only noninterleaved audio streams will be supported.

  • One Jack client may consume or produce multiple data streams.

  • The Jack API should be specified in ANSI C. There are no restrictions on how servers and clients are to be implemented.

  • It should be possible to connect already running applications.

  • It should be possible to add or remove Jack clients while the server is running.

To pick the eyes out of this, the principal goals are as follows:

  • Jack should allow streaming of low-latency, high-bandwidth data between independent applications.

  • Jack should provide full, sample-accurate synchronization (in other words, totally synchronous execution of all client plug-ins).

The second is guaranteed by the Jack framework. The first is supplied by the Jack framework, as long as the applications are coded correctly.

Under the hood Jack uses fast Linux (Unix) pipelines to stream data from one application to another. Within each Jack application is a real-time loop that takes data off the input pipe and sends data to the output pipe. To avoid latency delays, there should essentially be no (or as little as possible) processing between reading and writing data; the ideal would be to pass pointer data from input to output, or at most to just do a memcpy.

So, how can processing be done? Copy the data read to another data structure and pass processing off to another thread, or copy data processed in another thread to the output pipe. Anything else will cause latency, which may become noticeable. In particular, certain system calls are essentially banned: malloc can cause swapping; sleep is an obvious no-no; read/write, and so on, can cause disk I/O; and pthread_cond_wait will…wait.

Jack applications are inherently multithreaded. In a Linux world this means Posix threads, and fortunately there is the book PThreads Primer ( http://www8.cs.umu.se/kurser/TDBC64/VT03/pthreads/pthread-primer.pdf ) by Bil Lewis and Daniel J. Berg to tell you all about Posix threads!

These are the mechanisms to set up a Jack application:

  1. Open a connection to a Jack server: jack_client_open.

  2. Examine the status of the connection and bailout if needed.

  3. Install a process callback handler to manage I/O: jack_set_process_callback.

  4. Install a shutdown callback: jack_on_shutdown .

  5. Register input and output ports with the Jack server: jack_port_register. Note that each port carries only a mono channel, so for stereo you will get two input ports. This does not as yet link them to the pipelines.

  6. Activate the ports. In other words, tell Jack to start its processing thread: jack_activate.

  7. Connect the ports to the pipelines: jack_connect.

  8. Sit there in some way. For a text client, just sleep in a loop. A GUI client might have a GUI processing loop.

Compiling

The following examples need to be linked to various libraries. These are the jack, sndfile, pthread, and math libraries. The appropriate flags are as follows:

INCLUDES = $(shell pkg-config --cflags jack sndfile)
LDLIBS =  $(shell pkg-config --libs jack sndfile) -lpthread -lm

Port Information

Jack uses ports that carry mono 32-bit data. Each port has a name as a string and properties such as input and output. Once a connection to a Jack server has been made, queries for ports known to the server can be made using jack_get_ports. If the arguments are NULL or zero, then all ports are returned, or patterns can be used to restrict the port names returned. Once a port name is found, it can be turned into a jack_port_t, and its properties can be queried.

A program to do this is listports.c, shown here:

/** @file listports.c
 *
 * @brief This client delays one channel by 4096 framse.
 */


#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <signal.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include <jack/jack.h>


jack_client_t *client;

void print_port_info(char *name) {
    printf("Port name is %s ", name);
    jack_port_t *port = jack_port_by_name (client, name);
    if (port == NULL) {
        printf("No port by name %s ", name);
        return;
    }
    printf("  Type is %s ", jack_port_type(port));


    int flags = jack_port_flags(port);
    if (flags & JackPortIsInput)
        printf("  Is an input port ");
    else
        printf("  Is an output port ");
    char **connections = jack_port_get_connections(port);
    char **c = connections;
    printf("  Connected to: ");
    while ((c != NULL) && (*c != NULL)) {
        printf("    %s ", *c++);
    }
    if (connections != NULL)
        jack_free(connections);
}


int
main ( int argc, char *argv[] )
{
    int i;
    const char **ports;
    const char *client_name;
    const char *server_name = NULL;
    jack_options_t options = JackNullOption;
    jack_status_t status;


    if ( argc >= 2 )        /* client name specified? */
    {
        client_name = argv[1];
        if ( argc >= 3 )    /* server name specified? */
        {
            server_name = argv[2];
            options |= JackServerName;
        }
    }
    else              /* use basename of argv[0] */
    {
        client_name = strrchr ( argv[0], '/' );
        if ( client_name == 0 )
        {
            client_name = argv[0];
        }
        else
        {
            client_name++;
        }
    }


    /* open a client connection to the JACK server */

    client = jack_client_open ( client_name, options, &status, server_name );
    if ( client == NULL )
    {
        fprintf ( stderr, "jack_client_open() failed, "
                  "status = 0x%2.0x ", status );
        if ( status & JackServerFailed )
        {
            fprintf ( stderr, "Unable to connect to JACK server " );
        }
        exit ( 1 );
    }
    if ( status & JackServerStarted )
    {
        fprintf ( stderr, "JACK server started " );
    }
    if ( status & JackNameNotUnique )
    {
        client_name = jack_get_client_name ( client );
        fprintf ( stderr, "unique name `%s' assigned ", client_name );
    }


    if ( jack_activate ( client ) )
    {
        fprintf ( stderr, "cannot activate client" );
        exit ( 1 );
    }


     ports = jack_get_ports ( client, NULL, NULL, 0 );
    if ( ports == NULL )
    {
        fprintf ( stderr, "no ports " );
        exit ( 1 );
    }
    char **p = ports;
    while (*p != NULL)
        print_port_info(*p++);
    jack_free(ports);


    jack_client_close ( client );
    exit ( 0 );
}

Copy Input to Output

The Jack source code distribution has an example clients subdirectory. Included in this subdirectory is the client thru_client.c, which just copies input to output. The processing heart of this example is the function process. This function takes a number of frames available on both input and output as parameters and the function loops through the (stereo) channels, gets corresponding input and output buffers (for input and output pipelines), and copies data from input to corresponding output.

The code is as follows:

/** @file thru_client.c
 *
 * @brief This simple through client demonstrates the basic features of JACK
 * as they would be used by many applications.
 */


#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <signal.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include <jack/jack.h>


jack_port_t **input_ports;
jack_port_t **output_ports;
jack_client_t *client;


static void signal_handler ( int sig )
{
    jack_client_close ( client );
    fprintf ( stderr, "signal received, exiting ... " );
    exit ( 0 );
}


/**
 * The process callback for this JACK application is called in a
 * special realtime thread once for each audio cycle.
 *
 * This client follows a simple rule: when the JACK transport is
 * running, copy the input port to the output.  When it stops, exit.
 */


int
process ( jack_nframes_t nframes, void *arg )
{
    int i;
    jack_default_audio_sample_t *in, *out;
    for ( i = 0; i < 2; i++ )
    {
        in = jack_port_get_buffer ( input_ports[i], nframes );
        out = jack_port_get_buffer ( output_ports[i], nframes );
        memcpy ( out, in, nframes * sizeof ( jack_default_audio_sample_t ) );
    }
    return 0;
}


/**
 * JACK calls this shutdown_callback if the server ever shuts down or
 * decides to disconnect the client.
 */
void
jack_shutdown ( void *arg )
{
    free ( input_ports );
    free ( output_ports );
    exit ( 1 );
}


int
main ( int argc, char *argv[] )
{
    int i;
    const char **ports;
    const char *client_name;
    const char *server_name = NULL;
    jack_options_t options = JackNullOption;
    jack_status_t status;


    if ( argc >= 2 )        /* client name specified? */
    {
        client_name = argv[1];
        if ( argc >= 3 )    /* server name specified? */
        {
            server_name = argv[2];
            options |= JackServerName;
        }
    }
    else              /* use basename of argv[0] */
    {
        client_name = strrchr ( argv[0], '/' );
        if ( client_name == 0 )
        {
            client_name = argv[0];
        }
        else
        {
            client_name++;
        }
    }


    /* open a client connection to the JACK server */

    client = jack_client_open ( client_name, options, &status, server_name );
    if ( client == NULL )
    {
        fprintf ( stderr, "jack_client_open() failed, "
                  "status = 0x%2.0x ", status );
        if ( status & JackServerFailed )
        {
            fprintf ( stderr, "Unable to connect to JACK server " );
        }
        exit ( 1 );
    }
    if ( status & JackServerStarted )
    {
        fprintf ( stderr, "JACK server started " );
    }
    if ( status & JackNameNotUnique )
    {
        client_name = jack_get_client_name ( client );
        fprintf ( stderr, "unique name `%s' assigned ", client_name );
    }


    /* tell the JACK server to call `process()' whenever
       there is work to be done.
    */


    jack_set_process_callback ( client, process, 0 );

    /* tell the JACK server to call `jack_shutdown()' if
       it ever shuts down, either entirely, or if it
       just decides to stop calling us.
    */


    jack_on_shutdown ( client, jack_shutdown, 0 );

    /* create two ports pairs*/
    input_ports = ( jack_port_t** ) calloc ( 2, sizeof ( jack_port_t* ) );
    output_ports = ( jack_port_t** ) calloc ( 2, sizeof ( jack_port_t* ) );


    char port_name[16];
    for ( i = 0; i < 2; i++ )
    {
        sprintf ( port_name, "input_%d", i + 1 );
        input_ports[i] = jack_port_register ( client, port_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 );
        sprintf ( port_name, "output_%d", i + 1 );
        output_ports[i] = jack_port_register ( client, port_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 );
        if ( ( input_ports[i] == NULL ) || ( output_ports[i] == NULL ) )
        {
            fprintf ( stderr, "no more JACK ports available " );
            exit ( 1 );
        }
    }


    /* Tell the JACK server that we are ready to roll.  Our
     * process() callback will start running now. */


    if ( jack_activate ( client ) )
    {
        fprintf ( stderr, "cannot activate client" );
        exit ( 1 );
    }


    /* Connect the ports.  You can't do this before the client is
     * activated, because we can't make connections to clients
     * that aren't running.  Note the confusing (but necessary)
     * orientation of the driver backend ports: playback ports are
     * "input" to the backend, and capture ports are "output" from
     * it.
     */


    ports = jack_get_ports ( client, NULL, NULL, JackPortIsPhysical|JackPortIsOutput );
    if ( ports == NULL )
    {
        fprintf ( stderr, "no physical capture ports " );
        exit ( 1 );
    }


    for ( i = 0; i < 2; i++ )
        if ( jack_connect ( client, ports[i], jack_port_name ( input_ports[i] ) ) )
            fprintf ( stderr, "cannot connect input ports " );


    free ( ports );

    ports = jack_get_ports ( client, NULL, NULL, JackPortIsPhysical|JackPortIsInput );
    if ( ports == NULL )
    {
        fprintf ( stderr, "no physical playback ports " );
        exit ( 1 );
    }


    for ( i = 0; i < 2; i++ )
        if ( jack_connect ( client, jack_port_name ( output_ports[i] ), ports[i] ) )
            fprintf ( stderr, "cannot connect input ports " );


    free ( ports );

    /* install a signal handler to properly quits jack client */
#ifdef WIN32
    signal ( SIGINT, signal_handler );
    signal ( SIGABRT, signal_handler );
    signal ( SIGTERM, signal_handler );
#else
    signal ( SIGQUIT, signal_handler );
    signal ( SIGTERM, signal_handler );
    signal ( SIGHUP, signal_handler );
    signal ( SIGINT, signal_handler );
#endif


    /* keep running until the transport stops */

    while (1)
    {
#ifdef WIN32
        Sleep ( 1000 );
#else
        sleep ( 1 );
#endif
    }


    jack_client_close ( client );
    exit ( 0 );
}

Delaying Audio

While this book is not about audio effects, you can easily introduce one effect—latency—by just delaying sounds. Now this—and any time-consuming actions—are against the spirit (and implementation!) of Jack, so it can be done only in cooperation with the Jack model.

The simplest idea is just to throw in sleep commands at the right places. This would assume that calls to the process callback happen asynchronously, but they don't—they happen synchronously within the Jack processing thread. Activities that cost time aren’t allowed. If you try it, you will end up with lots of xruns at best and seizures of Jack at worst.

In this case, the solution is straightforward: keep a buffer in which previous inputs are kept, and read older entries out of this buffer when output is requested. A “big enough” wrap-around array will do this, where old entries are read out and new entries read in.

The following program, delay.c, will copy the left channel in real time but delay the left channel by 4,096 samples:

/** @file delay.c
 *
 * @brief This client delays one channel by 4096 framse.
 */


#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <signal.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include <jack/jack.h>


jack_port_t **input_ports;
jack_port_t **output_ports;
jack_client_t *client;


#define SIZE 8192
#define DELAY 4096
jack_default_audio_sample_t buffer[SIZE];
int idx, delay_idx;


static void signal_handler ( int sig )
{
    jack_client_close ( client );
    fprintf ( stderr, "signal received, exiting ... " );
    exit ( 0 );
}


static void copy2out( jack_default_audio_sample_t *out,
                      jack_nframes_t nframes) {
    if (delay_idx + nframes < SIZE) {
        memcpy(out, buffer + delay_idx,
               nframes * sizeof ( jack_default_audio_sample_t ) );
    } else {
        int frames_to_end = SIZE - delay_idx;
        int overflow = delay_idx + nframes - SIZE;
        memcpy(out, buffer + delay_idx,
               frames_to_end * sizeof ( jack_default_audio_sample_t ) );
        memcpy(out, buffer, overflow * sizeof(jack_default_audio_sample_t));
    }
    delay_idx = (delay_idx + nframes) % SIZE;
}


static void copy2buffer( jack_default_audio_sample_t *in,
                      jack_nframes_t nframes) {
    if (idx + nframes < SIZE) {
        memcpy(buffer + idx, in,
               nframes * sizeof ( jack_default_audio_sample_t ) );
    } else {
        int frames_to_end = SIZE - idx;
        int overflow = idx + nframes - SIZE;
        memcpy(buffer + idx, in,
               frames_to_end * sizeof ( jack_default_audio_sample_t ) );
        memcpy(buffer, in, overflow * sizeof(jack_default_audio_sample_t));
    }
    idx = (idx + nframes) % SIZE;
}


/**
 * The process callback for this JACK application is called in a
 * special realtime thread once for each audio cycle.
 *
 * This client follows a simple rule: when the JACK transport is
 * running, copy the input port to the output.  When it stops, exit.
 */


int
process ( jack_nframes_t nframes, void *arg )
{
    int i;
    jack_default_audio_sample_t *in, *out;


    in = jack_port_get_buffer ( input_ports[0], nframes );
    out = jack_port_get_buffer ( output_ports[0], nframes );
    memcpy ( out, in, nframes * sizeof ( jack_default_audio_sample_t ) );


    in = jack_port_get_buffer ( input_ports[1], nframes );
    out = jack_port_get_buffer ( output_ports[1], nframes );
    copy2out(out, nframes);
    copy2buffer(in, nframes);


    return 0;
}


/**
 * JACK calls this shutdown_callback if the server ever shuts down or
 * decides to disconnect the client.
 */
void
jack_shutdown ( void *arg )
{
    free ( input_ports );
    free ( output_ports );
    exit ( 1 );
}


int
main ( int argc, char *argv[] )
{
    int i;
    const char **ports;
    const char *client_name;
    const char *server_name = NULL;
    jack_options_t options = JackNullOption;
    jack_status_t status;


    if ( argc >= 2 )        /* client name specified? */
    {
        client_name = argv[1];
        if ( argc >= 3 )    /* server name specified? */
        {
            server_name = argv[2];
            options |= JackServerName;
        }
    }
    else              /* use basename of argv[0] */
    {
        client_name = strrchr ( argv[0], '/' );
        if ( client_name == 0 )
        {
            client_name = argv[0];
        }
        else
        {
            client_name++;
        }
    }


    /* open a client connection to the JACK server */

    client = jack_client_open ( client_name, options, &status, server_name );
    if ( client == NULL )
    {
        fprintf ( stderr, "jack_client_open() failed, "
                  "status = 0x%2.0x ", status );
        if ( status & JackServerFailed )
        {
            fprintf ( stderr, "Unable to connect to JACK server " );
        }
        exit ( 1 );
    }
    if ( status & JackServerStarted )
    {
        fprintf ( stderr, "JACK server started " );
    }
    if ( status & JackNameNotUnique )
    {
        client_name = jack_get_client_name ( client );
        fprintf ( stderr, "unique name `%s' assigned ", client_name );
    }


    /* tell the JACK server to call `process()' whenever
       there is work to be done.
    */


    jack_set_process_callback ( client, process, 0 );

    /* tell the JACK server to call `jack_shutdown()' if
       it ever shuts down, either entirely, or if it
       just decides to stop calling us.
    */


    jack_on_shutdown ( client, jack_shutdown, 0 );

    /* create two ports pairs*/
    input_ports = ( jack_port_t** ) calloc ( 2, sizeof ( jack_port_t* ) );
    output_ports = ( jack_port_t** ) calloc ( 2, sizeof ( jack_port_t* ) );


    char port_name[16];
    for ( i = 0; i < 2; i++ )
    {
        sprintf ( port_name, "input_%d", i + 1 );
        input_ports[i] = jack_port_register ( client, port_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 );
        sprintf ( port_name, "output_%d", i + 1 );
        output_ports[i] = jack_port_register ( client, port_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 );
        if ( ( input_ports[i] == NULL ) || ( output_ports[i] == NULL ) )
        {
            fprintf ( stderr, "no more JACK ports available " );
            exit ( 1 );
        }
    }


    bzero(buffer, SIZE * sizeof ( jack_default_audio_sample_t ));
    delay_idx = 0;
    idx = DELAY;


    /* Tell the JACK server that we are ready to roll.  Our
     * process() callback will start running now. */


    if ( jack_activate ( client ) )
    {
        fprintf ( stderr, "cannot activate client" );
        exit ( 1 );
    }


    /* Connect the ports.  You can't do this before the client is
     * activated, because we can't make connections to clients
     * that aren't running.  Note the confusing (but necessary)
     * orientation of the driver backend ports: playback ports are
     * "input" to the backend, and capture ports are "output" from
     * it.
     */


    ports = jack_get_ports ( client, NULL, NULL, JackPortIsPhysical|JackPortIsOutput );
    if ( ports == NULL )
    {
        fprintf ( stderr, "no physical capture ports " );
        exit ( 1 );
    }


    for ( i = 0; i < 2; i++ )
        if ( jack_connect ( client, ports[i], jack_port_name ( input_ports[i] ) ) )
            fprintf ( stderr, "cannot connect input ports " );


    free ( ports );

    ports = jack_get_ports ( client, NULL, NULL, JackPortIsPhysical|JackPortIsInput );
    if ( ports == NULL )
    {
        fprintf ( stderr, "no physical playback ports " );
        exit ( 1 );
    }


    for ( i = 0; i < 2; i++ )
        if ( jack_connect ( client, jack_port_name ( output_ports[i] ), ports[i] ) )
            fprintf ( stderr, "cannot connect input ports " );


    free ( ports );

    /* install a signal handler to properly quits jack client */
#ifdef WIN32
    signal ( SIGINT, signal_handler );
    signal ( SIGABRT, signal_handler );
    signal ( SIGTERM, signal_handler );
#else
    signal ( SIGQUIT, signal_handler );
    signal ( SIGTERM, signal_handler );
    signal ( SIGHUP, signal_handler );
    signal ( SIGINT, signal_handler );
#endif


    /* keep running until the transport stops */

    while (1)
    {
#ifdef WIN32
        Sleep ( 1000 );
#else
        sleep ( 1 );
#endif
    }


    jack_client_close ( client );
    exit ( 0 );
}

Audacity with Jack

Audacity is Jack-aware. You can use it to capture and display Jack streams. But that doesn’t mean that for the user it plays in a nice way! With a running Jack system, starting Audacity registers it with Jack, but there are no input or output ports. These show up only when you start a record session with Audacity. It then establishes its own links within Jack.

For example, with thru_client as the only client within Jack, qjackctl shows the connections, as shown in Figure 7-2.

A435426_1_En_7_Fig2_HTML.jpg
Figure 7-2. Qjackctl showing thru_client

In this figure, the capture devices are connected to the thru_client inputs, and the thru_client outputs are connected to the playback outputs.

Just starting Audacity but not recording anything makes no changes to this connection graph.

But when Audacity starts recording with thru_client already running, qjackctl shows the links established, as in Figure 7-3.

A435426_1_En_7_Fig3_HTML.jpg
Figure 7-3. Qjackctl with thru_client and Audacity

This is a lot messier: Audacity shows as PortAudio devices , the capture devices are linked to the PortAudio inputs, and the PortAudio outputs are linked to the playback devices. The existing thru_client links are basically discarded. To set up your desired situation, these have to be relinked as needed.

To demonstrate the effects of delaying one channel, start Jack, start delay, and then start Audacity. Relink the ports according to Figure 7-4.

A435426_1_En_7_Fig4_HTML.jpg
Figure 7-4. Qjackctl wih delay

That is, capture ports are linked to delay input ports, delay output ports are linked to PortAudio (Audacity) input ports, and PortAudio output ports are linked to playback ports.

The waveforms captured by Audacity clearly show the delay on the left channel compared to the right (Figure 7-5).

A435426_1_En_7_Fig5_HTML.jpg
Figure 7-5. Audacity showing delay

Play a Sine Wave

The copy example does not show the detail of what is in the buffers: the contents are from jack_default_audio_sample_t. What these are is described in the macro JACK_DEFAULT_AUDIO_TYPE with the default value “32 bit float mono audio.”

To do anything more than simply pass audio through, you need to handle the data in this format. The example program simple_client.c fills an array with 32-bit floating point sine curve values. On each call to process, it copies data from the sine curve array into the output buffers. The increment into the sine curve array is different for the left and right channels to give a different note on each channel.

Note that the calculation of the sine curve array is not done within the process function. That would be too slow and would cause latency.

The program is as follows:

/** @file simple_client.c
 *
 * @brief This simple client demonstrates the basic features of JACK
 * as they would be used by many applications.
 */


#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <signal.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include <jack/jack.h>


jack_port_t *output_port1, *output_port2;
jack_client_t *client;


#ifndef M_PI
#define M_PI  (3.14159265)
#endif


#define TABLE_SIZE   (200)
typedef struct
{
    float sine[TABLE_SIZE];
    int left_phase;
    int right_phase;
}
paTestData;


static void signal_handler(int sig)
{
        jack_client_close(client);
        fprintf(stderr, "signal received, exiting ... ");
        exit(0);
}


/**
 * The process callback for this JACK application is called in a
 * special realtime thread once for each audio cycle.
 *
 * This client follows a simple rule: when the JACK transport is
 * running, copy the input port to the output.  When it stops, exit.
 */


int
process (jack_nframes_t nframes, void *arg)
{
        jack_default_audio_sample_t *out1, *out2;
        paTestData *data = (paTestData*)arg;
        int i;


        out1 = (jack_default_audio_sample_t*)jack_port_get_buffer (output_port1, nframes);
        out2 = (jack_default_audio_sample_t*)jack_port_get_buffer (output_port2, nframes);


        for( i=0; i<nframes; i++ )
    {
        out1[i] = data->sine[data->left_phase];  /* left */
        out2[i] = data->sine[data->right_phase];  /* right */
        data->left_phase += 1;
        if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE;
        data->right_phase += 3; /* higher pitch so we can distinguish left and right. */
        if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE;
    }


        return 0;
}


/**
 * JACK calls this shutdown_callback if the server ever shuts down or
 * decides to disconnect the client.
 */
void
jack_shutdown (void *arg)
{
        exit (1);
}


int
main (int argc, char *argv[])
{
        const char **ports;
        const char *client_name;
        const char *server_name = NULL;
        jack_options_t options = JackNullOption;
        jack_status_t status;
        paTestData data;
        int i;


        if (argc >= 2) {                /* client name specified? */
                client_name = argv[1];
                if (argc >= 3) {        /* server name specified? */
                        server_name = argv[2];
            int my_option = JackNullOption | JackServerName;
                        options = (jack_options_t)my_option;
                }
        } else {                        /* use basename of argv[0] */
                client_name = strrchr(argv[0], '/');
                if (client_name == 0) {
                        client_name = argv[0];
                } else {
                        client_name++;
                }
        }


        for( i=0; i<TABLE_SIZE; i++ )
    {
        data.sine[i] = 0.2 * (float) sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. );
    }
    data.left_phase = data.right_phase = 0;


        /* open a client connection to the JACK server */

        client = jack_client_open (client_name, options, &status, server_name);
        if (client == NULL) {
                fprintf (stderr, "jack_client_open() failed, "
                         "status = 0x%2.0x ", status);
                if (status & JackServerFailed) {
                        fprintf (stderr, "Unable to connect to JACK server ");
                }
                exit (1);
        }
        if (status & JackServerStarted) {
                fprintf (stderr, "JACK server started ");
        }
        if (status & JackNameNotUnique) {
                client_name = jack_get_client_name(client);
                fprintf (stderr, "unique name `%s' assigned ", client_name);
        }


        /* tell the JACK server to call `process()' whenever
           there is work to be done.
        */


        jack_set_process_callback (client, process, &data);

        /* tell the JACK server to call `jack_shutdown()' if
           it ever shuts down, either entirely, or if it
           just decides to stop calling us.
        */


        jack_on_shutdown (client, jack_shutdown, 0);

        /* create two ports */

        output_port1 = jack_port_register (client, "output1",
                                          JACK_DEFAULT_AUDIO_TYPE,
                                          JackPortIsOutput, 0);


        output_port2 = jack_port_register (client, "output2",
                                          JACK_DEFAULT_AUDIO_TYPE,
                                          JackPortIsOutput, 0);


        if ((output_port1 == NULL) || (output_port2 == NULL)) {
                fprintf(stderr, "no more JACK ports available ");
                exit (1);
        }


        /* Tell the JACK server that we are ready to roll.  Our
         * process() callback will start running now. */


        if (jack_activate (client)) {
                fprintf (stderr, "cannot activate client");
                exit (1);
        }


        /* Connect the ports.  You can't do this before the client is
         * activated, because we can't make connections to clients
         * that aren't running.  Note the confusing (but necessary)
         * orientation of the driver backend ports: playback ports are
         * "input" to the backend, and capture ports are "output" from
         * it.
         */


        ports = jack_get_ports (client, NULL, NULL,
                                JackPortIsPhysical|JackPortIsInput);
        if (ports == NULL) {
                fprintf(stderr, "no physical playback ports ");
                exit (1);
        }


        if (jack_connect (client, jack_port_name (output_port1), ports[0])) {
                fprintf (stderr, "cannot connect output ports ");
        }


        if (jack_connect (client, jack_port_name (output_port2), ports[1])) {
                fprintf (stderr, "cannot connect output ports ");
        }


        free (ports);

    /* install a signal handler to properly quits jack client */
#ifdef WIN32
        signal(SIGINT, signal_handler);
    signal(SIGABRT, signal_handler);
        signal(SIGTERM, signal_handler);
#else
        signal(SIGQUIT, signal_handler);
        signal(SIGTERM, signal_handler);
        signal(SIGHUP, signal_handler);
        signal(SIGINT, signal_handler);
#endif


        /* keep running until the Ctrl+C */

        while (1) {
        #ifdef WIN32
                Sleep(1000);
        #else
                sleep (1);
        #endif
        }


        jack_client_close (client);
        exit (0);
}

Saving Input to Disk

Disk I/O cannot be performed within the Jack processing loop; it is just too slow. Saving input to a file requires use of a separate thread to manage disk I/O and pass control between the Jack and disk threads.

The program capture_client.c from the examples does this.

/*
    Copyright (C) 2001 Paul Davis
    Copyright (C) 2003 Jack O'Quin


    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.


    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.


    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.


    * 2002/08/23 - modify for libsndfile 1.0.0 <[email protected]>
    * 2003/05/26 - use ringbuffers - joq
*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sndfile.h>
#include <pthread.h>
#include <signal.h>
#include <getopt.h>
#include <jack/jack.h>
#include <jack/ringbuffer.h>


typedef struct _thread_info {
    pthread_t thread_id;
    SNDFILE *sf;
    jack_nframes_t duration;
    jack_nframes_t rb_size;
    jack_client_t *client;
    unsigned int channels;
    int bitdepth;
    char *path;
    volatile int can_capture;
    volatile int can_process;
    volatile int status;
} jack_thread_info_t;


/* JACK data */
unsigned int nports;
jack_port_t **ports;
jack_default_audio_sample_t **in;
jack_nframes_t nframes;
const size_t sample_size = sizeof(jack_default_audio_sample_t);


/* Synchronization between process thread and disk thread. */
#define DEFAULT_RB_SIZE 16384           /* ringbuffer size in frames */
jack_ringbuffer_t *rb;
pthread_mutex_t disk_thread_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  data_ready = PTHREAD_COND_INITIALIZER;
long overruns = 0;
jack_client_t *client;


static void signal_handler(int sig)
{
        jack_client_close(client);
        fprintf(stderr, "signal received, exiting ... ");
        exit(0);
}


static void *
disk_thread (void *arg)
{
        jack_thread_info_t *info = (jack_thread_info_t *) arg;
        static jack_nframes_t total_captured = 0;
        jack_nframes_t samples_per_frame = info->channels;
        size_t bytes_per_frame = samples_per_frame * sample_size;
        void *framebuf = malloc (bytes_per_frame);


        pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
        pthread_mutex_lock (&disk_thread_lock);


        info->status = 0;

        while (1) {

                /* Write the data one frame at a time.  This is
                 * inefficient, but makes things simpler. */
                while (info->can_capture &&
                       (jack_ringbuffer_read_space (rb) >= bytes_per_frame)) {


                        jack_ringbuffer_read (rb, framebuf, bytes_per_frame);

                        if (sf_writef_float (info->sf, framebuf, 1) != 1) {
                                char errstr[256];
                                sf_error_str (0, errstr, sizeof (errstr) - 1);
                                fprintf (stderr,
                                         "cannot write sndfile (%s) ",
                                         errstr);
                                info->status = EIO; /* write failed */
                                goto done;
                        }


                        if (++total_captured >= info->duration) {
                                printf ("disk thread finished ");
                                goto done;
                        }
                }


                /* wait until process() signals more data */
                pthread_cond_wait (&data_ready, &disk_thread_lock);
        }


 done:
        pthread_mutex_unlock (&disk_thread_lock);
        free (framebuf);
        return 0;
}


static int
process (jack_nframes_t nframes, void *arg)
{
        int chn;
        size_t i;
        jack_thread_info_t *info = (jack_thread_info_t *) arg;


        /* Do nothing until we're ready to begin. */
        if ((!info->can_process) || (!info->can_capture))
                return 0;


        for (chn = 0; chn < nports; chn++)
                in[chn] = jack_port_get_buffer (ports[chn], nframes);


        /* Sndfile requires interleaved data.  It is simpler here to
         * just queue interleaved samples to a single ringbuffer. */
        for (i = 0; i < nframes; i++) {
                for (chn = 0; chn < nports; chn++) {
                        if (jack_ringbuffer_write (rb, (void *) (in[chn]+i),
                                              sample_size)
                            < sample_size)
                                overruns++;
                }
        }


        /* Tell the disk thread there is work to do.  If it is already
         * running, the lock will not be available.  We can't wait
         * here in the process() thread, but we don't need to signal
         * in that case, because the disk thread will read all the
         * data queued before waiting again. */
        if (pthread_mutex_trylock (&disk_thread_lock) == 0) {
            pthread_cond_signal (&data_ready);
            pthread_mutex_unlock (&disk_thread_lock);
        }


        return 0;
}


static void
jack_shutdown (void *arg)
{
        fprintf(stderr, "JACK shut down, exiting ... ");
        exit(1);
}


static void
setup_disk_thread (jack_thread_info_t *info)
{
        SF_INFO sf_info;
        int short_mask;


        sf_info.samplerate = jack_get_sample_rate (info->client);
        sf_info.channels = info->channels;


        switch (info->bitdepth) {
                case 8: short_mask = SF_FORMAT_PCM_U8;
                        break;
                case 16: short_mask = SF_FORMAT_PCM_16;
                         break;
                case 24: short_mask = SF_FORMAT_PCM_24;
                         break;
                case 32: short_mask = SF_FORMAT_PCM_32;
                         break;
                default: short_mask = SF_FORMAT_PCM_16;
                         break;
        }
        sf_info.format = SF_FORMAT_WAV|short_mask;


        if ((info->sf = sf_open (info->path, SFM_WRITE, &sf_info)) == NULL) {
                char errstr[256];
                sf_error_str (0, errstr, sizeof (errstr) - 1);
                fprintf (stderr, "cannot open sndfile "%s" for output (%s) ", info->path, errstr);
                jack_client_close (info->client);
                exit (1);
        }


        info->duration *= sf_info.samplerate;
        info->can_capture = 0;


        pthread_create (&info->thread_id, NULL, disk_thread, info);
}


static void
run_disk_thread (jack_thread_info_t *info)
{
        info->can_capture = 1;
        pthread_join (info->thread_id, NULL);
        sf_close (info->sf);
        if (overruns > 0) {
                fprintf (stderr,
                         "jackrec failed with %ld overruns. ", overruns);
                fprintf (stderr, " try a bigger buffer than -B %"
                         PRIu32 ". ", info->rb_size);
                info->status = EPIPE;
        }
}


static void
setup_ports (int sources, char *source_names[], jack_thread_info_t *info)
{
        unsigned int i;
        size_t in_size;


        /* Allocate data structures that depend on the number of ports. */
        nports = sources;
        ports = (jack_port_t **) malloc (sizeof (jack_port_t *) * nports);
        in_size =  nports * sizeof (jack_default_audio_sample_t *);
        in = (jack_default_audio_sample_t **) malloc (in_size);
        rb = jack_ringbuffer_create (nports * sample_size * info->rb_size);


        /* When JACK is running realtime, jack_activate() will have
         * called mlockall() to lock our pages into memory.  But, we
         * still need to touch any newly allocated pages before
         * process() starts using them.  Otherwise, a page fault could
         * create a delay that would force JACK to shut us down. */
        memset(in, 0, in_size);
        memset(rb->buf, 0, rb->size);


        for (i = 0; i < nports; i++) {
                char name[64];


                sprintf (name, "input%d", i+1);

                if ((ports[i] = jack_port_register (info->client, name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0)) == 0) {
                        fprintf (stderr, "cannot register input port "%s"! ", name);
                        jack_client_close (info->client);
                        exit (1);
                }
        }


        for (i = 0; i < nports; i++) {
                if (jack_connect (info->client, source_names[i], jack_port_name (ports[i]))) {
                        fprintf (stderr, "cannot connect input port %s to %s ", jack_port_name (ports[i]), source_names[i]);
                        jack_client_close (info->client);
                        exit (1);
                }
        }


        info->can_process = 1;          /* process() can start, now */
}


int
main (int argc, char *argv[])
{
    jack_thread_info_t thread_info;
        int c;
        int longopt_index = 0;
        extern int optind, opterr;
        int show_usage = 0;
        char *optstring = "d:f:b:B:h";
        struct option long_options[] = {
                { "help", 0, 0, 'h' },
                { "duration", 1, 0, 'd' },
                { "file", 1, 0, 'f' },
                { "bitdepth", 1, 0, 'b' },
                { "bufsize", 1, 0, 'B' },
                { 0, 0, 0, 0 }
        };


        memset (&thread_info, 0, sizeof (thread_info));
        thread_info.rb_size = DEFAULT_RB_SIZE;
        opterr = 0;


        while ((c = getopt_long (argc, argv, optstring, long_options, &longopt_index)) != -1) {
                switch (c) {
                case 1:
                        /* getopt signals end of '-' options */
                        break;


                case 'h':
                        show_usage++;
                        break;
                case 'd':
                        thread_info.duration = atoi (optarg);
                        break;
                case 'f':
                        thread_info.path = optarg;
                        break;
                case 'b':
                        thread_info.bitdepth = atoi (optarg);
                        break;
                case 'B':
                        thread_info.rb_size = atoi (optarg);
                        break;
                default:
                        fprintf (stderr, "error ");
                        show_usage++;
                        break;
                }
        }


        if (show_usage || thread_info.path == NULL || optind == argc) {
                fprintf (stderr, "usage: jackrec -f filename [ -d second ] [ -b bitdepth ] [ -B bufsize ] port1 [ port2 ... ] ");
                exit (1);
        }


        if ((client = jack_client_open ("jackrec", JackNullOption, NULL)) == 0) {
                fprintf (stderr, "JACK server not running? ");
                exit (1);
        }


        thread_info.client = client;
        thread_info.channels = argc - optind;
        thread_info.can_process = 0;


        setup_disk_thread (&thread_info);

        jack_set_process_callback (client, process, &thread_info);
        jack_on_shutdown (client, jack_shutdown, &thread_info);


        if (jack_activate (client)) {
                fprintf (stderr, "cannot activate client");
        }


        setup_ports (argc - optind, &argv[optind], &thread_info);

     /* install a signal handler to properly quits jack client */
    signal(SIGQUIT, signal_handler);
        signal(SIGTERM, signal_handler);
        signal(SIGHUP, signal_handler);
        signal(SIGINT, signal_handler);


        run_disk_thread (&thread_info);

        jack_client_close (client);

        jack_ringbuffer_free (rb);

        exit (0);
}

Interacting with ALSA Devices

Jack will eventually get its input from, and send its output to, devices. Currently, they are most likely to be ALSA devices. Consequently, there must be a bridge between Jack processing and ALSA input and output. This will involve all the complexity of ALSA programming.

Fortunately, there are Jack clients that do this. The Jack framework will talk to these, as specified when starting the Jack server.

jackd -dalsa

So, you don’t need to worry about that interface. For the brave and curious, the Jack source has a directory of examples, which includes the files alsa_in.c and alsa_out.c. They contains comments from the author such as "// Alsa stuff… i dont want to touch this bullshit in the next years.... please…", giving you fair warning that it’s not easy and not necessary for general Jack programming.

Conclusion

This chapter covered using Jack from a user viewpoint and also looked at programming Jack clients.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset