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)*/ struct snd_pcm_substream *trigger_master; snd_timestamp_t trigger_tstamp;/*觸發(fā)時(shí)間戳*/、 int overrange; snd_pcm_uframes_t avail_max; snd_pcm_uframes_t hw_ptr_base /*緩沖區(qū)復(fù)位時(shí)的位置*/ snd_pcm_uframes_t hw_ptr_interrupt;/*中斷時(shí)的位置*/ /*硬件參數(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_uframe_t period_size; /*周期大小*/ unsigned int periods /*周期數(shù)*/ snd_pcm_uframes_t buffer_size; /*緩沖區(qū)大小*/ unsigned int tick_time; /*tick time滴答時(shí)間*/ snd_pcm_uframes_t min_align; /*格式對(duì)應(yīng)的最小對(duì)齊*/ 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時(shí)間戳被更新*/ unsigned int sleep_min; /*睡眠的最小節(jié)拍數(shù)*/ snd_pcm_uframes_t xfer_align; /*xfer的大小需要是成倍數(shù)的*/ 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); /*定時(shí)器*/ unsigned int timer_resolution; /*timer resolution*/ /*DMA*/ unsigned char *dma_area; 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) struct snd_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)行解釋。
硬件描述 硬件描述(struct snd_pcm_hardware)包含了基本硬件配置的定義。如前面所述,你需要在open的時(shí)候?qū)λ鼈冞M(jìn)行定義。注意runtime實(shí)例擁有這個(gè)描述符的拷貝而不是已經(jīng)存在的描述符的指針。換句話說,在open函數(shù)中,你可以根據(jù)需要修改描述符的拷貝。例如,假如在一些聲卡上最大的通道數(shù)是1,你仍然可以使用相同的硬件描述符,同時(shí)在后面你可以改變最大通道數(shù)。 Struct snd_pcm_runtime *runtime = substream->runtime; .... runtime->hw = snd_mychip_playback_hw; /*通用定義*/ if (chip->model == VERY_OLD_ONE) runtime->hw.channels_max = 1;
典型的硬件描述如下: static struct 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ì)OSS mmap模式,通常情況它們都要設(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支持“暫?!辈僮?,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里面。通??梢栽?/font>open函數(shù)中做。不要和pcm->private_data混攪了,pcm->private_data主要是在創(chuàng)建PCM的時(shí)候指向chip實(shí)例,而runtime->private_data是在PCM open的時(shí)候指向一個(gè)動(dòng)態(tài)數(shù)據(jù)。 Struct int snd_xxx_open(struct snd_pcm_substream *substream) { struct my_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(){ struct mychip *chip = snd_pcm_substream_chip(substream); .... }
open函數(shù) static int snd_xxx_open(struct snd_pcm_substream *substream); 當(dāng)打開一個(gè)pcm子流的時(shí)候調(diào)用。 在這里,你至少要初始化runtime->hw記錄。典型應(yīng)用如下: static int snd_xxx_open(struct snd_pcm_substream *substream) { struct mychip *chip = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime;
runtime->hw = snd_mychip_playback_hw; return 0; } 其中snd_mychip_playback_hw是預(yù)先定義的硬件描述。
close函數(shù) static int snd_xxx_close(struct snd_pcm_substream *substream) 顯然這是在pcm子流被關(guān)閉的時(shí)候調(diào)用。 所有在open的時(shí)候被分配的pcm子流的私有的實(shí)例都應(yīng)該在這里被釋放。 Static int snd_xxx_close(struct snd_pcm_substream *substream) { ... kfree(substream->runtime->private_data); ... }
ioctl函數(shù) 這個(gè)函數(shù)主要是完成一些pcm ioctl的特殊功能。但是通常你可以采用通用的ioctl函數(shù)snd_pcm_lib_ioctl。
hw_params函數(shù) static int snd_xxx_hw_params(struct snd_pcm_substream *substream, struct snd_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 int snd_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 int snd_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 int snd_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){ case SNDRV_PCM_TRIGGER_START: //啟動(dòng)PCM引擎 break; case SNDRV_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ù) static snd_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)存頁的地址。后續(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 struct irqreturn_t snd_mychip_interrupt(int irq, void *dev_id) { struct mychip *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); return IRQ_HANDLED; }
高頻率時(shí)鐘中斷 當(dāng)硬件不再產(chǎn)生一個(gè)period(周期)中斷的時(shí)候,就需要一個(gè)固定周期的timer中斷了(例如 es1968,ymfpci驅(qū)動(dòng))。這時(shí)候,在每次中斷都要檢查當(dāng)前硬件位置,同時(shí)計(jì)算已經(jīng)累積的采樣的長度。當(dāng)長度超過period長度時(shí)候,需要調(diào)用 snd_pcm_period_elapsed()同時(shí)復(fù)位計(jì)數(shù)值。 典型代碼如下: Example5-4.中斷函數(shù)處理#2 static irqreturn_t snd_mychip_interrupt(int irq, void *dev_id) { struct mychip *chip = dev_id; spin_lock(&chip->lock); .... if (pcm_irq_invoked(chip)){ unsigned int 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); return IRQ_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)的問題就是競爭條件。Linux內(nèi)核中,一般是通過自旋鎖和信號(hào)量來解決的。通常來說,假如競爭發(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.硬件約束示例 static unsigned int rates[] = {4000,10000,22050,44100}; static unsigned snd_pcm_hw_constraint_list constraints_rates = { .count = ARRAY_SIZE(rates), .list = rates, .mask = 0, }; static int snd_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) return err; .... }
有多種不同的約束。請(qǐng)參考sound/pcm.h中的完整的列表。甚至可以定義自己的約束條件。例如,假如my_chip可以管理一個(gè)單通道的子流,格式是S16_LE,另外,它還支持snd_pcm_hareware中設(shè)定的格式(或其他constraint_list)??梢栽O(shè)定一個(gè): Example5-6.為通道設(shè)定一個(gè)硬件規(guī)則 static int hw_rule_format_by_channels(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) { struct snd_interval *c = hw_params_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); struct snd_mask *f = hw_param_mask(params,SNDRV_PCM_HW_PARAM_FORMAT); struct snd_mask fmt; snd_mask_any(&fmt); /*初始化結(jié)構(gòu)體*/ if (c->min < 2){ fmt.bits[0] &= SNDRV_PCM_FMTBIT_S16_LE; return snd_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 int hw_rule_format_by_format(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) { struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); struct snd_mask *f = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); struct snd_interval ch; snd_interval_any(&ch); if (f->bits[0] == SNDRV_PCM_FORMAT_S16_LE){ ch.min = ch.max = 1; ch.integer = 1; return snd_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ì)的描述,我仍然想說“直接看源碼吧”。 |
|