注:这部分主要是tinyplay和tinycap涉及到
先把涉及到的数据结构列出:
struct pcm {
int fd;
unsigned int flags;
int running:1;
int prepared:1;
int underruns;
unsigned int buffer_size;
unsigned int boundary;
char error[PCM_ERROR_MAX];
struct pcm_config config;
struct snd_pcm_mmap_status *mmap_status;
struct snd_pcm_mmap_control *mmap_control;
struct snd_pcm_sync_ptr *sync_ptr;
void *mmap_buffer;
unsigned int noirq_frames_per_msec;
int wait_for_avail_min;
unsigned int subdevice;
struct pcm_ops *ops;
void *data;
void *snd_node;
};
/* Configuration for a stream */
struct pcm_config {
unsigned int channels;
unsigned int rate;
unsigned int period_size;
unsigned int period_count;
enum pcm_format format;
/* Values to use for the ALSA start, stop and silence thresholds, and
* silence size. Setting any one of these values to 0 will cause the
* default tinyalsa values to be used instead.
* Tinyalsa defaults are as follows.
*
* start_threshold : period_count * period_size
* stop_threshold : period_count * period_size
* silence_threshold : 0
* silence_size : 0
*/
unsigned int start_threshold;
unsigned int stop_threshold;
unsigned int silence_threshold;
unsigned int silence_size;
/* Minimum number of frames available before pcm_mmap_write() will actually
* write into the kernel buffer. Only used if the stream is opened in mmap mode
* (pcm_open() called with PCM_MMAP flag set). Use 0 for default.
*/
int avail_min;
};
/* pcm parameters */
enum pcm_param
{
/* mask parameters */
PCM_PARAM_ACCESS,
PCM_PARAM_FORMAT,
PCM_PARAM_SUBFORMAT,
/* interval parameters */
PCM_PARAM_SAMPLE_BITS,
PCM_PARAM_FRAME_BITS,
PCM_PARAM_CHANNELS,
PCM_PARAM_RATE,
PCM_PARAM_PERIOD_TIME,
PCM_PARAM_PERIOD_SIZE,
PCM_PARAM_PERIOD_BYTES,
PCM_PARAM_PERIODS,
PCM_PARAM_BUFFER_TIME,
PCM_PARAM_BUFFER_SIZE,
PCM_PARAM_BUFFER_BYTES,
PCM_PARAM_TICK_TIME,
};
struct pcm *pcm_open(unsigned int card, unsigned int device,
unsigned int flags, struct pcm_config *config)
{
struct pcm *pcm;
struct snd_pcm_info info;
struct snd_pcm_hw_params params;
struct snd_pcm_sw_params sparams;
int rc, pcm_type;
...
}
判断config存在 --- if (!config)
为pcm分配空间 --- calloc()
拿到pcm设备的节点 --- pcm->snd_node = snd_utils_get_dev_node()
拿到pcm类型 --- pcm_type = snd_utils_get_node_type()
打开设备 --- pcm->fd = pcm->ops->open(card, device, flags, &pcm->data, pcm->snd_node)
拿到相关信息 --- pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_INFO, &info)
初始化参数信息 --- param_init(¶ms)
设置参数 --- param_set_mask() param_set_int()
写入参数 --- pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)
读出设置好后的参数,拿到映射的内存地址 ---
/* get our refined hw_params */
config->period_size = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
config->period_count = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS);
pcm->buffer_size = config->period_count * config->period_size;
...
pcm->mmap_buffer = pcm->ops->mmap(pcm->data, NULL,
pcm_frames_to_bytes(pcm, pcm->buffer_size),
PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, 0);
根据config的值设置sparam以及pcm->config里的相关参数
写入参数 --- pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)
检测映射状态 --- rc = pcm_hw_mmap_status(pcm);
设置pcm状态并返回 --- pcm->underruns = 0; return pcm;
注:补充一下第3和4点,其实3里面涉及到了一个动态库"libsndcardparser.so",但是这个库一般默认不存在,它属于一个插件框架,所以3返回为NULL,这也导致了4返回SND_NODE_TYPE_HW,最后导致ops = &hw_ops;
下面再解析一下ops的相关内容
struct pcm_ops hw_ops = {
.open = pcm_hw_open,
.close = pcm_hw_close,
.ioctl = pcm_hw_ioctl,
.mmap = pcm_hw_mmap,
.munmap = pcm_hw_munmap,
.poll = pcm_hw_poll,
};
struct pcm_hw_data {
unsigned int card;
unsigned int device;
unsigned int fd;
void *snd_node;
};
来看看pcm_ops_open()
static int pcm_hw_open(unsigned int card, unsigned int device,
unsigned int flags, void **data,
__attribute__((unused)) void *node)
{
struct pcm_hw_data *hw_data;
char fn[256];
int fd;
...
}
为pcm_hw_data分配内存 --- calloc()
拼凑出节点名称并打开 --- snprintf(fn, ...) open(fn, ...)
设置该文件读写方式为阻塞方式 --- fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK)
为hw_data赋值之后返回
hw_data->snd_node = node;
hw_data->card = card;
hw_data->device = device;
hw_data->fd = fd;
*data = hw_data;
return fd;
pcm_hw_ioctl()则是通过调用ioctl()完成相关操作,不展开讨论
int pcm_write(struct pcm *pcm, const void *data, unsigned int count)
{
struct snd_xferi x;
...
}
struct snd_xferi {
snd_pcm_sframes_t result;
void __user *buf;
snd_pcm_uframes_t frames;
};
判断标志位 --- if (pcm->flags & PCM_IN)
为x赋值 ---
x.buf = (void*)data;
x.frames = count / (pcm->config.channels *
pcm_format_to_bits(pcm->config.format) / 8);
进入无限for循环,这里也就是写数据、判断是否成功及改变标志位 ---
for (;;) {
if (!pcm->running) {
int prepare_error = pcm_prepare(pcm);
if (prepare_error)
return prepare_error;
if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x))
return oops(pcm, errno, "cannot write initial data");
pcm->running = 1;
return 0;
}
if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) {
pcm->prepared = 0;
pcm->running = 0;
if (errno == EPIPE) {
/* we failed to make our window -- try to restart if we are
* allowed to do so. Otherwise, simply allow the EPIPE error to
* propagate up to the app level */
pcm->underruns++;
if (pcm->flags & PCM_NORESTART)
return -EPIPE;
continue;
}
return oops(pcm, errno, "cannot write stream data");
}
return 0;
}
pcm_read()和read_write()十分类似,直接列出pcm_read()里面的循环部分:
for (;;) {
if (!pcm->running) {
if (pcm_start(pcm) < 0) {
fprintf(stderr, "start error");
return -errno;
}
}
if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) {
pcm->prepared = 0;
pcm->running = 0;
if (errno == EPIPE) {
/* we failed to make our window -- try to restart */
pcm->underruns++;
continue;
}
return oops(pcm, errno, "cannot read stream data");
}
return 0;
}