- 论坛徽章:
- 0
|
PCM信息运行时指针
当打开一个一个PCM子流的时候,PCM运行时实例就会分配给这个子流。这个指针可以通过substream->runtime获得。运行时指针拥有多种信息:hw_params和sw_params的配置的拷贝,缓冲区指针,mmap记录,自旋锁等等。几乎你想控制PCM的所有信息都可以在这里得到。
Struct
_snd_pcm_runtime {
/*状态*/
struct
snd_pcm_substream *trigger_master;
snd_timestamp_t
trigger_tstamp;/*触发时间戳*/、
int
overrange;
snd_pcm_uframes_t
avail_max;
snd_pcm_uframes_t
hw_ptr_base /*缓冲区复位时的位置*/
snd_pcm_uframes_t
hw_ptr_interrupt;/*中断时的位置*/
/*硬件参数*/
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 /*周期数*/
snd_pcm_uframes_t
buffer_size; /*缓冲区大小*/
unsigned
int tick_time; /*tick time滴答时间*/
snd_pcm_uframes_t
min_align; /*格式对应的最小对齐*/
size_t
byte_align;
unsigned
int frame_bits;
unsigned
int sample_bits;
unsigned
int info;
unsigned
int rate_num;
unsigned
int rate_den;
/*软件参数*/
struct
timespec tstamp_mode; /*mmap时间戳被更新*/
unsigned
int sleep_min; /*睡眠的最小节拍数*/
snd_pcm_uframes_t
xfer_align; /*xfer的大小需要是成倍数的*/
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;
/*锁/调度*/
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;
/*中断的回调函数*/
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_addr_t
dma_addr; /*总线物理地址*/
size_t
dma_bytes; /*DMA区域大小*/
struct
snd_dma_buffer *dma_buffer_p; /*分配的缓冲区*/
#if
defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
struct
snd_pcm_oss_runtime oss;
#endif
};
snd_pcm_runtime
对于大部分的驱动程序操作集的函数来说是只读的。仅仅PCM中间层可以改变/更新这些信息。但是硬件描述,中断响应,DMA缓冲区信息和私有数据是例外的。此外,假如你采用标准的内存分配函数snd_pcm_lib_malloc_pages(),就不再需要自己设定DMA缓冲区信息了。
下面几章,会对上面记录的现实进行解释。
硬件描述
硬件描述(struct
snd_pcm_hardware)包含了基本硬件配置的定义。如前面所述,你需要在open的时候对它们进行定义。注意runtime实例拥有这个描述符的拷贝而不是已经存在的描述符的指针。换句话说,在open函数中,你可以根据需要修改描述符的拷贝。例如,假如在一些声卡上最大的通道数是1,你仍然可以使用相同的硬件描述符,同时在后面你可以改变最大通道数。
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[color="#000000"],
.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[color="#000000"]字段包含pcm[color="#000000"]的类型和能力。位标志在[color="#000000"]中定义,如:SNDRV_PCM_INFO_XXX[color="#000000"]。这里,你必须定义mmap[color="#000000"]是否支持和支持哪种interleaved[color="#000000"]格式。当支持mmap[color="#000000"]的时候,应当设定SNDRV_PCM_INFO_MMAP[color="#000000"]。当硬件支持interleaved[color="#000000"]或no-interleaved[color="#000000"]格式的时候,要设定SNDRV_PCM_INFO_INTERLEAVED[color="#000000"]或SNDRV_PCM_INFO_NONINTERLEAVED[color="#000000"]标志位。假如两者都支持,你也可以都设定。
如上面的例子,MMAP_VALID[color="#000000"]和BLOCK_TRANSFER[color="#000000"]都是针对OSS
mmap[color="#000000"]模式,通常情况它们都要设定。当然,MMAP_VALID[color="#000000"]仅仅当mmap[color="#000000"]真正被支持的时候才会被设定。
其他一些标志位是SNDRV_PCM_INFO_PAUSE[color="#000000"]和SNDRV_PCM_INFO_RESUME[color="#000000"]。SNDRV_PCM_INFO_PAUSE[color="#000000"]标志位意思是pcm[color="#000000"]支持“暂停”操作,SNDRV_PCM_INFO_RESUME[color="#000000"]表示是pcm[color="#000000"]支持“挂起/[color="#000000"]恢复”操作。假如PAUSE[color="#000000"]标志位被设定,trigger函数就必须执行一个对应的(暂停
按下/[color="#000000"]释放)命令。就算没有RESUME[color="#000000"]标志位,也可以被定义挂起/[color="#000000"]恢复触发命令。
[color="#000000"]更详细的部分请参考“电源管理”一章。
当PCM[color="#000000"]子系统能被同步(如:播放流和录音流的开始/[color="#000000"]结束的同步)的时候,你可以设定SNDRV_PCM_INFO_SYNC_START[color="#000000"]标志位。在这种情况下,你必须在trigger[color="#000000"]函数中检查PCM[color="#000000"]子流链。下面的章节会想笑介绍这个部分。
formats[color="#000000"]字段包含了支持格式的标志位(SNDRV_PCM_FMTBIT_XXX)[color="#000000"]。假如硬件支持超过一个的格式,需要对位标志位进行“或”运算。上面的例子就是支持16bit[color="#000000"]有符号的小端格式。
rates[color="#000000"]字段包含了支持的采样率(SNDRV_PCM_RATE_XXX)[color="#000000"]。当声卡支持多种采样率的时候,应该附加一个CONTINUOUS[color="#000000"]标志。已经预先定义的典型的采样率,假如你的声卡支持不常用的采样率,你需要加入一个KNOT[color="#000000"]标志,同时手动的对硬件进行控制(稍后解释)。
rate_min[color="#000000"]和rate_max[color="#000000"]定义了最小和最大的采样率。应该和采样率相对应。
channel_min[color="#000000"]和channel_max[color="#000000"]定义了最大和最小的通道,以前可能你已看到。
buffer_bytes_max[color="#000000"]定义了以字节为单位的最大的缓冲区大小。这里没有buffer_bytes_min[color="#000000"]字段,因为它可以通过最小的period[color="#000000"]大小和最小的period[color="#000000"]数量计算得出。同时,period_bytes_min[color="#000000"]和定义的最小和最大的period[color="#000000"]。periods_max[color="#000000"]和periods_min[color="#000000"]定义了最大和最小的periods[color="#000000"]。
period[color="#000000"]信息和OSS[color="#000000"]中的fragment[color="#000000"]相对应。period[color="#000000"]定义了PCM[color="#000000"]中断产生的周期。这个周期非常依赖硬件。一般来说,一个更短的周期会提供更多的中断和更多的控制。如在录音中,周期大小定义了输入延迟,另外,整个缓存区大小也定义了播放的输出延迟。
字段fifo_size[color="#000000"]。这个主要是和硬件的FIFO[color="#000000"]大小有关,但是目前驱动中或alsa-lib[color="#000000"]中都没有使用。所以你可以忽略这个字段。
[color="#000000"]PCM[color="#000000"]配置
OK,[color="#000000"]让我们再次回到PCM[color="#000000"]运行时记录。最经常涉及的运行时实例中的记录就是PCM[color="#000000"]配置了。PCM[color="#000000"]可以让应用程序通过alsa-lib[color="#000000"]发送hw_params[color="#000000"]来配置。有很多字段都是从hw_params[color="#000000"]和sw_params[color="#000000"]结构中拷贝过来的。例如:format[color="#000000"]保持了应用程序选择的格式类型,这个字段包含了enum[color="#000000"]值SNDRV_PCM_FORMAT_XXX[color="#000000"]。
其中要注意的一个就是,配置的buffer[color="#000000"]和period[color="#000000"]大小被放在运行时记录的“frame”[color="#000000"]中。在ALSA[color="#000000"]里,1frame=channel*samples-size[color="#000000"]。为了在帧和字节之间转换,你可以用下面的函数,frames_to_bytes()[color="#000000"]和bytes_to_frames()[color="#000000"]。
period_bytes
= frames_to_bytes(runtime,runtime->period_size);
同样,许多的软件参数(sw_params)[color="#000000"]也存放在frames[color="#000000"]字段里面。请检查这个字段的类型。snd_pcm_uframes_t[color="#000000"]是作为表示frames[color="#000000"]的无符号整数,而snd_pcm_sframes_t[color="#000000"]是作为表示frames[color="#000000"]的有符号整数。
[color="#000000"]DMA[color="#000000"]缓冲区信息
DMA[color="#000000"]缓冲区通过下面4[color="#000000"]个字段定义,dma_area,dma_addr,dma_bytes,dma_private[color="#000000"]。其中dma_area[color="#000000"]是缓冲区的指针(逻辑地址)。可以通过memcopy[color="#000000"]来向这个指针来操作数据。dma_addr[color="#000000"]是缓冲区的物理地址。这个字段仅仅当缓冲区是线性缓存的时候才要特别说明。dma_bytes是缓冲区的
大小。dma_private[color="#000000"]是被ALSA[color="#000000"]的DMA[color="#000000"]管理用到的。
如果采用ALSA[color="#000000"]的标准内存分配函数snd_pcm_lib_mallock_pages()[color="#000000"]分配内存,那些字段会被ALSA[color="#000000"]的中间层设定,你不能自己改变他们,可以读取而不能写入。而如果你想自己分配内存,你就需要在hw_params[color="#000000"]回调里面自己管理它们。当内存被mmap[color="#000000"]之后,你至少要设定dma_bytes[color="#000000"]和dma_area[color="#000000"]。但是如果你的驱动不支持mmap[color="#000000"],这些字段就不必一定设定.dma_addr[color="#000000"]也不是强制的,你也可以根据灵活来用dma_private[color="#000000"]。
[color="#000000"]运行状态
可以通过runtime->status[color="#000000"]来获得运行状态。它是一个指向snd_pcm_mmap_status[color="#000000"]记录的指针。例如,可以通过runtime->status->hw_ptr[color="#000000"]来得到当前DMA[color="#000000"]硬件指针。
可以通过runtime->control[color="#000000"]来查看DMA[color="#000000"]程序的指针,它是指向snd_pcm_mmap_control[color="#000000"]记录。但是,不推荐直接存取这些数据。
私有数据
可以为子流分配一个记录,让它保存在runtime->private_data里面。通常可以在open函数中做。不要和pcm->private_data混搅了,pcm->private_data主要是在创建PCM的时候指向chip实例,而runtime->private_data是在PCM
open的时候指向一个动态数据。
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;
....
}
上述分配的对象要在close函数中释放。
中断函数
transfer_ack_begin()和transfer_ack_end()将会在snd_pcm_period_elapsed()的开始和结束。
操作函数
现在让我来详细介绍每个pcm的操作函数吧(ops)。通常每个回调函数成功的话返回0,出错的话返回一个带错误码的负值,如:-EINVAL。
每个函数至少要有一个snd_pcm_substream指针变量。主要是为了从给定的子流实例中得到chip记录,你可以采用下面的宏。
Int xxx(){
struct
mychip *chip = snd_pcm_substream_chip(substream);
....
}
open函数
static int
snd_xxx_open(struct snd_pcm_substream *substream);
当打开一个pcm子流的时候调用。
在这里,你至少要初始化runtime->hw记录。典型应用如下:
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是预先定义的硬件描述。
close函数
static int
snd_xxx_close(struct snd_pcm_substream *substream)
显然这是在pcm子流被关闭的时候调用。
所有在open的时候被分配的pcm子流的私有的实例都应该在这里被释放。
Static int
snd_xxx_close(struct snd_pcm_substream *substream)
{
...
kfree(substream->runtime->private_data);
...
}
ioctl函数
这个函数主要是完成一些pcm
ioctl的特殊功能。但是通常你可以采用通用的ioctl函数snd_pcm_lib_ioctl。
hw_params函数
static int
snd_xxx_hw_params(struct snd_pcm_substream *substream,
struct
snd_pcm_substream *hw_params);
这个函数和hw_free函数仅仅在ALSA0.9.X版本出现。
当pcm子流中已经定义了缓冲区大小,period大小,格式等的时候,应用程序可以通过这个函数来设定硬件参数。
很多的硬件设定都要在这里完成,包括分配内存。
设定的参数可以通过params_xxx()宏得到。对于分配内存,可以采用下面一个有用的函数,
snd_pcm_lib_malloc_pages(substream,params_buffer_bytes(hw_params));
snd_pcm_lib_malloc_pages()仅仅当DMA缓冲区已经被预分配之后才可以用。参考“缓存区类型”一章获得更详细的细节。
注意这个和prepare函数会在初始化当中多次被调用。例如,OSSemulation可能在每次通过ioctl改变的时候都要调用这些函数。
因而,千万不要对一个相同的内存分配多次,可能会导致内存的漏洞!而上面的几个函数是可以被多次调用的,如果它已经被分配了,它会先自动释放之前的内存。
另外一个需要注意的是,这些函数都不是原子的(可以被调到)。这个是非常重要的,因为trigger函数是原子的(不可被调度)。因此,mutex和其他一些和调度相关的功能函数在trigger里面都不需要了。具体参看“原子操作”一节。
hw_free函数
static int
snd_xxx_hw_free(struct snd_pcm_substream *substream);
这个函数可以是否通过hw_params分配的资源。例如:通过如下函数释放那些通过snd_pcm_lib_malloc_pages()申请的缓存。
snd_pcm_lib_free_pages(substream)
这个函数要在close调用之前被调用。同时,它也可以被多次调用。它也会知道资源是否已经被分配。
Prepare函数
static int
snd_xxx_prepare(struct snd_pcm_substream *substream);
当pcm“准备好了”的时候调用这个函数。可以在这里设定格式类型,采样率等等。和hw_params不同的是,每次调用snd_pcm_prepare()的时候都要调用prepare函数。
注意最近的版本prepare变成了非原子操作的了。这个函数中,你要做一些调度安全性策略。
在下面的函数中,你会涉及到runtime记录的值(substream->runtime)。例如:得到当前的采样率,格式或声道,可以分别存取runtime->rate,runtime->format,runtime->channels。分配的内存的地址放到runtime->dma_area中,内存和period大小分别保存在runtime->buffer_size和runtime->period_size中。
要注意在每次设定的时候都有可能多次调用这些函数。
trigger函数
static int
snd_xxx_trigger(struct snd_pcm_substream *substream, int cmd);
当pcm开始,停止或暂停的时候都会调用这个函数。
具体执行那个操作主要是根据第二个参数,在中声明了SNDRV_PCM_TRIGGER_XXX。最少START和STOP的命令必须定义的。
switch(cmd){
case
SNDRV_PCM_TRIGGER_START:
//启动PCM引擎
break;
case
SNDRV_PCM_TRIGGER_STOP:
//停止PCM引擎
break;
default:
break;
}
当pcm支持暂停操作(在hareware表里面有这个),也必须处理PAUSE_PAUSE和PAUSE_RELEASE命令。前者是暂停命令,后者是重新播放命令。
假如pcm支持挂起/恢复操作,不管是全部或部分的挂起/恢复支持,都要处理SUSPEND和RESUME命令。这些命令主要是在电源状态改变的时候需要,通常它们和STOP,START命令放到一起。具体参看“电源管理”一章。
如前面提到的,这个操作上原子的。不要在调用这些函数的时候进入睡眠。而trigger函数要尽量小,甚至仅仅触发DMA。另外的工作如初始化hw_params和prepare应该在之前做好。
pointer函数
static
snd_pcm_uframes_t snd_xxx_pointer(struct snd_pcm_substream
*substream);
PCM中间层通过调用这个函数来获得缓冲区中的硬件位置。返回值需要以为frames为单位(在ALSA0.5.X是以字节为单位的),范围从0到buffer_size-1。
一般情况下,在中断程序中,调用snd_pcm_period_elapsed()的时候,在pcm中间层在在更新buffer的程序中调用它。然后,pcm中间层会更新指针位置和计算可用的空间,然后唤醒那些等待的线程。
这个函数也是原子的。
Copy和silence函数
这些函数不是必须的,同时在大部分的情况下是被忽略的。这些函数主要应用在硬件内存不在正常的内存空间的时候。一些声卡有一些没有被影射的自己的内存。在这种情况下,你必须把内存传到硬件的内存空间去。或者,缓冲区在物理内存和虚拟内存中都是不连续的时候就需要它们了。
假如定义了copy和silence,就可以做copy和set-silence的操作了。更详细的描述请参考“缓冲区和内存管理”一章。
Ack函数
这个函数也不是必须的。当在读写操作的时候更新appl_ptr的时候会调用它。一些类似于emu10k1-fx和cs46xx的驱动程序会为了内部缓存来跟踪当前的appl_ptr,这个函数仅仅对于这个情况才会被用到。
这个函数也是原子的。
page函数
这个函数也不是必须的。这个函数主要对那些不连续的缓存区
。mmap会调用这个函数得到内存页的地址。后续章节“缓冲区和内存管理”会有一些例子介绍。
中断处理
下面的pcm工作就是PCM中断处理了。声卡驱动中的PCM中断处理的作用主要是更新缓存的位置,然后在缓冲位置超过预先定义的period大小的时候通知PCM中间层。可以通过调用snd_pcm_period_elapsed()来通知。
声卡有如下几种产生中断。
period(周期)中断
这是一个很常见的类型:硬件会产生周期中断。每次中断都会调用snd_pcm_period_elapsed()。
snd_pcm_period_elapsed()的参数是substream的指针。因为,需要从chip实例中得到substream的指针。例如:在chip记录中定义一个substream字段来保持当前运行的substream指针,在open函数中要设定这个字段而在close函数中要复位这个字段。
假如在中断处理函数中获得了一个自旋锁,如果其他pcm也会调用这个锁,那你必须要在调用snd_pcm_period_elapsed()之前释放这个锁。
典型代码如下:
Example5-3.中断函数处理#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);
//如果需要的话,可以响应中断
}
....
spin_unlock(&chip->lock);
return
IRQ_HANDLED;
}
高频率时钟中断
当硬件不再产生一个period(周期)中断的时候,就需要一个固定周期的timer中断了(例如
es1968,ymfpci驱动)。这时候,在每次中断都要检查当前硬件位置,同时计算已经累积的采样的长度。当长度超过period长度时候,需要调用
snd_pcm_period_elapsed()同时复位计数值。
典型代码如下:
Example5-4.中断函数处理#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;
/*得到当前的硬件指针(帧为单位)*/
last_ptr
= get_hw_ptr(chip);
/*计算自从上次更新之后又处理的帧*/
if
(last_ptr 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计数器*/
chip->size
+= size;
/*超过period的边界?*/
if
(chip->size >= runtime->period_size){
/*重置size计数器*/
chip->size
%= runtime->period_size;
spin_unlock(&chip->lock);
snd_pcm_period_elapsed(substream);
spin_lock(&chip->lock);
}
//需要的话,要相应中断
}
....
spin_unlock(&chip->lock);
return
IRQ_HANDLED;
}
在调用snd_pcm_period_elapsed()的时候
就算超过一个period的时间已经过去,你也不需要多次调用snd_pcm_period_elapsed(),因为pcm层会自己检查当前的硬件指针和上次和更新的状态。
原子操作
在内核编程的时候,一个非常重要(又很难dubug)的问题就是竞争条件。Linux内核中,一般是通过自旋锁和信号量来解决的。通常来说,假如竞争发生在中断函数中,中断函数要具有原子性,你必须采用自旋锁来包含临界资源。假如不是发生在中断部分,同时比较耗时,可以采用信号量。
如我们看到的,pcm的操作函数一些是原子的而一些不是。例如:hw_params函数不是原子的,而trigger函数是原子的。这意味着,后者调用的时候,PCM中间层已经拥有了锁。
在这些函数中申请的自旋锁和信号量要做个计算。
在这些原子的函数中,不能那些可能调用任务切换和进入睡眠的函数。其中信号量和互斥体可能会进入睡眠,因此,在原子操作的函数中(如:trigger函数)不能调用它们。如果在这种函数中调用delay,可以用udelay(),或mdelay()。
约束
假如你的声卡支持不常用的采样率,或仅仅支持固定的采样率,就需要设定一个约束条件。
例如:为了把采样率限制在一些支持的几种之中,就需要用到函数snd_pcm_hw_constraint_list()。需要在open函数中调用它。
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
return
err;
....
}
有多种不同的约束。请参考sound/pcm.h中的完整的列表。甚至可以定义自己的约束条件。例如,假如my_chip可以管理一个单通道的子流,格式是S16_LE,另外,它还支持snd_pcm_hareware中设定的格式(或其他constraint_list)。可以设定一个:
Example5-6.为通道设定一个硬件规则
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);
/*初始化结构体*/
if
(c->min
fmt.bits[0]
&= SNDRV_PCM_FMTBIT_S16_LE;
return
snd_mask_refine(f, &fmt);
}
return 0;
}
之后,需要把上述函数加入到你的规则当中去:
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);
当应用程序设定声道数量的时候会调用上面的规则函数。但是应用程序可以在设定声道数之前设定格式。所以也需要设定对应的规则。
Example5-7.为通道设定一个硬件规则
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函数中:
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);
这里我们不会更详细的描述,我仍然想说“直接看源码吧”。
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u1/49088/showart_1006031.html |
|