协议: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 API (
http://jackaudio.org/files/docs/html/index.html
) - 【认识 Jack】(
http://linux-sound.org/knowing-jack.html
) - ArchLinux Pro 音频(
https://wiki.archlinux.org/index.php/Pro_Audio
) - 巴布亚 Jack(
http://gentoo-en.vfose.ru/wiki/JACK
) - 保罗·戴维斯谈 Jack 建筑(
http://lac.linuxaudio.org/2003/zkm/recordings/paul_davis-jack.ogg
) - 《认识 Jack》(
www.linux-magazine.com/content/download/63041/486886/version/1/file/JACK_Audio_Server.pdf
)戴夫·菲利普斯在 Linux 杂志上的文章 - 用 JACK (
http://dis-dot-dat.net/index.cgi?item=/jacktuts/starting/
)编写音频应用
启动千斤顶
大多数发行版的软件仓库中都有 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 用户运行服务器。
-
将用户添加到
audio
和jackuser
组,如下所示:
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 文件一起唱。
-
将麦克风采集端口连接到回放端口。避免在笔记本电脑的麦克风和扬声器之间建立反馈回路,例如,插入耳机。
-
启动一个播放器,比如
mplayer
,它也将连接到播放端口,如下所示:mplayer -ao jack <MP3 file >
-
开始唱歌。
当然,每个信号源都没有音量控制。你可以在你的发行版中插入一个混音器,比如 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 应用的机制:
- 打开与 Jack 服务器的连接:
jack_client_open
。 - 如果需要,检查连接和紧急援助的状态。
- 安装一个进程回调处理器来管理 I/O:
jack_set_process_callback
。 - 安装一个关机回调:
jack_on_shutdown
。 - 向 Jack 服务器注册输入和输出端口:
jack_port_register
。请注意,每个端口只携带一个单声道通道,因此对于立体声,您将获得两个输入端口。这还没有将它们与管道联系起来。 - 激活端口。换句话说,告诉 Jack 启动它的处理线程:
jack_activate
。 - 将端口连接到管道:
jack_connect
。 - 以某种方式坐在那里。对于文本客户端,只需循环休眠即可。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.c
和alsa_out.c
。它们包含作者的评论,比如"// Alsa stuff… i dont want to touch this bullshit in the next years.... please…"
,给你一个合理的警告,对于一般的 Jack 编程来说,这并不容易,也没有必要。
结论
这一章从用户的角度讲述了 Jack 的使用,也讲述了 Jack 客户端的编程。
八、会话管理
一个复杂的声音系统可能由多个声源、多个滤波器和多个输出组成。如果每次使用它们都必须重新设置,那么就会出现错误,浪费时间,等等。会话管理试图解决这些问题。
资源
以下是一些资源:
- “Linux 音频会话管理器的简要调查”作者戴夫·菲利普斯(
http://lwn.net/Articles/533594/
) - “会话管理概述”(
http://wiki.linuxaudio.org/wiki/session_management
)包含许多资源的链接 - JACK-AUDIO-CONNECTION-KIT,客户端会话 API(
http://jackaudio.org/files/docs/html/group__SessionClientFunctions.html
) - LADI 会话处理器(
http://ladish.org/
) - 非会话管理 API (
http://non.tuxfamily.org/nsm/API.html
会话管理问题
每当有多个模块以某种方式链接时,就需要管理这些模块及其链接。这些需求在 Jack 环境中很快出现,它是为多重链接而设计的。管理变得繁琐并不需要复杂的插孔模块排列。例如,考虑图 8-1 中上一章的混音器会话。
图 8-1。
Jack connecting multiple applications
从头开始设置需要以下条件:
- 启动
jackd
和qjackctl
。 - 开始
jack_mixer
。 - 打开混音器上的两组新输入端口。
- 将主混音器输出端口连接到回放端口。
- 将麦克风端口连接到一组混音器输入端口。
- 启动
mplayer
,自动连接到播放端口。 - 从回放端口断开
mplayer
输出端口,并将它们重新连接到另一组混音器输入端口。
你不想每次放一首歌都这样吧!
LADISH 会话管理器通过会话管理器( http://ladish.org/wiki/levels
)识别应用的不同控制级别。除去对特定管理器和框架的显式引用,级别如下:
- 级别 0:应用没有链接到会话处理库。用户必须手动保存应用项目,或者依赖应用的自动保存支持。
- 级别 1:应用没有链接到会话处理库。当接收到特定的消息或信号时,应用进行保存。
- 第 2 级:应用链接到会话管理库。由于会话管理器的限制,它与会话处理程序的交互是有限的。
- 级别 3:应用链接到复杂的会话管理器。它与会话处理程序有完全的交互。
正如 Dave Phillips 所指出的,“使用这些级别是为了对任何 Linux 音频应用的各种可能条件进行分类和调整。这些条件包括符合 JACK 的程度、任何 WINE 或 DOS 要求、网络操作、现有 API 的多样性等等。”
当前一批用于 Linux 音频的会话管理框架包括
- 讽刺
- Jack 会话管理
- 拉迪什
- 非会话管理器
- 奇诺
多个管理器的存在意味着大多数应用将只支持一个或至多几个协议。如果您选择一个特定的管理器,那么您将被限制在您可以在其控制下运行的应用。
插孔 _ 连接
程序jack_connect
和jack_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
不会自动重新映射它的连接。
这里的问题是mplayer
和jack_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。然而,情况并不特别令人满意,还有很大的改进余地。