USB-AUDIO初步分析

在这里插入图片描述

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

    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;
}

猜你喜欢

转载自blog.csdn.net/zhuyong006/article/details/85701332