Android USB AUDIO初步分析

本文详细解析了USB-AUDIO声卡在Linux内核中的注册过程,包括usb_probe_interface、snd_usb_create_stream等关键函数的作用。同时,深入介绍了tinycap应用的录音流程,从打开声卡设备到通过ioctl进行内核调度,直至音频数据的最终采集。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

å¨è¿éæå¥å¾çæè¿°

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

 

### 回答1: UVCCamera 是一款非常实用的摄像头驱动程序和API。它是为 Android 设备设计的,可以用于访问和控制 USB 摄像头。 使用 UVCCamera 的第一步是在 Android 设备上下载并安装相应的驱动程序。然后,将摄像头连接到设备上的 USB 端口。一旦连接成功,我们就可以开始使用 UVCCamera 进行录像和拍照。 UVCCamera 具有丰富的功能。它支持多个摄像头同时连接,并可以实时预览摄像头的画面。此外,我们可以对摄像头的参数进行调整,如亮度、对比度、饱和度等,以获得更好的图像效果。 使用 UVCCamera 进行录像非常简单。首先,我们需要创建一个用于保存录像的文件。然后,通过调用 startRecording() 方法,即可开始录制。录像过程中,我们可以实时预览录像的画面,并可以随时停止录制。 除了录制,UVCCamera 还支持拍照功能。我们可以通过调用 takePicture() 方法,即可拍摄一张照片,并保存到指定的文件中。 在使用 UVCCamera 进行开发时,我们可以通过编写自定义的回调函数,来处理摄像头的各种事件。例如,我们可以监听摄像头的连接和断开事件,并在事件发生时进行相应的处理。 总之,UVCCamera 是一款功能强大且易于使用的摄像头驱动程序和API。无论是进行录像还是拍照,都可以通过简单的调用实现。它为 Android 设备提供了更多的摄像头功能,并为我们的应用程序提供了更多的可能性。 ### 回答2: UVCCamera是一款Android平台上常用的摄像头操作库,它提供了方便、灵活的摄像头连接和使用功能。下面是一个简要的教程介绍: 1. 导入依赖:首先,在你的Android项目中的build.gradle文件中添加UVCCamera的依赖项。 2. 设置权限:在AndroidManifest.xml文件中添加以下权限: <uses-feature android:name="android.hardware.usb.host" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> 3. 初始化CameraHelper:在你的Activity类中创建一个CameraHelper对象来管理摄像头连接和调用功能: mCameraHelper = new CameraHelper(this); 4. 连接摄像头:使用CameraHelper的openCamera方法来连接摄像头: mCameraHelper.openCamera(new CameraHelper.OnCameraConnectListener() { @Override public void onCameraConnectError(Exception e) { // 连接错误处理 } @Override public void onCameraConnected() { // 摄像头连接成功处理 } @Override public void onCameraDisconnect() { // 摄像头断开连接处理 } }); 5. 预览画面:当摄像头连接成功后,可以使用CameraHelper的setPreviewTexture方法来设置预览画面的显示。你可以选择SurfaceView、TextureView或者自定义的View来显示预览画面。 6. 拍照或录制视频:通过CameraHelper的captureStill()方法来拍照,通过CameraHelper的startRecording()和stopRecording()方法来录制视频。 7. 释放资源:在不再使用摄像头时,要记得释放资源,可以在Activity的onDestroy()方法中调用CameraHelper的release()方法来释放资源。 除了上述基本功能,UVCCamera还提供了一些其他的功能和设置选项,比如设置分辨率、曝光、对焦、闪光灯等。 希望通过这个简要的教程,你能够对UVCCamera的基本使用有一个初步的了解。如果你有更多的需求和问题,可以查阅官方文档或者在相关的开发社区进行进一步的学习和讨论。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值