Linux 声音编程教程(三)

原文:Linux Sound Programming

协议:CC BY-NC-SA 4.0

七、Jack

在 Linux 中,声音服务器的作用是从多个源获取输入,并将它们路由到多个接收器。Linux 中有几种音频服务器,主要的是 PulseAudio 和 Jack。它们是为不同的角色设计的:PulseAudio 是为消费音频系统设计的,而 Jack 是为专业音频设计的。在 http://0pointer.de/blog/projects/when-pa-and-when-not.html 吟诗的伦纳德拟定了一张差异表。主要的一点是,Jack 适用于低延迟至关重要的环境,Jack 在音频链中引入的延迟不到 5 毫秒,而 PulseAudio 可引入高达 2 秒的延迟。其他的区别是 PulseAudio 可以运行在包括手机在内的低质量系统上,而 Jack 通常运行在高质量的音频设备上。文章“认识 Jack”对 Jack 进行了委婉的介绍。本章着眼于专门为 Jack 构建的工具,应用如何使用 Jack,最后是用 Jack 编程。

资源

以下是一些资源:

启动千斤顶

大多数发行版的软件仓库中都有 Jack。您希望安装 Jack2 而不是 Jack1。对于编程,您还需要 libjack2 dev 包,它可能与 jack2 包一起安装。

Jack 服务器是jackd。它有一个必需的参数,这是一个声音后端,如 ALSA。最小的命令如下:

jackd -dalsa

跟随选项-dalsa会出现 ALSA 选项。在我的一台电脑上,aplay -l显示卡 0 有设备 3、7 和 8,我需要指定其中一个:

jackd -dalsa -d hw:0,3

如果您使用的是普通的 Linux 发行版,比如 Fedora 或 Ubuntu,那么如果 PulseAudio 系统正在运行,这很可能会失败。在运行 Jack 时,这可能需要停止,或者至少暂停。停止 PulseAudio 参见上一章。要暂停它,我通常在终端窗口中运行:

pasuspender cat

这将暂停 PulseAudio,直到cat终止,当您输入 Ctrl-D 时就会终止。

jackd将尝试开始使用 Linux 实时调度程序。如果您想在没有它的情况下运行,请使用以下选项:

jackd --no-realtime -dalsa

如果你想用实时调度程序运行,有几种方法。

  • 从 root 用户运行服务器。

  • 将用户添加到audiojackuser组,如下所示:

sudo jackd -dalsa

useradd -G audio newmarch
useradd -G jackuser newmarch

(在此生效之前,您需要注销并重新登录。)

请注意,如果您以 root 用户身份运行服务器,那么您将无法从不在jackuser组中的客户端连接到它。

Jack 没有明显的 systemd 或 upstart 脚本,但是有在 http://gentoo-en.vfose.ru/wiki/JACK#Starting_JACK_at_boot_time 引导时启动 Jack 的指令。以下说明摘自 GPL 许可下的说明(最后一次修改是在 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
 }

文件:/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

创建并保存这两个文件。别忘了把JACKDUSER调整到想要的用户名(我猜和你的一样;[作者:是的,这就是 Gentoo 指令所说的!]).我们需要使/etc/init.d/jackd可执行:

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

将脚本添加到默认运行级别:

# rc-update add jackd default

在重启你的系统或者启动这个脚本之前,你必须确定jackd已经为$JACKUSER配置好了,否则jackd将会失败。这是因为脚本会读/ home/${USER}/.jackdrc。如果这个文件不存在,创建它的最简单的方法是运行 QJackCtl,如上所述。

关于实时的说明:由于 start-stop-daemon 实现的限制,如果使用pam_limits. start-stop-daemon没有实现对pam_sessions的支持,则不能通过这种方法以非根用户的身份在实时模式下启动jackd,这意味着对limits.conf的更改在这种情况下没有影响。

用户工具

Jack 只需要使用一个工具:qjackctl。这提供了正在播放的 Jack 应用的图形视图,并允许您链接输入和输出。

使用qjackctl的一个简单教程是 how to qjackctlconnections(https://help.ubuntu.com/community/HowToQjackCtlConnections)。它实际上使用起来非常简单:单击一个源,然后通过单击目的地将它链接到目的地。将显示一条连接它们的线。你要做的就是这些。很多 Jack 应用会帮你做这个,所以你只要观察结果就好了。这方面的说明将在本章后面给出。

使用插孔的应用

使用 JACK 的软件有很多,在“使用 Jack 的应用”( http://jackaudio.org/applications )中有描述。

mplayer

要使用千斤顶运行mplayer,添加选项-ao jack

mplayer -ao jack 54154.mp3

用这种方式将mplayer连接到插孔system输出装置。要输出到另一个插孔应用,如jack-rack,将输出应用附加到音频输出命令。

mplayer -ao jack:port=jack_rack 54154.mp3

可见光通讯

如果包含插孔模块( https://wiki.videolan.org/Documentation:Modules/jack/ ),VLC 将播放插孔输出。这是一个可下载的 Debian 包,名为vlc-plugin-jack。你可以通过查看jack是否在vlc --list显示 ALSA 而不是 Jack 中被列为一个模块来检查你是否有它。

通过执行以下操作,使用 Jack 播放文件:

vlc --aout jack 54154.mp3

您应该能够使用选项--jack-connect-regex <string>连接到特定的 Jack 应用。

TiMidity

TiMidity 是第二十一章中讨论的 MIDI 播放器。它可以用这个来播放插孔输出设备:

timidity -Oj 54154.mid

Jack 提供的程序

Jack 带来了大量的客户。

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

对于其中的许多,源代码可以在 Jack 源代码发行版中获得,并且每个都有一个man页面。

比方说,jack_thru运行将系统捕获端口连接到jack_thru输入端口,将jack_thru输出端口连接到系统回放端口。然后,您可以使用client:port作为端口名来断开端口连接,如下所示:

jack_disconnect jack_thru:output_1 system:playback_1

这些命令行工具允许您执行与qjackctl相同的操作。

其他 Jack 程序

使用插孔的应用页面( http://jackaudio.org/applications )列出了许多使用插孔的应用。

linuxaudio.org的页面 Jack MIDI Apps ( http://apps.linuxaudio.org/apps/categories/jack_midi )列出了很多使用 Jack 的 MIDI 应用。

使用不同的声卡

Jack 的默认 ALSA 设备将是hw:0。如果您想使用不同的声卡,那么您可以在启动 Jack 时指定它,如下所示:

jackd -dalsa -dhw:0

我有一个 USB 声霸卡,需要一些额外的参数。

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

这不是很好;我听到有规律的“滴答”声。

如果没有-S (16 位)标志,我只能得到这样一行代码:

ALSA: cannot set hardware parameters for playback

或者,我可以这样运行:

jackd -dalsa -dplughw:2 -r 48000

当我以这种方式开始时,Jack 建议不要使用 ALSA 插头设备,但它迄今为止工作得最好。

如何在 Jack 上使用多个声卡?

插孔用于专业音频使用。在这样的系统中,通常只有一个数字采样“时钟”在这个“理想”的插孔世界中,不会有多个独立的声卡,每个都有自己的时钟。我就来说说这个理想世界。如果您需要在有多个声卡的情况下运行 JACK,那么请参阅“我如何将多个声卡用于 Jack?”( http://jackaudio.org/multiple_devices )。

混合音频

如果来自两个不同源的两个输出端口连接到同一个输入端口,那么 Jack 将为您混合它们。这可以让你毫不费力地跟着你最喜欢的 MP3 文件一起唱。

  1. 将麦克风采集端口连接到回放端口。避免在笔记本电脑的麦克风和扬声器之间建立反馈回路,例如,插入耳机。

  2. 启动一个播放器,比如mplayer,它也将连接到播放端口,如下所示:

    mplayer -ao jack <MP3 file >
    
    
  3. 开始唱歌。

当然,每个信号源都没有音量控制。你可以在你的发行版中插入一个混音器,比如 jack_mixer ( http://home.gna.org/jackmixer/ ),然后用它来控制每个源的音量,如图 7-1 中的qjackctl屏幕所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 7-1。

qjackctl showing a mixer of mplayer and system

用 Jack 编写音频应用

插孔的设计在其主要作者保罗·戴维斯的插孔音频连接套件( http://lac.linuxaudio.org/2003/zkm/slides/paul_davis-jack/title.html )中进行了讨论。目标如下:

  • Jack 应该允许独立应用之间的低延迟、高带宽数据流。
  • 虽然不是必需的,但 Jack 应该支持任何流数据类型,而不仅仅是音频。
  • 在 active Jack 设置中,将有一台服务器和一个或多个 Jack 插件。可以运行多个 Jack 服务器,但每个服务器将形成一个独立的 Jack 设置。Jack 不会在 Jack 服务器之间定义任何接口。
  • 使用 Jack 连接的应用可能有自己的图形界面。Jack 不会对不同的 GUI 工具包或库做任何说明。由于这一要求,下入式千斤顶设置的不同部分可能分布在多个过程中。
  • Jack 应该提供完全的、样本精确的同步(换句话说,所有客户机插件的完全同步执行)。
  • 为了表示音频数据,Jack 应该使用 32 位 IEEE 浮点数,标准化为值域[-1,1]。
  • 仅支持非交错音频流。
  • 一个 Jack 客户端可以消耗或产生多个数据流。
  • Jack API 应该在 ANSI C 中指定,对于如何实现服务器和客户机没有限制。
  • 应该可以连接已经运行的应用。
  • 应该可以在服务器运行时添加或删除 Jack 客户端。

从这个角度来看,主要目标如下:

  • Jack 应该允许独立应用之间的低延迟、高带宽数据流。
  • Jack 应该提供完全的、样本精确的同步(换句话说,所有客户机插件的完全同步执行)。

第二个由 Jack 框架保证。第一个是由 Jack 框架提供的,只要应用编码正确。

在幕后,Jack 使用快速 Linux (Unix)管道将数据从一个应用传输到另一个应用。每个 Jack 应用中都有一个实时循环,它从输入管道获取数据,并将数据发送到输出管道。为了避免延迟,在读取和写入数据之间应该基本上没有(或者尽可能少)处理;理想的情况是将指针数据从输入传递到输出,或者最多只是做一个memcpy

那么,如何进行加工呢?将读取的数据复制到另一个数据结构并将处理传递给另一个线程,或者将在另一个线程中处理的数据复制到输出管道。任何其他因素都会导致延迟,这可能会变得很明显。特别是某些系统调用本质上是被禁止的:malloc可以引起交换;sleep是明显的大忌;read/write等等,都会引起磁盘 I/O;而pthread_cond_wait会……等。

Jack 应用本质上是多线程的。在 Linux 世界中,这意味着 Posix 线程,幸运的是有 Bil Lewis 和 Daniel J. Berg 的书《PThreads Primer ( http://www8.cs.umu.se/kurser/TDBC64/VT03/pthreads/pthread-primer.pdf )告诉你关于 Posix 线程的一切!

以下是设置 Jack 应用的机制:

  1. 打开与 Jack 服务器的连接:jack_client_open
  2. 如果需要,检查连接和紧急援助的状态。
  3. 安装一个进程回调处理器来管理 I/O: jack_set_process_callback
  4. 安装一个关机回调:jack_on_shutdown
  5. 向 Jack 服务器注册输入和输出端口:jack_port_register。请注意,每个端口只携带一个单声道通道,因此对于立体声,您将获得两个输入端口。这还没有将它们与管道联系起来。
  6. 激活端口。换句话说,告诉 Jack 启动它的处理线程:jack_activate
  7. 将端口连接到管道:jack_connect
  8. 以某种方式坐在那里。对于文本客户端,只需循环休眠即可。GUI 客户端可能有一个 GUI 处理循环。

收集

下面的例子需要链接到各种库。这些是 jack、sndfile、pthread 和 math 库。适当的标志如下:

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

港口信息

Jack 使用传输单声道 32 位数据的端口。每个端口都有一个字符串形式的名称和属性,如输入和输出。一旦连接到 Jack 服务器,就可以使用jack_get_ports查询服务器已知的端口。如果参数是NULL或零,那么返回所有端口,或者可以使用模式来限制返回的端口名称。一旦找到一个端口名,就可以把它变成一个jack_port_t,并且可以查询它的属性。

完成这项工作的程序是listports.c,如下所示:

/** @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\n", name);
    jack_port_t *port = jack_port_by_name (client, name);
    if (port == NULL) {
        printf("No port by name %s\n", name);
        return;
    }
    printf("  Type is %s\n", jack_port_type(port));

    int flags = jack_port_flags(port);
    if (flags & JackPortIsInput)
        printf("  Is an input port\n");
    else
        printf("  Is an output port\n");
    char **connections = jack_port_get_connections(port);
    char **c = connections;
    printf("  Connected to:\n");
    while ((c != NULL) && (*c != NULL)) {
        printf("    %s\n", *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\n", status );
        if ( status & JackServerFailed )
        {
            fprintf ( stderr, "Unable to connect to JACK server\n" );
        }
        exit ( 1 );
    }
    if ( status & JackServerStarted )
    {
        fprintf ( stderr, "JACK server started\n" );
    }
    if ( status & JackNameNotUnique )
    {
        client_name = jack_get_client_name ( client );
        fprintf ( stderr, "unique name `%s' assigned\n", 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\n" );
        exit ( 1 );
    }
    char **p = ports;
    while (*p != NULL)
        print_port_info(*p++);
    jack_free(ports);

    jack_client_close ( client );
    exit ( 0 );
}

将输入复制到输出

Jack 源代码发行版有一个example clients子目录。这个子目录中包含客户端thru_client.c,它只是将输入复制到输出。这个例子的处理核心是函数process。该函数将输入和输出上可用的若干帧作为参数,并且该函数循环通过(立体声)通道,获得相应的输入和输出缓冲器(用于输入和输出管道),并且将数据从输入复制到相应的输出。

代码如下:

/** @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 ...\n" );
    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\n", status );
        if ( status & JackServerFailed )
        {
            fprintf ( stderr, "Unable to connect to JACK server\n" );
        }
        exit ( 1 );
    }
    if ( status & JackServerStarted )
    {
        fprintf ( stderr, "JACK server started\n" );
    }
    if ( status & JackNameNotUnique )
    {
        client_name = jack_get_client_name ( client );
        fprintf ( stderr, "unique name `%s' assigned\n", 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\n" );
            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\n" );
        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\n" );

    free ( ports );

    ports = jack_get_ports ( client, NULL, NULL, JackPortIsPhysical|JackPortIsInput );
    if ( ports == NULL )
    {
        fprintf ( stderr, "no physical playback ports\n" );
        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\n" );

    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 );
}

延迟音频

虽然这本书不是关于音频效果的,但是你可以通过延迟声音很容易地引入一种效果——延迟。现在这—以及任何耗时的行动—都违背了精神(和实现!)的 Jack,所以只有配合 Jack 模型才能做到。

最简单的想法就是在正确的地方插入sleep命令。这将假设对process回调的调用是异步发生的,但事实并非如此——它们是在 Jack 处理线程中同步发生的。耗费时间的活动是不允许的。如果你尝试它,你将会在最好的情况下结束大量的 xruns,在最坏的情况下结束 Jack 的癫痫发作。

在这种情况下,解决方案很简单:保留一个保存以前输入的缓冲区,并在请求输出时从该缓冲区中读取旧的条目。一个“足够大”的回绕数组可以做到这一点,旧的条目被读出,新的条目被读入。

以下程序delay.c将实时复制左声道,但将左声道延迟 4,096 个样本:

/** @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 ...\n" );
    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\n", status );
        if ( status & JackServerFailed )
        {
            fprintf ( stderr, "Unable to connect to JACK server\n" );
        }
        exit ( 1 );
    }
    if ( status & JackServerStarted )
    {
        fprintf ( stderr, "JACK server started\n" );
    }
    if ( status & JackNameNotUnique )
    {
        client_name = jack_get_client_name ( client );
        fprintf ( stderr, "unique name `%s' assigned\n", 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\n" );
            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\n" );
        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\n" );

    free ( ports );

    ports = jack_get_ports ( client, NULL, NULL, JackPortIsPhysical|JackPortIsInput );
    if ( ports == NULL )
    {
        fprintf ( stderr, "no physical playback ports\n" );
        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\n" );

    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 );
}

勇敢面对 Jack

Audacity 是 Jack-aware。您可以使用它来捕获和显示 Jack 流。但这并不意味着对用户来说,它的播放方式很好!对于正在运行的 Jack 系统,启动 Audacity 会向 Jack 注册它,但是没有输入或输出端口。只有当您使用 Audacity 开始记录会话时,这些才会显示出来。然后它在 Jack 内部建立自己的链接。

例如,thru_client作为 Jack 中唯一的客户端,qjackctl显示连接,如图 7-2 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 7-2。

Qjackctl showing thru_client

在该图中,捕捉设备连接到thru_client输入,而thru_client输出连接到回放输出。

仅仅启动 Audacity 而不记录任何东西并不会对这个连接图产生任何变化。

但是当 Audacity 在thru_client已经运行的情况下开始记录时,qjackctl显示已经建立的链接,如图 7-3 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 7-3。

Qjackctl with thru_client and Audacity

这要复杂得多:Audacity 显示为 PortAudio 设备,捕获设备链接到 PortAudio 输入,PortAudio 输出链接到回放设备。现有的thru_client环节基本废弃。要设置您想要的情况,这些必须根据需要重新链接。

为了演示延迟一个通道的效果,启动 Jack,启动delay,然后启动 Audacity。根据图 7-4 重新连接端口。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 7-4。

Qjackctl wih delay

也就是说,捕捉端口链接到delay输入端口,delay输出端口链接到 PortAudio (Audacity)输入端口,PortAudio 输出端口链接到回放端口。

Audacity 捕获的波形清楚地显示了左声道相对于右声道的延迟(图 7-5 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 7-5。

Audacity showing delay

演奏正弦波

复制示例没有显示缓冲区中内容的细节:内容来自jack_default_audio_sample_t。宏JACK_DEFAULT_AUDIO_TYPE中描述了这些内容,默认值为“32 位浮点单声道音频”

除了简单地传递音频之外,您还需要处理这种格式的数据。示例程序simple_client.c用 32 位浮点正弦曲线值填充数组。在每次调用process时,它将数据从正弦曲线数组复制到输出缓冲区。对于左声道和右声道,正弦曲线阵列的增量是不同的,以在每个声道上给出不同的音符。

注意,正弦曲线数组的计算不是在process函数中完成的。那太慢了,会造成延迟。

该计划如下:

/** @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 ...\n");
        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\n", status);
                if (status & JackServerFailed) {
                        fprintf (stderr, "Unable to connect to JACK server\n");
                }
                exit (1);
        }
        if (status & JackServerStarted) {
                fprintf (stderr, "JACK server started\n");
        }
        if (status & JackNameNotUnique) {
                client_name = jack_get_client_name(client);
                fprintf (stderr, "unique name `%s' assigned\n", 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\n");
                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\n");
                exit (1);
        }

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

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

        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);
}

将输入保存到磁盘

磁盘 I/O 不能在 Jack 处理循环中执行;只是太慢了。将输入保存到文件需要使用单独的线程来管理磁盘 I/O,并在插孔和磁盘线程之间传递控制。

示例中的程序capture_client.c就是这样做的。

/*
    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 <andy@alsaplayer.org>
    * 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 ...\n");
        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)\n",
                                         errstr);
                                info->status = EIO; /* write failed */
                                goto done;
                        }

                        if (++total_captured >= info->duration) {
                                printf ("disk thread finished\n");
                                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 ...\n");
        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)\n", 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.\n", overruns);
                fprintf (stderr, " try a bigger buffer than -B %"
                         PRIu32 ".\n", 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\"!\n", 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\n", 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\n");
                        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 ... ]\n");
                exit (1);
        }

        if ((client = jack_client_open ("jackrec", JackNullOption, NULL)) == 0) {
                fprintf (stderr, "JACK server not running?\n");
                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);
}

与 ALSA 设备交互

Jack 最终将从设备获取输入,并将输出发送到设备。目前,它们最有可能是 ALSA 的设备。因此,在 Jack 处理和 ALSA 输入输出之间必须有一个桥梁。这将涉及 ALSA 编程的所有复杂性。

幸运的是,有 Jack 客户端可以做到这一点。Jack 框架将按照启动 Jack 服务器时指定的那样与它们对话。

jackd -dalsa

所以,你不需要担心那个接口。对于勇敢和好奇的人来说,Jack source 有一个示例目录,其中包括文件alsa_in.calsa_out.c。它们包含作者的评论,比如"// Alsa stuff… i dont want to touch this bullshit in the next years.... please…",给你一个合理的警告,对于一般的 Jack 编程来说,这并不容易,也没有必要。

结论

这一章从用户的角度讲述了 Jack 的使用,也讲述了 Jack 客户端的编程。

八、会话管理

一个复杂的声音系统可能由多个声源、多个滤波器和多个输出组成。如果每次使用它们都必须重新设置,那么就会出现错误,浪费时间,等等。会话管理试图解决这些问题。

资源

以下是一些资源:

会话管理问题

每当有多个模块以某种方式链接时,就需要管理这些模块及其链接。这些需求在 Jack 环境中很快出现,它是为多重链接而设计的。管理变得繁琐并不需要复杂的插孔模块排列。例如,考虑图 8-1 中上一章的混音器会话。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-1。

Jack connecting multiple applications

从头开始设置需要以下条件:

  1. 启动jackdqjackctl
  2. 开始jack_mixer
  3. 打开混音器上的两组新输入端口。
  4. 将主混音器输出端口连接到回放端口。
  5. 将麦克风端口连接到一组混音器输入端口。
  6. 启动mplayer,自动连接到播放端口。
  7. 从回放端口断开mplayer输出端口,并将它们重新连接到另一组混音器输入端口。

你不想每次放一首歌都这样吧!

LADISH 会话管理器通过会话管理器( http://ladish.org/wiki/levels )识别应用的不同控制级别。除去对特定管理器和框架的显式引用,级别如下:

  • 级别 0:应用没有链接到会话处理库。用户必须手动保存应用项目,或者依赖应用的自动保存支持。
  • 级别 1:应用没有链接到会话处理库。当接收到特定的消息或信号时,应用进行保存。
  • 第 2 级:应用链接到会话管理库。由于会话管理器的限制,它与会话处理程序的交互是有限的。
  • 级别 3:应用链接到复杂的会话管理器。它与会话处理程序有完全的交互。

正如 Dave Phillips 所指出的,“使用这些级别是为了对任何 Linux 音频应用的各种可能条件进行分类和调整。这些条件包括符合 JACK 的程度、任何 WINE 或 DOS 要求、网络操作、现有 API 的多样性等等。”

当前一批用于 Linux 音频的会话管理框架包括

  • 讽刺
  • Jack 会话管理
  • 拉迪什
  • 非会话管理器
  • 奇诺

多个管理器的存在意味着大多数应用将只支持一个或至多几个协议。如果您选择一个特定的管理器,那么您将被限制在您可以在其控制下运行的应用。

插孔 _ 连接

程序jack_connectjack_disconnect可用于重新配置客户端之间的连接。例如,MIDI 播放器 TiMidity 会将其输出端口连接到第一个可用的插孔输入端口,这些端口通常是连接到声卡的系统端口。如果你想把 TiMidity 连接到,比方说,jack-rack,那么它的输出端口必须首先断开,然后再连接到正确的端口。另一方面,jack-rack默认情况下不连接任何东西,因此可能需要连接到系统端口。这是通过以下方式完成的:

jack_disconnect TiMidity:port_1 system:playback_1
jack_disconnect TiMidity:port_2 system:playback_2

jack_connect TiMidity:port_1 jack_rack:in_1
jack_connect TiMidity:port_2 jack_rack:in_2

jack_connect jack_rack:out_1 system:playback_1
jack_connect jack_rack:out_2 system:playback_2

讽刺

这是最早成功的用于 Linux 音频的会话管理器,但是已经不再使用了。它似乎已经不在 Ubuntu 的仓库里了。

需要 LASH 的应用之一是jack_mixer。更糟糕的是,它使用了来自python-lash.2.7.4-0ubuntu包的 Python LASH模块。我能找到的唯一一个副本需要 Python 2.7 以下的版本,Python 的安装版本是 2.7.4。这是一个目前无法从当前会话管理工具中获益的应用。虽然它可以作为第 1 级运行,但对于其他会话管理器,它只能在第 0 级运行。

因此,有一些 Jack 应用需要 LASH 来进行会话管理,但是这种支持似乎不再存在了。

Jack·塞申斯

您可以在 http://wiki.linuxaudio.org/apps/categories/jack_session 找到截至 2016 年的 Jack 会话感知应用列表。

qjackctl有一个会话管理器,允许您保存和恢复会话。您可以通过单击会话按钮,然后选择会话名称和目录来保存会话。qjackctl将会话信息作为 XML 文件存储在您保存它的任何目录中。对于上一个会话,如下所示:

<!DOCTYPE qjackctlSession>
<session name="session2">
 <client name="jack_mixer">
  <port type="out" name="MAIN L">
   <connect port="playback_1" client="system"/>
  </port>
  <port type="out" name="MAIN R">
   <connect port="playback_2" client="system"/>
  </port>
  <port type="in" name="midi in"/>
  <port type="out" name="Monitor L"/>
  <port type="out" name="Monitor R"/>
  <port type="in" name="Mixer L">
   <connect port="capture_1" client="system"/>
  </port>
  <port type="in" name="Mixer R">
   <connect port="capture_2" client="system"/>
  </port>
  <port type="out" name="Mixer Out L"/>
  <port type="out" name="Mixer Out R"/>
  <port type="in" name="mixer2 L">
   <connect port="out_0" client="MPlayer [8955]"/>
  </port>
  <port type="in" name="mixer2 R">
   <connect port="out_1" client="MPlayer [8955]"/>
  </port>
  <port type="out" name="mixer2 Out L"/>
  <port type="out" name="mixer2 Out R"/>
 </client>
 <client name="system">
  <port type="out" name="capture_1">
   <connect port="Mixer L" client="jack_mixer"/>
  </port>
  <port type="out" name="capture_2">
   <connect port="Mixer R" client="jack_mixer"/>
  </port>
  <port type="in" name="playback_1">
   <connect port="MAIN L" client="jack_mixer"/>
  </port>
  <port type="in" name="playback_2">
   <connect port="MAIN R" client="jack_mixer"/>
  </port>
 </client>
 <client name="MPlayer [8955]">
  <port type="out" name="out_0">
   <connect port="mixer2 L" client="jack_mixer"/>
  </port>
  <port type="out" name="out_1">
   <connect port="mixer2 R" client="jack_mixer"/>
  </port>
 </client>
</session>

在加载会话时,它看起来如图 8-2 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-2。

qjackctl showing Jack session

如你所见,有许多红色的 x。恢复会话不会启动这些特定的应用。如果你手动重启jack_mixer,那么它会在它的主输出端口和系统回放端口之间建立链接,几个红色的 x 会消失。但是它不会创建之前创建的额外端口。您需要重复创建具有正确名称的新输入端口的工作;然后qjackctl重新建立连接,更多的红色 x 消失。

如果你再次运行mplayer,它只是建立自己的默认连接到播放端口,并且必须手动重新映射。它甚至不符合 0 级,因为qjackctl不会自动重新映射它的连接。

这里的问题是mplayerjack_mixer不使用 Jack 会话管理协议。会话管理器会重置某些应用建立的任何连接,但不是所有应用。后面给出了一个将 Jack 会话管理添加到应用中的例子,然后它将被正确地重启和重新连接。

拉迪什

LADISH 被设计为 LASH 的继任者,可以在资源库中获得。

LADISH 可以启动、停止和配置会话。特别是,它可以设置不同的插孔配置。这意味着你不能先启动 Jack,然后再启动 LADISH 反过来:启动 GUI 工具gladish,配置 Jack,然后启动一个会话。该过程在 LADI 会话处理程序 Wiki ( http://ladish.org/wiki/tutorial )中有描述。跟踪它,特别是把 Jack 和 ALSA 联系起来。否则,你将听不到声音!另请参见企鹅制作人的 LADI 会话处理程序( www.penguinproducer.com/Blog/2011/12/the-ladi-session-handler/ )。

一旦你设置好 LADISH,启动一个新的工作室,然后从它的应用菜单中启动应用。要运行mplayer,您需要给出如下完整命令:

          mplayer -ao jack 54154.mp3

你可以从应用菜单启动jack_mixer,然后添加两组新的输入端口,如第七章所示。重新连接它们之后,你就得到如图 8-3 所示的连接图。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-3。

LADISH session

连接图作为 XML 文件存储在$HOME/.ladish中。例如,图 8-3 中的图形存储如下:

<?xml version="1.0"?>
<!--
ladish Studio configuration.
-->
<!-- Sun Sep 29 10:49:54 2013 -->
<studio>
  <jack>
    <conf>
      <parameter path="/engine/driver">alsa</parameter>
      <parameter path="/engine/client-timeout">500</parameter>
      <parameter path="/engine/port-max">64</parameter>
    </conf>
    <clients>
      <client name="system" uuid="5ef937c6-46f7-45cd-8441-8ff6e2aee4eb">
        <ports>
          <port name="capture_1" uuid="9432f206-44c3-45cb-8024-3ba7160962bc" />
          <port name="capture_2" uuid="3c9acf5c-c91d-4692-add2-e3defb7c508a" />
          <port name="playback_1" uuid="95c68011-dab9-401c-8904-b3d149e20570" />
          <port name="playback_2" uuid="5b8e9215-3ff4-4973-8c0b-1eb5ab7ccc9b" />
        </ports>
      </client>
      <client name="jack_mixer-3" uuid="4538833e-d7e7-47d0-8a43-67ee25d17898">
        <ports>
          <port name="midi in" uuid="17d04191-f59d-4d16-970c-55030162aae7" />
          <port name="MAIN L" uuid="9d986401-c303-4f35-89b7-a32e10120ce4" />
          <port name="MAIN R" uuid="fae94d01-00ef-449d-8e05-f95df84c5357" />
          <port name="Monitor L" uuid="1758d824-75cd-46b3-8e53-82c6be1ca200" />
          <port name="Monitor R" uuid="d14815e9-d3bc-457b-8e4f-29ad29ea36f7" />
          <port name="Mixer L" uuid="07d388ed-d00a-4ee0-92aa-3ae79200e11e" />
          <port name="Mixer R" uuid="d1eb3400-75ce-422d-b9b8-b7e670f95428" />
          <port name="Mixer Out L" uuid="fad2a77e-6146-4919-856f-b6f7befdb84d" />
          <port name="Mixer Out R" uuid="920c5d12-9f62-46aa-b191-52bfbb94065d" />
          <port name="mixer2 L" uuid="c2b96996-9cd1-41dd-a750-192bb5717438" />
          <port name="mixer2 R" uuid="3de52738-d7e8-4733-bf08-3ea2b6372a4c" />
          <port name="mixer2 Out L" uuid="4e08eba4-a0c1-4e76-9dff-c14f76d5328e" />
          <port name="mixer2 Out R" uuid="9d2f79a5-e2d0-484b-b094-98ef7a4f61a7" />
        </ports>
      </client>
      <client name="mplayer" uuid="66e0d45f-2e21-4fbf-ac34-5d3658ee018a">
        <ports>
          <port name="out_0" uuid="83152a6e-e6f6-4357-93ce-020ba58b7d00" />
          <port name="out_1" uuid="55a05594-174d-48a5-805b-96d2c9e77cf1" />
        </ports>
      </client>
    </clients>
  </jack>
  <clients>
    <client name="Hardware Capture" uuid="47c1cd18-7b21-4389-bec4-6e0658e1d6b1" naming="app">
      <ports>
        <port name="capture_1" uuid="9432f206-44c3-45cb-8024-3ba7160962bc" type="audio" direction="output" />
        <port name="capture_2" uuid="3c9acf5c-c91d-4692-add2-e3defb7c508a" type="audio" direction="output" />
      </ports>
      <dict>
        <key name="http://ladish.org/ns/canvas/x">1364.000000</key>
        <key name="http://ladish.org/ns/canvas/y">1083.000000</key>
      </dict>
    </client>
    <client name="Hardware Playback" uuid="b2a0bb06-28d8-4bfe-956e-eb24378f9629" naming="app">
      <ports>
        <port name="playback_1" uuid="95c68011-dab9-401c-8904-b3d149e20570" type="audio" direction="input" />
        <port name="playback_2" uuid="5b8e9215-3ff4-4973-8c0b-1eb5ab7ccc9b" type="audio" direction="input" />
      </ports>
      <dict>
        <key name="http://ladish.org/ns/canvas/x">1745.000000</key>
        <key name="http://ladish.org/ns/canvas/y">1112.000000</key>
      </dict>
    </client>
    <client name="jack_mixer-3" uuid="4b198f0f-5a77-4486-9f54-f7ec044d9bf2" naming="app" app="98729282-8b18-4bcf-b929-41bc53f2b4ed">
      <ports>
        <port name="midi in" uuid="17d04191-f59d-4d16-970c-55030162aae7" type="midi" direction="input" />
        <port name="MAIN L" uuid="9d986401-c303-4f35-89b7-a32e10120ce4" type="audio" direction="output" />
        <port name="MAIN R" uuid="fae94d01-00ef-449d-8e05-f95df84c5357" type="audio" direction="output" />
        <port name="Monitor L" uuid="1758d824-75cd-46b3-8e53-82c6be1ca200" type="audio" direction="output" />
        <port name="Monitor R" uuid="d14815e9-d3bc-457b-8e4f-29ad29ea36f7" type="audio" direction="output" />
        <port name="Mixer L" uuid="07d388ed-d00a-4ee0-92aa-3ae79200e11e" type="audio" direction="input" />
        <port name="Mixer R" uuid="d1eb3400-75ce-422d-b9b8-b7e670f95428" type="audio" direction="input" />
        <port name="Mixer Out L" uuid="fad2a77e-6146-4919-856f-b6f7befdb84d" type="audio" direction="output" />
        <port name="Mixer Out R" uuid="920c5d12-9f62-46aa-b191-52bfbb94065d" type="audio" direction="output" />
        <port name="mixer2 L" uuid="c2b96996-9cd1-41dd-a750-192bb5717438" type="audio" direction="input" />
        <port name="mixer2 R" uuid="3de52738-d7e8-4733-bf08-3ea2b6372a4c" type="audio" direction="input" />
        <port name="mixer2 Out L" uuid="4e08eba4-a0c1-4e76-9dff-c14f76d5328e" type="audio" direction="output" />
        <port name="mixer2 Out R" uuid="9d2f79a5-e2d0-484b-b094-98ef7a4f61a7" type="audio" direction="output" />
      </ports>
      <dict>
        <key name="http://ladish.org/ns/canvas/x">1560.000000</key>
        <key name="http://ladish.org/ns/canvas/y">1104.000000</key>
      </dict>
    </client>
    <client name="mplayer" uuid="2f15cfec-7f6d-41b4-80e8-e1ae80c3be9e" naming="app" app="7a9be17b-eb40-4be3-a9dc-82f36bbceeeb">
      <ports>
        <port name="out_0" uuid="83152a6e-e6f6-4357-93ce-020ba58b7d00" type="audio" direction="output" />
        <port name="out_1" uuid="55a05594-174d-48a5-805b-96d2c9e77cf1" type="audio" direction="output" />
      </ports>
      <dict>
        <key name="http://ladish.org/ns/canvas/x">1350.000000</key>
        <key name="http://ladish.org/ns/canvas/y">1229.000000</key>
      </dict>
    </client>
  </clients>
  <connections>
    <connection port1="9432f206-44c3-45cb-8024-3ba7160962bc" port2="07d388ed-d00a-4ee0-92aa-3ae79200e11e" />
    <connection port1="3c9acf5c-c91d-4692-add2-e3defb7c508a" port2="d1eb3400-75ce-422d-b9b8-b7e670f95428" />
    <connection port1="fad2a77e-6146-4919-856f-b6f7befdb84d" port2="95c68011-dab9-401c-8904-b3d149e20570" />
    <connection port1="920c5d12-9f62-46aa-b191-52bfbb94065d" port2="5b8e9215-3ff4-4973-8c0b-1eb5ab7ccc9b" />
    <connection port1="83152a6e-e6f6-4357-93ce-020ba58b7d00" port2="c2b96996-9cd1-41dd-a750-192bb5717438" />
    <connection port1="55a05594-174d-48a5-805b-96d2c9e77cf1" port2="3de52738-d7e8-4733-bf08-3ea2b6372a4c" />
  </connections>
  <applications>
    <application name="jack_mixer-3" uuid="98729282-8b18-4bcf-b929-41bc53f2b4ed" terminal="false" level="0" autorun="true">jack_mixer</application>
    <application name="mplayer" uuid="7a9be17b-eb40-4be3-a9dc-82f36bbceeeb" terminal="true" level="0" autorun="true">mplayer -ao jack %2Fhome%2Fhttpd%2Fhtml%2FLinuxSound%2FKaraoke%2FSubtitles%2Fsongs%2F54154.mp3</application>
  </applications>
</studio>

重启mplayer的完整命令存储在该文件中,所有连接也是如此。

在停止和重新启动会话时,mplayer使用相同的 MP3 文件启动,但具有默认连接。它忽略了 LADISH 会话的连接。类似地,jack_mixer重新启动,但是必须手动重新创建附加端口。这不是一个 LADISH 感知的应用,所以它运行在 0 级。然而,一旦创建,拉迪什重连就形成了。

你可以在 http://wiki.linuxaudio.org/apps/all/ladish 找到 LADISH 感知应用列表。

从用户的角度来看,这些会话管理器之间的差异如下:

  • Jack 应用可以以任何方式启动,并将被 Jack 会话管理器拾取。但是,任何特定的命令行参数都将丢失。
  • 应用需要由 LADISH 会话管理器启动,以便由它来管理。但是,它可以记录命令行参数,并使用它们重新启动应用。

从开发人员的角度来看,这些会话管理器之间的区别如下:

  • Jack 会话感知应用可以以任何方式启动,并将在程序中对重启它们所需的命令行进行编码。

Jack 会话 API

可以由 Jack sessions (JS)管理的应用可以是第 1 级的 Jack session-aware 或 Jack session-unaware。对于那些没有意识到的人来说,最好的办法就是由会话管理器来启动和停止他们。对于支持 Jack 会话的应用,它们必须设置为执行以下操作:

  • 向 Jack 会话管理器注册
  • 响应来自 Jack 会话管理器的消息
  • 可以从会话信息开始

对 Jack 会话消息的响应通常会执行以下操作:

  • 将应用的状态保存到一个文件中,该文件的目录由会话管理器给出。
  • 使用可用于重启应用的命令回复会话管理器,并提供足够的信息以恢复其状态(通常是存储其状态信息的文件的名称)。

Jack 会话感知客户端通过唯一的通用标识符(UUID)向会话管理器标识自己。这是什么或者是如何产生的,似乎并不重要。只要它是一个用字符串表示的整数,客户端应用就可以创建它。这在注册时传递给会话管理器,但在会话管理器重新启动客户端时也应该传递回客户端。这是通过应用的命令行参数完成的,命令行的格式也由客户端决定。

一个简单的例子可能是两个选项(-u用于 UUID,-f用于保存的状态文件)。这将使用getopt进行解析,如下所示:

int main(int argc, char **argv) {
  int c;
  char *file = NULL;
  char *uuid = "13";
  while ((c = getopt (argc, argv, "f:u:")) != -1)
    switch (c) {
      case 'u':
        uuid = optarg;
        break;
      case 'f':
        file = optarg;
        break;
      ...
    }
  }
  ...
}

然后,应用可以使用先前存储在状态文件中的信息来恢复其状态,然后使用以下信息再次向会话管理器注册:

jack_client *client;
client = jack_client_open("myapp", JackSessionID, NULL, uuid);
jack_set_session_callback(client, session_callback, NULL);

每当会话管理器需要与应用通信时,就调用回调函数session_callback。它需要一个jack_session_event和作为最后一个参数传递给jack_set_session_callback的任何东西。

回调的工作是保存状态信息,将信息传递回会话管理器,并可能退出。

int session_callback(jack_session_event_t *ev) {
  char filename[256];
  char command[256];

  snprintf(filename, sizeof(filename), "%smyfile.state", ev->session_dir);
  snprintf(command,  sizeof(command),
           "my_app -u %s -f ${SESSION_DIR}myfile.state", ev->client_uuid);
  your_save_function(filename);
  ev->command_line = strdup(command);
  jack_session_reply(jack_client, ev);
  if(ev->type == JackSessionSaveAndQuit)
      quit();
  jack_session_event_free(ev);
  return 0;
}

trac 建议( http://trac.jackaudio.org/wiki/WalkThrough/Dev/JackSession )如果这是在 GTK 这样的多线程环境中运行,那么应该在其他线程空闲的时候运行,比如用g_idel_add

我可以用第七章中的delay程序来说明这一点。添加额外的代码给出了修改后的delay.c。我用#ifdef JACK_SESSION附上了额外的代码,以便于查看变化。

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

#define JACK_SESSION

#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>

#ifdef JACK_SESSION

#include <jack/session.h>

#endif

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 ...\n" );
    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 );
}

#ifdef JACK_SESSION

/*

 * Callback function for JS

 */

void session_callback(jack_session_event_t *ev, void *args) {

    char command[256];

    snprintf(command,  sizeof(command),

             "/home/httpd/html/LinuxSound/Sampled/SessionManagement/delay -u %s",

             ev->client_uuid);

    ev->flags = JackSessionNeedTerminal;

    ev->command_line = strdup(command);

    jack_session_reply(client, ev);

    if(ev->type == JackSessionSaveAndQuit)

         jack_shutdown(NULL);

    jack_session_event_free(ev);

}

#endif

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

#ifdef JACK_SESSION

    /*

     * Extra code for JS

     */

    int c;

    char *uuid = "13";

    while ((c = getopt (argc, argv, "u:")) != -1)

        switch (c) {

        case 'u':

            uuid = optarg;

            break;

        }

    printf("UUID is %s\n", uuid);

#endif

    client_name = strrchr ( argv[0], '/' );
    if ( client_name == 0 ) {
        client_name = argv[0];
    }
    else {
        client_name++;
    }

    /* open a client connection to the JACK server */
    /* Changed args for JS */

#ifdef JACK_SESSION

    client = jack_client_open ( client_name, JackSessionID, &status, uuid);

#else

    client = jack_client_open ( client_name, JackNullOption, &status);

#endif

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

#ifdef JACK_SESSION

    /* Set callback function for JS

     */

    jack_set_session_callback(client, session_callback, NULL);

#endif

    /* 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\n" );
                    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\n" );
            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\n" );

    free ( ports );

    ports = jack_get_ports ( client, NULL, NULL, JackPortIsPhysical|JackPortIsInput );
    if ( ports == NULL )
        {
            fprintf ( stderr, "no physical playback ports\n" );
            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\n" );

    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 );
}

拉迪什蜜蜂

如果一个应用支持 Jack 会话,那么 LADISH GUI 工具gladish可以将该应用作为 1 级应用来管理。换句话说,gladish可以平等地管理 Jack 会话和 LADISH 客户端。从这个意义上说,除非您更喜欢 LADISH 的会话管理方式,否则没有必要在应用中额外添加 LADISH 意识。

关于如何在 1 级构建 LADISH-aware 应用,参见 http://ladish.org/wiki/code_examples 。对于 LADI 会话处理程序,参见 http://ladish.org/

结论

本章介绍了一些会话管理系统。所涵盖的会话管理器集并不详尽。访问 http://lwn.net/Articles/533594/ 获得更多列表,例如非会话管理器和 Chino。然而,情况并不特别令人满意,还有很大的改进余地。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值