Jack Audio 编程简介 - 哆啦比猫的技术瞎扯 - Arch Linux · ドラえもん · 实时绘制
Jack Audio 编程简介
Jack 的两个特点促使我开始学习它:
- 非“读/写”模型。Jack 通过回调函数获取数据,然后由 Jack Server 实现同步、混合、路由(routing)等麻烦的东西。
- 可以把一个程序的输出作为另一个程序的输入。目前我知道的音频系统中只有 Jack 能做到。
下面,我们就来实现一个简单“噪音”程序。这个程序在它的输入上加上噪音,然后输出。源文件名为 noise.c。
注:我使用了 Enlightenment Foundation Library 的 Elementary 库来实现 GUI。
首先,实现一个“自我编译功能”的文件头:
#if 0
PKGS=""
PKGS="$PKGS $(pkg-config --cflags --libs elementary)"
PKGS="$PKGS $(pkg-config --cflags --libs jack)"
CCFLAGS="-Wall -Werror -Ofast $PKGS"
case $1 in
clean) rm -f "${0%.c}" ;;
run) [ -x "${0%.c}" ] || bash $0 ; "./${0%.c}" ;;
*) gcc $CCFLAGS -o ${0%.c} $0 ;;
esac
exit
#endif
就可以这样使用:
bash noise.c # 编译 bash noise.c run # 运行 bash noise.c clean # 清理
导入头文件:
#include <stdlib.h> // srand, rand #include <time.h> // time #include <Elementary.h> // for GUI #include <jack/jack.h> // the dearest jack
函数和全局变量声明:
static void end(); static void _change_cb(void * data, Evas_Object * o, void * ev); static int _process_cb(jack_nframes_t nframes, void * arg); static double volumn; static jack_port_t * port_in; static jack_port_t * port_out;
声明了三个函数:
- end:退出程序的回调函数
- _change_cb:音量滑竿改变值后的回调函数
- _process_cb:Jack 的回调函数,稍后解释
和三个全局变量:
- volumn:保存音量大小,取值范围 [0, 100]。
- port_in 和 port_out:jack 的两个端口。port_in 是这个程序的输入,连接到别的程序的输出 port_out 是这个程序的输出,连接到别的程序的输入。
置随机数种子(注:由于使用了 Elementary,程序的入口函数为 elm_main):
EAPI_MAIN int elm_main(int argc, char * argv[])
{
srand(time(NULL));
由于 Jack 采用客户端/服务器模型,我们要先连接到 Jack 服务器:
jack_client_t * cli;
if (!(cli = jack_client_open("Noise", 0, NULL))) {
fprintf(stderr, "Oh, where are you, my dear Jack?\n");
return 1;
}
其中,“Noise”是我们的程序的名字,在路由(routing)时会显示这个名字。
接着设置回调函数:
jack_set_process_callback(cli, &_process_cb, NULL); jack_on_shutdown(cli, (void *)&end, NULL);
打开两个端口:
port_in = jack_port_register(cli, "input", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); port_out = jack_port_register(cli, "output", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
“input”和“output”是端口的名字,在路由(routing)时会显示这个名字。
激活自己,表明一切都准备好了:
if (jack_activate(cli)) {
fprintf(stderr, "Oh my dear Jack, are you alive?");
return 1;
}
创建窗口、音量滑竿(与 Jack 无关,这里不做说明):
Evas_Object * win; win = elm_win_add(NULL, "noise", ELM_WIN_BASIC); elm_win_title_set(win, "Jack Noise Generator"); evas_object_smart_callback_add(win, "delete,request", (void *)&end, NULL); Evas_Object * bg; bg = elm_bg_add(win); evas_object_size_hint_weight_set(bg, 1.0, 1.0); elm_win_resize_object_add(win, bg); evas_object_show(bg); Evas_Object * slider; slider = elm_slider_add(win); elm_object_text_set(slider, "volumn:"); elm_slider_unit_format_set(slider, "%1.1f%%"); elm_slider_min_max_set(slider, 0, 100); evas_object_size_hint_weight_set(slider, 1.0, 1.0); elm_win_resize_object_add(win, slider); evas_object_smart_callback_add(slider, "changed", &_change_cb, NULL); evas_object_show(slider); evas_object_show(win); elm_run(); elm_shutdown();
窗口关闭后要关掉 Jack:
jack_client_close(cli); return 0; } ELM_MAIN()
常规的回调函数,不解释:
static void end()
{
elm_exit();
}
static void _change_cb(void * data, Evas_Object * o, void * ev)
{
volumn = elm_slider_value_get(o);
}
前面说的 _process_cb 函数:
static int _process_cb(jack_nframes_t nframes, void * arg)
nframes 是缓存的大小。缓存是 32 位浮点型的。按 Jack 的要求,缓存中数据的取值范围是[-1, 1]。对于 arg,习惯回调式编程的人应该很熟悉,就是在设置回调函数时给的参数,如下面代码中的 NULL。
jack_set_process_callback(cli, &_process_cb, NULL);
接着就是获取输入端的缓存和输出端的缓存:
{
float * in = jack_port_get_buffer(port_in , nframes);
float * out = jack_port_get_buffer(port_out, nframes);
如果只想把输入复制到输出,memcpy 一下就好了。这里我们要在 in 里加上噪音然后输出出去:
jack_nframes_t i;
for(i=0; i<nframes; i++) {
double noise = rand() & 0xFFFF;
noise /= (double)0xFFFF;
noise = noise * 2 - 1;
noise *= volumn / 100.0;
out[i] = in[i] + noise;
}
return 0;
}
注意,由于我们只是加噪音,无需知道采样率等信息,所以这样就可以了。要获取采样率也得设置一个回调函数。Jack 默认的采样率是 48kHz;Jack 只支持 32 位 IEEE 浮点数;Jack 的每个端口都是“单声道”的,要想要立体声就得开两个端口。
更多信息可以从 Jack 官网上获得:http://jackaudio.org/
让我们来测试一下:

完整代码如下:
#if 0
# compile by: bash noise.c
# run by: bash noise.c run
# clean by: bash noise.c clean
PKGS=""
PKGS="$PKGS $(pkg-config --cflags --libs elementary)"
PKGS="$PKGS $(pkg-config --cflags --libs jack)"
CCFLAGS="-Wall -Werror -Ofast $PKGS"
case $1 in
clean) rm -f "${0%.c}" ;;
run) [ -x "${0%.c}" ] || bash $0 ; "./${0%.c}" ;;
*) gcc $CCFLAGS -o ${0%.c} $0 ;;
esac
exit
#endif
// vim: noet ts=4 sw=4 sts=0
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <Elementary.h>
#include <jack/jack.h>
static void end();
static void _change_cb(void * data, Evas_Object * o, void * ev);
static int _process_cb(jack_nframes_t nframes, void * arg);
static double volumn;
static jack_port_t * port_in;
static jack_port_t * port_out;
EAPI_MAIN int elm_main(int argc, char * argv[])
{
srand(time(NULL));
// init jack
jack_client_t * cli;
if (!(cli = jack_client_open("Noise", 0, NULL))) {
fprintf(stderr, "Oh, where are you, my dear Jack?\n");
return 1;
}
jack_set_process_callback(cli, &_process_cb, NULL);
jack_on_shutdown(cli, (void *)&end, NULL);
port_in = jack_port_register(cli, "input",
JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
port_out = jack_port_register(cli, "output",
JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
if (jack_activate(cli)) {
fprintf(stderr, "Oh my dear Jack, are you alive?");
return 1;
}
// init GUI
Evas_Object * win;
win = elm_win_add(NULL, "noise", ELM_WIN_BASIC);
elm_win_title_set(win, "Jack Noise Generator");
evas_object_smart_callback_add(win,
"delete,request", (void *)&end, NULL);
Evas_Object * bg;
bg = elm_bg_add(win);
evas_object_size_hint_weight_set(bg, 1.0, 1.0);
elm_win_resize_object_add(win, bg);
evas_object_show(bg);
Evas_Object * slider;
slider = elm_slider_add(win);
elm_object_text_set(slider, "volumn:");
elm_slider_unit_format_set(slider, "%1.1f%%");
elm_slider_min_max_set(slider, 0, 100);
evas_object_size_hint_weight_set(slider, 1.0, 1.0);
elm_win_resize_object_add(win, slider);
evas_object_smart_callback_add(slider, "changed", &_change_cb, NULL);
evas_object_show(slider);
evas_object_show(win);
elm_run();
elm_shutdown();
jack_client_close(cli);
return 0;
}
ELM_MAIN()
static void end()
{
elm_exit();
}
static void _change_cb(void * data, Evas_Object * o, void * ev)
{
volumn = elm_slider_value_get(o);
}
static int _process_cb(jack_nframes_t nframes, void * arg)
{
float * in = jack_port_get_buffer(port_in , nframes);
float * out = jack_port_get_buffer(port_out, nframes);
jack_nframes_t i;
for(i=0; i<nframes; i++) {
double noise = rand() & 0xFFFF;
noise /= (double)0xFFFF;
noise = noise * 2 - 1;
noise *= volumn / 100.0;
out[i] = in[i] + noise;
}
return 0;
}
凡未特殊声明(转载/翻译),所有文章均为原创。
by Giumo Xavier Clanjor (哆啦比猫/兰威举), 2010-2019.
本作品采用知识共享署名·非商业性使用·相同方式共享 3.0 中国大陆许可协议进行许可。
文中凡未特殊声明且未声明为引用的代码均以 MIT 协议授权。