1. 程式人生 > >ALSA聲音程式設計介紹

ALSA聲音程式設計介紹

ALSA是Advanced Linux Sound Architecture簡稱。它包含一組kernel 驅動,一個應用程式設計介面(API)庫以及一組工具函式。本文中,我們會向讀者展示ALSA專案和組成部件的概況。後面會重點介紹ALSA PCM介面的程式設計。

ALSA不僅僅是sound API。選擇ALSA可以讓你最大程度的控制和執行執行低階的audio函式,或者使用其它sound API不支援的特定功能。如果你已經寫了一個audio應用程式,那麼你可能希望為它增加ALSA sound驅動支援。如果你的主要興趣不是audio,僅僅是想播放聲音,那麼你最好使用高階的sound toolkits,比如SDL,OpenAL或者桌面環境提供的其他開發包。如果你想使用ALSA,那麼要確保你的Linux系統支援ALSA。

History of ALSA

ALSA專案的出現是因為Linux kernel sound driver OSS/Free drivers不能得到很好的維護,而導致驅動無法支援新的sound技術。Jaroslav Kysela最初為一個sound card寫了一個驅動,啟動了這個專案,隨著時間的推移越來越多的開發者加入進來,重新定義了API以支援更多的音效卡。

在Linux kernel 2.5的開發過程中,ALSA被merged到官方核心中。隨著kernel2.6的釋出,ALSA成為穩定版核心的一部分,並且得到了廣泛使用。

Digital Audio Basics

聲音由空氣壓力變化的波形組成,被轉換器(如麥克風)轉換為電訊號。一個模數轉換器(ADC)把模擬電壓訊號轉換為離散值,稱為取樣,取樣是按照固定時間間隔進行的,稱為取樣率。傳送這些取樣到數模轉換器(DAC),再輸出到loudspeaker,原始的聲音就被重現了。

取樣的位數大小,是決定聲音數字精度的一個因素,另外一個主要因素是取樣率。奈奎斯特理論指出當訊號頻寬小於1/2取樣率時,可通過取樣訊號還原出原始訊號。

ALSA Basics

ALSA包括支援各種音效卡的Kernel裝置驅動,API庫libasound。應用開發者應該使用API而不是kernel系統呼叫介面。API庫函式提供了高層次,程式設計友好的介面,開發者使用邏輯裝置名而不需要考慮低階細節(如裝置檔案)。

與ALSA相反,OSS/Free要求應用在kernel系統呼叫級進行程式設計,這就需要開發者指定裝置檔名並且使用ioctl來完成功能。為了相容OSS,ALSA提供了核心模組來模擬OSS/Free聲音驅動,所以大部分現存的audio應用都無須修。libaoss是模擬包裝庫,可以用來模擬OSS/Free API而不需要核心模組的模擬。

ALSA還提供了plugins能力,允許擴充套件一個新裝置,甚至包括完全用軟體實現的虛擬裝置。ALSA提供了一組命令列工具,包括mixer,聲音檔案播放,以及對特定音效卡特定功能的控制。

ALSA Architecture

ALSA API可以分為以下幾個主要部分:

  • Control介面:一個通用的功能,用來管理音效卡的暫存器以及查詢可用裝置。
  • PCM 介面:管理數字audio capture和playback的介面,本文的其餘部分將主要介紹著個介面,因為這是audio應用最常用的介面
  • Raw MIDI介面:支援MIDI(Musical Instrument DIgital Interface,電子音樂裝置的標準)。這個API提供了對音效卡MIDI bus的訪問。Raw介面由MIDI events驅動,程式負責管理協議和計時
  • Timer 介面:提供對音效卡上計時硬體的訪問,用於同步聲音事件。
  • Sequencer interface:一個MIPI程式設計和聲音同步介面,比raw MIDI介面級別更高,它管理大部分MIDI協議和計時。
  • Mixer介面:控制音效卡上的訊號路由和音量調節的裝置。它是建立在control介面之上的。

Device Naming

API操作的是邏輯裝置名而不是裝置檔案,裝置名可以是真正的硬體裝置或者外掛。硬體裝置使用hw:i,j這種格式,i是卡號而j是在這個卡上的裝置。第一個聲音裝置是hw:0,0。第一個sound裝置的別名為defalut,本文後面的例子都使用default。Plugins使用另外一種命名模式:例如plughw:是一個外掛除了提供對硬體裝置的訪問還提供某種功能,比如軟體實現取樣率轉換,因為硬體不支援這種操作。dmix外掛允許合成幾路資料 dshare外掛允許把單路資料動態分配到不同的應用中。

Sound Buffers and Data Transfer

音效卡有一個硬體buffer。在錄音時(capture)儲存錄音的取樣值,當buffer填滿後音效卡生成一箇中斷,kernel sound驅動使用DMA傳輸取樣資料到記憶體buffer。類似的在playback時,應用buffer資料通過DMA傳輸給sound card的硬體buffer

這個硬體buffer是一個ring buffers,意味著當到達buffer末端後,就會重新回到起始端。一個指標用來維護硬體buffer和應用buffer的當前位置。在核心外部,僅能訪問application buffer,所以在這裡我們主要討論application buffer。

buffer的尺寸可以通過ALSA庫函式呼叫指定。buffer可以非常的大,一次傳輸完整個buffer的資料可能導致無法接收的延遲,所以,ALSA把這個buffer分割為一系列periods,以period做為傳輸資料的單位。

Period由多個frames組成,每一個frames包含在一個時間點的取樣值,對於立體聲裝置來說,一個frames包含兩個channels的採用,圖1演示了一個buffer, period,sample之間的關係,在這裡,左右聲道的資料儲存在一幀中,這種模式稱為interleaved。對於non-interleaved 模式,所有的左聲道資料存放在一起,然後所有的右聲道儲存在一起。


Over and Under Run

當一個聲音裝置被啟用,資料持續的在硬體和應用buffer間傳送。在錄音情況下(capture),如果應用沒有快速的從buffer中讀取資料,環狀buffer將被新到的資料覆蓋,導致資料丟失,我們稱之為overrun。在playback情況下,如果應用無法快速傳送資料到buffer中,那麼導致hardware無資料可播放,這種情況我們稱之為underrun。ALSA文件有時把這兩種情況統稱為XRUN。正確設計的應用最小化XRUN的發生並且在XRUN發生後能夠恢復操作。

A typical Sound Application

通常可以用下面偽程式碼表示PCM介面程式設計模式:

open interface for capture or playback  
set hardware parameters(access mode, data format, channels, rate, etc.)  
while there is data to be processed:  
    read PCM data(capture) or write PCM data(playback)  
close interface

Listing 1. Display Some PCM Types and Formats

#include <alsa/asoundlib.h>  
  
int main() {  
    int val;  
  
    printf("ALSA library version: %s\n", SND_LIB_VERSION_STR);  
  
    printf("PCM stream types:\n");  
    for (val = 0; val <= SND_PCM_STREAM_LAST; val++) {  
        printf(" %s\n", snd_pcm_stream_name((snd_pcm_stream_t)val));  
    }  
  
    printf("PCM access types:\n");  
    for (val = 0; val <= SND_PCM_ACCESS_LAST; val++) {  
        printf(" %s\n", snd_pcm_access_name((snd_pcm_access_t)val));  
    }  
  
    printf("PCM formats:\n");  
    for (val = 0; val <= SND_PCM_STREAM_LAST; val++) {  
        if (snd_pcm_format_name((snd_pcm_format_t)val) != NULL) {  
            printf(" %s (%s)\n",   
                  snd_pcm_format_name((snd_pcm_format_t)val),  
                  snd_pcm_format_description((snd_pcm_format_t)val));  
        }  
   }  
  
  
   printf("\nPCM subformats:\n;);  
   for (val = 0; val <= SND_PCM_SUBFORMAT_LAST; val++) {  
       printf(" %s (%s)\n", snd_pcm_subformat_name((snd_pcm_subformat_t)val),  
           snd_pcm_subformat_description((snd_pcm_subformat_t)val));  
   }  
   return 0;  
}

gcc -o test test.c -lasound

編譯為可執行檔案,程式必須連結ALSA庫,libasound。有些ALSA庫函式需要使用dlopen功能和浮點運算,所以有時需要增加-ldl和-lmgcc -o listing1 listing1.c -lasound在我的機器上,執行結果如下

ALSA library version: 1.0.22  
  
PCM stream types:  
 PLAYBACK  
 CAPTURE  
  
PCM access types:  
 MMAP_INTERLEAVED  
 MMAP_NONINTERLEAVED  
 MMAP_COMPLEX  
 RW_INTERLEAVED  
 RW_NONINTERLEAVED  
  
PCM formats:  
 S8 (Signed 8 bit)  
 U8 (Unsigned 8 bit)  
  
PCM subformats:  
 STD (Standard)

listing1 展示了一些ALSA使用的PCM資料型別和引數。需要包含標頭檔案alsa/asoundlib.h,ALSA庫函式以及常用巨集都在這個檔案中定義。這個程式的其餘部分重複的列印了PCM資料型別。

Listing 2. Opening PCM Device and Setting Parameters

/* 
    This example opens the default PCM device, sets some parameters, 
and then displays the value of most of the hardware parameters. It does 
not perform any sound playback or recording. 
*/
/* Use the newer ALSA API */  
#define ALSA_PCM_NEW_HW_PARAMS_API  
  
/* All of the ALSA library API is defined in this header */  
  
#include <alsa/asoundlib.h>  
  
int main()  
{  
    int rc;  
    snd_pcm_t *handle;  
    snd_pcm_hw_params_t *params;  
    unsigned int val, val2;  
    int dir;  
    snd_pcm_uframes_t frames;  
  
    /* Open PCM device for playback */  
    rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);  
    if (rc < 0) {  
        printf("unable to open pcm device\n");  
    }  
  
    /* Allocate a hardware parameters object */  
    snd_pcm_hw_params_alloca(¶ms);  
  
    /* Fill it in with default values. */  
    snd_pcm_hw_params_any(handle, params);  
  
    /*Set the desired hardware parameters. */  
  
    /* Interleaved mode */  
    snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);  
  
    /* Signed 16-bit little-endian format */  
    snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);  
  
    /* Two channels (stero) */  
    snd_pcm_hw_params_set_channels(handle, params, 2);  
  
    /* 44100 bits/second sampling rate */  
    val = 44100;  
    snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);  
  
    /* Write the parameters to the dirver */  
    rc = snd_pcm_hw_params(handle, params);  
    if (rc < 0) {  
        printf("unable to set hw parameters: %s\n", snd_strerror(rc));  
        exit(1);  
    }  
  
    /* Display information about the PCM interface */  
    printf("PCM handle name = '%s'\n", snd_pcm_name(handle));  
  
    printf("PCM state = %s\n", snd_pcm_state_name(snd_pcm_state(handle)));  
  
    snd_pcm_hw_params_get_format(params, &val);  
    printf("format = '%s'(%s)\n", snd_pcm_format_name((snd_pcm_format_t)val),   
             snd_pcm_format_description((snd_pcm_format_t)val));  
  
    snd_pcm_hw_params_get_subformat(params, (snd_pcm_subformat_t *)&val);  
    printf("subformat = '%s' (%s)\n", snd_pcm_subformat_name((snd_pcm_subformat_t)val),  
             snd_pcm_subformat_description((snd_pcm_subformat_t)val));  
  
    snd_pcm_hw_params_get_channels(params, &val);  
    printf("channels=%d\n", val);  
  
    snd_pcm_hw_params_get_rate(params, &val, &dir);  
    printf("rate = %d bps\n", val);  
  
    snd_pcm_hw_params_get_period_time(params, &val, &dir);  
    printf("period time = %d us\n", val);  
  
    snd_pcm_hw_params_get_period_size(params, &frames, &dir);  
    printf("period size = %d frames\n", (int)frames);  
  
    snd_pcm_hw_params_get_buffer_time(params, &val, &dir);  
    printf("buffer time = %d us\n", val);  
  
    snd_pcm_hw_params_get_buffer_size(params, (snd_pcm_uframes_t *) &val);  
    printf("buffer size = %d frames\n", val);  
  
    snd_pcm_hw_params_get_periods(params, &val, &dir);  
    printf("periods per buffer = %d frames\n", val);  
  
    snd_pcm_hw_params_get_rate_numden(params, &val, &val2);  
    printf("exact rate=%d/%d bps\n", val, val2);  
  
    val = snd_pcm_hw_params_get_sbits(params);  
    printf("significant bits = %d\n", val);  
  
    snd_pcm_hw_params_get_tick_time(params, &val, &dir);  
    printf("tick time = %d us\n", val);  
  
    val = snd_pcm_hw_params_is_batch(params);  
    printf("is batch = %d\n", val);  
  
    val = snd_pcm_hw_params_is_block_transfer(params);  
    printf("is block transfer = %d\n", val);  
  
    val = snd_pcm_hw_params_is_double(params);  
    printf("is double = %d\n", val);  
  
    val = snd_pcm_hw_params_is_half_duplex(params);  
    printf("is half duplex = %d\n", val);  

    val = snd_pcm_hw_params_is_joint_duplex(params);  
    printf("is joint duplex = %d\n", val);  

    val = snd_pcm_hw_params_can_overrange(params);  
    printf("can overrange = %d\n", val);  
  
    val = snd_pcm_hw_params_can_mmap_sample_resolution(params);  
    printf("can mmap = %d\n", val);  
  
    val = snd_pcm_hw_params_can_pause(params);  
    printf("can overrange = %d\n", val);  
  
    val = snd_pcm_hw_params_can_sync_start(params);  
    printf("can sync start = %d\n", val);  
    snd_pcm_close(handle);  

    return 0;  
}

執行結果為

PCM handle name = 'default'  
PCM state = PREPARED  
format = 'S16_LE'(Signed 16 bit Little Endian)  
subformat = 'STD' (Standard)  
channels=2  
rate = 44100 bps  
period time = 23219 us  
period size = 1024 frames  
buffer time = 23219 us  
buffer size = 1048576 frames  
periods per buffer = 1024 frames  
exact rate=44100/1 bps  
significant bits = 16  
tick time = 0 us  
is batch = 0  
is block transfer = 1  
is double = 0  
is half duplex = 0  
is joint duplex = 0  
can overrange = 0  
can mmap = 0  
can overrange = 1  
can sync start = 0

List2開啟預設的PCM裝置,設定一些引數,然後顯示大部分的硬體引數。這個測試程式不會執行playback和recording。呼叫snd_pcm_open開啟預設的PCM裝置,開啟模式為PLAYBACK。這個函式通過第一個引數返回一個控制代碼,後續的操作使用這個控制代碼操作這個PCM裝置。像大部分ALSA庫函式呼叫,snd_pcm_open返回一個整數表示呼叫狀態,負數指明出錯原因。為了使測試程式碼更簡潔,我忽略了大部分的函式返回值。在產品級應用中,開發者應該檢查每一個API呼叫,以便提供相應的出錯處理。

為了設定流的硬體引數,我們需要分配一個snd_pcm_hw_param_t變數,首先呼叫macro snd_pcm_hw_params_alloca,接下來使用snd_pcm_hw_params_any來初始化這個變數。接下來使用ALSA提供的API設定PCM 流的硬體引數,這些API的引數形式為(PCM handle, snd_pcm_hw_param_t *, val)。我們設定流為interleaved mode,16 bit sample size,2 channels和44100Hz取樣率。對於取樣率,音效卡硬體不一定支援指定的取樣率。我們使用函式snd_pcm_hw_params_set_rate_near請求設定指定值附近的取樣率。在呼叫snd_pcm_hw_params函式之前,所有設定的硬體引數並不會被啟用。

這個程式的其他部分獲得並顯示PCM 流的引數,包括週期和buffer sizes。顯示結果依賴於測試機器的硬體。

在你的機器上執行這個程式,並嘗試做一些修改。比如把device名從defalut改為hw:0,0或者plughw:檢視結果是否改變。修改硬體引數的值,檢視顯示部分的變化。

Listing 3. SImple Sound Playback

/* 
 * This example reads standard from input and writes to  
 * the default PCM device for 5 seconds of data. 
 */
/* Use the newer ALSA API */  
#define ALSA_PCM_NEW_HW_PARAMS_API  
  
#include <alsa/asoundlib.h>  
int main()  
{  
    long loops;  
    int rc;  
    int size;  
    snd_pcm_t *handle;  
    snd_pcm_hw_params_t *params;  
    unsigned int val;  
    int dir;  
    snd_pcm_uframes_t frames;  
    char *buffer;  
  
    /* Open PCM device for playback */  
    rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);  
    if (rc < 0) {  
        printf("unable to open pcm device: %s\n", snd_strerror(rc));  
        exit(1);  
    }  
  
    snd_pcm_hw_params_alloca(¶ms);  
  
    snd_pcm_hw_params_any(handle, params);  
  
    snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);  
  
    snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);  
  
    snd_pcm_hw_params_set_channels(handle, params, 2);  
  
    val = 44100;  
    snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);  
  
    frames = 32;  
    snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);  
  
    rc = snd_pcm_hw_params(handle, params);  
    if (rc < 0) {  
        printf("unable to set hw parameters: %s\n", snd_strerror(rc));  
        exit(1);  
    }  
  
    snd_pcm_hw_params_get_period_size(params, &frames, &dir);  
  
    size = frames * 4;  
    buffer = (char *)malloc(size);  
  
    snd_pcm_hw_params_get_period_time(params, &val, &dir);  
    loops = 5000000 / val;  
  
    while (loops > 0) {  
        loops--;  
        rc = read(0, buffer, size);  
  
        rc = snd_pcm_writei(handle, buffer, frames);  
        if (rc == -EPIPE) {  
            printf("underrun occured\n");  
        }  
        else if (rc < 0) {  
            printf("error from writei: %s\n", snd_strerror(rc));  
        }  
    }  
  
    snd_pcm_drain(handle);  
    snd_pcm_close(handle);  
    free(buffer);  
  
    return 0;  
}


Listing 3擴充套件了前面的例子,寫了一些隨機的取樣資料到音效卡的playback。在這個例子中,我們從標準輸入獲取資料,當獲取的資料達到一個period時,就把取樣資料寫入音效卡。

這個程式的開始部分和前面的例子一樣:開啟PCM裝置並設定硬體引數。我們使用ALSA選擇的period size作為儲存取樣資料buffer的大小。通過這個period time我們就可以計算出5秒鐘大概需要多少個periods。

在迴圈中我們從標準輸入讀取資料填充一個period的取樣資料到buffer。我們檢查並處理檔案結束以及讀取位元組數和期望數不一致的情況。

使用snd_pcm_write函式傳送資料到PCM裝置。這個操作很像核心的write系統呼叫,除了size的單位是frames。檢查返回的錯誤碼,錯誤碼EPIPE表示underrun錯誤發生,這會導致PCM 流進入了XRUN狀態,停止處理資料。從這種狀態恢復的標準方法是呼叫snd_pcm_prepare,使得流進入PREPARED狀態,這樣我們就可以再次向流中寫入資料了,如果接收的是其他的錯誤碼,那麼我們顯示錯誤碼,並且繼續執行。

這個程式迴圈執行5秒或者到達輸入檔案的結束符。我們呼叫snd_pcm_drain使得所有pending的取樣資料被傳輸,然後關閉這個流。釋放分配的buffer並退出。

執行這個程式,並使用/dev/urandom作為輸入

./example < /dev/urandom

Listing 4. Simple Sound Recording

/* 
  This example reads from the default PCM device 
  and writes to standard output for 5 seconds of data. 
 */  
  
/* Use the newer ALSA ALI */  
#define ALSA_PCM_NEW_HW_PARAMS_API  
  
#include <alsa/asoundlib.h>  
  
int main()   
{  
    long loops;  
    int rc;  
    int size;  
    snd_pcm_t *handle;  
    snd_pcm_hw_params_t *params;  
    unsigned int val;  
    int dir;  
    snd_pcm_uframes_t frames;  
    char *buffer;  
  
    rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, 0);  
    <pre name="code" class="programlisting">    if (rc < 0) {  
        fprintf(stderr,  
            "unable to open pcm device: %s\n",  
            snd_strerror(rc));  
        exit(1);  
    }  
  
    /* Allocate a hardware parameters object. */  
    snd_pcm_hw_params_alloca(¶ms);  
  
    /* Fill it in with default values. */  
    snd_pcm_hw_params_any(handle, params);  
  
    /* Set the desired hardware parameters. */  
  
    /* Interleaved mode */  
    snd_pcm_hw_params_set_access(handle, params,  
                      SND_PCM_ACCESS_RW_INTERLEAVED);  
  
    /* Signed 16-bit little-endian format */  
    snd_pcm_hw_params_set_format(handle, params,  
                              SND_PCM_FORMAT_S16_LE);  
  
    /* Two channels (stereo) */  
    snd_pcm_hw_params_set_channels(handle, params, 2);  
  
    /* 44100 bits/second sampling rate (CD quality) */  
    val = 44100;  
    snd_pcm_hw_params_set_rate_near(handle, params,  
                                  &val, &dir);  
  
    /* Set period size to 32 frames. */  
    frames = 32;  
    snd_pcm_hw_params_set_period_size_near(handle,  
                              params, &frames, &dir);  
  
    /* Write the parameters to the driver */  
    rc = snd_pcm_hw_params(handle, params);  
    if (rc < 0) {  
        fprintf(stderr,  
            "unable to set hw parameters: %s\n",  
            snd_strerror(rc));  
        exit(1);  
    }  
  
    /* Use a buffer large enough to hold one period */  
    snd_pcm_hw_params_get_period_size(params,  
                                      &frames, &dir);  
    size = frames * 4; /* 2 bytes/sample, 2 channels */  
    buffer = (char *) malloc(size);  
  
    /* We want to loop for 5 seconds */  
    snd_pcm_hw_params_get_period_time(params,  
                                         &val, &dir);  
    loops = 5000000 / val;  
  
    while (loops > 0) {  
        loops--;  
        rc = snd_pcm_readi(handle, buffer, frames);  
        if (rc == -EPIPE) {  
            /* EPIPE means overrun */  
            fprintf(stderr, "overrun occurred\n");  
            snd_pcm_prepare(handle);  
        } else if (rc < 0) {  
        fprintf(stderr,  
              "error from read: %s\n",  
              snd_strerror(rc));  
        } else if (rc != (int)frames) {  
            fprintf(stderr, "short read, read %d frames\n", rc);  
        }  
        rc = write(1, buffer, size);  
        if (rc != size)  
            fprintf(stderr,  
              "short write: wrote %d bytes\n", rc);  
    }  
  
    snd_pcm_drain(handle);  
    snd_pcm_close(handle);  
    free(buffer);  
  
    return 0;  
}

Listing 4和Listing3很相像,除了執行了PCM capture。在我們開啟PCM流時,我們指定了操作模式為SND_PCM_STREAM_CAPTURE。主迴圈中,使用snd_pcm_readi從本地聲音硬體讀取samples,然後把取樣輸出到標準輸出。如果你有一個microphone連線到音效卡,使用mixer程式設定錄音源和級別。相應的,你可以執行一個CD player程式然後設定錄音源為CD。如果你把listing4的輸出重定向到一個檔案,那麼你可以用listing3來播放listing4的錄音資料:

./listing4 > sound.raw  
./listing3 < sound.raw

如果你的音效卡支援全雙工,那麼你可以通過管道把這個執行命令連線起來,可以播放錄音資料
./listing4 | ./listing3

通過修改PCM引數,你可以體驗取樣率和格式變化的效果。

Advanced Features

在前面的例子中,PCM流工作在阻塞模式,也就是說,在資料傳輸結束完之前不會函式不會返回。在一個互動式為主導的應用中,這種情況將使得應用長時間無響應。ALSA允許非阻塞模式操縱流,這種方式下read/write操作立刻返回,如果資料傳輸無法立刻進行,那麼read/write立刻返回一個EBUSY錯誤碼。
有些圖形介面應用使用事件callback機制。ALSA支援非同步PCM stream,當一個period的取樣資料完成以後,註冊的callback被呼叫。
snd_pcm_readi和snd_pcm_writei呼叫類似於linux的read/write系統呼叫。字母i表示幀是interleaved;相對的是non-interleaved模式。Linux下的一些裝置也支援mmap系統呼叫,ALSA支援mmap模式開啟PCM channel,應用層不需要資料copy就可以有效訪問聲音資料

Conclusion

我希望這篇文章能夠成為你使用ALSA的動力。當2.6 kernel被大部分發行版使用後,ALSA的使用更加廣泛,它的高階features應該能幫助linux audio應用開發者。
感謝Jaroslav Kysela和Takashi Iwai reviewing本文的初稿並提供了有用的反饋。