譯者: Kevin Lei
轉(zhuǎn)載請注明出處:http://kevinlei.cublog.cn
1. 介紹
這個HOWTO計劃提供一個簡短的介紹,使用ALSA 0.9.0寫一個簡單的音頻應(yīng)用程序.
Section2解釋了PCM音頻最基本的函數(shù).如果你刪除其中的解釋文本,那么最后你會得到一個極小的PCM回放程序. Section3簡短地討論了一些PCM截獲函數(shù).
在Section4你將學(xué)習(xí)怎么為ALSA音序器寫一個簡單的客戶端.這一章節(jié)基于seqdemo.c這個例子,一個可以接收MIDI事件并且表示大多數(shù)重要的事件類型的程序.Section5演示ALSAMIDI音序器怎樣被用于"路由"MIDI事件,從一個輸入端口到一些輸出端口.這一段基于midiroute.c這個例子.
Section6使PCM回放和MIDI輸入聯(lián)結(jié)起來,并且解釋了迷你合成器--miniFMsynth.c.這一章節(jié)引入基于回調(diào)的音頻回放,就像PaulDavis在linux-audio-dev郵件列表里建議的那樣.Section7對基于ALSA音序器隊列的MIDI調(diào)度提供一個短小的介紹,基于miniArp.c這個例子.
推薦你也看看doxygen生成的ALSA庫函數(shù)參考.
編譯一個ALSA應(yīng)用程序:
只要用-lasound參數(shù)并且確保你已包含了
#include
2. 基本PCM音頻
為ALSA 0.9.0寫一個簡單的PCM應(yīng)用程序我們首先需要一個PCM設(shè)備的句柄.然后我們必須指定同時可供回放或截獲的PCM流的方向.我們同樣必須提供一些關(guān)于我們想要使用的設(shè)置選項的信息,比如緩沖區(qū)大小,采樣率,PCM數(shù)據(jù)格式等.所以,首先我們聲明:
??
??? /* Handle for the PCM device */?
??? snd_pcm_t *pcm_handle;??????????
??? /* Playback stream */
??? snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
??? /* This structure contains information about??? */
??? /* the hardware and can be used to specify the? */??????
??? /* configuration to be used for the PCM stream. */?
??? snd_pcm_hw_params_t *hwparams;
??
最重要的ALSA PCM設(shè)備接口是"plughw"和"hw"接口.如果你使用"plughw"接口,你不需要很在意聲卡硬件.如果你的聲卡不支持你指定的采樣率或采樣格式,你的數(shù)據(jù)將自動被轉(zhuǎn)換.這同樣適用于聲道的訪問類型和號碼.對于"hw"接口,你必須檢查你的硬件是否支持你想要使用的設(shè)置選項.
??? /* Name of the PCM device, like plughw:0,0????????? */
??? /* The first number is the number of the soundcard, */
??? /* the second number is the number of the device.?? */
??? char *pcm_name;
??
然后我們初始化變量并分配一個hwparams結(jié)構(gòu):
??? /* Init pcm_name. Of course, later you */
??? /* will make this configurable ;-)???? */
??? pcm_name = strdup("plughw:0,0");
??
??? /* Allocate the snd_pcm_hw_params_t structure on the stack. */
??? snd_pcm_hw_params_alloca(&hwparams);
??
現(xiàn)在我們可以打開PCM設(shè)備:
??? /* Open PCM. The last parameter of this function is the mode. */
??? /* If this is set to 0, the standard mode is used. Possible?? */
??? /* other values are SND_PCM_NONBLOCK and SND_PCM_ASYNC.?????? */?
??? /* If SND_PCM_NONBLOCK is used, read / write access to the??? */
??? /* PCM device will return immediately. If SND_PCM_ASYNC is??? */
??? /* specified, SIGIO will be emitted whenever a period has???? */
??? /* been completely processed by the soundcard.??????????????? */
??? if (snd_pcm_open(&pcm_handle, pcm_name, stream, 0) < 0) {
????? fprintf(stderr, "Error opening PCM device %sn", pcm_name);
????? return(-1);
??? }
??
在我們可以往聲卡寫PCM數(shù)據(jù)之前,我們必須指定訪問類型,采樣格式,采樣率,聲道號碼,周期數(shù)目以及周期大小.首先,我們以聲卡的全部設(shè)置選項空間來初始化hwparams結(jié)構(gòu).
??? /* Init hwparams with full configuration space */
??? if (snd_pcm_hw_params_any(pcm_handle, hwparams) < 0) {
????? fprintf(stderr, "Can not configure this PCM device.n");
????? return(-1);
??? }
??
Information about possible configurations can be obtained with a set of functions named
關(guān)于合適的設(shè)置選項的信息,能以函數(shù)命名的一個集合獲得.
??? snd_pcm_hw_params_can_
??? snd_pcm_hw_params_is_
??? snd_pcm_hw_params_get_
??
絕大多數(shù)重要的參數(shù)的可用性,換句話說,訪問類型,緩沖區(qū)大小,聲道號碼,采樣格式,采樣率,以及周期數(shù)目,可以以函數(shù)命名的一個集合來檢驗.
??? snd_pcm_hw_params_test_
如果"hw"接口被使用,這些查詢函數(shù)尤其重要.設(shè)置選項空間能以一個函數(shù)命名的集合限制在某一設(shè)置選項
??? snd_pcm_hw_params_set_
??
例如,我們假設(shè)聲卡可以被設(shè)置為16位,Little Endian數(shù)據(jù)的立體聲回放,44100Hz采樣率.相應(yīng)地,我們限制設(shè)置選項空間匹配于這個設(shè)置選項:
??? int rate = 44100; /* Sample rate */
??? int exact_rate;?? /* Sample rate returned by */
????????????????????? /* snd_pcm_hw_params_set_rate_near */?
??? int dir;????????? /* exact_rate == rate --> dir = 0 */
????????????????????? /* exact_rate < rate? --> dir = -1 */
????????????????????? /* exact_rate > rate? --> dir = 1 */
??? int periods = 2;?????? /* Number of periods */
??? snd_pcm_uframes_t periodsize = 8192; /* Periodsize (bytes) */
??
訪問類型指定了哪一個多聲道數(shù)據(jù)儲存在緩沖區(qū)的方法.對于交錯訪問,緩沖區(qū)里的每一個幀為聲道容納連續(xù)的采樣數(shù)據(jù).對于16位立體聲數(shù)據(jù),這意味著緩沖區(qū)以字為單位為左右聲道交錯地容納采樣數(shù)據(jù).對于非交錯訪問,每一個周期為第一個聲道容納所有采樣數(shù)據(jù)接著是第二個聲道的采樣數(shù)據(jù).
??
??? /* Set access type. This can be either??? */
??? /* SND_PCM_ACCESS_RW_INTERLEAVED or?????? */
??? /* SND_PCM_ACCESS_RW_NONINTERLEAVED.????? */
??? /* There are also access types for MMAPed */
??? /* access, but this is beyond the scope?? */
??? /* of this introduction.????????????????? */
??? if (snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
????? fprintf(stderr, "Error setting access.n");
????? return(-1);
??? }
??
??? /* Set sample format */
??? if (snd_pcm_hw_params_set_format(pcm_handle, hwparams, SND_PCM_FORMAT_S16_LE) < 0) {
????? fprintf(stderr, "Error setting format.n");
????? return(-1);
??? }
??? /* Set sample rate. If the exact rate is not supported */
??? /* by the hardware, use nearest possible rate.???????? */?
??? exact_rate = rate;
??? if (snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &exact_rate, 0) < 0) {
????? fprintf(stderr, "Error setting rate.n");
????? return(-1);
??? }
??? if (rate != exact_rate) {
????? fprintf(stderr, "The rate %d Hz is not supported by your hardware.n?
?????????????????????? ==> Using %d Hz instead.n", rate, exact_rate);
??? }
??? /* Set number of channels */
??? if (snd_pcm_hw_params_set_channels(pcm_handle, hwparams, 2) < 0) {
????? fprintf(stderr, "Error setting channels.n");
????? return(-1);
??? }
??? /* Set number of periods. Periods used to be called fragments. */?
??? if (snd_pcm_hw_params_set_periods(pcm_handle, hwparams, periods, 0) < 0) {
????? fprintf(stderr, "Error setting periods.n");
????? return(-1);
??? }
緩沖區(qū)尺寸的單元依賴于函數(shù).一些時候是字節(jié),一些時候是必須指定的幀的數(shù)目.一個幀是對所有聲道的采樣數(shù)據(jù)數(shù)組.對于16位立體聲數(shù)據(jù),一個幀的長度是4個字節(jié).
??? /* Set buffer size (in frames). The resulting latency is given by */
??? /* latency = periodsize * periods / (rate * bytes_per_frame)???? */
??? if (snd_pcm_hw_params_set_buffer_size(pcm_handle, hwparams, (periodsize * periods)>>2) < 0) {
????? fprintf(stderr, "Error setting buffersize.n");
????? return(-1);
??? }
??
如果你的硬件不支持2的N次方的緩沖區(qū)大小,你可以使用snd_pcm_hw_params_set_buffer_size_near函數(shù).這個函數(shù)工作起來與snd_pcm_hw_params_set_rate_near相似.現(xiàn)在我們?yōu)镻CM設(shè)備申請由pcm_handle指向的設(shè)置選項.這同樣也將準(zhǔn)備好PCM設(shè)備.
??
??? /* Apply HW parameter settings to */
??? /* PCM device and prepare device? */
??? if (snd_pcm_hw_params(pcm_handle, hwparams) < 0) {
????? fprintf(stderr, "Error setting HW params.n");
????? return(-1);
??? }
??
在PCM設(shè)備被設(shè)置以后,我們可以開始對其寫PCM數(shù)據(jù).第一個寫訪問將開始PCM回放.對于交錯寫訪問,我們使用函數(shù):
???
??? /* Write num_frames frames from buffer data to??? */?
??? /* the PCM device pointed to by pcm_handle.?????? */
??? /* Returns the number of frames actually written. */
??? snd_pcm_sframes_t snd_pcm_writei(pcm_handle, data, num_frames);
??
對于非交錯訪問,我們將必須使用函數(shù):
??? /* Write num_frames frames from buffer data to??? */?
??? /* the PCM device pointed to by pcm_handle.?????? */?
??? /* Returns the number of frames actually written. */
??? snd_pcm_sframes_t snd_pcm_writen(pcm_handle, data, num_frames);
??
在PCM回放開始之后,我們必須確保我們的應(yīng)用程序發(fā)送足夠的數(shù)據(jù)到聲卡緩沖區(qū).否則,將發(fā)生緩沖區(qū)欠載.當(dāng)這樣一個緩沖區(qū)欠載發(fā)生以后,snd_pcm_prepare將被調(diào)用.一個簡單的立體聲鋸齒波能以這樣的方式生成:
??? unsigned char *data;
??? int pcmreturn, l1, l2;
??? short s1, s2;
??? int frames;
??? data = (unsigned char *)malloc(periodsize);
??? frames = periodsize >> 2;
??? for(l1 = 0; l1 < 100; l1++) {
????? for(l2 = 0; l2 < num_frames; l2++) {
??????? s1 = (l2 % 128) * 100 - 5000;??
??????? s2 = (l2 % 256) * 100 - 5000;??
??????? data[4*l2] = (unsigned char)s1;
??????? data[4*l2+1] = s1 >> 8;
??????? data[4*l2+2] = (unsigned char)s2;
??????? data[4*l2+3] = s2 >> 8;
????? }
????? while ((pcmreturn = snd_pcm_writei(pcm_handle, data, frames)) < 0) {
??????? snd_pcm_prepare(pcm_handle);
???????fprintf(stderr,"<<<<<<<<<<<<<<< BufferUnderrun>>>>>>>>>>>>>>>n");
????? }
??? }
如果我們想中止回放,我們既可以使用snd_pcm_drop,也可以使用snd_pcm_drain.第一個函數(shù)將立即中止回放并丟棄未回放的幀.后一個函數(shù)將在回放完所有幀后中止回放.
??? /* Stop PCM device and drop pending frames */
??? snd_pcm_drop(pcm_handle);
??? /* Stop PCM device after pending frames have been played */?
??? snd_pcm_drain(pcm_handle);
(全文完)
參考鏈接:
原文
http://www.suse.de/~mana/alsa090_howto.html
Linux音頻編程指南
http://www-128.ibm.com/developerworks/cn/linux/l-audio/
A Tutorial on Using the ALSA Audio API
http://www.equalarea.com/paul/alsa-audio.html
The ALSA library API reference
http://www.alsa-project.org/alsa-doc/alsa-lib/