Linux音频驱动整理
典型的數(shù)字音頻系統(tǒng)電路組成如圖所示:
?
通過PCM、 IIS 或 AC97 這些音頻接口連接外部的音頻編解碼器即可實現(xiàn)聲音的 AD 和 DA 轉(zhuǎn)換,圖中的功放完成模擬信號的放大功能。音頻編解碼器是數(shù)字音頻系統(tǒng)的核心,主要指標(biāo)有:
1、采樣頻率
采樣頻率是每秒鐘的采樣次數(shù),理論上采樣頻率越高,轉(zhuǎn)換精度越高,目前主流的采樣頻率是48KHZ
2、量化精度
量化精度是指對采樣數(shù)據(jù)分析的精度,比如24bit精度就是指將標(biāo)準(zhǔn)電平信號按照2^24進(jìn)行分析,量化精度越高,聲音就越逼真。
?
音頻設(shè)備硬件接口
1、PCM接口
PCM(脈沖編碼調(diào)制)接口,該接口由時鐘脈沖(BCLK)、幀同步信號(FS)及接收數(shù)據(jù)(DR)和發(fā)送數(shù)據(jù)(DX)組成。在 FS 信號的上升沿,數(shù)據(jù)傳輸從 MSB(Most Significant Bit)開始, FS 頻率等于采樣率。 FS 信號之后開始數(shù)據(jù)字的傳輸,單個的數(shù)據(jù)位按順序進(jìn)行傳輸,一個時鐘周期傳輸一個數(shù)據(jù)字。發(fā)送 MSB 時,信號的等級首先降到最低,以避免在不同終端的接口使用不同的數(shù)據(jù)方案時造成 MSB 的丟失。PCM接口需要每個音頻通道獲得一個獨立的數(shù)據(jù)隊列。
2、IIS接口
IIS接口在一個稱為LRCLK(Left/Right Clock)的信號機(jī)制中經(jīng)過多路轉(zhuǎn)換將兩路音頻信號變成單一的數(shù)據(jù)隊列。當(dāng) LRCLK 為高時,左聲道數(shù)據(jù)被傳輸; LRCLK 為低時,右聲道數(shù)據(jù)被傳輸。與 PCM 相比, IIS 更適合于立體聲系統(tǒng)。
3、AC97接口
AC'97 不只是一種數(shù)據(jù)格式,用于音頻編碼的內(nèi)部架構(gòu)規(guī)格,它還具有控制功能。AC'97 采用 AC-Link 與外部的編解碼器相連, AC-Link 接口包括位時鐘(BITCLK)、同步信號校正(SYNC) 和從編碼到處理器及從處理器中解碼(SDATDIN 與 SDATAOUT) 的數(shù)據(jù)隊列。
AC'97數(shù)據(jù)幀以 SYNC 脈沖開始,包括 12 個 20 位時間段(時間段為標(biāo)準(zhǔn)中定義的不同的目的服務(wù))及 16 位“tag”段,共計 256 個數(shù)據(jù)序列。例如,時間段“1”和“2”用于訪問編碼的控制寄存器,而時段“3”和“4”分別負(fù)載左、右兩個音頻通道。“tag”段表示其他段中哪一個包含有效數(shù)據(jù)。把幀分成時間段使傳輸控制信號和音頻數(shù)據(jù)僅通過 4 根線到達(dá) 9 個音頻通道或轉(zhuǎn)換成其他數(shù)據(jù)流成為可能。
?
PCM、 IIS 和 AC97 各有其優(yōu)點和應(yīng)用范圍,例如在 CD、 MD、 MP3 隨身聽多采用 IIS 接口,移動電話會采用 PCM 接口,具有音頻功能的 PDA 則多使用和 PC 一樣的 AC'97 編碼格式。
?
在Linux中,主要有OSS和ALSA兩種音頻設(shè)備驅(qū)動框架。
Linux ALSA 音頻設(shè)備驅(qū)動
ALSA的組成
ALSA的主要特點:
? 支持多種聲卡設(shè)備。
? 模塊化的內(nèi)核驅(qū)動程序。
? 支持 SMP 和多線程。
? 提供應(yīng)用開發(fā)函數(shù)庫(alsa-lib)以簡化應(yīng)用程序開發(fā)。
? 支持 OSS API,兼容 OSS 應(yīng)用程序。
ALSA 系統(tǒng)包括驅(qū)動包 alsa-driver(內(nèi)核驅(qū)動程序)、開發(fā)包 alsa-libs(用戶函數(shù)庫,應(yīng)用程序使用時應(yīng)包含頭文件 asoundlib.h,并使用共享庫libasound.so)、開發(fā)包插件 alsa-libplugins、設(shè)置管理工具包 alsa-utils(包含一些基于 ALSA 的用于控制聲卡的應(yīng)用程序)、其他聲音相關(guān)處理小程序包 alsa-tools、特殊音頻固件支持包 alsa-firmware、 OSS 接口兼容模擬層工具alsa-oss 共 7 個子項目,其中只有驅(qū)動包是必需的。
目前 ALSA 內(nèi)核提供給用戶空間的接口有:
? 信息接口(Information Interface, /proc/asound);
? 控制接口(Control Interface, /dev/snd/controlCX);
? 混音器接口(Mixer Interface, /dev/snd/mixerCXDX);
? PCM 接口(PCM Interface, /dev/snd/pcmCXDX);
? Raw 迷笛接口(Raw MIDI Interface, /dev/snd/midiCXDX);
? 音序器接口(Sequencer Interface, /dev/snd/seq);
? 定時器接口(Timer Interface, /dev/snd/timer)。
這些接口被提供給alsa-lib使用。
?
card和組件管理
對于每個聲卡而言,必須創(chuàng)建一個card實例。card管理這個聲卡上所有設(shè)備(組件),如 PCM、 mixers、 MIDI、 synthesizer 等。
1、創(chuàng)建card
struct snd_card *snd_card_new(int idx, const char *xid,struct module *module, int extra_size);
idx 是 card 索引號, xid 是標(biāo)識字符串, module 一般為 THIS_MODULE, extra_size 是要分配的額外數(shù)據(jù)的大小,分配的 extra_size 大小的內(nèi)存將作為 card->private_data。
2、創(chuàng)建組件
int snd_device_new(struct snd_card *card, snd_device_type_t type,void *device_data, struct snd_device_ops *ops);
當(dāng) card 被創(chuàng)建后,設(shè)備(組件)能夠被創(chuàng)建并關(guān)聯(lián)于該 card。第 1 個參數(shù)是 snd_card_new()創(chuàng)建的 card指 針 , 第 2 個 參 數(shù) type 指 的 是 device-level 即 設(shè) 備 類 型 , 形 式 為 SNDRV_DEV_XXX , 包 括SNDRV_DEV_CODEC、 SNDRV_DEV_CONTROL、 SNDRV_DEV_PCM、 SNDRV_DEV_ RAWMIDI 等,用戶自定義設(shè)備的 device-level是 SNDRV_DEV_LOWLEVEL, device_data 是設(shè)備數(shù)據(jù)指針,注意函數(shù) snd_device_ new()本身不會分配設(shè)備數(shù)據(jù)的內(nèi)存,因此應(yīng)事先分配,ops參數(shù)是 1個函數(shù)集(定義為 snd_device_ops結(jié)構(gòu)體)的指針。
3、組件釋放
每個 ALSA 預(yù)定義的組件在構(gòu)造時需調(diào)用 snd_device_new(),而每個組件的析構(gòu)方法則在函數(shù)集中被包含。
對于 PCM、 AC97 此類預(yù)定義組件, 我們不需關(guān)心它們的析構(gòu), 而對于自定義的組件, 則需要填充 snd_device_ops中的析構(gòu)函數(shù)指針 dev_free,這樣,當(dāng) snd_card_free()被調(diào)用時,組件將自動被釋放。
4、芯片特定的數(shù)據(jù)(Chip-Specific Data)
芯片特定的數(shù)據(jù)一般以 struct xxxchip 結(jié)構(gòu)體形式組織,這個結(jié)構(gòu)體中包含芯片相關(guān)的 I/O 端口地址、資源指針、中斷號等,其意義等同于字符設(shè)備驅(qū)動中的 file->private_data。
定義芯片特定的數(shù)據(jù)主要有兩種方法,一種方法是將 sizeof(struct xxxchip)傳入 snd_card_new()作為extra_size 參數(shù),它將自動成為 snd_card 的 private_data 成員,代碼如下:
struct xxxchip //芯片特定的數(shù)據(jù)結(jié)構(gòu)體 {...};card = snd_card_new(index, id, THIS_MODULE, sizeof(struct xxxchip));//創(chuàng)建聲卡并申請xxx_ch 內(nèi)存作為card-> private_datastruct xxxchip *chip = card->private_data;另一種方法是在snd_card_new()傳入給 extra_size 參數(shù) 0,再分配 sizeof(struct xxxchip)的內(nèi)存,將分配內(nèi)存的地址傳入snd_device_new()的 device_data 的參數(shù)
struct snd_card *card;struct xxxchip *chip;//使用 0 作為第 4 個參數(shù),并動態(tài)分配 xxx_chip 的內(nèi)存card=snd_card_new(index[dev], id[dev], THIS_MODULE, 0);...chip = kzalloc(sizeof(*chip), GFP_KERNEL);//在 xxxchip 結(jié) 構(gòu) 體 中 , 應(yīng) 該 包 括 聲 卡 指 針struct xxxchip{struct snd_card *card;...};//并將其 card 成員賦值為 snd_card_new()創(chuàng)建的 card 指針chip->card = card;static struct snd_device_ops ops ={. dev_free = snd_xxx_chip_dev_free, //組件析構(gòu)};...//創(chuàng)建自定義組件snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);//在析構(gòu)函數(shù)中釋放 xxxchip 內(nèi)存static int snd_xxx_chip_dev_free(struct snd_device *device){return snd_xxx_chip_free(device->device_data); //釋放}5、注釋/釋放聲卡
當(dāng) snd_card 被準(zhǔn)備好以后,可使用 snd_card_register()函數(shù)注冊這個聲卡,如下所示:
int snd_card_register(struct snd_card *card);
對應(yīng)的 snd_card_free()完成相反的功能,如下所示:
int snd_card_free(struct snd_card *card);
?
PCM設(shè)備
每個聲卡最多可以有四個 PCM 實例,一個 PCM 實例對應(yīng)一個設(shè)備文件。 PCM 實例由 PCM 播放和錄音流組成,而每個 PCM 流又由一個或多個 PCM 子流組成。
1、PCM實例構(gòu)造
int snd_pcm_new(struct snd_card *card, char *id, int device,int playback_count, int capture_count, struct snd_pcm ** rpcm);
第 1 個參數(shù)是 card 指針,第 2 個是標(biāo)識字符串,第 3 個是 PCM 設(shè)備索引(0 表示第 1 個 PCM 設(shè)備),第 4 和第 5 個分別為播放和錄音設(shè)備的子流數(shù)。在每個回調(diào)函數(shù)中, 可以通過 snd_pcm_substream 的 number 成員得知目前操作的究竟是哪個子流,如下所示:
struct snd_pcm_substream *substream;int index = substream->number;一般習(xí)慣的做法是在驅(qū)動中定義一個PCM“構(gòu)造函數(shù)”,負(fù)責(zé)PCM實例的創(chuàng)建。
2、設(shè)置PCM操作
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction, struct snd_pcm_ops *ops);
第 1 個參數(shù)是 snd_pcm 的指針,第 2 個參數(shù)是SNDRV_PCM_STREAM_PLAYBACK 或 SNDRV_PCM_STREAM_CAPTURE,而第 3 個參數(shù)是 PCM 操作結(jié)構(gòu)體 snd_pcm_ops,這個結(jié)構(gòu)體的定義如下所示:
struct snd_pcm_ops{int (*open)(struct snd_pcm_substream *substream);//打開int (*close)(struct snd_pcm_substream *substream);//關(guān)閉int (*ioctl)(struct snd_pcm_substream * substream,unsigned int cmd, void *arg);//I/O 控制int (*hw_params)(struct snd_pcm_substream *substream,struct snd_pcm_hw_params *params);//硬件參數(shù)int (*hw_free)(struct snd_pcm_substream *substream); //資源釋放int (*prepare)(struct snd_pcm_substream *substream);//準(zhǔn)備//在 PCM 被開始、停止或暫停時調(diào)用int (*trigger)(struct snd_pcm_substream *substream, int cmd);snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);// 當(dāng)前緩沖區(qū)的硬件位置//緩沖區(qū)復(fù)制int (*copy)(struct snd_pcm_substream *substream, int channel,snd_pcm_uframes_t pos,void _ _user *buf, snd_pcm_uframes_t count);int (*silence)(struct snd_pcm_substream *substream, int channel,snd_pcm_uframes_t pos, snd_pcm_uframes_t count);struct page *(*page)(struct snd_pcm_substream *substream,unsigned long offset);int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);int (*ack)(struct snd_pcm_substream *substream);};snd_pcm_ops 中的所有操作都需事先通過 snd_pcm_substream_chip()獲得 xxxchip 指針,例如:
int xxx() {struct xxxchip *chip = snd_pcm_substream_chip(substream); ... }當(dāng)一個 PCM 子流被打開時, snd_pcm_ops 中的 open()函數(shù)將被調(diào)用,在這個函數(shù)中,至少需要初始化runtime->hw 字段,如下:
static int snd_xxx_open(struct snd_pcm_substream *substream){//從子流獲得 xxxchip 指針struct xxxchip *chip = snd_pcm_substream_chip(substream);//獲得 PCM 運行時信息指針struct snd_pcm_runtime *runtime = substream->runtime;...//初始化 runtime->hwruntime->hw = snd_xxxchip_playback_hw; //預(yù)先定義的硬件描述return 0;}snd_pcm_ops 的 hw_params()成員函數(shù)將在應(yīng)用程序設(shè)置硬件參數(shù)(PCM 子流的周期大小、緩沖區(qū)大小和格式等)的時候被調(diào)用,它的形式如下:
static int snd_xxx_hw_params(struct snd_pcm_substream *substream,struct snd_pcm_hw_params *hw_params);
在這個函數(shù)中,將完成大量硬件設(shè)置,甚至包括緩沖區(qū)分配,這時可調(diào)用如下輔助函數(shù):
snd_pcm_lib_malloc_pages(substream,params_buffer_bytes(hw_params));
僅當(dāng) DMA 緩沖區(qū)已被預(yù)先分配的情況下,上述調(diào)用才可成立。
與 hw_params()對應(yīng)的函數(shù)是 hw_free(),它釋放由 hw_params()分配的資源,例如,通過如下調(diào)用釋放snd_pcm_lib_malloc_pages()緩沖區(qū):
snd_pcm_lib_free_pages(substream);
當(dāng) PCM 子流被關(guān)閉時, close()函數(shù)將被調(diào)用。如果 open()函數(shù)中分配了私有數(shù)據(jù),則在 close()函數(shù)中應(yīng)該釋放 substream 的私有數(shù)據(jù),代碼如下:
static int snd_xxx_close(struct snd_pcm_substream *substream){//釋放子流私有數(shù)據(jù)kfree(substream->runtime->private_data);//...}當(dāng) PCM 被“準(zhǔn)備”時, prepare()函數(shù)將被調(diào)用,在其中可以設(shè)置采樣率、格式等。 prepare()函數(shù)與hw_params()函數(shù)的不同在于對 prepare()的調(diào)用發(fā)生在 snd_pcm_prepare()每次被調(diào)用的時候。 prepare()的形式如下:
static int snd_xxx_prepare(struct snd_pcm_substream *substream);
trigger()成員函數(shù)在PCM被開始、停止或暫停時使用,函數(shù)的形式如下:
static int snd_xxx_trigger(struct snd_pcm_substream *stream,int cmd);
cmd參數(shù)定義了具體的行為,在trigger()成員函數(shù)中至少要處理SNDRV_PCM_TRIGGER和SNDRV_PCM_TRIGGER_STOP 命 令 , 如 果 PCM 支 持 暫 停 , 還 應(yīng) 處 理 SNDRV_PCM_TRIGGER_PAUSE_PUSH 和 SNDRV_PCM_TRIGGER_PAUSE_RELEASE 命令。如果設(shè)備支持掛起/恢復(fù),當(dāng) 能 量 管 理 狀 態(tài) 發(fā) 生 變 化 時 將 處 理 SNDRV_PCM_TRIGGER_SUSPEND 和 SNDRV_PCM_TRIGGER_RESUME 這兩個命令。注意 trigger()函數(shù)是原子的,中途不能睡眠。代碼如下:
static int snd_xxx_trigger(struct snd_pcm_substream *substream, int cmd){switch (cmd){case SNDRV_PCM_TRIGGER_START:// 開啟 PCM 引擎break;case SNDRV_PCM_TRIGGER_STOP:// 停止 PCM 引擎break;...//其他命令default:return - EINVAL;}}pointer()函數(shù)用于 PCM 中間層查詢目前緩沖區(qū)的硬件位置,該函數(shù)以幀的形式返回 0~buffer_size – 1的位置(ALSA 0.5.x 中為字節(jié)形式),此函數(shù)也是原子的。
3、分配緩沖區(qū)
int snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm,int type, void *data, size_t size, size_t max);
type 參數(shù)是 緩 沖區(qū) 的類 型 ,包含 SNDRV_DMA_TYPE_UNKNOWN ( 未 知 )、 SNDRV_DMA_TYPE_CONTINUOUS(連續(xù)的非 DMA 內(nèi)存)、 SNDRV_DMA_TYPE_DEV(連續(xù)的通用設(shè)備)SNDRV_DMA_TYPE_DEV_SG(通用設(shè)備 SG-buffer)和 SNDRV_DMA_TYPE_SBUS(連續(xù)的 SBUS)。如下
代碼將分配 64KB 的緩沖區(qū):
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,snd_dma_pci_data(chip->pci),64*1024, 64*1024);
4.設(shè)置標(biāo)志
在構(gòu)造 PCM 實例、設(shè)置操作集并分配緩沖區(qū)之后,如果有需要,應(yīng)設(shè)置 PCM 的信息標(biāo)志,例如,如果 PCM 設(shè)備只支持半雙工,則這樣定義標(biāo)志:
pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
5.PCM 實例析構(gòu)
PCM 實例的“析構(gòu)函數(shù)”并非是必須的,因為 PCM 實例會被 PCM 中間層代碼自動釋放,如果驅(qū)動中分配了一些特別的內(nèi)存空間,則必須定義“析構(gòu)函數(shù)”,“析構(gòu)函數(shù)”會釋放“構(gòu)造函數(shù)”中創(chuàng)建的 xxx_private_pcm_data。
static void xxxchip_pcm_free(struct snd_pcm *pcm){/* 從 pcm 實例得到 chip */struct xxxchip *chip = snd_pcm_chip(pcm);/* 釋放自定義用途的內(nèi)存 */kfree(chip->xxx_private_pcm_data);...}static int __devinit snd_xxxchip_new_pcm(struct xxxchip *chip){struct snd_pcm *pcm;.../* 分配自定義用途的內(nèi)存 */chip->xxx_private_pcm_data = kmalloc(...);pcm->private_data = chip;/* 設(shè)置“析構(gòu)函數(shù)” */pcm->private_free = xxxchip_pcm_free;...}?
6、PCM信息運行時結(jié)構(gòu)體
當(dāng)PCM子流被打開后,PCM運行時實例(定義為結(jié)構(gòu)體為snd_pcm_runtime)將被分配給這個子流, 這個指針通過 substream->runtime 獲得。運行時指針包含各種各樣的信息: hw_params及 sw_params 配置的拷貝、緩沖區(qū)指針、 mmap 記錄、自旋鎖等,幾乎 PCM 的所有控制信息均能從中取得。
struct snd_pcm_runtime{/* 狀態(tài) */struct snd_pcm_substream *trigger_master;snd_timestamp_t trigger_tstamp; /* 觸發(fā)時間戳 */int overrange;snd_pcm_uframes_t avail_max;snd_pcm_uframes_t hw_ptr_base; /* 緩沖區(qū)復(fù)位時的位置 */snd_pcm_uframes_t hw_ptr_interrupt; /* 中斷時的位置*//* 硬件參數(shù) */snd_pcm_access_t access; /* 存取模式 */snd_pcm_format_t format; /* SNDRV_PCM_FORMAT_* */snd_pcm_subformat_t subformat; /* 子格式 */unsigned int rate; /* rate in Hz */unsigned int channels; /* 通道 */snd_pcm_uframes_t period_size; /* 周期大小 */unsigned int periods; /* 周期數(shù) */snd_pcm_uframes_t buffer_size; /* 緩沖區(qū)大小 */unsigned int tick_time; /* tick time */snd_pcm_uframes_t min_align; /* 格式對應(yīng)的最小對齊*/size_t byte_align;unsigned int frame_bits;unsigned int sample_bits;unsigned int info;unsigned int rate_num;unsigned int rate_den;/* 軟件參數(shù) */struct timespec tstamp_mode; /* mmap 時間戳被更新*/unsigned int period_step;unsigned int sleep_min; /* 睡眠的最小節(jié)拍 */snd_pcm_uframes_t xfer_align;snd_pcm_uframes_t start_threshold;snd_pcm_uframes_t stop_threshold;snd_pcm_uframes_t silence_threshold; //Silence 填充閾值snd_pcm_uframes_t silence_size; /* Silence 填充大小 */snd_pcm_uframes_t boundary;snd_pcm_uframes_t silenced_start;snd_pcm_uframes_t silenced_size;snd_pcm_sync_id_t sync; /* 硬件同步 ID *//* mmap */volatile struct snd_pcm_mmap_status *status;volatile struct snd_pcm_mmap_control *control;atomic_t mmap_count;/* 鎖/調(diào)度 */spinlock_t lock;wait_queue_head_t sleep;struct timer_list tick_timer;struct fasync_struct *fasync;/* 私有段 */void *private_data;void(*private_free)(struct snd_pcm_runtime *runtime);/* 硬件描述 */struct snd_pcm_hardware hw;struct snd_pcm_hw_constraints hw_constraints;/* 中斷回調(diào)函數(shù) */void(*transfer_ack_begin)(struct snd_pcm_substream*substream);void(*transfer_ack_end)(struct snd_pcm_substream *substream);/* 定時器 */unsigned int timer_resolution; /* timer resolution *//* DMA */unsigned char *dma_area; /* DMA 區(qū)域*/dma_addr_t dma_addr; /* 總線物理地址*/size_t dma_bytes; /* DMA 區(qū)域大小 */struct snd_dma_buffer *dma_buffer_p; //被分配的緩沖區(qū) #if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)/* OSS 信息 */struct snd_pcm_oss_runtime oss;#endif};snd_pcm_runtime 中的大多數(shù)記錄對被聲卡驅(qū)動操作集中的函數(shù)是只讀的,僅僅 PCM 中間層可從更新或修改這些信息,但是硬件描述、中斷回調(diào)函數(shù)、 DMA 緩沖區(qū)信息和私有數(shù)據(jù)是例外的。
snd_pcm_runtime結(jié)構(gòu)體中的重要成員:
(1)硬件描述
硬件描述(snd_pcm_hardware 結(jié)構(gòu)體)包含了基本硬件配置的定義, 需要在 open()函數(shù)中賦值。 runtime實例保存的是硬件描述的拷貝而非指針,這意味著在 open()函數(shù)中可以修改被拷貝的描述(runtime->hw),
例如:
struct snd_pcm_runtime *runtime = substream->runtime;...runtime->hw = snd_xxchip_playback_hw; /* “大眾”硬件描述 *//* 特定的硬件描述 */if (chip->model == VERY_OLD_ONE)runtime->hw.channels_max = 1;snd_pcm_hardware結(jié)構(gòu)體定義如下:
struct snd_pcm_hardware{unsigned int info; /* 標(biāo)識 PCM 設(shè)備的類型和能力, 形式為 SNDRV_PCM_ INFO_XXX* /u64 formats; /* PCM 設(shè)備支持的格式,形式為 SNDRV_PCM_FMTBIT_XXX */unsigned int rates; /* PCM 設(shè)備支持的采樣率,形式如SNDRV_PCM_RATE_XXX,如果支持連續(xù)的采樣率,則傳遞 CONTINUOUS。*/unsigned int rate_min; /* 最小采樣率 */unsigned int rate_max; /* 最大采樣率 */unsigned int channels_min; /* 最小的通道數(shù) */unsigned int channels_max; /* 最大的通道數(shù) */size_t buffer_bytes_max; /* 最大緩沖區(qū)大小 */size_t period_bytes_min; /* 最小周期大小 */size_t period_bytes_max; /* 最大奏曲大小 */unsigned int periods_min; /* 最小周期數(shù) */unsigned int periods_max; /* 最大周期數(shù) */size_t fifo_size; /* FIFO 字節(jié)數(shù) */};info 字段至少需要定義是否支持 mmap,當(dāng)支持時,應(yīng)設(shè)置 SNDRV_PCM_INFO_MMAP 標(biāo)志;SNDRV_PCM_INFO_PAUSE 意 味 著 設(shè) 備 可 支 持 暫 停 操 作 , 而 SNDRV_PCM_INFO_RESUME 意味著設(shè)備可支持掛起/恢復(fù)操作;當(dāng) PCM 子流能被同步,如同步播放和錄音流的start/stop,可設(shè)置 SNDRV_PCM_INFO_SYNC_START 標(biāo)志。
PCM 可被應(yīng)用程序通過 alsa-lib 發(fā)送 hw_params 來配置,配置信息將保存在運行時實例中。對緩沖區(qū)和周期大小的配置以幀形式存儲,而 frames_to_bytes()和 bytes_to_frames()可完成幀和字節(jié)的轉(zhuǎn)換,如:
period_bytes = frames_to_bytes(runtime, runtime->period_size);
(2) DMA 緩沖區(qū)信息。
包含 dma_area(邏輯地址)、 dma_addr(物理地址)、dma_bytes(緩沖區(qū)大小)和 dma_private(被 ALSADMA 分配器使用)。可以由 snd_pcm_lib_malloc_pages()實現(xiàn), ALSA 中間層會設(shè)置 DMA 緩沖區(qū)信息的相關(guān)字段,這種情況下,驅(qū)動中不能再寫這些信息,只能讀取。如果使用標(biāo)準(zhǔn)的緩沖區(qū)分配函數(shù)snd_pcm_lib_malloc_pages()分配緩沖區(qū),則我們不需要自己維護(hù) DMA 緩沖區(qū)信息。如果緩沖區(qū)由自己分配,則需要在 hw_params()函數(shù)中管理緩沖區(qū)信息,至少需管理 dma_bytes 和 dma_addr,如果支持 mmap,則必須管理 dma_area,對 dma_private 的管理視情況而定。
(3)運行狀態(tài)。
通過 runtime->status 可以獲得運行狀態(tài),它是snd_pcm_mmap_status 結(jié)構(gòu)體的指針,例如,通過runtime->status->hw_ptr 可以獲得目前的 DMA 硬件指針。此外,通過 runtime->control 可以獲得 DMA 應(yīng)用指針,它指向 snd_pcm_mmap_control 結(jié)構(gòu)體指針。
(4)私有數(shù)據(jù)。
驅(qū)動中可以為子流分配一段內(nèi)存并賦值給 runtime->private_data,例如:
static int snd_xxx_open(struct snd_pcm_substream *substream) { struct xxx_pcm_data *data; .... data = kmalloc(sizeof(*data), GFP_KERNEL); substream->runtime->private_data = data; //賦值 runtime->private_data .... }(5)中斷回調(diào)函數(shù):
transfer_ack_begin()和 transfer_ack_end()函數(shù)分別在 snd_pcm_period_elapsed()的開始和結(jié)束時被調(diào)用。
?
PCM接口模板:
#include <sound/pcm.h>..../* 播放設(shè)備硬件定義 */static struct snd_pcm_hardware snd_xxxchip_playback_hw ={.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID),.formats = SNDRV_PCM_FMTBIT_S16_LE,.rates = SNDRV_PCM_RATE_8000_48000,.rate_min = 8000,.rate_max = 48000,.channels_min = 2,.channels_max = 2,.buffer_bytes_max = 32768,.period_bytes_min = 4096,.period_bytes_max = 32768,.periods_min = 1,.periods_max = 1024,};/* 錄音設(shè)備硬件定義 */static struct snd_pcm_hardware snd_xxxchip_capture_hw ={.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID),.formats = SNDRV_PCM_FMTBIT_S16_LE,.rates = SNDRV_PCM_RATE_8000_48000,.rate_min = 8000,.rate_max = 48000,.channels_min = 2,.channels_max = 2,.buffer_bytes_max = 32768,.period_bytes_min = 4096,.period_bytes_max = 32768,.periods_min = 1,.periods_max = 1024,};/* 播放:打開函數(shù) */static int snd_xxxchip_playback_open(struct snd_pcm_substream*substream){struct xxxchip *chip = snd_pcm_substream_chip(substream);struct snd_pcm_runtime *runtime = substream->runtime;runtime->hw = snd_xxxchip_playback_hw;... // 硬件初始化代碼return 0;}/* 播放:關(guān)閉函數(shù) */static int snd_xxxchip_playback_close(struct snd_pcm_substream*substream){struct xxxchip *chip = snd_pcm_substream_chip(substream);// 硬件相關(guān)的代碼return 0;}/* 錄音:打開函數(shù) */static int snd_xxxchip_capture_open(struct snd_pcm_substream*substream){struct xxxchip *chip = snd_pcm_substream_chip(substream);struct snd_pcm_runtime *runtime = substream->runtime;runtime->hw = snd_xxxchip_capture_hw;... // 硬件初始化代碼return 0;}/* 錄音:關(guān)閉函數(shù) */static int snd_xxxchip_capture_close(struct snd_pcm_substream*substream){struct xxxchip *chip = snd_pcm_substream_chip(substream);... // 硬件相關(guān)的代碼return 0;}/* hw_params 函數(shù) */static int snd_xxxchip_pcm_hw_params(struct snd_pcm_substream*substream, structsnd_pcm_hw_params *hw_params){return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));}/* hw_free 函數(shù) */static int snd_xxxchip_pcm_hw_free(struct snd_pcm_substream*substream){return snd_pcm_lib_free_pages(substream);}/* prepare 函數(shù) */static int snd_xxxchip_pcm_prepare(struct snd_pcm_substream*substream){struct xxxchip *chip = snd_pcm_substream_chip(substream);struct snd_pcm_runtime *runtime = substream->runtime;/* 根據(jù)目前的配置信息設(shè)置硬件* 例如:*/xxxchip_set_sample_format(chip, runtime->format);xxxchip_set_sample_rate(chip, runtime->rate);xxxchip_set_channels(chip, runtime->channels);xxxchip_set_dma_setup(chip, runtime->dma_addr, chip->buffer_size, chip->period_size);return 0;}/* trigger 函數(shù) */static int snd_xxxchip_pcm_trigger(struct snd_pcm_substream*substream, int cmd){switch (cmd){case SNDRV_PCM_TRIGGER_START:// do something to start the PCM enginebreak;case SNDRV_PCM_TRIGGER_STOP:// do something to stop the PCM enginebreak;default:return - EINVAL;}}/* pointer 函數(shù) */static snd_pcm_uframes_t snd_xxxchip_pcm_pointer(struct snd_pcm_substream *substream){struct xxxchip *chip = snd_pcm_substream_chip(substream);unsigned int current_ptr;/*獲得當(dāng)前的硬件指針*/current_ptr = xxxchip_get_hw_pointer(chip);return current_ptr;}/* 放音設(shè)備操作集 */static struct snd_pcm_ops snd_xxxchip_playback_ops ={.open = snd_xxxchip_playback_open,.close = snd_xxxchip_playback_close,.ioctl = snd_pcm_lib_ioctl,.hw_params = snd_xxxchip_pcm_hw_params,.hw_free = snd_xxxchip_pcm_hw_free,.prepare = snd_xxxchip_pcm_prepare,.trigger = snd_xxxchip_pcm_trigger,.pointer = snd_xxxchip_pcm_pointer,};/* 錄音設(shè)備操作集 */static struct snd_pcm_ops snd_xxxchip_capture_ops ={.open = snd_xxxchip_capture_open,.close = snd_xxxchip_capture_close,.ioctl = snd_pcm_lib_ioctl,.hw_params = snd_xxxchip_pcm_hw_params,.hw_free = snd_xxxchip_pcm_hw_free,.prepare = snd_xxxchip_pcm_prepare,.trigger = snd_xxxchip_pcm_trigger,.pointer = snd_xxxchip_pcm_pointer,};/* 創(chuàng)建一個 PCM 設(shè)備 */static int _ _devinit snd_xxxchip_new_pcm(struct xxxchip *chip){struct snd_pcm *pcm;int err;if ((err = snd_pcm_new(chip->card, "xxx Chip", 0, 1, 1, &pcm)) < 0)return err;pcm->private_data = chip;strcpy(pcm->name, "xxx Chip");chip->pcm = pcm;/* 設(shè)置操作集 */snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_xxxchip_playback_ops);snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_xxxchip_capture_ops);/* 分配緩沖區(qū) */snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,snd_dma_pci_data(chip - > pci), 64 *1024, 64 *1024);return 0;}控制接口
1、control
control的最主要用途是 mixer,所有的 mixer 元素基于 control 內(nèi)核 API 實現(xiàn),在 ALSA 中, control 用 snd_kcontrol結(jié)構(gòu)體描述。創(chuàng)建一個新的 control 至少需要實現(xiàn) snd_kcontrol_new 中的 info()、 get()和 put()這 3 個成員函數(shù),snd_kcontrol_new 結(jié)構(gòu)體的定義如下:
struct snd_kcontrol_new{snd_ctl_elem_iface_t iface; /*接口 ID,定義control的類型,形式為 SNDRV_CTL_ELEM_IFACE_XXX,通常是MIXER,對于不屬于mixer的全局控制,使用CARD */unsigned int device; /* 設(shè)備號 */unsigned int subdevice; /* 子流(子設(shè)備)號 */unsigned char *name; /* 名稱(ASCII 格式),control的作用根據(jù)名稱來區(qū)分,對于名稱相同的control,根據(jù)index區(qū)分 */unsigned int index; /* 索引 */unsigned int access; /* 訪問權(quán)限 */unsigned int count; /* 享用元素的數(shù)量 */snd_kcontrol_info_t *info;snd_kcontrol_get_t *get;snd_kcontrol_put_t *put;unsigned long private_value;};?
2. info()函數(shù)
snd_kcontrol_new 結(jié)構(gòu)體中的 info()函數(shù)用于獲得該 control 的詳細(xì)信息,該函數(shù)必須填充傳遞給它的第二個參數(shù) snd_ctl_elem_info 結(jié)構(gòu)體, info()函數(shù)的形式如下:
static int snd_xxxctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo);
snd_ctl_elem_info 結(jié)構(gòu)體的定義如下:
struct snd_ctl_elem_info{struct snd_ctl_elem_id id; /* W: 元素 ID */snd_ctl_elem_type_t type; /*定義control的類型,包括 BOOLEAN、 INTEGER、ENUMERATED、 BYTES、 IEC958 和 INTEGER64 */unsigned int access; /* R: 值訪問權(quán)限(位掩碼) - SNDRV_CTL_ELEM_ACCESS_* */unsigned int count; /* 值的計數(shù),control中包含的元素的個數(shù) */pid_t owner; /* 該 control 的擁有者 PID */union{struct{long min; /* R: 最小值 */long max; /* R: 最大值 */long step; /* R: 值步進(jìn) (0 可變的) */} integer;struct{long long min; /* R: 最小值 */long long max; /* R: 最大值 */long long step; /* R: 值步進(jìn) (0 可變的) */} integer64;struct{unsigned int items; /* R: 項目數(shù) */unsigned int item; /* W: 項目號 */char name[64]; /* R: 值名稱 */} enumerated; /* 枚舉 */unsigned char reserved[128];}value;union{unsigned short d[4];unsigned short *d_ptr;} dimen;unsigned char reserved[64-4 * sizeof(unsigned short)];};?
3、get()函數(shù)
get()函數(shù)用于得到 control 的目前值并返回用戶空間。
static int snd_xxxctl_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol){//從 snd_kcontrol 獲得 xxxchip 指針struct xxxchip *chip = snd_kcontrol_chip(kcontrol);//從 xxxchip 獲得值并寫入 snd_ctl_elem_valueucontrol->value.integer.value[0] = get_some_value(chip);return 0;}snd_ctl_elem_value,定義如下:
struct snd_ctl_elem_value{struct snd_ctl_elem_id id; /* W: 元素 ID */unsigned int indirect: 1; /* W: 使用間接指針(xxx_ptr 成員) *///值聯(lián)合體union{union{long value[128];long *value_ptr;} integer;union{long long value[64];long long *value_ptr;} integer64;union{unsigned int item[128];unsigned int *item_ptr;} enumerated;union{unsigned char data[512];unsigned char *data_ptr;} bytes;struct snd_aes_iec958 iec958;}value; /* 只讀 */struct timespec tstamp;unsigned char reserved[128-sizeof(struct timespec)];};?
4、put()函數(shù)
put()用于從用戶空間寫入值,如果值被改變,該函數(shù)返回 1,否則返回 0;如果發(fā)生錯誤,該函數(shù)返回一個錯誤碼。范例如下:
static int snd_xxxctl_put(struct snd_kcontrol *kcontrol, structsnd_ctl_elem_value *ucontrol){//從 snd_kcontrol 獲得 xxxchip 指針struct xxxchip *chip = snd_kcontrol_chip(kcontrol);int changed = 0;//默認(rèn)返回值為 0//值 被 改 變if (chip->current_value != ucontrol->value.integer.value[0]){change_current_value(chip, ucontrol->value.integer.value[0]);changed = 1;//返回值為 1}return changed;}對于 get()和 put()函數(shù)而言,如果 control 有多于一個元素,即 count >1,則每個元素都需要被返回或?qū)?/span>入。
5.構(gòu)造 control
當(dāng)所有事情準(zhǔn)備好后,我們需要創(chuàng)建一個 control,調(diào)用 snd_ctl_add()和 snd_ctl_new1()這兩個函數(shù)來完成,這兩個函數(shù)的原型為:
int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol);
struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,void *private_data);
snd_ctl_new1() 函 數(shù) 用 于 創(chuàng) 建 一 個 snd_kcontrol 并 返 回 其 指針 ,
snd_ctl_add() 函 數(shù) 用于 將 創(chuàng) 建的snd_kcontrol 添加到對應(yīng)的 card 中。
6.變更通知
如果驅(qū)動中需要在中斷服務(wù)程序中改變或更新一個 control,可以調(diào)用 snd_ctl_notify()函數(shù),此函數(shù)原型為:
void snd_ctl_notify(struct snd_card *card, unsigned int mask, struct snd_ctl_elem_id *id);
該函數(shù)的第二個參數(shù)為事件掩碼(event-mask),第三個參數(shù)為該通知的 control 元素 id 指針。例如, 如下語句定義的事件掩碼 SNDRV_CTL_EVENT_MASK_VALUE 意味著 control 值的改變被通知:
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, id_pointer);
?
AC97 API接口:
1. AC97 實例構(gòu)造
為了創(chuàng)建一個 AC97 實例,首先需要調(diào)用 snd_ac97_bus()函數(shù)構(gòu)建 AC97 總線及其操作,這個函數(shù)的原型為:
int snd_ac97_bus(struct snd_card *card, int num, struct snd_ac97_bus_ops *ops,void *private_data, struct snd_ac97_bus **rbus);
該函數(shù)的第 3 個參數(shù) ops 是一個 snd_ac97_bus_ops 結(jié)構(gòu)體,其定義如下:
struct snd_ac97_bus_ops{void(*reset)(struct snd_ac97 *ac97); //復(fù)位函數(shù)//寫入函數(shù)void(*write)(struct snd_ac97 *ac97, unsigned short reg, unsigned short val);//讀取函數(shù)unsigned short(*read)(struct snd_ac97 *ac97, unsigned short reg);void(*wait)(struct snd_ac97 *ac97);void(*init)(struct snd_ac97 *ac97);};接下來,調(diào)用 snd_ac97_mixer()函數(shù)注冊混音器,這個函數(shù)的原型為:
int snd_ac97_mixer(struct snd_ac97_bus *bus, struct snd_ac97_template *template, struct snd_ac97 **rac97);
代碼所示為 AC97 實例的創(chuàng)建過程:
struct snd_ac97_bus *bus;//AC97 總線操作static struct snd_ac97_bus_ops ops ={.write = snd_mychip_ac97_write,.read = snd_mychip_ac97_read,};//AC97 總線與操作創(chuàng)建snd_ac97_bus(card, 0, &ops, NULL, &bus);//AC97 模板struct snd_ac97_template ac97;int err;memset(&ac97, 0, sizeof(ac97));ac97.private_data = chip;//私有數(shù)據(jù)//注冊混音器snd_ac97_mixer(bus, &ac97, &chip->ac97);2. snd_ac97_bus_ops 成員函數(shù)
snd_ac97_bus_ops 結(jié)構(gòu)體中的 read()和 write()成員函數(shù)完成底層的硬件訪問, reset()函數(shù)用于復(fù)位編解碼器, wait()函數(shù)用于編解碼器標(biāo)準(zhǔn)初始化過程中的特定等待,如果芯片要求額外的等待時間,則應(yīng)實現(xiàn)這個函數(shù), init()用于完成編解碼器附加的初始化。
3.修改寄存器
如果需要在驅(qū)動中訪問編解碼器,可使用如下函數(shù):
void snd_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short value);
int snd_ac97_update(struct snd_ac97 *ac97, unsigned short reg, unsigned short value);
int snd_ac97_update_bits(struct snd_ac97 *ac97, unsigned short reg, unsigned short mask,unsigned short value);
unsigned short snd_ac97_read(struct snd_ac97 *ac97, unsigned short reg);
snd_ac97_update()與 void snd_ac97_write()的區(qū)別在于前者在值已經(jīng)設(shè)置的情況下不會再設(shè)置,而后者則會再寫一次。 snd_ac97_update_bits()用于更新寄存器的某些位,由 mask 決定。
除此之外,還有一個函數(shù)可用于設(shè)置采樣率:
int snd_ac97_set_rate(struct snd_ac97 *ac97, int reg, unsigned int rate);
這個函數(shù)的第二個參數(shù) reg 可以是 AC97_PCM_MIC_ADC_RATE、 AC97_PCM_FRONT_DAC_ RATE、AC97_PCM_LR_ADC_RATE 和 AC97_SPDIF,對于 AC97 _SPDIF 而言,寄存器并非真地被改變了,只是相應(yīng)的 IEC958 狀態(tài)位將被更新。
4.時鐘調(diào)整
在一些芯片上,編解碼器的時鐘頻率不是 48000Hz,而是使用 PCI 時鐘以節(jié)省一個晶振,在這種情況下,我們應(yīng)該改變 bus->clock 為相應(yīng)的值,例如 intel8x0 和 es1968 包含時鐘的自動測量函數(shù)。
?
ALSA 用戶空間編程
ALSA 驅(qū)動的聲卡在用戶空間不宜直接使用文件接口,而應(yīng)使用 alsa-lib,代碼所示為基于ALSA 音頻驅(qū)動的最簡單的播放應(yīng)用程序
#include <stdio.h>#include <stdlib.h>#include <alsa/asoundlib.h>main(int argc, char *argv[]){int i;int err;short buf[128];snd_pcm_t *playback_handle; //PCM 設(shè)備句柄snd_pcm_hw_params_t *hw_params; //硬件信息和 PCM 流配置//打開 PCM,最后一個參數(shù)為 0 意味著標(biāo)準(zhǔn)配置if ((err = snd_pcm_open(&playback_handle, argv[1], SND_PCM_STREAM_PLAYBACK, 0)) < 0){fprintf(stderr, "cannot open audio device %s (%s)\n", argv[1], snd_strerror(err));exit(1);}//分配 snd_pcm_hw_params_t 結(jié)構(gòu)體if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0){fprintf(stderr, "cannot allocate hardware parameter structure (%s)\n",snd_strerror(err));exit(1);}//初始化 hw_paramsif ((err = snd_pcm_hw_params_any(playback_handle, hw_params)) < 0){fprintf(stderr, "cannot initialize hardware parameter structure (%s)\n",snd_strerror(err));exit(1);}//初始化訪問權(quán)限if ((err=snd_pcm_hw_params_set_access(playback_handle, hw_params,SND_PCM_ACCESS_RW_INTERLEAVED)) < 0){fprintf(stderr, "cannot set access type (%s)\n", snd_strerror(err));exit(1);}//初始化采樣格式if ((err=snd_pcm_hw_params_set_format(playback_handle, hw_params,SND_PCM_FORMAT_S16_LE)) < 0){fprintf(stderr, "cannot set sample format (%s)\n", snd_strerror(err));exit(1);}//設(shè)置采樣率,如果硬件不支持我們設(shè)置的采樣率,將使用最接近的if((err=snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, 44100,0)) < 0){fprintf(stderr, "cannot set sample rate (%s)\n", snd_strerror(err));exit(1);}//設(shè)置通道數(shù)量 if((err=snd_pcm_hw_params_set_channels(playback_handle, hw_params, 2)) < 0){fprintf(stderr, "cannot set channel count (%s)\n", snd_strerror(err));exit(1);}//設(shè)置 hw_paramsif((err = snd_pcm_hw_params(playback_handle, hw_params)) < 0){fprintf(stderr, "cannot set parameters (%s)\n", snd_strerror(err));exit(1);}//釋放分配的 snd_pcm_hw_params_t 結(jié)構(gòu)體snd_pcm_hw_params_free(hw_params);//完成硬件參數(shù)設(shè)置,使設(shè)備準(zhǔn)備好if ((err = snd_pcm_prepare(playback_handle)) < 0){fprintf(stderr, "cannot prepare audio interface for use (%s)\n",snd_strerror(err));exit(1);}for (i = 0; i < 10; ++i){//寫音頻數(shù)據(jù)到 PCM 設(shè)備if ((err = snd_pcm_writei(playback_handle, buf, 128)) != 128){fprintf(stderr, "write to audio interface failed (%s)\n", snd_strerror(err));exit(1);}}//關(guān)閉 PCM 設(shè)備句柄snd_pcm_close(playback_handle);exit(0);}?
?
Linux OSS音頻設(shè)備驅(qū)動
OSS驅(qū)動的組成
OSS標(biāo)準(zhǔn)中有兩個基本的音頻設(shè)備:mixer(混音器)和dsp(數(shù)字信號處理)。
?
?
MIXER
mixer的作用是將多個信號組成或者疊加在一起,在OSS驅(qū)動中,/dev/mixer設(shè)備文件是應(yīng)用程序?qū)ixer進(jìn)行操作的軟件接口。
混音器電路通常由兩部分組成:輸入混音器(input mixer)和輸出混音器(output mixer)。
輸入混音器負(fù)責(zé)從不同的信號源接收模擬信號(混音通道或混音設(shè)備)。模擬信號通過增益控制器和由軟件控制的音量調(diào)節(jié)器,在不同的混音通道中進(jìn)行級別(level)調(diào)制,然后被送到輸入混音器中進(jìn)行聲音的合成。混音器上的電子開關(guān)可以控制哪些通道中有信號與混音器相連,有些聲卡只允許連接一個混音通道作為錄音的音源, 而有些聲卡則允許對混音通道做任意的連接。經(jīng)過輸入混音器處理后的信號仍然為模擬信號,它們將被送到 A/D 轉(zhuǎn)換器進(jìn)行數(shù)字化處理。
當(dāng)輸出混音器對所有的模擬信號進(jìn)行了混合之后,通常還會有一個總控增益調(diào)節(jié)器來控制輸出聲音的大小, 此外還有一些音調(diào)控制器來調(diào)節(jié)輸出聲音的音調(diào)。 經(jīng)過輸出混音器處理后的信號也是模擬信號,它們最終會被送給喇叭或者其他的模擬輸出設(shè)備。
對混音器的編程包括如何設(shè)置增益控制器的級別,以及怎樣在不同的音源間進(jìn)行切換,這些操作通常來講是不連續(xù)的,大部分的操作都是通過 ioctl()系統(tǒng)調(diào)用來完成的。/dev/mixer 允許多個應(yīng)用程序同時訪問,并且混音器的設(shè)置值會一直保持到對應(yīng)的設(shè)備文件被關(guān)閉為止。
?
DSP
DSP也稱為編解碼器,實現(xiàn)錄音和放音(播放),其對應(yīng)的設(shè)備文件是/dev/dsp或/dev/sound/dsp。OSS 聲卡驅(qū)動程序提供的/dev/dsp 是用于數(shù)字采樣和數(shù)字錄音的設(shè)備文件,向該設(shè)備寫數(shù)據(jù)即意味著激活聲卡上的 D/A 轉(zhuǎn)換器進(jìn)行播放,而向該設(shè)備讀數(shù)據(jù)則意味著激活聲卡上的 A/D 轉(zhuǎn)換器進(jìn)行錄音。
從 DSP 設(shè)備讀取數(shù)據(jù)時,從聲卡輸入的模擬信號經(jīng)過 A/D 轉(zhuǎn)換器變成數(shù)字采樣后的樣本,保存在聲卡驅(qū)動程序的內(nèi)核緩沖區(qū)中,當(dāng)應(yīng)用程序通過 read()系統(tǒng)調(diào)用從聲卡讀取數(shù)據(jù)時,保存在內(nèi)核緩沖區(qū)中的數(shù)字采樣結(jié)果將被復(fù)制到應(yīng)用程序所指定的用戶緩沖區(qū)中。需要指出的是,聲卡采樣頻率是由內(nèi)核中的驅(qū)動程序所決定的,而不取決于應(yīng)用程序從聲卡讀取數(shù)據(jù)的速度。
向 DSP 設(shè)備寫入數(shù)據(jù)時,數(shù)字信號會經(jīng)過 D/A 轉(zhuǎn)換器變成模擬信號,然后產(chǎn)生聲音。應(yīng)用程序寫入數(shù)據(jù)的速度應(yīng)該至少等于聲卡的采樣頻率,過慢會產(chǎn)生聲音暫停或者停頓的現(xiàn)象(即 underflow)。如果用戶寫入過快的話,它會被內(nèi)核中的聲卡驅(qū)動程序阻塞,直到硬件有能力處理新的數(shù)據(jù)為止。
聲卡通常不需要支持非阻塞的I/O操作。
無論是從聲卡讀取數(shù)據(jù),或是向聲卡寫入數(shù)據(jù),事實上都具有特定的格式(format),如無符號 8 位、單聲道、 8kHz 采樣率,如果默認(rèn)值無法達(dá)到要求,可以通過 ioctl()系統(tǒng)調(diào)用來改變它們。通常說來,在應(yīng)用程序中打開設(shè)備文件/dev/dsp 之后,接下去就應(yīng)該為其設(shè)置恰當(dāng)?shù)母袷?#xff0c;然后才能從聲卡讀取或者寫入數(shù)據(jù)。
?
mixer接口
int register_sound_mixer(struct file_operations *fops, int dev);
上述函數(shù)用于注冊一個混音器,第一個參數(shù) fops 即是文件操作接口,第二個參數(shù) dev 是設(shè)備編號,如果填入-1,則系統(tǒng)自動分配一個設(shè)備編號。 mixer 是一個典型的字符設(shè)備,因此編碼的主要工作是實現(xiàn)file_operations 中的 open()、 ioctl()等函數(shù)。
mixer 接口 file_operations 中的最重要函數(shù)是 ioctl(), 它實現(xiàn)混音器的不同 I/O 控制命令。
?
DSP接口
int register_sound_dsp(struct file_operations *fops, int dev);
上述函數(shù)用于注冊一個 dsp 設(shè)備,第一個參數(shù) fops 即是文件操作接口,第二個參數(shù) dev 是設(shè)備編號,如果填入-1,則系統(tǒng)自動分配一個設(shè)備編號。 dsp 也是一個典型的字符設(shè)備,因此編碼的主要工作是實現(xiàn) file_operations 中的 read()、 write()、 ioctl()等函數(shù)。
dsp 接口 file_operations 中的 read()和 write()函數(shù)非常重要, read()函數(shù)從音頻控制器中獲取錄音數(shù)據(jù)到緩沖區(qū)并復(fù)制到用戶空間, write()函數(shù)從用戶空間復(fù)制音頻數(shù)據(jù)到內(nèi)核空間緩沖區(qū)并最終發(fā)送到音頻控制器。
dsp 接口 file_operations 中的 ioctl()函數(shù)處理對采樣率、量化精度、 DMA 緩沖區(qū)塊大小等參數(shù)設(shè)置 I/O控制命令的處理。
dsp接口中的poll()函數(shù)通常用于實現(xiàn)向用戶反饋目前能否讀寫DMA緩沖區(qū)。
在數(shù)據(jù)從緩沖區(qū)復(fù)制到音頻控制器的過程中,通常會使用 DMA, DMA 對聲卡而言非常重要。例如,在放音時, 驅(qū)動設(shè)置完 DMA 控制器的源數(shù)據(jù)地址(內(nèi)存中的 DMA 緩沖區(qū))、 目的地址(音頻控制器 FIFO)和 DMA 的數(shù)據(jù)長度, DMA 控制器會自動發(fā)送緩沖區(qū)的數(shù)據(jù)填充 FIFO,直到發(fā)送完相應(yīng)的數(shù)據(jù)長度后才中斷一次。
在 OSS 驅(qū)動中,建立存放音頻數(shù)據(jù)的環(huán)形緩沖區(qū)(ring buffer)通常是值得推薦的方法。此外,在 OSS驅(qū)動中, 一般會將一個較大的 DMA 緩沖區(qū)分成若干個大小相同的塊(這些塊也被稱為“段”, 即 fragment),驅(qū)動程序使用 DMA 每次在聲音緩沖區(qū)和聲卡之間搬移一個 fragment。在用戶空間,可以使用 ioctl()系統(tǒng)調(diào)用來調(diào)整塊的大小和個數(shù)。
?
OSS用戶空間編程
1、dsp編程
對OSS驅(qū)動聲卡的編程使用Linux文件接口函數(shù),dsp接口的操作一般包括如下幾個步驟:
?
(1)打開設(shè)備文件/dev/dsp
對于不支持全雙工的聲卡來說,應(yīng)該使用只讀或只寫的方式打開,只有那些支持全雙工的聲卡才能以讀寫的方式打開。Linux允許應(yīng)用程序多次打開/關(guān)閉與聲卡相關(guān)的設(shè)備文件,從而能夠很方便地在放音狀態(tài)和錄音狀態(tài)之間進(jìn)行轉(zhuǎn)換。
(2)如果有需要,設(shè)置緩沖區(qū)大小
運行在Linux內(nèi)核中的聲卡驅(qū)動程序?qū)iT維護(hù)了一個緩沖區(qū),其大小會影響播放或錄音的效果,使用ioctl()系統(tǒng)調(diào)用可以對它的尺寸進(jìn)行恰當(dāng)設(shè)置。如果需要設(shè)置緩沖區(qū)大小,通常緊跟在設(shè)備文件打開之后。
(3)設(shè)置聲道(channel)數(shù)量
根據(jù)硬件設(shè)備和驅(qū)動程序的具體情況,可以設(shè)置為單聲道或者立體聲。
(4)設(shè)置采樣格式和采樣頻率
采樣格式包括 AFMT_U8(無符號 8 位)、 AFMT_S8(有符號 8 位)、 AFMT_U16_LE(小端模式,無符號 16 位)、 AFMT_U16_BE(大端模式,無符號 16 位)、 AFMT_MPEG、 AFMT_AC3 等。使用SNDCTL_DSP_SETFMT IO 控制命令可以設(shè)置采樣格式。
對于大多數(shù)聲卡來說,其支持的采樣頻率范圍一般為5khz~44.1khz或者48khz,在 Linux 系統(tǒng)下進(jìn)行音頻編程時最常用到的幾種采樣頻率是11025Hz、 16000Hz、 22050Hz、 32000Hz 和 44100Hz。使用 SNDCTL_DSP_SPEED IO 控制命令可以設(shè)置采樣頻率。
(5)讀寫/dev/dsp 實現(xiàn)播放或錄音。
如下代碼清單的程序?qū)崿F(xiàn)了利用/dev/dsp 接口進(jìn)行聲音錄制和播放的過程,它的功能是先錄制幾秒鐘音頻數(shù)據(jù),將其存放在內(nèi)存緩沖區(qū)中,然后再進(jìn)行播放。
#include <unistd.h>#include <fcntl.h>#include <sys/types.h>#include <sys/ioctl.h>#include <stdlib.h>#include <stdio.h>#include <linux/soundcard.h>#define LENGTH 3 /* 存儲秒數(shù) */#define RATE 8000 /* 采樣頻率 */#define SIZE 8 /* 量化位數(shù) */#define CHANNELS 1 /* 聲道數(shù)目 *//* 用于保存數(shù)字音頻數(shù)據(jù)的內(nèi)存緩沖區(qū) */unsigned char buf[LENGTH *RATE * SIZE * CHANNELS / 8];int main(){int fd; /* 聲音設(shè)備的文件描述符 */int arg; /* 用于 ioctl 調(diào)用的參數(shù) */int status; /* 系統(tǒng)調(diào)用的返回值 *//* 打開聲音設(shè)備 */fd = open("/dev/dsp", O_RDWR);if(fd < 0){perror("open of /dev/dsp failed");exit(1);}/* 設(shè)置采樣時的量化位數(shù) */arg = SIZE;status = ioctl(fd, SOUND_PCM_WRITE_BITS, &arg);if (status == - 1)perror("SOUND_PCM_WRITE_BITS ioctl failed");if (arg != SIZE)perror("unable to set sample size");/* 設(shè)置采樣時的通道數(shù)目 */arg = CHANNELS;status = ioctl(fd, SOUND_PCM_WRITE_CHANNELS, &arg);if(status == - 1)perror("SOUND_PCM_WRITE_CHANNELS ioctl failed");if(arg != CHANNELS)perror("unable to set number of channels");/* 設(shè)置采樣率 */arg = RATE;status = ioctl(fd, SOUND_PCM_WRITE_RATE, &arg);if(status == - 1)perror("SOUND_PCM_WRITE_WRITE ioctl failed");/* 循環(huán),直到按下[ControltC]*/while (1){printf("Say something:\n");status = read(fd, buf, sizeof(buf)); /* 錄音 */if(status != sizeof(buf))perror("read wrong number of bytes");printf("You said:\n");status = write(fd, buf, sizeof(buf)); /* 放音 */if(status != sizeof(buf))perror("wrote wrong number of bytes");/* 在繼續(xù)錄音前等待放音結(jié)束 */status = ioctl(fd, SOUND_PCM_SYNC, 0);if(status == - 1)perror("SOUND_PCM_SYNC ioctl failed");}}?
2、mix編程
聲卡上的混音器由多個混音通道組成,它們可以通過驅(qū)動程序提供的設(shè)備文件/dev/mixer 進(jìn)行編程。對混音器的操作一般都通過 ioctl()系統(tǒng)調(diào)用來完成, 所有控制命令都以 SOUND_MIXER 或者 MIXER 開頭,下表列出了常用的混音器控制命令。
?
對聲卡的輸入增益和輸出增益進(jìn)行調(diào)節(jié)是混音器的一個主要作用,目前大部分聲卡采用的是 8 位或者16 位的增益控制器, 聲卡驅(qū)動程序會將它們變換成百分比的形式, 也就是說無論是輸入增益還是輸出增益,其取值范圍都是從 0~100。
(1) SOUND_MIXER_READ 宏。
在進(jìn)行混音器編程時,可以使用 SOUND_MIXER_READ 宏來讀取混音通道的增益大小,例如,如下代碼可以獲得麥克風(fēng)的輸入增益:
ioctl(fd, SOUND_MIXER_READ(SOUND_MIXER_MIC), &vol);
對于只有一個混音通道的單聲道設(shè)備來說,返回的增益大小保存在低位字節(jié)中。而對于支持多個混音通道的雙聲道設(shè)備來說,返回的增益大小實際上包括兩個部分,分別代表左、右兩個聲道的值,其中低位字節(jié)保存左聲道的音量,而高位字節(jié)則保存右聲道的音量。下面的代碼可以從返回值中依次提取左右聲道的增益大小:
int left, right;left = vol & 0xff;right = (vol & 0xff00) >> 8;(2) SOUND_MIXER_WRITE 宏。
如果想設(shè)置混音通道的增益大小,則可以通過 SOUND_MIXER_WRITE 宏來實現(xiàn),例如下面的語句可以用來設(shè)置麥克風(fēng)的輸入增益:
vol = (right << 8) + left;ioctl(fd, SOUND_MIXER_WRITE(SOUND_MIXER_MIC), &vol);(3)查詢 MIXER 信息。
聲卡驅(qū)動程序提供了多個 ioctl()系統(tǒng)調(diào)用來獲得混音器的信息,它們通常返回一個整型的位掩碼,其中每一位分別代表一個特定的混音通道,如果相應(yīng)的位為 1,則說明與之對應(yīng)的混音通道是可用的。
通過 SOUND_MIXER_READ_DEVMASK 返回的位掩碼查詢出能夠被聲卡支持的每一個混音通道, 而通過 SOUND_MIXER_READ_RECMAS 返回的位掩碼則可以查詢出能夠被當(dāng)作錄音源的每一個通道。
大多數(shù)聲卡提供了多個錄音源,通過 SOUND_MIXER_READ_RECSRC 可以查詢出當(dāng)前正在使用的錄音源,同一時刻可使用兩個或兩個以上的錄音源,具體由聲卡硬件本身決定。相應(yīng)地,使用SOUND_MIXER_WRITE_RECSRC 可以設(shè)置聲卡當(dāng)前使用的錄音源, 如下代碼可以將 CD 輸入作為聲卡的錄音源使用。
devmask = SOUND_MIXER_CD;ioctl(fd, SOUND_MIXER_WRITE_RECSRC, &devmask);此外,所有的混音通道都有單聲道和雙聲道的區(qū)別,如果需要知道哪些混音通道提供了對立體聲的支持,可以通過 SOUND_MIXER_READ_STEREODEVS 來獲得。
以下程序?qū)崿F(xiàn)了利用/dev/mixer 接口對混音器進(jìn)行編程的過程,該程序可對各種混音通道的增益進(jìn)行調(diào)節(jié)。
#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <sys/ioctl.h>#include <fcntl.h>#include <linux/soundcard.h>/* 用來存儲所有可用混音設(shè)備的名稱 */const char *sound_device_names[] = SOUND_DEVICE_NAMES;int fd; /* 混音設(shè)備所對應(yīng)的文件描述符 */int devmask, stereodevs; /* 混音器信息對應(yīng)的 bit 掩碼 */char *name;/* 顯示命令的使用方法及所有可用的混音設(shè)備 */void usage(){int i;fprintf(stderr, "usage: %s <device> <left-gain%%> <right-gain%%>\n""%s <device> <gain%%>\n\n""Where <device> is one of:\n", name, name);for(i = 0; i < SOUND_MIXER_NRDEVICES; i++)if ((1 << i) &devmask)/* 只顯示有效的混音設(shè)備 */fprintf(stderr, "%s ", sound_device_names[i]);fprintf(stderr, "\n");exit(1);}int main(int argc, char *argv[]){int left, right, level; /* 增益設(shè)置 */int status; /* 系統(tǒng)調(diào)用的返回值 */int device; /* 選用的混音設(shè)備 */char *dev; /* 混音設(shè)備的名稱 */int i;name = argv[0];/* 以只讀方式打開混音設(shè)備 */fd = open("/dev/mixer", O_RDONLY);if (fd == - 1){perror("unable to open /dev/mixer");exit(1);}/* 獲得所需要的信息 */status = ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask);//被聲卡支持的每一個混音通道if(status == - 1)perror("SOUND_MIXER_READ_DEVMASK ioctl failed");status = ioctl(fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs);//獲取對立體聲支持的通道if (status == - 1)perror("SOUND_MIXER_READ_STEREODEVS ioctl failed");/* 檢查用戶輸入 */if (argc != 3 && argc != 4)usage();/* 保存用戶輸入的混音器名稱 */dev = argv[1];/* 確定即將用到的混音設(shè)備 */for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)if (((1 << i) &devmask) && !strcmp(dev, sound_device_names[i]))break;if (i == SOUND_MIXER_NRDEVICES){/* 沒有找到匹配項 */fprintf(stderr, "%s is not a valid mixer device\n", dev);usage();}/* 查找到有效的混音設(shè)備 */device = i;/* 獲取增益值 */if (argc == 4){/* 左、右聲道均給定 */left = atoi(argv[2]);right = atoi(argv[3]);}else{/* 左、右聲道設(shè)為相等 */left = atoi(argv[2]);right = atoi(argv[2]);}/* 對非立體聲設(shè)備給出警告信息 */if ((left != right) && !((1 << i) &stereodevs)){fprintf(stderr, "warning: %s is not a stereo device\n", dev);}/* 將兩個聲道的值合到同一變量中 */level = (right << 8) + left;/* 設(shè)置增益 */status = ioctl(fd, MIXER_WRITE(device), &level);if (status == - 1){perror("MIXER_WRITE ioctl failed");exit(1);}/* 獲得從驅(qū)動返回的左右聲道的增益 */left = level &0xff;right = (level &0xff00) >> 8;/* 顯示實際設(shè)置的增益 */fprintf(stderr, "%s gain set to %d%% / %d%%\n", dev, left, right);/* 關(guān)閉混音設(shè)備 */close(fd);return 0;}編譯上述程序為可執(zhí)行文件 mixer,執(zhí)行./mixer <device> <left-gain%> <right-gain%>或./mixer <device> <gain%>可設(shè)置增益, device 可以是 vol、 pcm、 speaker、 line、 mic、 cd、 igain、 line1、 phin、 video。
?
總結(jié)
以上是生活随笔為你收集整理的Linux音频驱动整理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php mysql 抽奖_使用jQuer
- 下一篇: linux建立ssh、scp互信