Linux Audio ALSA Technical specification(Linux 音頻ALSA技術(shù)說明)分類:
C語言編程 android framework Linux ALSA2011-03-24 23:28 1402人閱讀
評論(0)
收藏 舉報Linux Audio ALSA Technical specification
備注:整理于2011.01.20, 本篇博客百度文庫: http://wenku.baidu.com/view/34ca5351ad02de80d4d84084.html
Email:
safransx@gmail.com QQ: 1104472716
TABLE OF CONTENTS
1 ALSA Overview... 31.1 ALSA features. 31.2 ALSA子項目.... 31.3 ALSA接口.... 41.4 ALSA體系結(jié)構(gòu).... 41.5 ALSA-driver文件結(jié)構(gòu).... 51.6 Android中alsa相關(guān)代碼路徑.... 72 音頻基礎(chǔ).... 72.1 數(shù)字音頻基礎(chǔ).... 72.2 ALSA基礎(chǔ).... 82.3 設(shè)備命名.... 82.4 聲音緩存和數(shù)據(jù)傳輸.... 82.5 訪問音頻設(shè)備.... 92.6 音頻設(shè)備文件.... 113 ALSA Example.. 133.1 Example1. Display Some PCM Types and Formats. 133.2 Example2. Opening PCM Device and Setting Parameters. 163.3 Example3.Simple Sound Playback.. 193.4 Example4. Simple Sound Recording.. 213.5 高級特性.... 234 ALSA移植.... 235 Preference.. 241 ALSA Overview
ALSA(Advanced Linux Sound Architecture(高級Linux聲音體系)的縮寫)是為聲卡提供驅(qū)動的Linux內(nèi)核組件,以替代原先的OSS(開放聲音系統(tǒng))。ALSA除了像OSS那樣提供一組內(nèi)核驅(qū)動程序模塊以外,還專門為簡化應(yīng)用程序的編寫提供了相應(yīng)的庫函數(shù),與OSS提供的基于ioctl的原始編程接口相比,ALSA函數(shù)庫使用起來要更加方便一點。
1.1 ALSA features
ALSA has the following significant features:
1.Efficient support for all types of audio interfaces, from consumer sound cards to professional multichannel audio interfaces. (支持多種聲卡設(shè)備)
2.Fully modularized sound drivers. (模塊化的內(nèi)核驅(qū)動程序)
3.SMP and thread-safe design. (支持SMP和多線程)
4.User space library (alsa-lib) to simplify application programming and provide higher level functionality. (提供應(yīng)用開發(fā)函數(shù)庫以簡化應(yīng)用程序開發(fā))
5.Support for the older Open Sound System (OSS) API, providing binary compatibility for most OSS programs. (支持OSS API,兼容OSS應(yīng)用程序)
1.2 ALSA子項目
ALSA具有更加友好的編程接口,并且完全兼容于OSS,對應(yīng)用程序來講無疑是一個更佳地選擇。ALSA系統(tǒng)包括以下7個子項目,其中只有驅(qū)動包是必須的:
驅(qū)動包alsa-driver
開發(fā)包alsa-libs
開發(fā)包插件alsa-libplugins
設(shè)置管理工具包alsa-utils
其他聲音相關(guān)處理小程序包alsa-tools
特殊音頻固件支持包alsa-firmware
OSS接口兼容模擬層工具alsa-oss.
alsa-driver指內(nèi)核驅(qū)動程序,包括硬件相關(guān)的代碼和一些公共代碼,非常龐大。
alsa-libs指用戶空間的函數(shù)庫,提供給應(yīng)用程序使用,應(yīng)用程序應(yīng)包括頭文件asoundlib.h。并使用共享庫libasound.so。
alsa-utils包含一些基于ALSA的用于控制聲卡的應(yīng)用程序,如alsaconf(偵測系統(tǒng)中聲卡并寫一個適合的ALSA配置文件),aplay(基于命令行的聲音文件播放),arecord(基于命令行的聲音文件錄制)等。
1.3 ALSA接口
目前ALSA內(nèi)核提供給用戶空間的接口有:
信息接口(proc/asound)
控制接口(dev/snd/controlCX):提供管理聲卡注冊和請求可用設(shè)備的通用功能
混音器接口(dev/snd/mixerCXDX)
PCM接口(dev/snd/pcmCXDX):管理數(shù)字音頻回放(playback)和錄音(capture)的接口
Raw迷笛接口(dev/snd/midiCXDX):支持MIDI(Musical Instrument Digital Interface),標準的電子樂器。這些API提供對聲卡上MIDI總線的訪問。這個原始接口基于MIDI事件工作,由程序員負責管理協(xié)議以及時間處理。
音序器接口(dev/snd/seq)
定時器接口(dev/snd/timer):為同步音頻事件提供對聲卡上時間處理硬件的訪問。
和OSS類似,上述接口也以文件的方式被提供,不同的是這些接口被提供給alsa-lib使用,而不是直接給應(yīng)用程序使用的。應(yīng)用程序最好使用alsa-lib或者更高級的接口。
1.4 ALSA體系結(jié)構(gòu)
下圖所示為ALSA聲卡驅(qū)動與用戶空間體系結(jié)構(gòu)的簡圖,從中可以看出ALSA內(nèi)核驅(qū)動與用戶空間庫及OSS之間的關(guān)系
ALSA體系結(jié)構(gòu)
1.5 ALSA-driver文件結(jié)構(gòu)
從code文件夾中找到alsa-driver-1.0.23.tar.bz2,在linux下解壓,我們可以得到alsa-driver-1.0.23文件夾,我們可以看到ALSA驅(qū)動文件的目錄結(jié)構(gòu):
sound
/core
/oss
/seq
/oss
/include
/drivers
/mpu401
/op13
/opl4
/pcsp
/vx
/i2c
/other
/synth
/emux
/pci
/(cards)
/isa
/(cards)
/arm
/ppc
/sparc
/usb
/pcmcia/(cards)
/oss
下面我們來看一下各個目錄的具體作用:
core目錄
這個目錄包含了中間層,ALSA的核心驅(qū)動。
core/oss
關(guān)于PCM和mixer的OSS模擬的模塊保存在這個目錄里面。Raw midi OSS模擬也被包含在ALSA rawmidi代碼中,因為它非常小。音序器代碼被保存在core/seq/oss目錄里面
*core/ioctl32(老版本里面的)
這個目錄包含32bit-ioctl到64bit架構(gòu)(如x86-64,ppc64,sparc64)的轉(zhuǎn)換。對于32bit和alpha的架構(gòu),他們是不被編譯的。
core/seq
它和它的子目錄主要是關(guān)于ALSA的音序器。它包含了音序器的core和一些主要的音序器模塊如:snd-seq-midi,snd-seq-virmidi等等。它們僅僅在內(nèi)核配置中當CONFIG_SND_SEQUENCER被設(shè)定的時候才會被編譯。我們在使用的ALSA驅(qū)動中也沒有使用。
core/seq/oss
包含了OSS音序器的模擬的代碼。
core/seq/instr
包含了一些音序器工具層的一些模塊。
include目錄
這里面放的是ALSA驅(qū)動程序開放給用戶空間,或者被其他不同目錄引用的共同頭文件。
Drivers目錄
這個目錄包含了不同架構(gòu)的系統(tǒng)中的不同驅(qū)動共享的文件部分。它們是硬件無關(guān)的。在子目錄里面,會放一些不同組件的代碼,他們是根據(jù)不同的bus和cpu架構(gòu)實現(xiàn)的。
i2c目錄
這里面包含了ALSA的i2c組件。雖然LINUX有i2c的標準協(xié)議層,ALSA還是擁有它關(guān)于一些card的專用i2c代碼,因為一些聲卡僅僅需要一些簡單的操作,而標準的i2c的API函數(shù)對此顯得太過復雜了。
synth目錄
它包含了synth(合成器)的中間層模塊
pci目錄
它和它的一些子目錄文件負責PCI聲卡和一些PCI BUS的上層card模塊。
isa目錄
它和它的一些子目錄文件是處理ISA聲卡的上層card模塊。
arm,ppc和sparc目錄
這里放置一些和芯片架構(gòu)相關(guān)的一些上層的card模塊。
usb目錄
這里包含一些USB-AUDIO驅(qū)動。在最新版本里面,已經(jīng)把USB MIDI 驅(qū)動也集成進USB-AUDIO驅(qū)動了。
pcmcia目錄
PCMCIA卡,特別是PCCcard驅(qū)動會放到這里。CardBus驅(qū)動將會放到pci目錄里面,因為API函數(shù)和標準PCI卡上統(tǒng)一的。
oss目錄
和ALSA無關(guān)。
1.6 Android中alsa相關(guān)代碼路徑
alsa HAL:U6715_Android/android/src/hardware/alsa_sound
alsa-utils:U6715_Android/android/src/external/alsa-utils
alsa-lib: U6715_Android/android/src/external/alsa-lib
alsa-driver:SDK/src/linux/kernel/linux/sound
2 音頻基礎(chǔ)
2.1 數(shù)字音頻基礎(chǔ)
音頻信號是一種連續(xù)變化的模擬信號,但計算機只能處理和記錄二進制的數(shù)字信號,由自然音源得到的音頻信號必須經(jīng)過一定的變換,成為數(shù)字音頻信號之后,才能送到計算機中作進一步的處理。
數(shù)字音頻系統(tǒng)通過將聲波的波型轉(zhuǎn)換成一系列二進制數(shù)據(jù),來實現(xiàn)對原始聲音的重現(xiàn),實現(xiàn)這一步驟的設(shè)備常被稱為模/數(shù)轉(zhuǎn)換器(A/D)。A/D轉(zhuǎn)換器以每秒鐘上萬次的速率對聲波進行采樣,每個采樣點都記錄下了原始模擬聲波在某一時刻的狀態(tài),通常稱之為樣本(sample),而每一秒鐘所采樣的數(shù)目則稱為采樣頻率,通過將一串連續(xù)的樣本連接起來,就可以在計算機中描述一段聲音了。對于采樣過程中的每一個樣本來說,數(shù)字音頻系統(tǒng)會分配一定存儲位來記錄聲波的振幅,一般稱之為采樣分辯率或者采樣精度,采樣精度越高,聲音還原時就會越細膩。
數(shù)字音頻涉及到的概念非常多,對于在Linux下進行音頻編程的程序員來說,最重要的是理解聲音數(shù)字化的兩個關(guān)鍵步驟:采樣和量化。采樣就是每隔一定時間就讀一次聲音信號的幅度,而量化則是將采樣得到的聲音信號幅度轉(zhuǎn)換為數(shù)字值,從本質(zhì)上講,采樣是時間上的數(shù)字化,而量化則是幅度上的數(shù)字化。下面介紹幾個在進行音頻編程時經(jīng)常需要用到的技術(shù)指標:
1.采樣頻率
采樣頻率是指將模擬聲音波形進行數(shù)字化時,每秒鐘抽取聲波幅度樣本的次數(shù)。采樣頻率的選擇應(yīng)該遵循奈奎斯特(Harry Nyquist)采樣理論:如果對某一模擬信號進行采樣,則采樣后可還原的最高信號頻率只有采樣頻率的一半,或者說只要采樣頻率高于輸入信號最高頻率的兩倍,就能從采樣信號系列重構(gòu)原始信號。正常人聽覺的頻率范圍大約在20Hz~20kHz之間,根據(jù)奈奎斯特采樣理論,為了保證聲音不失真,采樣頻率應(yīng)該在40kHz左右。常用的音頻采樣頻率有8kHz、11.025kHz、22.05kHz、16kHz、37.8kHz、44.1kHz、48kHz等,如果采用更高的采樣頻率,還可以達到DVD的音質(zhì)。
2.量化位數(shù)
量化位數(shù)是對模擬音頻信號的幅度進行數(shù)字化,它決定了模擬信號數(shù)字化以后的動態(tài)范圍,常用的有8位、12位和16位。量化位越高,信號的動態(tài)范圍越大,數(shù)字化后的音頻信號就越可能接近原始信號,但所需要的存貯空間也越大。
3.聲道數(shù)
聲道數(shù)是反映音頻數(shù)字化質(zhì)量的另一個重要因素,它有單聲道和雙聲道之分。雙聲道又稱為立體聲,在硬件中有兩條線路,音質(zhì)和音色都要優(yōu)于單聲道,但數(shù)字化后占據(jù)的存儲空間的大小要比單聲道多一倍。
2.2 ALSA基礎(chǔ)
ALSA由許多聲卡的聲卡驅(qū)動程序組成,同時它也提供一個稱為libasound的API庫。應(yīng)用程序開發(fā)者應(yīng)該使用libasound而不是內(nèi)核中的ALSA接口。因為libasound提供最高級并且編程方便的編程接口。并且提供一個設(shè)備邏輯命名功能,這樣開發(fā)者甚至不需要知道類似設(shè)備文件這樣的低層接口。相反,OSS/Free驅(qū)動是在內(nèi)核系統(tǒng)調(diào)用級上編程,它要求開發(fā)者提供設(shè)備文件名并且利用ioctrl來實現(xiàn)相應(yīng)的功能。為了向后兼容,ALSA提供內(nèi)核模塊來模擬OSS,這樣之前的許多在OSS基礎(chǔ)上開發(fā)的應(yīng)用程序不需要任何改動就可以在ALSA上運行。另外,libaoss庫也可以模擬OSS,而它不需要內(nèi)核模塊。
ALSA包含插件功能,使用插件可以擴展新的聲卡驅(qū)動,包括完全用軟件實現(xiàn)的虛擬聲卡。ALSA提供一系列基于命令行的工具集,比如混音器(mixer),音頻文件播放器(aplay),以及控制特定聲卡特定屬性的工具。
2.3 設(shè)備命名
API庫使用邏輯設(shè)備名而不是設(shè)備文件。設(shè)備名字可以是真實的硬件名字也可以是插件名字。硬件名字使用hw:i,j這樣的格式。其中i是卡號,j是這塊聲卡上的設(shè)備號。第一個聲音設(shè)備是hw:0,0.這個別名默認引用第一塊聲音設(shè)備并且在本文示例中一直會被用到。插件使用另外的唯一名字。比如plughw:,表示一個插件,這個插件不提供對硬件設(shè)備的訪問,而是提供像采樣率轉(zhuǎn)換這樣的軟件特性,硬件本身并不支持這樣的特性。
2.4 聲音緩存和數(shù)據(jù)傳輸
每個聲卡都有一個硬件緩存區(qū)來保存記錄下來的樣本。當緩存區(qū)足夠滿時,聲卡將產(chǎn)生一個中斷。內(nèi)核聲卡驅(qū)動然后使用直接內(nèi)存(DMA)訪問通道將樣本傳送到內(nèi)存中的應(yīng)用程序緩存區(qū)。類似地,對于回放,任何應(yīng)用程序使用DMA將自己的緩存區(qū)數(shù)據(jù)傳送到聲卡的硬件緩存區(qū)中。
這樣硬件緩存區(qū)是環(huán)緩存。也就是說當數(shù)據(jù)到達緩存區(qū)末尾時將重新回到緩存區(qū)的起始位置。ALSA維護一個指針來指向硬件緩存以及應(yīng)用程序緩存區(qū)中數(shù)據(jù)操作的當前位置。從內(nèi)核外部看,我們只對應(yīng)用程序的緩存區(qū)感興趣,所以本文只討論應(yīng)用程序緩存區(qū)。
應(yīng)用程序緩存區(qū)的大小可以通過ALSA庫函數(shù)調(diào)用來控制。緩存區(qū)可以很大,一次傳輸操作可能會導致不可接受的延遲,我們把它稱為延時(latency)。為了解決這個問題,ALSA將緩存區(qū)拆分成一系列周期(period)(OSS/Free中叫片斷fragments).ALSA以period為單元來傳送數(shù)據(jù)。
一個周期(period)存儲一些幀(frames)。每一幀包含時間上一個點所抓取的樣本。對于立體聲設(shè)備,一個幀會包含兩個信道上的樣本。圖1展示了分解過程:一個緩存區(qū)分解成周期,然后是幀,然后是樣本。圖中包含一些假定的數(shù)值。圖中左右信道信息被交替地存儲在一個幀內(nèi)。這稱為交錯(interleaved)模式。在非交錯模式中,一個信道的所有樣本數(shù)據(jù)存儲在另外一個信道的數(shù)據(jù)之后。
Over and Under Run
當一個聲卡活動時,數(shù)據(jù)總是連續(xù)地在硬件緩存區(qū)和應(yīng)用程序緩存區(qū)間傳輸。但是也有例外。在錄音例子中,如果應(yīng)用程序讀取數(shù)據(jù)不夠快,循環(huán)緩存區(qū)將會被新的數(shù)據(jù)覆蓋。這種數(shù)據(jù)的丟失被稱為overrun.在回放例子中,如果應(yīng)用程序?qū)懭霐?shù)據(jù)到緩存區(qū)中的速度不夠快,緩存區(qū)將會"餓死"。這樣的錯誤被稱為"underrun"。在ALSA文檔中,有時將這兩種情形統(tǒng)稱為"XRUN"。適當?shù)卦O(shè)計應(yīng)用程序可以最小化XRUN并且可以從中恢復過來。
2.5 訪問音頻設(shè)備
無論是OSS還是ALSA,都是以內(nèi)核驅(qū)動程序的形式運行在Linux內(nèi)核空間中的,應(yīng)用程序要想訪問聲卡這一硬件設(shè)備,必須借助于Linux內(nèi)核所提供的系統(tǒng)調(diào)用(system call)。從程序員的角度來說,對聲卡的操作在很大程度上等同于對磁盤文件的操作:首先使用open系統(tǒng)調(diào)用建立起與硬件間的聯(lián)系,此時返回的文件描述符將作為隨后操作的標識;接著使用read系統(tǒng)調(diào)用從設(shè)備接收數(shù)據(jù),或者使用write系統(tǒng)調(diào)用向設(shè)備寫入數(shù)據(jù),而其它所有不符合讀/寫這一基本模式的操作都可以由ioctl系統(tǒng)調(diào)用來完成;最后,使用close系統(tǒng)調(diào)用告訴Linux內(nèi)核不會再對該設(shè)備做進一步的處理。
open系統(tǒng)調(diào)用
系統(tǒng)調(diào)用open可以獲得對聲卡的訪問權(quán),同時還能為隨后的系統(tǒng)調(diào)用做好準備,其函數(shù)原型如下所示:
int open(const char *pathname, int flags, int mode);
參數(shù)pathname是將要被打開的設(shè)備文件的名稱,對于聲卡來講一般是/dev/dsp。參數(shù)flags用來指明應(yīng)該以什么方式打開設(shè)備文件,它可以是O_RDONLY、O_WRONLY或者O_RDWR,分別表示以只讀、只寫或者讀寫的方式打開設(shè)備文件;參數(shù)mode通常是可選的,它只有在指定的設(shè)備文件不存在時才會用到,指明新創(chuàng)建的文件應(yīng)該具有怎樣的權(quán)限。
如果open系統(tǒng)調(diào)用能夠成功完成,它將返回一個正整數(shù)作為文件標識符,在隨后的系統(tǒng)調(diào)用中需要用到該標識符。如果open系統(tǒng)調(diào)用失敗,它將返回-1,同時還會設(shè)置全局變量errno,指明是什么原因?qū)е铝隋e誤的發(fā)生。
read系統(tǒng)調(diào)用
系統(tǒng)調(diào)用read用來從聲卡讀取數(shù)據(jù),其函數(shù)原型如下所示:
int read(int fd, char *buf, size_t count);
參數(shù)fd是設(shè)備文件的標識符,它是通過之前的open系統(tǒng)調(diào)用獲得的;參數(shù)buf是指向緩沖區(qū)的字符指針,它用來保存從聲卡獲得的數(shù)據(jù);參數(shù)count則用來限定從聲卡獲得的最大字節(jié)數(shù)。如果read系統(tǒng)調(diào)用成功完成,它將返回從聲卡實際讀取的字節(jié)數(shù),通常情況會比count的值要小一些;如果read系統(tǒng)調(diào)用失敗,它將返回-1,同時還會設(shè)置全局變量errno,來指明是什么原因?qū)е铝隋e誤的發(fā)生。
write系統(tǒng)調(diào)用
系統(tǒng)調(diào)用write用來向聲卡寫入數(shù)據(jù),其函數(shù)原型如下所示:
size_t write(int fd, const char *buf, size_t count);
系統(tǒng)調(diào)用write和系統(tǒng)調(diào)用read在很大程度是類似的,差別只在于write是向聲卡寫入數(shù)據(jù),而read則是從聲卡讀入數(shù)據(jù)。參數(shù)fd同樣是設(shè)備文件的標識符,它也是通過之前的open系統(tǒng)調(diào)用獲得的;參數(shù)buf是指向緩沖區(qū)的字符指針,它保存著即將向聲卡寫入的數(shù)據(jù);參數(shù)count則用來限定向聲卡寫入的最大字節(jié)數(shù)。
如果write系統(tǒng)調(diào)用成功完成,它將返回向聲卡實際寫入的字節(jié)數(shù);如果write系統(tǒng)調(diào)用失敗,它將返回-1,同時還會設(shè)置全局變量errno,來指明是什么原因?qū)е铝隋e誤的發(fā)生。無論是read還是write,一旦調(diào)用之后Linux內(nèi)核就會阻塞當前應(yīng)用程序,直到數(shù)據(jù)成功地從聲卡讀出或者寫入為止。
ioctl系統(tǒng)調(diào)用
系統(tǒng)調(diào)用ioctl可以對聲卡進行控制,凡是對設(shè)備文件的操作不符合讀/寫基本模式的,都是通過ioctl來完成的,它可以影響設(shè)備的行為,或者返回設(shè)備的狀態(tài),其函數(shù)原型如下所示:
int ioctl(int fd, int request, ...);
參數(shù)fd是設(shè)備文件的標識符,它是在設(shè)備打開時獲得的;如果設(shè)備比較復雜,那么對它的控制請求相應(yīng)地也會有很多種,參數(shù)request的目的就是用來區(qū)分不同的控制請求;通常說來,在對設(shè)備進行控制時還需要有其它參數(shù),這要根據(jù)不同的控制請求才能確定,并且可能是與硬件設(shè)備直接相關(guān)的。
close系統(tǒng)調(diào)用
當應(yīng)用程序使用完聲卡之后,需要用close系統(tǒng)調(diào)用將其關(guān)閉,以便及時釋放占用的硬件資源,其函數(shù)原型如下所示:
int close(int fd);
參數(shù)fd是設(shè)備文件的標識符,它是在設(shè)備打開時獲得的。一旦應(yīng)用程序調(diào)用了close系統(tǒng)調(diào)用,Linux內(nèi)核就會釋放與之相關(guān)的各種資源,因此建議在不需要的時候盡量及時關(guān)閉已經(jīng)打開的設(shè)備。
2.6 音頻設(shè)備文件
對于Linux應(yīng)用程序員來講,音頻編程接口實際上就是一組音頻設(shè)備文件,通過它們可以從聲卡讀取數(shù)據(jù),或者向聲卡寫入數(shù)據(jù),并且能夠?qū)β暱ㄟM行控制,設(shè)置采樣頻率和聲道數(shù)目等等。
/dev/sndstat
設(shè)備文件/dev/sndstat是聲卡驅(qū)動程序提供的最簡單的接口,通常它是一個只讀文件,作用也僅僅只限于匯報聲卡的當前狀態(tài)。一般說來,/dev/sndstat是提供給最終用戶來檢測聲卡的,不宜用于程序當中,因為所有的信息都可以通過ioctl系統(tǒng)調(diào)用來獲得。 Linux提供的cat命令可以很方便地從/dev/sndstat獲得聲卡的當前狀態(tài):
safrans@safrans-desktop:~$ cat /dev/sndstat
/dev/dsp
聲卡驅(qū)動程序提供的/dev/dsp是用于數(shù)字采樣(sampling)和數(shù)字錄音(recording)的設(shè)備文件,它對于Linux下的音頻編程來講非常重要:向該設(shè)備寫數(shù)據(jù)即意味著激活聲卡上的D/A轉(zhuǎn)換器進行放音,而向該設(shè)備讀數(shù)據(jù)則意味著激活聲卡上的A/D轉(zhuǎn)換器進行錄音。目前許多聲卡都提供有多個數(shù)字采樣設(shè)備,它們在Linux下可以通過/dev/dsp1等設(shè)備文件進行訪問。
DSP是數(shù)字信號處理器(Digital Signal Processor)的簡稱,它是用來進行數(shù)字信號處理的特殊芯片,聲卡使用它來實現(xiàn)模擬信號和數(shù)字信號的轉(zhuǎn)換。聲卡中的DSP設(shè)備實際上包含兩個組成部分:在以只讀方式打開時,能夠使用A/D轉(zhuǎn)換器進行聲音的輸入;而在以只寫方式打開時,則能夠使用D/A轉(zhuǎn)換器進行聲音的輸出。嚴格說來,Linux下的應(yīng)用程序要么以只讀方式打開/dev/dsp輸入聲音,要么以只寫方式打開/dev/dsp輸出聲音,但事實上某些聲卡驅(qū)動程序仍允許以讀寫的方式打開/dev/dsp,以便同時進行聲音的輸入和輸出,這對于某些應(yīng)用場合(如IP電話)來講是非常關(guān)鍵的。
在從DSP設(shè)備讀取數(shù)據(jù)時,從聲卡輸入的模擬信號經(jīng)過A/D轉(zhuǎn)換器變成數(shù)字采樣后的樣本(sample),保存在聲卡驅(qū)動程序的內(nèi)核緩沖區(qū)中,當應(yīng)用程序通過read系統(tǒng)調(diào)用從聲卡讀取數(shù)據(jù)時,保存在內(nèi)核緩沖區(qū)中的數(shù)字采樣結(jié)果將被復制到應(yīng)用程序所指定的用戶緩沖區(qū)中。需要指出的是,聲卡采樣頻率是由內(nèi)核中的驅(qū)動程序所決定的,而不取決于應(yīng)用程序從聲卡讀取數(shù)據(jù)的速度。如果應(yīng)用程序讀取數(shù)據(jù)的速度過慢,以致低于聲卡的采樣頻率,那么多余的數(shù)據(jù)將會被丟棄;如果讀取數(shù)據(jù)的速度過快,以致高于聲卡的采樣頻率,那么聲卡驅(qū)動程序?qū)枞切┱埱髷?shù)據(jù)的應(yīng)用程序,直到新的數(shù)據(jù)到來為止。
在向DSP設(shè)備寫入數(shù)據(jù)時,數(shù)字信號會經(jīng)過D/A轉(zhuǎn)換器變成模擬信號,然后產(chǎn)生出聲音。應(yīng)用程序?qū)懭霐?shù)據(jù)的速度同樣應(yīng)該與聲卡的采樣頻率相匹配,否則過慢的話會產(chǎn)生聲音暫?;蛘咄nD的現(xiàn)象,過快的話又會被內(nèi)核中的聲卡驅(qū)動程序阻塞,直到硬件有能力處理新的數(shù)據(jù)為止。與其它設(shè)備有所不同,聲卡通常不會支持非阻塞(non-blocking)的I/O操作。
無論是從聲卡讀取數(shù)據(jù),或是向聲卡寫入數(shù)據(jù),事實上都具有特定的格式(format),默認為8位無符號數(shù)據(jù)、單聲道、8KHz采樣率,如果默認值無法達到要求,可以通過ioctl系統(tǒng)調(diào)用來改變它們。通常說來,在應(yīng)用程序中打開設(shè)備文件/dev/dsp之后,接下去就應(yīng)該為其設(shè)置恰當?shù)母袷?,然后才能從聲卡讀取或者寫入數(shù)據(jù)。
/dev/audio
/dev/audio類似于/dev/dsp,它兼容于Sun工作站上的音頻設(shè)備,使用的是mu-law編碼方式。如果聲卡驅(qū)動程序提供了對/dev/audio的支持,那么在Linux上就可以通過cat命令,來播放在Sun工作站上用mu-law進行編碼的音頻文件:
safrans@safrans-desktop:~$ cat audio.au > /dev/audio
由于設(shè)備文件/dev/audio主要出于對兼容性的考慮,所以在新開發(fā)的應(yīng)用程序中最好不要嘗試用它,而應(yīng)該以/dev/dsp進行替代。對于應(yīng)用程序來說,同一時刻只能使用/dev/audio或者/dev/dsp其中之一,因為它們是相同硬件的不同軟件接口。
/dev/mixer
在聲卡的硬件電路中,混音器(mixer)是一個很重要的組成部分,它的作用是將多個信號組合或者疊加在一起,對于不同的聲卡來說,其混音器的作用可能各不相同。運行在Linux內(nèi)核中的聲卡驅(qū)動程序一般都會提供/dev/mixer這一設(shè)備文件,它是應(yīng)用程序?qū)煲羝鬟M行操作的軟件接口?;煲羝麟娐吠ǔS蓛蓚€部分組成:輸入混音器(input mixer)和輸出混音器(output mixer)。
輸入混音器負責從多個不同的信號源接收模擬信號,這些信號源有時也被稱為混音通道或者混音設(shè)備。模擬信號通過增益控制器和由軟件控制的音量調(diào)節(jié)器后,在不同的混音通道中進行級別(level)調(diào)制,然后被送到輸入混音器中進行聲音的合成?;煲羝魃系碾娮娱_關(guān)可以控制哪些通道中有信號與混音器相連,有些聲卡只允許連接一個混音通道作為錄音的音源,而有些聲卡則允許對混音通道做任意的連接。經(jīng)過輸入混音器處理后的信號仍然為模擬信號,它們將被送到A/D轉(zhuǎn)換器進行數(shù)字化處理。
輸出混音器的工作原理與輸入混音器類似,同樣也有多個信號源與混音器相連,并且事先都經(jīng)過了增益調(diào)節(jié)。當輸出混音器對所有的模擬信號進行了混合之后,通常還會有一個總控增益調(diào)節(jié)器來控制輸出聲音的大小,此外還有一些音調(diào)控制器來調(diào)節(jié)輸出聲音的音調(diào)。經(jīng)過輸出混音器處理后的信號也是模擬信號,它們最終會被送給喇叭或者其它的模擬輸出設(shè)備。對混音器的編程包括如何設(shè)置增益控制器的級別,以及怎樣在不同的音源間進行切換,這些操作通常來講是不連續(xù)的,而且不會像錄音或者放音那樣需要占用大量的計算機資源。由于混音器的操作不符合典型的讀/寫操作模式,因此除了open和close兩個系統(tǒng)調(diào)用之外,大部分的操作都是通過ioctl系統(tǒng)調(diào)用來完成的。與/dev/dsp不同,/dev/mixer允許多個應(yīng)用程序同時訪問,并且混音器的設(shè)置值會一直保持到對應(yīng)的設(shè)備文件被關(guān)閉為止。
為了簡化應(yīng)用程序的設(shè)計,Linux上的聲卡驅(qū)動程序大多都支持將混音器的ioctl操作直接應(yīng)用到聲音設(shè)備上,也就是說如果已經(jīng)打開了/dev/dsp,那么就不用再打開/dev/mixer來對混音器進行操作,而是可以直接用打開/dev/dsp時得到的文件標識符來設(shè)置混音器。
/dev/sequencer
目前大多數(shù)聲卡驅(qū)動程序還會提供/dev/sequencer這一設(shè)備文件,用來對聲卡內(nèi)建的波表合成器進行操作,或者對MIDI總線上的樂器進行控制,一般只用于計算機音樂軟件中。
3 ALSA Example
一個典型的聲音程序使用PCM的程序通常類似下面的偽代碼:
打開回放或錄音接口
設(shè)置硬件參數(shù)(訪問模式,數(shù)據(jù)格式,信道數(shù),采樣率,等等)
while 有數(shù)據(jù)要被處理:
讀PCM數(shù)據(jù)(錄音)
或 寫PCM數(shù)據(jù)(回放)
關(guān)閉接口
設(shè)置參數(shù),參數(shù)設(shè)置不當將會導致音頻設(shè)備無法正常工作。在設(shè)置參數(shù)前,我們需要了解一下各個參數(shù)的含義以及一些基本概念。
樣本長度(sample):樣本是記錄音頻數(shù)據(jù)最基本的單位,常見的有8位和16位。
通道數(shù)(channel):該參數(shù)為1表示單聲道,2則是立體聲。
楨(frame):楨記錄了一個聲音單元,其長度為樣本長度與通道數(shù)的乘積。
采樣率(rate):每秒鐘采樣次數(shù),該次數(shù)是針對楨而言。
周期(period):音頻設(shè)備一次處理所需要的楨數(shù),對于音頻設(shè)備的數(shù)據(jù)訪問以及音頻數(shù)據(jù)的存儲,都是以此為單位。
交錯模式(interleaved):是一種音頻數(shù)據(jù)的記錄方式,在交錯模式下,數(shù)據(jù)以連續(xù)楨的形式存放,即首先記錄完楨1的左聲道樣本和右聲道樣本(假設(shè)為立體聲格式),再開始楨2的記錄。而在非交錯模式下,首先記錄的是一個周期內(nèi)所有楨的左聲道樣本,再記錄右聲道樣本,數(shù)據(jù)是以連續(xù)通道的方式存儲。不過多數(shù)情況下,我們只需要使用交錯模式就可以了。
我們將在下文中看到一些可以工作的代碼。我建議您在你的Linux系統(tǒng)上測試運行這些代碼。查看輸出并嘗試修改推薦的代碼。和本文相關(guān)的所有實例清單可以從FTP中獲?。?a target="_blank" >ftp.ssc.com/pub/lj/listings/issue126/6735.tgz。
3.1 Example1. Display Some PCM Types and Formats.
[c-sharp]
view plaincopy#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;
}
Example1顯示了一些ALSA使用的PCM數(shù)據(jù)類型和參數(shù)。首先需要做的是包括頭文件。這些頭文件包含了所有庫函數(shù)的聲明。其中之一就是顯示ALSA庫的版本。
這個程序剩下的部分的迭代一些PCM數(shù)據(jù)類型,以流類型開始。ALSA為每次迭代的最后值提供符號常量名,并且提供功能函數(shù)以顯示某個特定值的描述字符串。你將會看到,ALSA支持許多格式。
這個程序必須鏈接到alsalib庫,通過在編譯時需要加上-lasound選項。有些alsa庫函數(shù)使用dlopen函數(shù)以及浮點操作,所以您可能還需要加上-ldl,-lm選項。
下面是該程序的Makefile:
[c-sharp]
view plaincopyCC=gcc
TARGET=test
SRC=$(wildcard *.c)
OBJECT= ${SRC:.c=.o}
INCLUDES=-I/usr/include/alsa
LDFLAGS=-lasound
all:$(TARGET)
$(OBJECT):$(SRC)
$(CC) -c $(INCLUDES) $<
$(TARGET):$(OBJECT)
$(CC) -o $@ $< $(LDFLAGS)
.PHONY:clean
clean:
@rm -rf $(OBJECT) $(TARGET) *~
在電腦上運行,結(jié)果如下:
[c-sharp]
view plaincopyALSA 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)
S16_LE (Signed 16 bit Little Endian)
S16_BE (Signed 16 bit Big Endian)
U16_LE (Unsigned 16 bit Little Endian)
U16_BE (Unsigned 16 bit Big Endian)
S24_LE (Signed 24 bit Little Endian)
S24_BE (Signed 24 bit Big Endian)
U24_LE (Unsigned 24 bit Little Endian)
U24_BE (Unsigned 24 bit Big Endian)
S32_LE (Signed 32 bit Little Endian)
S32_BE (Signed 32 bit Big Endian)
U32_LE (Unsigned 32 bit Little Endian)
U32_BE (Unsigned 32 bit Big Endian)
FLOAT_LE (Float 32 bit Little Endian)
FLOAT_BE (Float 32 bit Big Endian)
FLOAT64_LE (Float 64 bit Little Endian)
FLOAT64_BE (Float 64 bit Big Endian)
IEC958_SUBFRAME_LE (IEC-958 Little Endian)
IEC958_SUBFRAME_BE (IEC-958 Big Endian)
MU_LAW (Mu-Law)
A_LAW (A-Law)
IMA_ADPCM (Ima-ADPCM)
MPEG (MPEG)
GSM (GSM)
SPECIAL (Special)
S24_3LE (Signed 24 bit Little Endian in 3bytes)
S24_3BE (Signed 24 bit Big Endian in 3bytes)
U24_3LE (Unsigned 24 bit Little Endian in 3bytes)
U24_3BE (Unsigned 24 bit Big Endian in 3bytes)
S20_3LE (Signed 20 bit Little Endian in 3bytes)
S20_3BE (Signed 20 bit Big Endian in 3bytes)
U20_3LE (Unsigned 20 bit Little Endian in 3bytes)
U20_3BE (Unsigned 20 bit Big Endian in 3bytes)
S18_3LE (Signed 18 bit Little Endian in 3bytes)
S18_3BE (Signed 18 bit Big Endian in 3bytes)
U18_3LE (Unsigned 18 bit Little Endian in 3bytes)
U18_3BE (Unsigned 18 bit Big Endian in 3bytes)
PCM subformats:
STD (Standard)
PCM states:
OPEN
SETUP
PREPARED
RUNNING
XRUN
DRAINING
PAUSED
SUSPENDED
DISCONNECTED
3.2 Example2. Opening PCM Device and Setting Parameters.
[c-sharp]
view plaincopy/*
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;
rc = snd_pcm_open(&handle, " hw:0,1", SND_PCM_STREAM_PLAYBACK, 0); /* Open PCM device for playback. */
if (rc < 0) {
fprintf(stderr, "unable to open pcm device: %s/n", snd_strerror(rc));
exit(1);
}
snd_pcm_hw_params_alloca(¶ms); /* Allocate a hardware parameters object. */
snd_pcm_hw_params_any(handle, params); /* Fill it in with default values. */
/* Set the desired hardware parameters. */
snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); /* Interleaved mode */
snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE); /* Signed 16-bit little-endian format */
snd_pcm_hw_params_set_channels(handle, params, 2); /* Two channels (stereo) */
val = 44100;
snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir); /* 44100 bits/second sampling rate (CD quality) */
rc = snd_pcm_hw_params(handle, params); /* Write the parameters to the driver */
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;
}
example2打開hw:0,1的PCM設(shè)備,設(shè)置一些硬件參數(shù)并且打印出最常用的硬件參數(shù)值。它并不做任何回放或錄音的操作。snd_pcm_open打開hw:0,1的PCM設(shè)備并設(shè)置訪問模式為PLAYBACK。這個函數(shù)返回一個句柄,這個句柄保存在第一個函數(shù)參數(shù)中。該句柄會在隨后的函數(shù)中用到。像其它函數(shù)一樣,這個函數(shù)返回一個整數(shù)。如果返回值小于0,則代碼函數(shù)調(diào)用出錯。如果出錯,我們用snd_errstr打開錯誤信息并退出。
為了設(shè)置音頻流的硬件參數(shù),我們需要分配一個類型為snd_pcm_hw_param的變量。分配用到函數(shù)宏snd_pcm_hw_params_alloca。下一步,我們使用函數(shù)snd_pcm_hw_params_any來初始化這個變量,傳遞先前打開的PCM流句柄。
接下來,我們調(diào)用API來設(shè)置我們所需的硬件參數(shù)。這些函數(shù)需要三個參數(shù):PCM流句柄,參數(shù)類型,參數(shù)值。我們設(shè)置流為交錯模式,16位的樣本大小,2個信道,44100bps的采樣率。對于采樣率而言,聲音硬件并不一定就精確地支持我們所定的采樣率,但是我們可以使用函數(shù)snd_pcm_hw_params_set_rate_near來設(shè)置最接近我們指定的采樣率的采樣率。其實只有當我們調(diào)用函數(shù)snd_pcm_hw_params后,硬件參數(shù)才會起作用。
程序的剩余部分獲得并打印一些PCM流參數(shù),包括周期和緩沖區(qū)大小。結(jié)果可能會因為聲音硬件的不同而不同。
運行該程序后,做實驗,改動一些代碼,設(shè)置不同的硬件參數(shù)然后觀察結(jié)果的變化。
在電腦上運行,結(jié)果如下:
[c-sharp]
view plaincopyPCM handle name = 'hw:0,1'
PCM state = PREPARED
access type = RW_INTERLEAVED
format = 'S16_LE' (Signed 16 bit Little Endian)
subformat = 'STD' (Standard)
channels = 2
rate = 44100 bps
period time = 362 us
period size = 16 frames
buffer time = 362 us
buffer size = 16384 frames
periods per buffer = 1024 frames
exact rate = 1445100000/32768 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 = 1
can pause = 1
can resume = 0
can sync start = 1
3.3 Example3.Simple Sound Playback.
[c-sharp]
view plaincopy/*Example 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;
rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0); /* Open PCM device for playback. */
if (rc < 0) {
fprintf(stderr, "unable to open pcm device: %s/n", snd_strerror(rc));
exit(1);
}
snd_pcm_hw_params_alloca(¶ms); /* Allocate a hardware parameters object. */
snd_pcm_hw_params_any(handle, params); /* Fill it in with default values. */
/* Set the desired hardware parameters. */
snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); /* Interleaved mode */
snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE); /* Signed 16-bit little-endian format */
snd_pcm_hw_params_set_channels(handle, params, 2); /* Two channels (stereo) */
val = 44100;
snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir); /* 44100 bits/second sampling rate (CD quality) */
frames = 32;
snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir); /* Set period size to 32 frames. */
rc = snd_pcm_hw_params(handle, params); /* Write the parameters to the driver */
if (rc < 0) {
fprintf(stderr, "unable to set hw parameters: %s/n", snd_strerror(rc));
exit(1);
}
snd_pcm_hw_params_get_period_size(params, &frames, &dir); /* Use a buffer large enough to hold one period */
size = frames * 4; /* 2 bytes/sample, 2 channels */
buffer = (char *) malloc(size);
snd_pcm_hw_params_get_period_time(params, &val, &dir); /* We want to loop for 5 seconds */
loops = 5000000 / val; /* 5 seconds in microseconds divided by period time */
while (loops > 0) {
loops--;
rc = read(0, buffer, size); //fd為0,表示為標準輸入
if (rc == 0) {
fprintf(stderr, "end of file on input/n");
break;
} else if (rc != size) {
fprintf(stderr, "short read: read %d bytes/n", rc);
}
rc = snd_pcm_writei(handle, buffer, frames);
if (rc == -EPIPE) {
fprintf(stderr, "underrun occurred/n");/* EPIPE means underrun */
snd_pcm_prepare(handle);
} else if (rc < 0) {
fprintf(stderr, "error from writei: %s/n", snd_strerror(rc));
} else if (rc != (int)frames) {
fprintf(stderr, "short write, write %d frames/n", rc);
}
}
snd_pcm_drain(handle);
snd_pcm_close(handle);
free(buffer);
return 0;
}
example3擴展了之前的示例。向聲卡中寫入了一些聲音樣本以實現(xiàn)聲音回放。在這個例子中,我們從標準輸入中讀取數(shù)據(jù),每個周期讀取足夠多的數(shù)據(jù),然后將它們寫入到聲卡中,直到5秒鐘的數(shù)據(jù)全部傳輸完畢。
這個程序的開始處和之前的版本一樣---打開PCM設(shè)備、設(shè)置硬件參數(shù)。我們使用由ALSA自己選擇的周期大小,申請該大小的緩沖區(qū)來存儲樣本。然后我們找出周期時間,這樣我們就能計算出本程序為了能夠播放5秒鐘,需要多少個周期。
在處理數(shù)據(jù)的循環(huán)中,我們從標準輸入中讀入數(shù)據(jù),并往緩沖區(qū)中填充一個周期的樣本。然后檢查并處理錯誤,這些錯誤可能是由到達文件結(jié)尾,或讀取的數(shù)據(jù)長度與我期望的數(shù)據(jù)長度不一致導致的。
我們調(diào)用snd_pcm_writei來發(fā)送數(shù)據(jù)。它操作起來很像內(nèi)核的寫系統(tǒng)調(diào)用,只是這里的大小參數(shù)是以幀來計算的。我們檢查其返回代碼值。返回值為EPIPE表明發(fā)生了underrun,使得PCM音頻流進入到XRUN狀態(tài)并停止處理數(shù)據(jù)。從該狀態(tài)中恢復過來的標準方法是調(diào)用snd_pcm_prepare函數(shù),把PCM流置于PREPARED狀態(tài),這樣下次我們向該PCM流中數(shù)據(jù)時,它就能重新開始處理數(shù)據(jù)。如果我們得到的錯誤碼不是EPIPE,我們把錯誤碼打印出來,然后繼續(xù)。最后,如果寫入的幀數(shù)不是我們期望的,則打印出錯誤消息。
這個程序一直循環(huán),直到5秒鐘的幀全部傳輸完,或者輸入流讀到文件結(jié)尾。然后我們調(diào)用snd_pcm_drain把所有掛起沒有傳輸完的聲音樣本傳輸完全,最后關(guān)閉該音頻流,釋放之前動態(tài)分配的緩沖區(qū),退出。
我們可以看到這個程序沒有什么用,除非標準輸入被重定向到了其它的文件。嘗試用設(shè)備/dev/urandom來運行這個程序,該設(shè)備產(chǎn)生隨機數(shù)據(jù):
./test </dev/urandom
隨機數(shù)據(jù)會產(chǎn)生5秒鐘的白色噪聲。然后,嘗試把標準輸入重定向到設(shè)備/dev/null和/dev/zero上,并比較結(jié)果。改變一些參數(shù),例如采樣率和數(shù)據(jù)格式,然后查看結(jié)果的變化。
3.4 Example4. Simple Sound Recording.
[c-sharp]
view plaincopy/*
Example 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 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;
rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, 0); /* Open PCM device for recording (capture). */
if (rc < 0) {
fprintf(stderr, "unable to open pcm device: %s/n", snd_strerror(rc));
exit(1);
}
snd_pcm_hw_params_alloca(¶ms); /* Allocate a hardware parameters object. */
snd_pcm_hw_params_any(handle, params); /* Fill it in with default values. */
/* Set the desired hardware parameters. */
snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); /* Interleaved mode */
snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE); /* Signed 16-bit little-endian format */
snd_pcm_hw_params_set_channels(handle, params, 2); /* Two channels (stereo) */
val = 44100;
snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir); /* 44100 bits/second sampling rate (CD quality) */
frames = 32;
snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir); /* Set period size to 32 frames. */
rc = snd_pcm_hw_params(handle, params); /* Write the parameters to the driver */
if (rc < 0) {
fprintf(stderr,"unable to set hw parameters: %s/n", snd_strerror(rc));
exit(1);
}
snd_pcm_hw_params_get_period_size(params, &frames, &dir); /* Use a buffer large enough to hold one period */
size = frames * 4; /* 2 bytes/sample, 2 channels */
buffer = (char *) malloc(size);
snd_pcm_hw_params_get_period_time(params, &val, &dir); /* We want to loop for 5 seconds */
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;
}
example4類似于example3中的程序,除了這里的程序是做聲音的抓取(錄音)。當打開PCM設(shè)備時我們指定打開模式為SND_PCM_STREAM_CPATURE。在主循環(huán)中,我們調(diào)用snd_pcm_readi從聲卡中讀取數(shù)據(jù),并把它們寫入到標準輸出。同樣地,我們檢查是否有overrun,如果存在,用與前例中相同的方式處理。
運行example4的程序?qū)浿茖⒔?秒鐘的聲音數(shù)據(jù),并把它們發(fā)送到標準輸出。你也可以重定向到某個文件。如果你有一個麥克風連接到你的聲卡,可以使用某個混音程序(mixer)設(shè)置錄音源和級別。同樣地,你也可以運行一個CD播放器程序并把錄音源設(shè)成CD。嘗試運行程序4并把輸出定向到某個文件,然后運行程序3播放該文件里的聲音數(shù)據(jù):
./listing4>sound.raw
./listing3<sound.raw
如果你的聲卡支持全雙工,你可以通過管道把兩個程序連接起來,這樣就可以從聲卡中聽到錄制的聲音:
./listing4 | ./listing3
同樣地,您可以做實驗,看看采樣率和樣本格式的變化會產(chǎn)生什么影響。
3.5 高級特性
在前面的例子中,PCM流是以阻塞模式操作的,也就是說,直到數(shù)據(jù)已經(jīng)傳送完,PCM接口調(diào)用才會返回。在事件驅(qū)動的交互式程序中,這樣會長時間阻塞應(yīng)用程序,通常是不能接受的。ALSA支持以非阻塞模式打開音頻流,這樣讀寫函數(shù)調(diào)用后立即返回。如果數(shù)據(jù)傳輸被掛起,調(diào)用不能被處理,ALSA就是返回一個EBUSY的錯誤碼。
許多圖形應(yīng)用程序使用回調(diào)來處理事件。ALSA支持以異步的方式打開一個PCM音頻流。這使得當某個周期的樣本數(shù)據(jù)被傳輸完后,某個已注冊的回調(diào)函數(shù)將會調(diào)用。
這里用到的snd_pcm_readi和snd_pcm_writei調(diào)用和Linux下的讀寫系統(tǒng)調(diào)用類似。字母i表示處理的幀是交錯式(interleaved)的。ALSA中存在非交互模式的對應(yīng)的函數(shù)。Linux下的許多設(shè)備也支持mmap系統(tǒng)調(diào)用,這個調(diào)用將設(shè)備內(nèi)存映射到主內(nèi)存,這樣數(shù)據(jù)就可以用指針來維護。ALSA也運行以mmap模式打開一個PCM信道,這允許有效的零拷貝(zero copy)方式訪問聲音數(shù)據(jù)。
4 ALSA移植
alsa的移植請參考以下網(wǎng)址:
http://blog.csdn.net/jiajie961/archive/2010/11/30/6045507.aspxhttp://blog.csdn.net/jiajie961/archive/2010/12/01/6047077.aspx5 Preference
1.
http://blog.csdn.net/jiajie961/archive/2010/11/30/6045507.aspx2.
http://blog.csdn.net/jiajie961/archive/2010/12/01/6047077.aspx3.
http://www.alsa-project.org/main/index.php/Main_Page4.
http://www.linuxjournal.com/article/67355.
http://apps.hi.baidu.com/share/detail/7910583