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 协议授权。