Jack Audio 编程简介 - 哆啦比猫的技术瞎扯 - Arch Linux · ドラえもん · 实时绘制

Jack Audio 编程简介

哆啦比猫 posted @ 2013年1月13日 14:53 in 音乐 with tags audio jack , 9764 阅读

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

blog comments powered by Disqus
© 2010-2019 Giumo Xavier Clanjor (哆啦比猫/兰威举).
© 2013, 2014, 2015-2016 and 2017 The Dark Colorscheme Designed by Giumo Xavier Clanjor (哆啦比猫/兰威举).
知识共享署名·非商业性使用·相同方式共享 3.0 中国大陆许可协议
| © 2007 LinuxGem | Design by Matthew "Agent Spork" McGee