一.聲音參數基本概念:
聲音是連續模擬量,計算機將它離散化之后用數字表示,就有了以下幾個名詞術語。
樣本長度(sample):樣本是記錄音頻數據最基本的單位,計算機對每個通道采樣量化時數字比特位數,常見的有8位和16位。
通道數(channel):該參數為1表示單聲道,2則是立體聲。
幀(frame):幀記錄了一個聲音單元,其長度為樣本長度與通道數的乘積,一段音頻數據就是由苦干幀組成的。
采樣率(rate):每秒鐘采樣次數,該次數是針對幀而言,常用的采樣率如8KHz的人聲,44.1KHz的mp3音樂, 96Khz的藍光音頻。
周期(period):音頻設備一次處理所需要的楨數,對于音頻設備的數據訪問以及音頻數據的存儲,都是以此為單位。
交錯模式(interleaved):是一種音頻數據的記錄方式
在交錯模式下,數據以連續楨的形式存放,即首先記錄完楨1的左聲道樣本和右聲道樣本(假設為立體聲格式),再開始楨2的記錄。
而在非交錯模式下,首先記錄的是一個周期內所有楨的左聲道樣本,再記錄右聲道樣本,數據是以連續通道的方式存儲。
不過多數情況下,我們只需要使用交錯模式就可以了。
period(周期):硬件中中斷間的間隔時間。它表示輸入延時。
比特率(Bits
Per Second):比特率表示每秒的比特數,比特率=采樣率×通道數×樣本長度
二.ALSA介紹
1.ALSA簡介
ALSA表示高級Linux聲音體系結構(Advanced Linux Sound Architecture)。ALSA是一個完全開放源代碼的音頻驅動程序集,除了像OSS那樣提供了一組內核驅動程序模塊之外,ALSA還專門為簡化應用程序的編寫提供了相應的函數庫,與OSS提供的基于ioctl的原始編程接口相比,ALSA函數庫使用起來要更加方便一些。利用該函數庫,開發人員可以方便快捷的開發出自己的應用程序,細節則留給函數庫內部處理。當然ALSA也提供了類似于OSS的系統接口,不過ALSA的開發者建議應用程序開發者使用音頻函數庫而不是驅動程序的API。
2.ALSA版本支持
Linux內核2.5在開發過程中,ALSA被合并到了官方的源碼樹中。在發布內核2.6后,ALSA已經內建在穩定的內核版本中并將廣泛地使用。在內核設備驅動層,ALSA提供了alsa-driver,同時在應用層,ALSA為我們提供了alsa-lib,應用程序只要調用alsa-lib提供的API,即可以完成對底層音頻硬件的控制。
3.ALSA基礎
ALSA由許多聲卡的聲卡驅動程序組成,同時它也提供一個稱為libasound的API庫。應用程序開發者應該使用libasound而不是內核中的ALSA接口。因為libasound提供最高級并且編程方便的編程接口。并且提供一個設備邏輯命名功能,這樣開發者甚至不需要知道類似設備文件這樣的低層接口。
用戶空間的alsa-lib對應用程序提供統一的API接口,這樣可以隱藏了驅動層的實現細節,簡化了應用程序的實現難度。內核空間中,alsa-soc其實是對alsa-driver的進一步封裝,他針對嵌入式設備提供了一些列增強的功能。
4.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庫使用邏輯設備名而不是設備文件。
5.偽代碼
一個典型的聲音程序使用PCM的程序通常類似下面的偽代碼:
1.打開回放或錄音接口
2.設置硬件參數(訪問模式,數據格式,信道數,采樣率,等等)
2.while有數據要被處理:讀PCM數據(錄音)或寫PCM數據(回放)
3.關閉接口
三.ALSA編譯安裝
1.ALSA相關庫下載
官方主頁http://www.alsa-project.org/
主要跟編程相關是
·alsa-lib. ALSA應用庫(最常用)
·ftp://ftp.alsa-project.org/pub/lib/alsa-lib-1.0.22.tar.bz2
·alsa-driver一些常見芯片的ALSA驅動代碼,一般內核會集成.
·ftp://ftp.alsa-project.org/pub/driver/alsa-driver-1.0.22.1.tar.bz2
·alsa-firmware一些DSP或ASIC的專用的微碼(運在芯片之上,啟動時由LINUX裝入到硬件中).
·ftp://ftp.alsa-project.org/pub/firmware/alsa-firmware-1.0.20.tar.bz2
·alsa-utils一般ALSA小的測試工具.如aplay/arecord播放和錄音小程序.
·ftp://ftp.alsa-project.org/pub/utils/alsa-utils-1.0.22.tar.bz2
·alsa-oss用alsa接口模擬舊的oss接口.
·ftp://ftp.alsa-project.org/pub/oss-lib/alsa-oss-1.0.17.tar.bz2
其中alsa-driver,alsa-firwware是內核開發者所接觸的東西,對于已經正常運行硬件,通常意味著這一部分已經整合到內核當中,無需修改.
而alsa-utils主要是測試一些小工具.
因此對于一個應用程序開發者,或者嵌入式應用開發者,接觸到主要是alsa-lib編譯出來的庫libasound.
2.ALSA驅動測試
cat
/proc/asound/devices驅動測試
ls -l/dev/snd設備測試
aplay –h工具alsa-utils測試
3.嵌入式linuxALSA移植
·ALSA driver移植
·ALSA lib移植.
解壓tar xvjf alsa-lib-1.0.22.tar.bz2
cd alsa-lib-1.0.22
生成Makefile
./configure --host=arm-linux? --prefix=$PWD/../../output/arm-linux --enable-static --enable-shared?? --disable-python? --with-configdir=/usr/local/share? --with-plugindir=/usr/local/lib/alsa_lib
在這里要注意--with--configdir的選項.它將影響include/config.h中的ALSA_CONFIG_DIR目錄.
它默認是你的--prefix目錄.這樣在嵌入式交叉編譯將是一個桌面機的路徑,在libasoud.so運行.會提示,如果出來這個提示,一般都是ALSA_CONFIG_DIR路徑錯誤造成的.
ALSA? lib pcm.c:2145:(snd_pcm_open_noupdate) Unknown PCM default
aplay:? main:546:?audio open error: No such file or directory
--with-plugindir也是同樣道理了.它是設為ALSA_PLUGIN_DIR宏.
編譯make
安裝make install
開發板發布注意:
在開發板上發布alsa庫.除了libasound.so庫以外,必須還要把alsa.conf發布到板上--with-configdir所指向目錄下的alsa目錄,否則還是會報"audio open error: No such file ordirectory".
這個文件可以在make install后在你安裝目錄下的share找到alsa目錄,把這個目錄整個拷貝到開發板即可.
·ALSA utils移植
解壓:tar xvjf alsa-utils-1.0.22.tar.bz2
cdalsa-utils-1.0.22
生成Makefile
./configure --host=arm-linux? --prefix=$PWD/../../output/arm-linux --enable-static? --enable-shared??? --with-configdir=/usr/local/share? --with-libiconv-prefix=$PWD/../../output/arm-linux? CFLAGS="-I$PWD/../../output/arm-linux/include"? LDFLAGS="-L$PWD/../../output/arm-linux/lib -lasound -liconv"?? --disable-alsamixer --disable-xmlto
注意這里LDFLAGS是必須,否則會找不到libasound.另外alsamixer是一個ncurses程序,基本上在嵌入式終端上很難移植.所以這里取消掉.--disable-xmlto也是因為找不到庫.
編譯make
安裝make install
四.ALSA錄音demo
#include
#include
#include
main (int argc, char *argv[])
{
int i;
int err;
short buf[128];
snd_pcm_t *capture_handle; // PCM設備句柄
snd_pcm_hw_params_t *hw_params;//硬件信息和PCM流配置
//1.打開PCM,最后一個參數0為標準配置
if ((err = snd_pcm_open (&capture_handle,argv[1], SND_PCM_STREAM_CAPTURE, 0)) < 0) {
fprintf (stderr, "cannot open audiodevice %s (%s)\n",
argv[1],
snd_strerror (err));
exit (1);
}
//2.分配snd_pcm_hw_params_t結構體
if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
fprintf (stderr, "cannot allocatehardware parameter structure (%s)\n",
snd_strerror (err));
exit (1);
}
//3.初始化hw_paraws
if ((err = snd_pcm_hw_params_any (capture_handle,hw_params)) < 0) {
fprintf (stderr, "cannot initializehardware parameter structure (%s)\n",
snd_strerror (err));
exit (1);
}
//4.初始化訪問權限
if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
fprintf (stderr, "cannot set accesstype (%s)\n",
snd_strerror (err));
exit (1);
}
//5.初始化采樣格式SND_PCM_FORMAT_U8,16位
if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
fprintf (stderr, "cannot set sampleformat (%s)\n",
snd_strerror (err));
exit (1);
}
1.//6.設置采樣率,如果硬件不支持我們設置的采樣率,將使用最接近的
2.//val?=?44100,有些錄音采樣頻率固定為8KHz
if ((err = snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, 44100, 0)) < 0) {
fprintf (stderr, "cannot set samplerate (%s)\n",
snd_strerror (err));
exit (1);
}
//7.設置通道數量
if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, 2)) < 0) {
fprintf (stderr, "cannot set channelcount (%s)\n",
snd_strerror (err));
exit (1);
}
//8.設置hw_params
if ((err = snd_pcm_hw_params (capture_handle,hw_params)) < 0) {
fprintf (stderr, "cannot setparameters (%s)\n",
snd_strerror (err));
exit (1);
}
//釋放硬件參數設置
snd_pcm_hw_params_free (hw_params);
//如果發生xrun故障,從該故障中恢復過來
if ((err = snd_pcm_prepare (capture_handle)) <0) {
fprintf (stderr, "cannot prepareaudio interface for use (%s)\n",
snd_strerror (err));
exit (1);
}
//從聲卡中讀取數據,寫入到標準輸出設備
for (i = 0; i < 10; ++i) {
if ((err = snd_pcm_readi (capture_handle,buf, 128)) != 128) {
fprintf (stderr, "read fromaudio interface failed (%s)\n",
snd_strerror (err));
exit (1);
}
}
//10.關閉PCM設備句柄
snd_pcm_close (capture_handle);
exit (0);
}
/*
This example reads from the default PCMdevice
and writes to standard output for 5 secondsof data.
*/
/* Use thenewer ALSA ALI */
#defineALSA_PCM_NEW_HW_PARAMS_API
#include
#include
#include
intmain()
{
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);
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, 1);
/* 44100 bits/second sampling rate (CDquality) */
val = 16000;
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 oneperiod */
snd_pcm_hw_params_get_period_size(params,
&frames, &dir);
size = frames*2; /* 2 bytes/sample, 2channels */
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, "overrunoccurred\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 %dbytes\n", rc);
}
snd_pcm_drain(handle);//把所有掛起沒有傳輸完的聲音樣本傳輸完全
snd_pcm_close(handle);
free(buffer);
return 0;
}
打開PCM設備、設置硬件參數。我們使用由ALSA自己選擇的周期大小,申請該大小的緩沖區來存儲樣本。然后我們找出周期時間,這樣我們就能計算出本程序為了能夠播放5秒鐘,需要多少個周期。
在處理數據的循環中,我們從標準輸入中讀入數據,并往緩沖區中填充一個周期的樣本。然后檢查并處理錯誤,這些錯誤可能是由到達文件結尾,或讀取的數據長度與我期望的數據長度不一致導致的。
我們調用snd_pcm_writei來發送數據。它操作起來很像內核的寫系統調用,只是這里的大小參數是以幀來計算的。我們檢查其返回代碼值。返回值為EPIPE表明發生了underrun,使得PCM音頻流進入到XRUN狀態并停止處理數據。從該狀態中恢復過來的標準方法是調用snd_pcm_prepare函數,把PCM流置于PREPARED狀態,這樣下次我們向該PCM流中數據時,它就能重新開始處理數據。如果我們得到的錯誤碼不是EPIPE,我們把錯誤碼打印出來,然后繼續。最后,如果寫入的幀數不是我們期望的,則打印出錯誤消息。這個程序一直循環,直到5秒鐘的幀全部傳輸完,或者輸入流讀到文件結尾。然后我們調用snd_pcm_drain把所有掛起沒有傳輸完的聲音樣本傳輸完全,最后關閉該音頻流,釋放之前動態分配的緩沖區,退出。
1.rc?=?snd_pcm_readi(handle,?buffer,?frames);
2.if?(rc?==?-EPIPE)?{
3./*?EPIPE?means?overrun?*/
4.fprintf(stderr,?"overrun?occurred\n");
5.snd_pcm_prepare(handle);
6.}?else?if?(rc<?0)?{
7.fprintf(stderr,
8."error?from?read:?%s\n",
9.snd_strerror(rc));
10.}?else?if?(rc?!=?(int)frames)?{
11.fprintf(stderr,?"short?read,?read?%d?frames\n",?rc);
12.}
snd_pcm結構用于表征一個PCM類型的snd_device.
struct snd_pcm {
struct snd_card *card; /*指向所屬的card設備*/
int device; /* device number */
struct snd_pcm_str streams[2]; /*播放和錄制兩個數據流*/
wait_queue_head_t open_wait; /*打開pcm設備時等待打開一個可獲得的substream */
}
應用程序緩存區的大小可以通過ALSA庫函數調用來控制。緩存區可以很大,一次傳輸操作可能會導致不可接受的延遲,我們把它稱為延時(latency)。為了解決這個問題,ALSA將緩存區拆分成一系列周期(period)(OSS/Free中叫片斷fragments).ALSA以period為單元來傳送數據。
一個周期(period)存儲一些幀(frames)。每一幀包含時間上一個點所抓取的樣本。對于立體聲設備,一個幀會包含兩個信道上的樣本。
Buffer_size計算邏輯:
Buffer_size =?c->min = a->min * b-> min = period_size *
periods
底層獲得的Periods = 4,是tinplay.c中賦值的,period_size是上面計算出來的值。
一.必須區分清alsa里的buffer
一. alsa展現的三層結構:
(1)audio interface:
audio interface就是聲卡,它含有hardware
buffer,注意,這個hardware buffer是在聲卡里面,不是內存。很多codec芯片并不像聲卡那么強大還有buffer,只是ap把數據通過i2s給codec,其實我也不清楚wm8994這寫codec芯片里面有多大buffer,只是以為數據給它了就立馬播放了!
(2)computer:
這個指的是計算機的內核和驅動,alsa驅動專門管一塊內存,這就是傳說中的ring buffer吧,alsa驅動空間里有period,
frames的概念,當(1)的audio interface引發中斷,內核會捕捉到,再把處理移交alsa。
(3)application:
這個就是你寫的程序,你在用戶空間開辟一個buffer,比如playback,就交給alsa來play。
在上面的框架下,流程如下:
(1)playback:
application開辟一個buffer,填上數據,調用alsa接口,alsa把buffer數據復制到其驅動空間的那塊內存,再把數據交給hardware buffer。
(2)record:
同playback,相似的。
Alsa編譯安裝:http://blog.csdn.net/liu_chunhai/article/details/6582090
http://blog.csdn.net/shui1025701856/article/details/7646197
http://www.cnblogs.com/cslunatic/p/3677729.html
http://blog.csdn.net/zd394071264/article/details/8300045
http://blog.csdn.net/ropenyuan/article/details/9344299
http://www.cnblogs.com/lifan3a/articles/5481775.html
http://blog.chinaunix.net/uid-27106528-id-3328766.html
http://blog.csdn.net/u012769691/article/details/46727543
slsa編譯
http://blog.chinaunix.net/uid-23065002-id-3884658.html
https://www.oschina.net/news/72059/alsa-lib-1-1-1
http://www.360doc.com/content/11/0613/13/168576_126609790.shtml
alsa錄音
http://blog.csdn.net/lijin6249/article/details/51955206