PCM信息運(yùn)行時(shí)指針
當(dāng)打開一個(gè)一個(gè)PCM子流的時(shí)候,PCM運(yùn)行時(shí)實(shí)例就會(huì)分配給這個(gè)子流。這個(gè)指針可以通過substream->runtime獲得。運(yùn)行時(shí)指針擁有多種信息:hw_params和sw_params的配置的拷貝,緩沖區(qū)指針,mmap記錄,自旋鎖等等。幾乎你想控制PCM的所有信息都可以在這里得到。
Struct_snd_pcm_runtime {
/*狀態(tài)*/
structsnd_pcm_substream *trigger_master;
snd_timestamp_ttrigger_tstamp;/*觸發(fā)時(shí)間戳*/、
intoverrange;
snd_pcm_uframes_tavail_max;
snd_pcm_uframes_thw_ptr_base /*緩沖區(qū)復(fù)位時(shí)的位置*/
snd_pcm_uframes_thw_ptr_interrupt;/*中斷時(shí)的位置*/
/*硬件參數(shù)*/
snd_pcm_access_taccess; /*存取模式*/
snd_pcm_format_tformat; /*SNDRV_PCM_FORMAT_* */
snd_pcm_subformat_tsubformat; /*子格式*/
unsignedint rate; /*rate in HZ*/
unsignedint channels; /*通道*/
snd_pcm_uframe_tperiod_size; /*周期大小*/
unsignedint periods /*周期數(shù)*/
snd_pcm_uframes_tbuffer_size; /*緩沖區(qū)大小*/
unsignedint tick_time; /*tick time滴答時(shí)間*/
snd_pcm_uframes_tmin_align; /*格式對(duì)應(yīng)的最小對(duì)齊*/
size_tbyte_align;
unsignedint frame_bits;
unsignedint sample_bits;
unsignedint info;
unsignedint rate_num;
unsignedint rate_den;
/*軟件參數(shù)*/
structtimespec tstamp_mode; /*mmap時(shí)間戳被更新*/
unsignedint sleep_min; /*睡眠的最小節(jié)拍數(shù)*/
snd_pcm_uframes_txfer_align; /*xfer的大小需要是成倍數(shù)的*/
snd_pcm_uframes_tstart_threshold;
snd_pcm_uframes_tstop_threshold;
snd_pcm_uframes_tsilence_threshold;/*silence填充閥值*/
snd_pcm_uframes_tsilence_size; /*silence填充大小*/
snd_pcm_uframes_tboundary;
snd_pcm_uframes_tsilenced_start;
snd_pcm_uframes_tsilenced_size;
snd_pcm_sync_id_tsync; /*硬件同步ID*/
/*mmap*/
volatilestruct snd_pcm_mmap_status *status;
volatilestruct snd_pcm_mmap_control *control;
atomic_tmmap_count;
/*鎖/調(diào)度*/
spinlock_tlock;
wait_queue_head_tsleep;
structtimer_list tick_timer;
structfasync_struct *fasync;
/*私有段*/
void*private_data;
void(*private_free)(struct snd_pcm_runtime *runtime);
/*硬件描述*/
structsnd_pcm_hardware hw;
structsnd_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);
/*定時(shí)器*/
unsignedint timer_resolution; /*timer resolution*/
/*DMA*/
unsignedchar *dma_area;
dma_addr_tdma_addr; /*總線物理地址*/
size_tdma_bytes; /*DMA區(qū)域大小*/
structsnd_dma_buffer *dma_buffer_p; /*分配的緩沖區(qū)*/
#ifdefined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
structsnd_pcm_oss_runtime oss;
#endif
};
snd_pcm_runtime對(duì)于大部分的驅(qū)動(dòng)程序操作集的函數(shù)來說是只讀的。僅僅PCM中間層可以改變/更新這些信息。但是硬件描述,中斷響應(yīng),DMA緩沖區(qū)信息和私有數(shù)據(jù)是例外的。此外,假如你采用標(biāo)準(zhǔn)的內(nèi)存分配函數(shù)snd_pcm_lib_malloc_pages(),就不再需要自己設(shè)定DMA緩沖區(qū)信息了。
下面幾章,會(huì)對(duì)上面記錄的現(xiàn)實(shí)進(jìn)行解釋。
硬件描述
硬件描述(structsnd_pcm_hardware)包含了基本硬件配置的定義。如前面所述,你需要在open的時(shí)候?qū)λ鼈冞M(jìn)行定義。注意runtime實(shí)例擁有這個(gè)描述符的拷貝而不是已經(jīng)存在的描述符的指針。換句話說,在open函數(shù)中,你可以根據(jù)需要修改描述符的拷貝。例如,假如在一些聲卡上最大的通道數(shù)是1,你仍然可以使用相同的硬件描述符,同時(shí)在后面你可以改變最大通道數(shù)。
Structsnd_pcm_runtime *runtime = substream->runtime;
....
runtime->hw= snd_mychip_playback_hw; /*通用定義*/
if(chip->model == VERY_OLD_ONE)
runtime->hw.channels_max= 1;
典型的硬件描述如下:
staticstruct snd_pcm_hardware snd_mychip_playback_hw = {
.info= (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED|
SNDRV_PCM_INFO_BLOCK_TRANSFER|
SNDRV_PCM_INFO_MMAP_VALID),
.formats= SNDRV_PCM_FORMAT_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,
};
info字段包含pcm的類型和能力。位標(biāo)志在
如上面的例子,MMAP_VALID和BLOCK_TRANSFER都是針對(duì)OSSmmap模式,通常情況它們都要設(shè)定。當(dāng)然,MMAP_VALID僅僅當(dāng)mmap真正被支持的時(shí)候才會(huì)被設(shè)定。
其他一些標(biāo)志位是SNDRV_PCM_INFO_PAUSE和SNDRV_PCM_INFO_RESUME。SNDRV_PCM_INFO_PAUSE標(biāo)志位意思是pcm支持“暫?!辈僮鳎?/span>SNDRV_PCM_INFO_RESUME表示是pcm支持“掛起/恢復(fù)”操作。假如PAUSE標(biāo)志位被設(shè)定,trigger函數(shù)就必須執(zhí)行一個(gè)對(duì)應(yīng)的(暫停按下/釋放)命令。就算沒有RESUME標(biāo)志位,也可以被定義掛起/恢復(fù)觸發(fā)命令。
更詳細(xì)的部分請(qǐng)參考“電源管理”一章。
當(dāng)PCM子系統(tǒng)能被同步(如:播放流和錄音流的開始/結(jié)束的同步)的時(shí)候,你可以設(shè)定SNDRV_PCM_INFO_SYNC_START標(biāo)志位。在這種情況下,你必須在trigger函數(shù)中檢查PCM子流鏈。下面的章節(jié)會(huì)想笑介紹這個(gè)部分。
formats字段包含了支持格式的標(biāo)志位(SNDRV_PCM_FMTBIT_XXX)。假如硬件支持超過一個(gè)的格式,需要對(duì)位標(biāo)志位進(jìn)行“或”運(yùn)算。上面的例子就是支持16bit有符號(hào)的小端格式。
rates字段包含了支持的采樣率(SNDRV_PCM_RATE_XXX)。當(dāng)聲卡支持多種采樣率的時(shí)候,應(yīng)該附加一個(gè)CONTINUOUS標(biāo)志。已經(jīng)預(yù)先定義的典型的采樣率,假如你的聲卡支持不常用的采樣率,你需要加入一個(gè)KNOT標(biāo)志,同時(shí)手動(dòng)的對(duì)硬件進(jìn)行控制(稍后解釋)。
rate_min和rate_max定義了最小和最大的采樣率。應(yīng)該和采樣率相對(duì)應(yīng)。
channel_min和channel_max定義了最大和最小的通道,以前可能你已看到。
buffer_bytes_max定義了以字節(jié)為單位的最大的緩沖區(qū)大小。這里沒有buffer_bytes_min字段,因?yàn)樗梢酝ㄟ^最小的period大小和最小的period數(shù)量計(jì)算得出。同時(shí),period_bytes_min和定義的最小和最大的period。periods_max和periods_min定義了最大和最小的periods。
period信息和OSS中的fragment相對(duì)應(yīng)。period定義了PCM中斷產(chǎn)生的周期。這個(gè)周期非常依賴硬件。一般來說,一個(gè)更短的周期會(huì)提供更多的中斷和更多的控制。如在錄音中,周期大小定義了輸入延遲,另外,整個(gè)緩存區(qū)大小也定義了播放的輸出延遲。
字段fifo_size。這個(gè)主要是和硬件的FIFO大小有關(guān),但是目前驅(qū)動(dòng)中或alsa-lib中都沒有使用。所以你可以忽略這個(gè)字段。
PCM配置
OK,讓我們?cè)俅位氐?/span>PCM運(yùn)行時(shí)記錄。最經(jīng)常涉及的運(yùn)行時(shí)實(shí)例中的記錄就是PCM配置了。PCM可以讓應(yīng)用程序通過alsa-lib發(fā)送hw_params來配置。有很多字段都是從hw_params和sw_params結(jié)構(gòu)中拷貝過來的。例如:format保持了應(yīng)用程序選擇的格式類型,這個(gè)字段包含了enum值SNDRV_PCM_FORMAT_XXX。
其中要注意的一個(gè)就是,配置的buffer和period大小被放在運(yùn)行時(shí)記錄的“frame”中。在ALSA里,1frame=channel*samples-size。為了在幀和字節(jié)之間轉(zhuǎn)換,你可以用下面的函數(shù),frames_to_bytes()和bytes_to_frames()。
period_bytes= frames_to_bytes(runtime,runtime->period_size);
同樣,許多的軟件參數(shù)(sw_params)也存放在frames字段里面。請(qǐng)檢查這個(gè)字段的類型。snd_pcm_uframes_t是作為表示frames的無符號(hào)整數(shù),而snd_pcm_sframes_t是作為表示frames的有符號(hào)整數(shù)。
DMA緩沖區(qū)信息
DMA緩沖區(qū)通過下面4個(gè)字段定義,dma_area,dma_addr,dma_bytes,dma_private。其中dma_area是緩沖區(qū)的指針(邏輯地址)。可以通過memcopy來向這個(gè)指針來操作數(shù)據(jù)。dma_addr是緩沖區(qū)的物理地址。這個(gè)字段僅僅當(dāng)緩沖區(qū)是線性緩存的時(shí)候才要特別說明。dma_bytes是緩沖區(qū)的大小。dma_private是被ALSA的DMA管理用到的。
如果采用ALSA的標(biāo)準(zhǔn)內(nèi)存分配函數(shù)snd_pcm_lib_mallock_pages()分配內(nèi)存,那些字段會(huì)被ALSA的中間層設(shè)定,你不能自己改變他們,可以讀取而不能寫入。而如果你想自己分配內(nèi)存,你就需要在hw_params回調(diào)里面自己管理它們。當(dāng)內(nèi)存被mmap之后,你至少要設(shè)定dma_bytes和dma_area。但是如果你的驅(qū)動(dòng)不支持mmap,這些字段就不必一定設(shè)定.dma_addr也不是強(qiáng)制的,你也可以根據(jù)靈活來用dma_private。
運(yùn)行狀態(tài)
可以通過runtime->status來獲得運(yùn)行狀態(tài)。它是一個(gè)指向snd_pcm_mmap_status記錄的指針。例如,可以通過runtime->status->hw_ptr來得到當(dāng)前DMA硬件指針。
可以通過runtime->control來查看DMA程序的指針,它是指向snd_pcm_mmap_control記錄。但是,不推薦直接存取這些數(shù)據(jù)。
私有數(shù)據(jù)
可以為子流分配一個(gè)記錄,讓它保存在runtime->private_data里面。通常可以在open函數(shù)中做。不要和pcm->private_data混攪了,pcm->private_data主要是在創(chuàng)建PCM的時(shí)候指向chip實(shí)例,而runtime->private_data是在PCMopen的時(shí)候指向一個(gè)動(dòng)態(tài)數(shù)據(jù)。
Struct intsnd_xxx_open(struct snd_pcm_substream *substream)
{
structmy_pcm_data *data;
data =kmalloc(sizeof(*data),GFP_KERNEL);
substream->runtime->private_data= data;
....
}
上述分配的對(duì)象要在close函數(shù)中釋放。
中斷函數(shù)
transfer_ack_begin()和transfer_ack_end()將會(huì)在snd_pcm_period_elapsed()的開始和結(jié)束。
操作函數(shù)
現(xiàn)在讓我來詳細(xì)介紹每個(gè)pcm的操作函數(shù)吧(ops)。通常每個(gè)回調(diào)函數(shù)成功的話返回0,出錯(cuò)的話返回一個(gè)帶錯(cuò)誤碼的負(fù)值,如:-EINVAL。
每個(gè)函數(shù)至少要有一個(gè)snd_pcm_substream指針變量。主要是為了從給定的子流實(shí)例中得到chip記錄,你可以采用下面的宏。
Int xxx(){
structmychip *chip = snd_pcm_substream_chip(substream);
....
}
open函數(shù)
static intsnd_xxx_open(struct snd_pcm_substream *substream);
當(dāng)打開一個(gè)pcm子流的時(shí)候調(diào)用。
在這里,你至少要初始化runtime->hw記錄。典型應(yīng)用如下:
static intsnd_xxx_open(struct snd_pcm_substream *substream)
{
structmychip *chip = snd_pcm_substream_chip(substream);
structsnd_pcm_runtime *runtime = substream->runtime;
runtime->hw= snd_mychip_playback_hw;
return0;
}
其中snd_mychip_playback_hw是預(yù)先定義的硬件描述。
close函數(shù)
static intsnd_xxx_close(struct snd_pcm_substream *substream)
顯然這是在pcm子流被關(guān)閉的時(shí)候調(diào)用。
所有在open的時(shí)候被分配的pcm子流的私有的實(shí)例都應(yīng)該在這里被釋放。
Static intsnd_xxx_close(struct snd_pcm_substream *substream)
{
...
kfree(substream->runtime->private_data);
...
}
ioctl函數(shù)
這個(gè)函數(shù)主要是完成一些pcmioctl的特殊功能。但是通常你可以采用通用的ioctl函數(shù)snd_pcm_lib_ioctl。
hw_params函數(shù)
static intsnd_xxx_hw_params(struct snd_pcm_substream *substream,
structsnd_pcm_substream *hw_params);
這個(gè)函數(shù)和hw_free函數(shù)僅僅在ALSA0.9.X版本出現(xiàn)。
當(dāng)pcm子流中已經(jīng)定義了緩沖區(qū)大小,period大小,格式等的時(shí)候,應(yīng)用程序可以通過這個(gè)函數(shù)來設(shè)定硬件參數(shù)。
很多的硬件設(shè)定都要在這里完成,包括分配內(nèi)存。
設(shè)定的參數(shù)可以通過params_xxx()宏得到。對(duì)于分配內(nèi)存,可以采用下面一個(gè)有用的函數(shù),
snd_pcm_lib_malloc_pages(substream,params_buffer_bytes(hw_params));
snd_pcm_lib_malloc_pages()僅僅當(dāng)DMA緩沖區(qū)已經(jīng)被預(yù)分配之后才可以用。參考“緩存區(qū)類型”一章獲得更詳細(xì)的細(xì)節(jié)。
注意這個(gè)和prepare函數(shù)會(huì)在初始化當(dāng)中多次被調(diào)用。例如,OSSemulation可能在每次通過ioctl改變的時(shí)候都要調(diào)用這些函數(shù)。
因而,千萬不要對(duì)一個(gè)相同的內(nèi)存分配多次,可能會(huì)導(dǎo)致內(nèi)存的漏洞!而上面的幾個(gè)函數(shù)是可以被多次調(diào)用的,如果它已經(jīng)被分配了,它會(huì)先自動(dòng)釋放之前的內(nèi)存。
另外一個(gè)需要注意的是,這些函數(shù)都不是原子的(可以被調(diào)到)。這個(gè)是非常重要的,因?yàn)?/font>trigger函數(shù)是原子的(不可被調(diào)度)。因此,mutex和其他一些和調(diào)度相關(guān)的功能函數(shù)在trigger里面都不需要了。具體參看“原子操作”一節(jié)。
hw_free函數(shù)
static intsnd_xxx_hw_free(struct snd_pcm_substream *substream);
這個(gè)函數(shù)可以是否通過hw_params分配的資源。例如:通過如下函數(shù)釋放那些通過snd_pcm_lib_malloc_pages()申請(qǐng)的緩存。
snd_pcm_lib_free_pages(substream)
這個(gè)函數(shù)要在close調(diào)用之前被調(diào)用。同時(shí),它也可以被多次調(diào)用。它也會(huì)知道資源是否已經(jīng)被分配。
Prepare函數(shù)
static intsnd_xxx_prepare(struct snd_pcm_substream *substream);
當(dāng)pcm“準(zhǔn)備好了”的時(shí)候調(diào)用這個(gè)函數(shù)??梢栽谶@里設(shè)定格式類型,采樣率等等。和hw_params不同的是,每次調(diào)用snd_pcm_prepare()的時(shí)候都要調(diào)用prepare函數(shù)。
注意最近的版本prepare變成了非原子操作的了。這個(gè)函數(shù)中,你要做一些調(diào)度安全性策略。
在下面的函數(shù)中,你會(huì)涉及到runtime記錄的值(substream->runtime)。例如:得到當(dāng)前的采樣率,格式或聲道,可以分別存取runtime->rate,runtime->format,runtime->channels。分配的內(nèi)存的地址放到runtime->dma_area中,內(nèi)存和period大小分別保存在runtime->buffer_size和runtime->period_size中。
要注意在每次設(shè)定的時(shí)候都有可能多次調(diào)用這些函數(shù)。
trigger函數(shù)
static intsnd_xxx_trigger(struct snd_pcm_substream *substream, int cmd);
當(dāng)pcm開始,停止或暫停的時(shí)候都會(huì)調(diào)用這個(gè)函數(shù)。
具體執(zhí)行那個(gè)操作主要是根據(jù)第二個(gè)參數(shù),在
switch(cmd){
caseSNDRV_PCM_TRIGGER_START:
//啟動(dòng)PCM引擎
break;
caseSNDRV_PCM_TRIGGER_STOP:
//停止PCM引擎
break;
default:
break;
}
當(dāng)pcm支持暫停操作(在hareware表里面有這個(gè)),也必須處理PAUSE_PAUSE和PAUSE_RELEASE命令。前者是暫停命令,后者是重新播放命令。
假如pcm支持掛起/恢復(fù)操作,不管是全部或部分的掛起/恢復(fù)支持,都要處理SUSPEND和RESUME命令。這些命令主要是在電源狀態(tài)改變的時(shí)候需要,通常它們和STOP,START命令放到一起。具體參看“電源管理”一章。
如前面提到的,這個(gè)操作上原子的。不要在調(diào)用這些函數(shù)的時(shí)候進(jìn)入睡眠。而trigger函數(shù)要盡量小,甚至僅僅觸發(fā)DMA。另外的工作如初始化hw_params和prepare應(yīng)該在之前做好。
pointer函數(shù)
staticsnd_pcm_uframes_t snd_xxx_pointer(struct snd_pcm_substream*substream);
PCM中間層通過調(diào)用這個(gè)函數(shù)來獲得緩沖區(qū)中的硬件位置。返回值需要以為frames為單位(在ALSA0.5.X是以字節(jié)為單位的),范圍從0到buffer_size-1。
一般情況下,在中斷程序中,調(diào)用snd_pcm_period_elapsed()的時(shí)候,在pcm中間層在在更新buffer的程序中調(diào)用它。然后,pcm中間層會(huì)更新指針位置和計(jì)算可用的空間,然后喚醒那些等待的線程。
這個(gè)函數(shù)也是原子的。
Copy和silence函數(shù)
這些函數(shù)不是必須的,同時(shí)在大部分的情況下是被忽略的。這些函數(shù)主要應(yīng)用在硬件內(nèi)存不在正常的內(nèi)存空間的時(shí)候。一些聲卡有一些沒有被影射的自己的內(nèi)存。在這種情況下,你必須把內(nèi)存?zhèn)鞯接布膬?nèi)存空間去?;蛘?,緩沖區(qū)在物理內(nèi)存和虛擬內(nèi)存中都是不連續(xù)的時(shí)候就需要它們了。
假如定義了copy和silence,就可以做copy和set-silence的操作了。更詳細(xì)的描述請(qǐng)參考“緩沖區(qū)和內(nèi)存管理”一章。
Ack函數(shù)
這個(gè)函數(shù)也不是必須的。當(dāng)在讀寫操作的時(shí)候更新appl_ptr的時(shí)候會(huì)調(diào)用它。一些類似于emu10k1-fx和cs46xx的驅(qū)動(dòng)程序會(huì)為了內(nèi)部緩存來跟蹤當(dāng)前的appl_ptr,這個(gè)函數(shù)僅僅對(duì)于這個(gè)情況才會(huì)被用到。
這個(gè)函數(shù)也是原子的。
page函數(shù)
這個(gè)函數(shù)也不是必須的。這個(gè)函數(shù)主要對(duì)那些不連續(xù)的緩存區(qū)。mmap會(huì)調(diào)用這個(gè)函數(shù)得到內(nèi)存頁(yè)的地址。后續(xù)章節(jié)“緩沖區(qū)和內(nèi)存管理”會(huì)有一些例子介紹。
中斷處理
下面的pcm工作就是PCM中斷處理了。聲卡驅(qū)動(dòng)中的PCM中斷處理的作用主要是更新緩存的位置,然后在緩沖位置超過預(yù)先定義的period大小的時(shí)候通知PCM中間層??梢酝ㄟ^調(diào)用snd_pcm_period_elapsed()來通知。
聲卡有如下幾種產(chǎn)生中斷。
period(周期)中斷
這是一個(gè)很常見的類型:硬件會(huì)產(chǎn)生周期中斷。每次中斷都會(huì)調(diào)用snd_pcm_period_elapsed()。
snd_pcm_period_elapsed()的參數(shù)是substream的指針。因?yàn)?,需要?/font>chip實(shí)例中得到substream的指針。例如:在chip記錄中定義一個(gè)substream字段來保持當(dāng)前運(yùn)行的substream指針,在open函數(shù)中要設(shè)定這個(gè)字段而在close函數(shù)中要復(fù)位這個(gè)字段。
假如在中斷處理函數(shù)中獲得了一個(gè)自旋鎖,如果其他pcm也會(huì)調(diào)用這個(gè)鎖,那你必須要在調(diào)用snd_pcm_period_elapsed()之前釋放這個(gè)鎖。
典型代碼如下:
Example5-3.中斷函數(shù)處理#1
structirqreturn_t snd_mychip_interrupt(int irq, void *dev_id)
{
structmychip *chip = dev_id;
spin_lock(&chip->lock);
....
if(pcm_irq_invoked(chip)){
spin_unlock(&chip->lock);
snd_pcm_period_elapsed(chip->substream);
spin_lock(&chip->lock);
//如果需要的話,可以響應(yīng)中斷
}
....
spin_unlock(&chip->lock);
returnIRQ_HANDLED;
}
高頻率時(shí)鐘中斷
當(dāng)硬件不再產(chǎn)生一個(gè)period(周期)中斷的時(shí)候,就需要一個(gè)固定周期的timer中斷了(例如es1968,ymfpci驅(qū)動(dòng))。這時(shí)候,在每次中斷都要檢查當(dāng)前硬件位置,同時(shí)計(jì)算已經(jīng)累積的采樣的長(zhǎng)度。當(dāng)長(zhǎng)度超過period長(zhǎng)度時(shí)候,需要調(diào)用snd_pcm_period_elapsed()同時(shí)復(fù)位計(jì)數(shù)值。
典型代碼如下:
Example5-4.中斷函數(shù)處理#2
staticirqreturn_t snd_mychip_interrupt(int irq, void *dev_id)
{
structmychip *chip = dev_id;
spin_lock(&chip->lock);
....
if(pcm_irq_invoked(chip)){
unsignedint last_ptr, size;
/*得到當(dāng)前的硬件指針(幀為單位)*/
last_ptr= get_hw_ptr(chip);
/*計(jì)算自從上次更新之后又處理的幀*/
if(last_ptr < chip->last_ptr)
{
size= runtime->buffer_size + last_ptr - chip->last_ptr
}else
{
size= last_ptr – chip->chip->last_ptr;
}
//保持上次更新的位置
chip->last_ptr= last_ptr;
/*累加size計(jì)數(shù)器*/
chip->size+= size;
/*超過period的邊界?*/
if(chip->size >= runtime->period_size){
/*重置size計(jì)數(shù)器*/
chip->size%= runtime->period_size;
spin_unlock(&chip->lock);
snd_pcm_period_elapsed(substream);
spin_lock(&chip->lock);
}
//需要的話,要相應(yīng)中斷
}
....
spin_unlock(&chip->lock);
returnIRQ_HANDLED;
}
在調(diào)用snd_pcm_period_elapsed()的時(shí)候
就算超過一個(gè)period的時(shí)間已經(jīng)過去,你也不需要多次調(diào)用snd_pcm_period_elapsed(),因?yàn)?/font>pcm層會(huì)自己檢查當(dāng)前的硬件指針和上次和更新的狀態(tài)。
原子操作
在內(nèi)核編程的時(shí)候,一個(gè)非常重要(又很難dubug)的問題就是競(jìng)爭(zhēng)條件。Linux內(nèi)核中,一般是通過自旋鎖和信號(hào)量來解決的。通常來說,假如競(jìng)爭(zhēng)發(fā)生在中斷函數(shù)中,中斷函數(shù)要具有原子性,你必須采用自旋鎖來包含臨界資源。假如不是發(fā)生在中斷部分,同時(shí)比較耗時(shí),可以采用信號(hào)量。
如我們看到的,pcm的操作函數(shù)一些是原子的而一些不是。例如:hw_params函數(shù)不是原子的,而trigger函數(shù)是原子的。這意味著,后者調(diào)用的時(shí)候,PCM中間層已經(jīng)擁有了鎖。
在這些函數(shù)中申請(qǐng)的自旋鎖和信號(hào)量要做個(gè)計(jì)算。
在這些原子的函數(shù)中,不能那些可能調(diào)用任務(wù)切換和進(jìn)入睡眠的函數(shù)。其中信號(hào)量和互斥體可能會(huì)進(jìn)入睡眠,因此,在原子操作的函數(shù)中(如:trigger函數(shù))不能調(diào)用它們。如果在這種函數(shù)中調(diào)用delay,可以用udelay(),或mdelay()。
約束
假如你的聲卡支持不常用的采樣率,或僅僅支持固定的采樣率,就需要設(shè)定一個(gè)約束條件。
例如:為了把采樣率限制在一些支持的幾種之中,就需要用到函數(shù)snd_pcm_hw_constraint_list()。需要在open函數(shù)中調(diào)用它。
Example5-5.硬件約束示例
staticunsigned int rates[] =
{4000,10000,22050,44100};
staticunsigned snd_pcm_hw_constraint_list constraints_rates = {
.count= ARRAY_SIZE(rates),
.list= rates,
.mask= 0,
};
static intsnd_mychip_pcm_open(struct snd_pcm_substream *substream)
{
int err;
....
err =snd_pcm_hw_constraint_list(substream->runtime,0,
SNDRV_PCM_HW_PARAM_RATE,
&constraints_rates);
if (err <0)
returnerr;
....
}
有多種不同的約束。請(qǐng)參考sound/pcm.h中的完整的列表。甚至可以定義自己的約束條件。例如,假如my_chip可以管理一個(gè)單通道的子流,格式是S16_LE,另外,它還支持snd_pcm_hareware中設(shè)定的格式(或其他constraint_list)。可以設(shè)定一個(gè):
Example5-6.為通道設(shè)定一個(gè)硬件規(guī)則
static inthw_rule_format_by_channels(struct snd_pcm_hw_params *params,
structsnd_pcm_hw_rule *rule)
{
structsnd_interval *c = hw_params_interval(params,
SNDRV_PCM_HW_PARAM_CHANNELS);
structsnd_mask *f = hw_param_mask(params,SNDRV_PCM_HW_PARAM_FORMAT);
structsnd_mask fmt;
snd_mask_any(&fmt);/*初始化結(jié)構(gòu)體*/
if(c->min < 2){
fmt.bits[0]&= SNDRV_PCM_FMTBIT_S16_LE;
returnsnd_mask_refine(f, &fmt);
}
return 0;
}
之后,需要把上述函數(shù)加入到你的規(guī)則當(dāng)中去:
snd_pcm_hw_rule_add(substream->runtime,0, SNDRV_PCM_HW_PARAM_CHANNELS,
hw_rule_channels_by_format,0,SNDRV_PCM_HW_PARAM_FORMAT,
-1);
當(dāng)應(yīng)用程序設(shè)定聲道數(shù)量的時(shí)候會(huì)調(diào)用上面的規(guī)則函數(shù)。但是應(yīng)用程序可以在設(shè)定聲道數(shù)之前設(shè)定格式。所以也需要設(shè)定對(duì)應(yīng)的規(guī)則。
Example5-7.為通道設(shè)定一個(gè)硬件規(guī)則
static inthw_rule_format_by_format(struct snd_pcm_hw_params *params,
structsnd_pcm_hw_rule *rule)
{
structsnd_interval *c = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_CHANNELS);
structsnd_mask *f = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
structsnd_interval ch;
snd_interval_any(&ch);
if(f->bits[0] == SNDRV_PCM_FORMAT_S16_LE){
ch.min= ch.max = 1;
ch.integer= 1;
returnsnd_interval_refine(c, &ch);
}
return 0;
}
在open函數(shù)中:
snd_pcm_hw_rule_add(substream->runtime,0, SNDRV_PCM_HW_PARAM_FORMAT,
hw_rule_channels_by_format,0,SNDRV_PCM_HW_PARAM_CHANNELS,
-1);
這里我們不會(huì)更詳細(xì)的描述,我仍然想說“直接看源碼吧”。
聯(lián)系客服