1.USB-AUDIO的声卡注册过程
usb_probe_interface ---- driver.c
usb_audio_probe ---- card.c
snd_usb_create_stream ---- card.c
snd_usb_parse_audio_interface ---- stream.c
snd_usb_add_audio_stream ---- stream.c
snd_pcm_new ---- pcm.c
_snd_pcm_new ---- pcm.c
static struct snd_device_ops ops = {
.dev_free = snd_pcm_dev_free,
.dev_register = snd_pcm_dev_register,
.dev_disconnect = snd_pcm_dev_disconnect,
};
snd_pcm_new_stream(...,SNDRV_PCM_STREAM_PLAYBACK,...) ---- pcm.c
snd_pcm_new_stream(...,SNDRV_PCM_STREAM_CAPTURE,...) ---- pcm.c
snd_device_new ---- pcm.c
struct snd_pcm {
//是分别对应的2条音频流,一条是playback,一条是capture
struct snd_pcm_str streams[2];
};
_snd_pcm_new中有个函数需要特别关注如下:
snd_pcm_dev_register
...
for (cidx = 0; cidx < 2; cidx++) {
...
switch (cidx) {
case SNDRV_PCM_STREAM_PLAYBACK:
sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);
devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
break;
case SNDRV_PCM_STREAM_CAPTURE:
sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);
devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
break;
}
...
snd_register_device_for_dev(devtype, pcm->card,
pcm->device,
&snd_pcm_f_ops[cidx],
pcm, str, dev)
...
如上是注册pcm设备的地方,同时我们需要关注pcm设备的ops,如下:
const struct file_operations snd_pcm_f_ops[2] = {
{
.owner = THIS_MODULE,
.write = snd_pcm_write,
.aio_write = snd_pcm_aio_write,
.open = snd_pcm_playback_open,
.release = snd_pcm_release,
.llseek = no_llseek,
.poll = snd_pcm_playback_poll,
.unlocked_ioctl = snd_pcm_playback_ioctl,
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = snd_pcm_get_unmapped_area,
},
{
.owner = THIS_MODULE,
.read = snd_pcm_read,
.aio_read = snd_pcm_aio_read,
.open = snd_pcm_capture_open,
.release = snd_pcm_release,
.llseek = no_llseek,
.poll = snd_pcm_capture_poll,
.unlocked_ioctl = snd_pcm_capture_ioctl,
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = snd_pcm_get_unmapped_area,
}
};
之后pcm的录音和播放都会通过这个ops完成
2.使用tinycap的录音过程
external\tinyalsa\tinycap.c
int main(){
//先预先留出wav的头部
fseek(file, sizeof(struct wav_header), SEEK_SET);
frames = capture_sample(file, card, device, header.num_channels,
header.sample_rate, format,
period_size, period_count);
//写入头部
fseek(file, 0, SEEK_SET);
fwrite(&header, sizeof(struct wav_header), 1, file);
}
一帧音频数据大小 = 通道数 * 采样深度
Period size:周期,每次硬件中断处理音频数据的帧数,对于音频设备的数据读写,以此为单位。
继续来看capture_sample
capture_sample
打开声卡的音频设备"/dev/snd/pcmC1D0c","/dev/snd/pcmC1D0p",其中"c"对应capture,"p"对应的是play
pcm_open(card, device, PCM_IN, &config);
通过样本大小(4个硬件周期,每个周期处理1024帧数据)获取总字节数
size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));
为这次样本的采集准备空间
buffer = malloc(size);
通过pcm_read从内核中获取音频数据到buffer中,随后将这个buffer的内容写入到文件file中,持续的读取声卡中的音频,直到结束录音
while (capturing && !pcm_read(pcm, buffer, size)) {
if (fwrite(buffer, 1, size, file) != size) {
fprintf(stderr,"Error capturing sample\n");
break;
}
bytes_read += size;
}
录音结束,释放buffer,关闭pcm设备,返回当前读取到的音频帧数
free(buffer);
pcm_close(pcm);
return pcm_bytes_to_frames(pcm, bytes_read);
随后进入到alsa的音频库(libtinyalsa.so)中
external\tinyalsa\pcm.c
int pcm_read(struct pcm *pcm, void *data, unsigned int count)
{
struct snd_xferi x;
int len = 10;
if (!(pcm->flags & PCM_IN))
return -EINVAL;
//x.buf指向的是内核返回的音频Buf数据
x.buf = data;
//x.frames 对应的是要收集满count个字节的音频数据,对应的声卡pcm设备需要采集的帧数
x.frames = count / (pcm->config.channels *
pcm_format_to_bits(pcm->config.format) / 8);
for (;;) {
if (!pcm->running) {
if (pcm_start(pcm) < 0) {
fprintf(stderr, "start error");
return -errno;
}
}
通过ioctl调度到到内核完成
if (ioctl(pcm->fd, 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;
}
}
3.tinycap的录音过程进入内核
snd_pcm_capture_ioctl
snd_pcm_capture_ioctl1
snd_pcm_lib_read
snd_pcm_lib_read1(substream, (unsigned long)buf, size, nonblock, snd_pcm_lib_read_transfer)
ok,我们看看snd_pcm_lib_read1这个函数
snd_pcm_lib_read1
....
//这里实现的是一个等待队列,等待usb收集到足够的音频数据后会返回avail的值
wait_for_avail(substream, &avail);
//调用transfer的函数指针callback到userspace
transfer(substream, appl_ofs, data, offset, frames);
USB采集音频数据的大致流程如下:
最后回到transfer的函数指针实现
static int snd_pcm_lib_read_transfer(struct snd_pcm_substream *substream,
unsigned int hwoff,
unsigned long data, unsigned int off,
snd_pcm_uframes_t frames)
{
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
char __user *buf = (char __user *) data + frames_to_bytes(runtime, off);
if (substream->ops->copy) {
if ((err = substream->ops->copy(substream, -1, hwoff, buf, frames)) < 0)
return err;
} else {
//走这儿,很简单,就是将dma采集到的buf传递到用户空间,完事
char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff);
if (copy_to_user(buf, hwbuf, frames_to_bytes(runtime, frames)))
return -EFAULT;
}
return 0;
}