【Linux&音頻】Alsa音頻編程【精華】__Arduino

來源:互聯網
上載者:User
【Linux&音頻】Alsa音頻編程【精華】  

前序:這裡瞭解一下各個參數的含義以及一些基本概念。


樣本長度(sample):樣本是記錄音頻資料最基本的單位,常見的有8位和16位。

通道數(channel):該參數為1表示單聲道,2則是立體聲。 楨(frame):楨記錄了一個聲音單元,其長度為樣本長度與通道數的乘積。
採樣率(rate):每秒鐘採樣次數,該次數是針對楨而言。
周期(period):音訊裝置一次處理所需要的楨數,對於音訊裝置的資料訪問以及音頻資料的儲存,都是以此為單位。

交錯模式(interleaved):是一種音頻資料的記錄方式,在交錯模式下,資料以連續楨的形式存放,即首先記錄完楨1的左聲道樣本和右聲道樣本(假設為立體聲格式),再開始楨2的記錄。而在非交錯模式下,首先記錄的是一個周期內所有楨的左聲道樣本,再記錄右聲道樣本,資料是以連續通道的方式儲存。不過多數情況下,我們只需要使用交錯模式就可以了。 period(周期):硬體中中斷間的間隔時間。它表示輸入延時。

音效卡介面中有一個指標來指示音效卡硬體緩衝區中當前的讀寫位置。只要介面在運行,這個指標將迴圈地指向緩衝區中的某個位置。
frame size = sizeof(one sample) * nChannels
alsa中配置的緩衝(buffer)和周期(size)大小在runtime中是以幀(frames)形式儲存的。
period_bytes = frames_to_bytes(runtime, runtime->period_size);
bytes_to_frames()


一,ALSA聲音編程介紹

ALSA表示進階Linux聲音體繫結構(Advanced Linux Sound Architecture)。它由一系列核心驅動,應用程式編譯介面(API)以及支援Linux下聲音的公用程式組成。這篇文章裡,我將簡單介紹 ALSA項目的基本架構以及它的軟體組成。主要集中介紹PCM介面編程,包括您可以自動實踐的程式樣本。

您使用ALSA的原因可能就是因為它很新,但它並不是唯一可用的聲音API。如果您想完成低級的聲音操作,以便能夠最大化地控制聲音並最大化地提高效能,或者如果您使用其它聲音API沒有的特性,那麼ALSA是很好的選擇。如果您已經寫了一個音頻程式,你可能想要為ALSA音效卡驅動添加本地支援。如果您對音頻不感興趣,只是想播放音頻檔案,那麼進階的API將是更好的選擇,比如SDL,OpenAL以及那些案頭環境提供的工具集。另外,您只能在有ALSA 支援的Linux環境中使用ALSA。

二,ALSA曆史

ALSA項目發起的起因是Linux下的音效卡驅動(OSS/Free drivers)沒有得到積極的維護。並且落後於新的音效卡技術。Jaroslav Kysela早先寫了一個音效卡驅動,並由此開始了ALSA項目,隨便,更多的開發人員加入到開發隊伍中,更多的音效卡得到支援,API的結構也得到了重組。

Linux核心2.5在開發過程中,ALSA被合并到了官方的源碼樹中。在發布核心2.6後,ALSA已經內建在穩定的核心版本中並將廣泛地使用。

三,數字音頻基礎

聲音由變化的氣壓組成。它被麥克風這樣的轉換器轉換成電子形式。模/數(ADC)轉換器將類比電壓轉換成離散的樣本值。聲音以固定的時間間隔被採樣,採樣的速率稱為採樣率。把樣本輸出到數/模(DAC)轉換器,比如擴音器,最後轉換成原來的類比訊號。

樣本大小以位來表示。樣本大小是影響聲音被轉換成數字訊號的精確程度的因素之一。另一個主要的因素是採樣率。奈奎斯特(Nyquist)理論中,只要離散系統的奈奎斯特頻率高於採樣訊號的最高頻率或頻寬,就可以避免混疊現象。

四,ALSA基礎

ALSA由許多音效卡的音效卡驅動程式組成,同時它也提供一個稱為libasound的API庫。應用程式開發人員應該使用libasound而不是核心中的 ALSA介面。因為libasound提供最進階並且編程方便的編程介面。並且提供一個裝置邏輯命名功能,這樣開發人員甚至不需要知道類似裝置檔案這樣的低層介面。相反,OSS/Free驅動是在核心系統調用級上編程,它要求開發人員提供裝置檔案名稱並且利用ioctrl來實現相應的功能。

為了向後相容,ALSA提供核心模組來類比OSS,這樣之前的許多在OSS基礎上開發的應用程式不需要任何改動就可以在ALSA上運行。另外,libaoss庫也可以類比OSS,而它不需要核心模組。

ALSA包含外掛程式功能,使用外掛程式可以擴充新的音效卡驅動,包括完全用軟體實現的虛擬音效卡。ALSA提供一系列基於命令列的工具集,比如混音器(mixer),音頻檔案播放器(aplay),以及控制特定音效卡特定屬性的工具。

五,ALSA體繫結構

ALSA API可以分解成以下幾個主要的介面:

1 控制介面:提供管理音效卡註冊和請求可用裝置的通用功能

2 PCM介面:管理數字音頻回放(playback)和錄音(capture)的介面。本文後續總結重點放在這個介面上,因為它是開發數字音頻程式最常用到的介面。

3 Raw MIDI介面:支援MIDI(Musical Instrument Digital Interface),標準的電子樂器。這些API提供對音效卡上MIDI匯流排的訪問。這個原始介面基於MIDI事件工作,由程式員負責管理協議以及時間處理。

4 定時器(Timer)介面:為同步音頻事件提供對音效卡上時間處理硬體的訪問。

5 時序器(Sequencer)介面

6 混音器(Mixer)介面

六,裝置命名

API庫使用邏輯裝置名而不是裝置檔案。裝置名稱字可以是真實的硬體名字也可以是外掛程式名字。硬體名字使用hw:i,j這樣的格式。其中i是卡號,j是這塊音效卡上的裝置號。

第一個聲音裝置是hw:0,0.這個別名預設引用第一塊聲音裝置並且在本文樣本中一真會被用到。

外掛程式使用另外的唯一名字,比如 plughw:,表示一個外掛程式,這個外掛程式不提供對硬體裝置的訪問,而是提供像採樣率轉換這樣的軟體特性,硬體本身並不支援這樣的特性。

七,聲音緩衝和資料轉送

每個音效卡都有一個硬體緩衝區來儲存記錄下來的樣本。當緩衝區足夠滿時,音效卡將產生一個中斷。核心音效卡驅動然後使用直接記憶體(DMA)訪問通道將樣本傳送到記憶體中的應用程式緩衝區。類似地,對於回放,任何應用程式使用DMA將自己的緩衝區資料傳送到音效卡的硬體緩衝區中。
這樣硬體緩衝區是環緩衝。也就是說當資料到達緩衝區末尾時將重新回到緩衝區的起始位置。ALSA維護一個指標來指向硬體緩衝以及應用程式緩衝區中資料操作的當前位置。從核心外部看,我們只對應用程式的緩衝區感興趣,所以本文只討論應用程式緩衝區。

應用程式緩衝區的大小可以通過ALSA庫函數調用來控制。緩衝區可以很大,一次傳輸操作可能會導致不可接受的延遲,我們把它稱為延時(latency)。為瞭解決這個問題,ALSA將緩衝區拆分成一系列周期(period)(OSS/Free中叫片斷fragments).ALSA以period為單元來傳送資料。

一個周期(period)儲存一些幀(frames)。每一幀包含時間上一個點所抓取的樣本。對於立體聲裝置,一個幀會包含兩個通道上的樣本。分解過程:一個緩衝區分解成周期,然後是幀,然後是樣本。左右通道資訊被交替地儲存在一個幀內。這稱為交錯 (interleaved)模式。在非交錯模式中,一個通道的所有樣本資料存放區在另外一個通道的資料之後。

八,Over and Under Run
當一個音效卡活動時,資料總是連續地在硬體緩衝區應用程式緩衝區間傳輸。但是也有例外。在錄音例子中,如果應用程式讀取資料不夠快,迴圈緩衝區將會被新的資料覆蓋。這種資料的丟失被稱為over run.在回放例子中,如果應用程式寫入資料到緩衝區中的速度不夠快,緩衝區將會"餓死"。這樣的錯誤被稱為"under run"。在ALSA文檔中,有時將這兩種情形統稱為"XRUN"。適當地設計應用程式可以最小化XRUN並且可以從中恢複過來。

九,一個典型的聲音程式

使用PCM的程式通常類似下面的虛擬碼:

開啟回放或錄音介面

設定硬體參數(訪問模式,資料格式,通道數,採樣率,等等) while 有資料要被處理:

讀PCM資料(錄音)

或 寫PCM資料(回放)

關閉介面


和本文相關的所有執行個體清單可以從FTP中擷取:ftp.ssc.com/pub/lj/listings/issue126/6735.tgz。

Listing 1. Display Some PCM Types and Formats
[html] view plain copy print ? #include <alsa/asoundlib.h> int main() { int val; printf("ALSA library version: %s\n", SND_LIB_VERSION_STR); printf("\nPCM 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("\nPCM 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("\nPCM formats:\n"); for (val = 0; val <= SND_PCM_FORMAT_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)); } printf("\nPCM states:\n"); for (val = 0; val <= SND_PCM_STATE_LAST; val++) printf(" %s\n", snd_pcm_state_name((snd_pcm_state_t)val)); return 0; }

#include <alsa/asoundlib.h>int main() {int val;printf("ALSA library version: %s\n",                       SND_LIB_VERSION_STR);printf("\nPCM 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("\nPCM 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("\nPCM formats:\n");for (val = 0; val <= SND_PCM_FORMAT_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));}printf("\nPCM states:\n");for (val = 0; val <= SND_PCM_STATE_LAST; val++)    printf(" %s\n",           snd_pcm_state_name((snd_pcm_state_t)val));return 0;}



清單一顯示了一些ALSA使用的PCM資料類型和參數。首先需要做的是包括標頭檔。這些標頭檔包含了所有庫函數的聲明。其中之一就是顯示ALSA庫的版本。

這個程式剩下的部分的迭代一些PCM資料類型,以流類型開始。ALSA為每次迭代的最後值提供符號常量名,並且提供功能函數以顯示某個特定值的描述字串。你將會看到,ALSA支援許多格式,在我的1.0.15版本裡,支援多達36種格式。

這個程式必須連結到alsalib庫,通過在編譯時間需要加上-lasound選項。有些alsa庫函數使用dlopen函數以及浮點操作,所以您可能還需要加上-ldl,-lm選項。

編譯:gcc -o main test.c -lasound

下面是該程式的Makefile:
Listing 2. Opening PCM Device and Setting Parameters

[html] view plain copy print ? /* 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) { 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); /* 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); } /* 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_access(params, (snd_pcm_access_t *) &val); printf("access type = %s\n", snd_pcm_access_name((snd_pcm_access_t)val)); 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 pause = %d\n", val); val = snd_pcm_hw_params_can_resume(params); printf("can resume = %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; }

/*This example opens the default PCM device, setssome parameters, and then displays the valueof most of the hardware parameters. It does notperform 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) {    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);/* 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);}/* 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_access(params,                          (snd_pcm_access_t *) &val);printf("access type = %s\n",         snd_pcm_access_name((snd_pcm_access_t)val));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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.