5.6 snd_pcm_runtime
struct snd_pcm_runtime {
/* -- Status -- */
struct snd_pcm_substream *trigger_master;
struct timespec64 trigger_tstamp; /* trigger timestamp */
bool trigger_tstamp_latched; /* trigger timestamp latched in low-level driver/hardware */
int overrange;
snd_pcm_uframes_t avail_max;
snd_pcm_uframes_t hw_ptr_base; /* Position at buffer restart */
snd_pcm_uframes_t hw_ptr_interrupt; /* Position at interrupt time */
unsigned long hw_ptr_jiffies; /* Time when hw_ptr is updated */
unsigned long hw_ptr_buffer_jiffies; /* buffer time in jiffies */
snd_pcm_sframes_t delay; /* extra delay; typically FIFO size */
u64 hw_ptr_wrap; /* offset for hw_ptr due to boundary wrap-around */
/* -- HW params -- */
snd_pcm_access_t access; /* access mode */
snd_pcm_format_t format; /* SNDRV_PCM_FORMAT_* */
snd_pcm_subformat_t subformat; /* subformat */
unsigned int rate; /* rate in Hz */
unsigned int channels; /* channels */
snd_pcm_uframes_t period_size; /* period size */
unsigned int periods; /* periods */
snd_pcm_uframes_t buffer_size; /* buffer size */
snd_pcm_uframes_t min_align; /* Min alignment for the format */
size_t byte_align;
unsigned int frame_bits;
unsigned int sample_bits;
unsigned int info;
unsigned int rate_num;
unsigned int rate_den;
unsigned int no_period_wakeup: 1;
/* -- SW params -- */
int tstamp_mode; /* mmap timestamp is updated */
unsigned int period_step;
snd_pcm_uframes_t start_threshold;
snd_pcm_uframes_t stop_threshold;
snd_pcm_uframes_t silence_threshold; /* Silence filling happens when
noise is nearest than this */
snd_pcm_uframes_t silence_size; /* Silence filling size */
snd_pcm_uframes_t boundary; /* pointers wrap point */
snd_pcm_uframes_t silence_start; /* starting pointer to silence area */
snd_pcm_uframes_t silence_filled; /* size filled with silence */
union snd_pcm_sync_id sync; /* hardware synchronization ID */
/* -- mmap -- */
struct snd_pcm_mmap_status *status;
struct snd_pcm_mmap_control *control;
/* -- locking / scheduling -- */
snd_pcm_uframes_t twake; /* do transfer (!poll) wakeup if non-zero */
wait_queue_head_t sleep; /* poll sleep */
wait_queue_head_t tsleep; /* transfer sleep */
struct fasync_struct *fasync;
bool stop_operating; /* sync_stop will be called */
struct mutex buffer_mutex; /* protect for buffer changes */
/* -- private section -- */
void *private_data;
void (*private_free)(struct snd_pcm_runtime *runtime);
/* -- hardware description -- */
struct snd_pcm_hardware hw;
struct snd_pcm_hw_constraints hw_constraints;
/* -- timer -- */
unsigned int timer_resolution; /* timer resolution */
int tstamp_type; /* timestamp type */
/* -- DMA -- */
unsigned char *dma_area; /* DMA area */
dma_addr_t dma_addr; /* physical bus address (not accessible from main CPU) */
size_t dma_bytes; /* size of DMA area */
struct snd_dma_buffer *dma_buffer_p; /* allocated buffer */
unsigned int buffer_changed:1; /* buffer allocation changed; set only in managed mode */
/* -- audio timestamp config -- */
struct snd_pcm_audio_tstamp_config audio_tstamp_config;
struct snd_pcm_audio_tstamp_report audio_tstamp_report;
struct timespec64 driver_tstamp;
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
/* -- OSS things -- */
struct snd_pcm_oss_runtime oss;
#endif
};
名字 | 类型 | 作用 | 举例 |
trigger_master | snd_pcm_substream | 指向所属的substream对象 | |
trigger_tstamp | timespec64 | 当音频开始,暂停,停止的时候都会记录时间 | |
trigger_tstamp_latched | bool | 是否要从底层的硬件得到时间 | |
overrange | int | 录制过程中,ADC转换超出范围的次数 | 好像没几个硬件支持该功能 |
avail_max | snd_pcm_uframes_t | 记录avail的最大值 | 返回给用户空间的状态有该值,不是太明白有什么用 |
hw_ptr_base | snd_pcm_uframes_t | hw_ptr的基地址 | 介绍snd_pcm_update_hw_ptr0的时候详细介绍 |
hw_ptr_interrupt | snd_pcm_uframes_t | 发生中断的时候,记录的hw_ptr | 介绍snd_pcm_update_hw_ptr0的时候详细介绍 |
hw_ptr_jiffies | unsigned long | 记录上次中断发生时的时钟计数 | 介绍snd_pcm_update_hw_ptr0的时候详细介绍 |
hw_ptr_buffer_jiffies | unsigned long | DMA缓冲中的数据,传送一轮需要的时钟计数 | 介绍snd_pcm_update_hw_ptr0的时候会有介绍 |
delay | snd_pcm_sframes_t | 也是用于更新hw_ptr的时候使用,允许一定的偏差 | 介绍snd_pcm_update_hw_ptr0的时候会有介绍 |
hw_ptr_wrap | u64 | 超过boundary,也继续累加 | 用于时间统计,事实上没什么用 |
access | snd_pcm_access_t | 访问方式,比如直接读写还是使用mmap | SNDRV_PCM_ACCESS_MMAP_INTERLEAVED SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED SNDRV_PCM_ACCESS_MMAP_COMPLEX SNDRV_PCM_ACCESS_RW_INTERLEAVED SNDRV_PCM_ACCESS_RW_NONINTERLEAVED |
format | snd_pcm_format_t | pcm数据格式 | SNDRV_PCM_FORMAT_S16_LE SNDRV_PCM_FORMAT_S16_BE ... |
subformat | snd_pcm_subformat_t | pcm数据子格式,这个参数现在没什么用。 | SNDRV_PCM_SUBFORMAT_STD 只有这个变量,值是0。 |
rate | unsigned int | 数据采样率 | 48KHZ,16KHZ |
channels | unsigned int | 声道数 | |
period_size | snd_pcm_uframes_t | 一个周期包含的字节数 | 也就是每次中断发生前,写到内存中的字节数 |
periods | unsigned int | dma内存可以存放几个周期的数据 | 最少是2个,这样才能保证一个周期数据写完后,另一个周期可以马上开始。 |
buffer_size | snd_pcm_uframes_t | dma buffer大小 | period_size * periods 的值 |
min_align | snd_pcm_uframes_t | 一次最少读几帧数据 | 正常都是1帧。看代码的意思,是为了处理pcm数据不按字节占位的情形,比如每个数据占7字节,就需要读2帧数据。 |
byte_align | size_t | 字节对齐 | |
frame_bits | unsigned int | 一帧数据占用的bit数 | sample_bits * channels |
sample_bits | unsigned int | 一个数据占用的bit数 | |
info | unsigned int | 硬件信息 | SNDRV_PCM_INFO_MMAP SNDRV_PCM_INFO_MMAP_VALID .... |
rate_den | unsigned int | 倍率分母 | 不是1的时候,会放慢播放节奏。 |
rate_num | unsigned int | 倍率分子 | 不是1的时候,加快播放节奏 |
no_period_wakeup: 1 | unsigned int | 是否支持传输中断 | |
tstamp_mode | int | 是否允许记录时间 | enum { SNDRV_PCM_TSTAMP_NONE = 0, SNDRV_PCM_TSTAMP_ENABLE, SNDRV_PCM_TSTAMP_LAST = SNDRV_PCM_TSTAMP_ENABLE, }; |
period_step | unsigned int | 默认设置为1 | 没看到有什么具体用处 |
start_threshold | snd_pcm_uframes_t | 开始播放的阈值 | 用来控制写满几个peroid,才去播放,防止破音的出现 |
stop_threshold | snd_pcm_uframes_t | 结束播放的阈值 | 与start_threshold类似 |
silence_threshold | snd_pcm_uframes_t | 静音的阈值 | |
silence_size | snd_pcm_uframes_t | 可以设置一段时间静音 | |
boundary | snd_pcm_uframes_t | 介绍过了,hw_ptr可以达到的最大值 | |
silence_start | snd_pcm_uframes_t | 记录静音开始 | |
silence_filled | snd_pcm_uframes_t | 记录静音结束 | |
sync | union snd_pcm_sync_id | 设置硬件的同步ID | 没明白有什么用 |
status | struct snd_pcm_mmap_status * | 当前的状态信息 | |
control | struct snd_pcm_mmap_control * | 控制信息 | |
twake | snd_pcm_uframes_t | 读写的时候,有多少可用period的时候,唤醒等待线程 | 一般都设置为1 |
sleep | wait_queue_head_t | poll 时候用到的等待变量 | |
tsleep | wait_queue_head_t | 读写操作时候用到的等待变量 | |
fasync | struct fasync_struct * | 针对异步通知 | |
stop_operating | bool | 记录是否停止播放 | |
buffer_mutex | struct mutex | 针对内存操作用到的mutex对象 | |
private_data | void * | 驱动可以保存一些自己的数据 | HDA中: runtime->private_data = azx_dev; |
(*private_free)(struct snd_pcm_runtime *runtime) | void | 用于清除private_data的函数指针 | |
hw | struct snd_pcm_hardware | 保存硬件信息 | |
hw_constraints | struct snd_pcm_hw_constraints | 硬件参数约束条件 | |
timer_resolution | unsigned int | 没太明白 | |
tstamp_type | int | 时间类型 | enum { SNDRV_PCM_TSTAMP_TYPE_GETTIMEOFDAY = 0, /* gettimeofday equivalent */ SNDRV_PCM_TSTAMP_TYPE_MONOTONIC, /* posix_clock_monotonic equivalent */ SNDRV_PCM_TSTAMP_TYPE_MONOTONIC_RAW, /* monotonic_raw (no NTP) */ SNDRV_PCM_TSTAMP_TYPE_LAST = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC_RAW, }; |
dma_area | unsigned char * | dma 缓冲的地址 | |
dma_addr | dma_addr_t | dma缓冲的物理地址 | |
dma_bytes | size_t | dma 缓冲的长度 | |
dma_buffer_p | struct snd_dma_buffer * | 预先分配的dma缓冲 | |
buffer_changed:1 | unsigned int | 标记dma缓冲是否发生变化 | 一般驱动都不关注它。有些可能是驱动自己生成的缓冲区,不希望被改变。 |
audio_tstamp_config | struct snd_pcm_audio_tstamp_config | 用于调试 | https://www.kernel.org/doc/html/latest/sound/designs/timestamping.html |
audio_tstamp_report | struct snd_pcm_audio_tstamp_report | 用于调试 | https://www.kernel.org/doc/html/latest/sound/designs/timestamping.html |
driver_tstamp | struct timespec64 | 用于调试 | https://www.kernel.org/doc/html/latest/sound/designs/timestamping.html |
• stauts 和 control被设置到mmap内存中,目的是可以和用户空间共享信息。讲述文件读写的时候详细展开其中的内容。
struct __snd_pcm_mmap_status {
snd_pcm_state_t state; /* RO: state - SNDRV_PCM_STATE_XXXX */
int pad1; /* Needed for 64 bit alignment */
snd_pcm_uframes_t hw_ptr; /* RO: hw ptr (0...boundary-1) */
struct __snd_timespec tstamp; /* Timestamp */
snd_pcm_state_t suspended_state; /* RO: suspended stream state */
struct __snd_timespec audio_tstamp; /* from sample counter or wall clock */
};
struct __snd_pcm_mmap_control {
snd_pcm_uframes_t appl_ptr; /* RW: appl ptr (0...boundary-1) */
snd_pcm_uframes_t avail_min; /* RW: min available frames for wakeup */
};
• 硬件信息
struct snd_pcm_hardware {
unsigned int info; /* SNDRV_PCM_INFO_* */
u64 formats; /* SNDRV_PCM_FMTBIT_* */
unsigned int rates; /* SNDRV_PCM_RATE_* */
unsigned int rate_min; /* min rate */
unsigned int rate_max; /* max rate */
unsigned int channels_min; /* min channels */
unsigned int channels_max; /* max channels */
size_t buffer_bytes_max; /* max buffer size */
size_t period_bytes_min; /* min period size */
size_t period_bytes_max; /* max period size */
unsigned int periods_min; /* min # of periods */
unsigned int periods_max; /* max # of periods */
size_t fifo_size; /* fifo size in bytes */
};
根据hda_controler.c中的代码来理解它们的含义:
static const struct snd_pcm_hardware azx_pcm_hw = {
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID |
/* No full-resume yet implemented */
/* SNDRV_PCM_INFO_RESUME |*/
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_SYNC_START |
SNDRV_PCM_INFO_HAS_WALL_CLOCK | /* legacy */
SNDRV_PCM_INFO_HAS_LINK_ATIME |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_48000,
.rate_min = 48000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = AZX_MAX_BUF_SIZE,
.period_bytes_min = 128,
.period_bytes_max = AZX_MAX_BUF_SIZE / 2,
.periods_min = 2,
.periods_max = AZX_MAX_FRAG,
.fifo_size = 0,
};
这里包含硬件的一些基本信息:比如支不支持mmap,数据格式,支持的数据采样率等等。
struct snd_pcm_hw_constraints {
struct snd_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK -
SNDRV_PCM_HW_PARAM_FIRST_MASK + 1];
struct snd_interval intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL -
SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1];
unsigned int rules_num;
unsigned int rules_all;
struct snd_pcm_hw_rule *rules;
};
这里也是硬件的基本信息。逐个看以下它们的具体作用。
1. snd_mask
#define SNDRV_MASK_MAX 256
struct snd_mask {
__u32 bits[(SNDRV_MASK_MAX+31)/32];
};
其实snd_mask的定义是 unsigned int bits[8],也就是8位无符号整数,可以代表256位。每位可以用来表示硬件是否支持某项功能,比如:
#define SNDRV_PCM_ACCESS_MMAP_INTERLEAVED ((__force snd_pcm_access_t) 0) /* interleaved mmap */
#define SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED ((__force snd_pcm_access_t) 1) /* noninterleaved mmap */
#define SNDRV_PCM_ACCESS_MMAP_COMPLEX ((__force snd_pcm_access_t) 2) /* complex mmap */
#define SNDRV_PCM_ACCESS_RW_INTERLEAVED ((__force snd_pcm_access_t) 3) /* readi/writei */
#define SNDRV_PCM_ACCESS_RW_NONINTERLEAVED ((__force snd_pcm_access_t) 4) /* readn/writen */
#define SNDRV_PCM_ACCESS_LAST SNDRV_PCM_ACCESS_RW_NONINTERLEAVED
在支持交错的mmap操作的时候,bits中的第1比特将被设置成0。
masks的定义如下:
struct snd_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK -
SNDRV_PCM_HW_PARAM_FIRST_MASK + 1];
实际上大小是3:
#define SNDRV_PCM_HW_PARAM_ACCESS 0 /* Access type */
#define SNDRV_PCM_HW_PARAM_FORMAT 1 /* Format */
#define SNDRV_PCM_HW_PARAM_SUBFORMAT 2 /* Subformat */
其实masks[0]代表访问类型,其实就是上面看到的mmap相关操作的内容,256bit其实只用了几位。mmap相关的内容,读写文件的时候会去展开。
masks[1]代表的是支持的数据格式,其实它就是snd_pcm_hardware中formats的值,会被支持拷贝过来。
masks[2]只用了一位,SNDRV_PCM_SUBFORMAT_STD。一直被设置成1,没看到有什么具体作用。
2. snd_interval
struct snd_interval {
unsigned int min, max;
unsigned int openmin:1,
openmax:1,
integer:1,
empty:1;
};
用来表示一个区间,最大值最小值,已经开区间闭区间。
snd_pcm_hw_constraints 中snd_interval 也是被定义成了数组,它支持的属性也是固定的,其实就是下面这些内容:
#define SNDRV_PCM_HW_PARAM_SAMPLE_BITS 8 /* Bits per sample */
#define SNDRV_PCM_HW_PARAM_FRAME_BITS 9 /* Bits per frame */
#define SNDRV_PCM_HW_PARAM_CHANNELS 10 /* Channels */
#define SNDRV_PCM_HW_PARAM_RATE 11 /* Approx rate */
#define SNDRV_PCM_HW_PARAM_PERIOD_TIME 12 /* Approx distance between
* interrupts in us
*/
#define SNDRV_PCM_HW_PARAM_PERIOD_SIZE 13 /* Approx frames between
* interrupts
*/
#define SNDRV_PCM_HW_PARAM_PERIOD_BYTES 14 /* Approx bytes between
* interrupts
*/
#define SNDRV_PCM_HW_PARAM_PERIODS 15 /* Approx interrupts per
* buffer
*/
#define SNDRV_PCM_HW_PARAM_BUFFER_TIME 16 /* Approx duration of buffer
* in us
*/
#define SNDRV_PCM_HW_PARAM_BUFFER_SIZE 17 /* Size of buffer in frames */
#define SNDRV_PCM_HW_PARAM_BUFFER_BYTES 18 /* Size of buffer in bytes */
#define SNDRV_PCM_HW_PARAM_TICK_TIME 19 /* Approx tick duration in us */
那一个简单的做例子,一个硬件可以支持单声道,双声道,设置八声道。这里就有了最小值和最大值,所以用snd_interval来表示。
3. snd_pcm_hw_rule
struct snd_pcm_hw_rule {
unsigned int cond;
int var;
int deps[5];
snd_pcm_hw_rule_func_t func;
void *private;
};
前面看到可以给硬件设置占位标记或者区间值,这些值本身之间是有联系的。举个简单的例子:
SNDRV_PCM_HW_PARAM_CHANNELS 表示硬件可以支持的声道数;
SNDRV_PCM_HW_PARAM_FRAME_BITS表示一帧数据可以支持的bit数;
SNDRV_PCM_HW_PARAM_SAMPLE_BITS表示一个采样数据占用的bit数。
它们之间有一个关系:
声道数 * 采样bit数 = 帧bit数。
当帧的bit数或者采样数据的bit数发生变化时,可支持的声道数就会发生变化。它们之中的任何一个改变,就需要重新计算声道数。
int snd_pcm_hw_rule_add(struct snd_pcm_runtime *runtime, unsigned int cond,
int var,
snd_pcm_hw_rule_func_t func, void *private,
int dep, ...)
看一下snd_pcm_hw_rule_add的函数声明:runtime是rule要添加到的对象;cond代表条件,什么条件下执行,一般都是0,也就是不需要检测条件;var代表要被改变的参数;func是rule使用时,需要调用的函数;dep可以有4个,代表什么参数改变时会调用该rule。
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
snd_pcm_hw_rule_div, NULL,
SNDRV_PCM_HW_PARAM_FRAME_BITS, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
这个例子中,SNDRV_PCM_HW_PARAM_FRAME_BITS
或者SNDRV_PCM_HW_PARAM_SAMPLE_BITS改变时,需要调用该规则,而改变的值是SNDRV_PCM_HW_PARAM_CHANNELS。