usb声卡驱动(五):声卡驱动的开始

这篇博客详细介绍了USB声卡驱动的创建过程,从源码分析开始,讲解了如何在内核中找到USB驱动入口,并跟随probe回调深入到snd_usb_audio_probe函数。这个函数主要负责创建snd_usb_audio对象,接着调用snd_usb_create_streams创建PCM流。文章还提到了描述符解析、接口匹配和组件创建,最终将声卡对象注册到ALSA系统中,使得外部可以访问USB声卡设备。
摘要由CSDN通过智能技术生成

usb声卡驱动(五)

我手上,刚好有高通msm8996的android 8.1源码。那么就直接进入它的kernel分析。

usb声卡驱动的源码位于sound/usb下面。查看对应的Makefile文件。可知相应的源码c文件。总共就那么几个文件,慢慢看来。

现在将部分makefile文件摘录如下:

snd-usb-audio-objs := 	card.o \
			clock.o \
			endpoint.o \
			format.o \
			helper.o \
			mixer.o \
			mixer_quirks.o \
			pcm.o \
			proc.o \
			quirks.o \
			stream.o

snd-usbmidi-lib-objs := midi.o

# Toplevel Module Dependency
# obj-$(CONFIG_SND_USB_AUDIO) += snd-usb-audio.o snd-usbmidi-lib.o
# separate midi and audio
obj-$(CONFIG_SND_USB_AUDIO) += snd-usb-audio.o
obj-$(CONFIG_SND_USB_MIDI) += snd-usbmidi-lib.o

从经验上面来讲,这个声卡驱动一定得先是USB驱动,因此定位USB驱动的地方,全局搜素usb_driver.可以在card.c中找到

如下:


static struct usb_device_id usb_audio_ids [] = {
#include "quirks-table.h"
    { .match_flags = (USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS),
      .bInterfaceClass = USB_CLASS_AUDIO,
      .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL },
    { }						/* Terminating entry */
};
/*
 * entry point for linux usb interface
 */

static struct usb_driver usb_audio_driver = {
	.name =		"snd-usb-audio",
	.probe =	usb_audio_probe,
	.disconnect =	usb_audio_disconnect,
	.suspend =	usb_audio_suspend,
	.resume =	usb_audio_resume,
	.reset_resume =	usb_audio_reset_resume,
	.id_table =	usb_audio_ids,
	.supports_autosuspend = 1,
};

module_usb_driver(usb_audio_driver);

因此,当一个USB设备是Audio设备,且该设备含有控制接口(见id_table)时,就会调用probe回调。

可得,usb_audio_probe是整个驱动的起始点。从上一篇笔记中,可知,要创建audio驱动,需要分别创建对应的card对象,和component对象。再再从上上一篇笔记可知,还需要解析对应的usb audio的各种描述符。

整个usb 声卡驱动,使用了struct snd_usb_audio来代替上一篇我们提及的chip对象。如下:

struct snd_usb_audio {
	int index;
	struct usb_device *dev;
	struct snd_card *card;
	struct usb_interface *pm_intf;
	u32 usb_id;
	struct mutex mutex;
	struct rw_semaphore shutdown_rwsem;
	unsigned int shutdown:1;
	unsigned int probing:1;
	unsigned int in_pm:1;
	unsigned int autosuspended:1;	
	unsigned int txfr_quirk:1; /* Subframe boundaries on transfers */
	
	int num_interfaces;
	int num_suspended_intf;

	struct list_head pcm_list;	/* list of pcm streams */
	struct list_head ep_list;	/* list of audio-related endpoints */
	int pcm_devs;

	struct list_head midi_list;	/* list of midi interfaces */

	struct list_head mixer_list;	/* list of mixer interfaces */

	int setup;			/* from the 'device_setup' module param */
	bool autoclock;			/* from the 'autoclock' module param */

	struct usb_host_interface *ctrl_intf;	/* the audio control interface */
};
  1. index:驱动内部使用一个数组来保存所有的chip对象,index是这个数组的索引
  2. dev:usb_device
  3. card:这就是真正的card对象
  4. pm_intf:当前接口
  5. usb_id:usb 设备id
  6. mutex,shutdown_rwsem:各种锁
  7. shutdown:处于shutdown状态
  8. probing:处于probing状态
  9. in_pm:从suspend状态到resume状态
  10. autosuspended:自动suspend状态
  11. txfr_quirk:不懂这个的用法
  12. num_interfaces:使用这个驱动的usb接口数,如果为0,则会调用snd_card_free_when_closed释放card对象
  13. num_suspended_intf:进入suspend状态的次数
  14. pcm_list:pcm这个componet的链表,因为一个声卡,可能有多个pcm对象,将多个pcm对象链接在一起,放入此处
  15. ep_list:audio相关的端点的链表,可见后面的端点操作
  16. midi_list:midi这个component的链表,同pcm_list类似。
  17. mixer_list:mixer链表,同pcm_list
  18. setup:不懂
  19. autoclock:不懂
  20. ctrl_intf:audio control对应的usb接口

snd_usb_audio对象的创建

snd_usb_audio对象的创建通过

static struct snd_usb_audio *
snd_usb_audio_probe(struct usb_device *dev,
		    struct usb_interface *intf,
		    const struct usb_device_id *usb_id);

在前面的笔记中,提到,Audio Control ,Audio Streaming都分别对应USB的Interface而这个驱动的匹配条件为Audio Control.因此,每检查到一个Audio Control这个snd_usb_audio_probe会被调用一次,所以需要注意,不要过多的创建card对象。

static struct snd_usb_audio *
snd_usb_audio_probe(struct usb_device *dev,
		    struct usb_interface *intf,
		    const struct usb_device_id *usb_id)
{
	//....
	//首先选取可选设置0.注意注意,在某些设备上,可选设置0,表示的是一种0带宽的设置
	alts = &intf->altsetting[0];
	ifnum = get_iface_desc(alts)->bInterfaceNumber;
	id = USB_ID(le16_to_cpu(dev->descriptor.idVendor),
		    le16_to_cpu(dev->descriptor.idProduct));
	
	//接下来就是读取配置,然后创建alsa世界需要的对象

	/* check whether it's already registered */
	chip = NULL;
	mutex_lock(&register_mutex);
	//防止多个Audio Control情况下重复创建chip对象
	for (i = 0; i < SNDRV_CARDS; i++) {
		if (usb_chip[i] && usb_chip[i]->dev == dev) {
			if (usb_chip[i]->shutdown) {
				dev_err(&dev->dev, "USB device is in the shutdown state, cannot create a card instance\n");
				goto __error;
			}
			chip = usb_chip[i];
			chip->probing = 1;
			break;
		}
	}
	if (! chip) {
		/* it's a fresh one.
		 * now look for an empty slot and create a new card instance
		 */
		 //下面的逻辑,创建一个新的chip对象,创建对象的函数,为snd_usb_audio_create函数
		for (i = 0; i < SNDRV_CARDS; i++)
			if (enable[i] && ! usb_chip[i] &&
			    (vid[i] == -1 || vid[i] == USB_ID_VENDOR(id)) &&
			    (pid[i] == -1 || pid[i] == USB_ID_PRODUCT(id))) {
				if (snd_usb_audio_create(intf, dev, i, quirk,
							 &chip) < 0) {
					goto __error;
				}
				chip->pm_intf = intf;
				break;
			}
		if (!chip) {
			dev_err(&dev->dev, "no available usb audio device\n");
			goto __error;
		}
	}
	//...

	//下面的代码,负责调用snd_usb_create_streams创建audio stream,它包括midi stream
	//调用snd_usb_create_mixer,创建mixer
	if (err > 0) {
		/* create normal USB audio interfaces */
		if (snd_usb_create_streams(chip, ifnum) < 0 ||
		    snd_usb_create_mixer(chip, ifnum, ignore_ctl_error) < 0) {
			goto __error;
		}
	}


    //创建完成之后,将card对象注册到alsa世界中,至此,可以从外部访问这些usb声卡设备
	/* we are allowed to call snd_card_register() many times */
	if (snd_card_register(chip->card) < 0) {
		goto __error;
	}

	usb_chip[chip->index] = chip;
	chip->num_interfaces++;
	chip->probing = 0;
	intf->needs_remote_wakeup = 1;
	mutex_unlock(&register_mutex);
	return chip;

	//...
}

上面的函数很简单,主要分成了三个步骤:

  1. 判断是不是已经创建了chip对象,这是因为某些声卡,具有多个Audio Control接口
  2. 如果没有chip对象,则调用snd_usb_audio_create,创建
  3. 然后调用snd_usb_create_streams,snd_usb_create_mixer创建对应的audio stream和audio mixer
  4. 将创建的card对象,注册到alsa世界中。至此,alsa驱动注册完毕,可以被外部的访问了

接下来看看snd_usb_audio_create函数。

static int snd_usb_audio_create(struct usb_interface *intf,
				struct usb_device *dev, int idx,
				const struct snd_usb_audio_quirk *quirk,
				struct snd_usb_audio **rchip)
{
	struct snd_card *card;
	struct snd_usb_audio *chip;
	int err, len;
	char component[14];
	//当释放card对象时,card对象里面的component也会被释放掉,为了能够释放为component分配的资源,所以传递下面的回调操作。
	static struct snd_device_ops ops = {
		.dev_free =	snd_usb_audio_dev_free,
	};

	*rchip = NULL;

	//速度判断
	switch (snd_usb_get_speed(dev)) {
	case USB_SPEED_LOW:
	case USB_SPEED_FULL:
	case USB_SPEED_HIGH:
	case USB_SPEED_WIRELESS:
	case USB_SPEED_SUPER:
		break;
	default:
		dev_err(&dev->dev, "unknown device speed %d\n", snd_usb_get_speed(dev));
		return -ENXIO;
	}

	//这里直接调用snd_card_new来创建一个card对象,注意此处的第五个参数0
	//表示card对象的私有数据不需要这个函数分配。
	//在usb声卡驱动中,为了管理chip对象,使用了《usb声卡驱动(四)》中的"chip对象的管理通常有两种方法"第二种方法
	//即创建一个虚拟的component对象。并为其传递snd_device_ops
	err = snd_card_new(&intf->dev, index[idx], id[idx], THIS_MODULE,
			   0, &card);
	if (err < 0) {
		dev_err(&dev->dev, "cannot create card instance %d\n", idx);
		return err;
	}

	//分配chip对象的空间
	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
	if (! chip) {
		snd_card_free(card);
		return -ENOMEM;
	}

	//初始化chip对象
	mutex_init(&chip->mutex);
	init_rwsem(&chip->shutdown_rwsem);
	chip->index = idx;
	chip->dev = dev;
	chip->card = card;
	chip->setup = device_setup[idx];
	chip->autoclock = autoclock;
	chip->probing = 1;

	chip->usb_id = USB_ID(le16_to_cpu(dev->descriptor.idVendor),
			      le16_to_cpu(dev->descriptor.idProduct));
	INIT_LIST_HEAD(&chip->pcm_list);
	INIT_LIST_HEAD(&chip->ep_list);
	INIT_LIST_HEAD(&chip->midi_list);
	INIT_LIST_HEAD(&chip->mixer_list);

	//将这个chip对象,当做一个虚拟的component对象进行管理
	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
		snd_usb_audio_free(chip);
		snd_card_free(card);
		return err;
	}

	strcpy(card->driver, "USB-Audio");
	sprintf(component, "USB%04x:%04x",
		USB_ID_VENDOR(chip->usb_id), USB_ID_PRODUCT(chip->usb_id));
	//将这个虚拟的component对象附着在card对象上
	snd_component_add(card, component);

	*rchip = chip;
	return 0;
}

上面的函数,也只做了那么几个简单的事情,大致流程如下:

  1. 调用标准的alsa api创建对应的card对象
  2. 对于chip对象的管理,这里使用了上一篇笔记介绍的第二种方法————创建一个虚拟的component对象
  3. 然后初始化chip对象中剩下的字段。并返回

解析音频设备的描述符

上面的步骤,创建了card对象,接下要做的事:根据描述符中的内容,创建不同的audio stream。
又因为篇幅原因,本篇笔记只会记录pcm对象的创建,对于各种control,mixer,midi等不在做过多的介绍,因为它和pcm几乎大同小异

使用snd_usb_create_streams函数,进入创建流程。

static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif)
{


	//首先第一步是,确认使用的版本
	//参照《usb声卡驱动二》的描述符层级图,可以知道,版本信息保存在header中
	//版本1,有bInCollection个audiostream,各个stream保存在baInterfaceNr数组中
	//版本2,不清楚,没有翻过相应的规范
	control_header = snd_usb_find_csint_desc(host_iface->extra,
						 host_iface->extralen,
						 NULL, UAC_HEADER);
	protocol = altsd->bInterfaceProtocol;


	switch (protocol) {
	case UAC_VERSION_1: {
		struct uac1_ac_header_descriptor *h1 = control_header;

		for (i = 0; i < h1->bInCollection; i++)
			snd_usb_create_stream(chip, ctrlif, h1->baInterfaceNr[i]);

		break;
	}

	case UAC_VERSION_2: {
		//母鸡呀···
	}
	}

	return 0;
}

上面的函数,超级简单,提取header描述符,然后判断版本,根据stream个数,调用
snd_usb_create_stream函数,来创建相应的对象。

接下来进入snd_usb_create_stream函数。

//注意第二参数,是控制接口,第三个参数是streaming接口
static int snd_usb_create_stream(struct snd_usb_audio *chip, int ctrlif, int interface)
{
	

	//判断这个是不是midiStreaming  接口,如果是,则调用snd_usbmidi_create创建midi
	if ((altsd->bInterfaceClass == USB_CLASS_AUDIO ||
	     altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC) &&
	    altsd->bInterfaceSubClass == USB_SUBCLASS_MIDISTREAMING) {
		int err = snd_usbmidi_create(chip->card, iface,
					     &chip->midi_list, NULL);
		if (err < 0) {
			dev_err(&dev->dev,
				"%u:%d: cannot create sequencer device\n",
				ctrlif, interface);
			return -EINVAL;
		}
		usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L);

		return 0;
	}

	//判断是不是audio streaming 如果不是则返回错误
	if ((altsd->bInterfaceClass != USB_CLASS_AUDIO &&
	     altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) ||
	    altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING) {
		dev_dbg(&dev->dev,
			"%u:%d: skipping non-supported interface %d\n",
			ctrlif, interface, altsd->bInterfaceClass);
		/* skip non-supported classes */
		return -EINVAL;
	}

	//如果是audio streaming ,则调用snd_usb_parse_audio_interface

	if (! snd_usb_parse_audio_interface(chip, interface)) {
		usb_set_interface(dev, interface, 0); /* reset the current interface */
		usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L);
		return -EINVAL;
	}

	return 0;
}

上面的函数,也很简单,根据描述符里面的信息,判断是不是midi,或者audio。如果是midi则调用snd_usbmidi_create函数。

如果是audio streaming,则将后续逻辑转交给snd_usb_parse_audio_interface函数。

int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no)
{

	/* parse the interface's altsettings */
	iface = usb_ifnum_to_if(dev, iface_no);

	//获取可选设置数
	num = iface->num_altsetting;

	//遍历所有可选设置
	for (i = 0; i < num; i++) {
		alts = &iface->altsetting[i];
		altsd = get_iface_desc(alts);
		protocol = altsd->bInterfaceProtocol;
		//跳过跟音频无关的设置
		if (((altsd->bInterfaceClass != USB_CLASS_AUDIO ||
		      (altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING &&
		       altsd->bInterfaceSubClass != USB_SUBCLASS_VENDOR_SPEC)) &&
		     altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) ||
		    altsd->bNumEndpoints < 1 ||
		    le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) == 0)
			continue;
		//0端点必须是,等时端点(规范要求),见《usb声卡驱动(二)》
		if ((get_endpoint(alts, 0)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) !=
		    USB_ENDPOINT_XFER_ISOC)
			continue;
		//判断端点方向
		stream = (get_endpoint(alts, 0)->bEndpointAddress & USB_DIR_IN) ?
			SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
		altno = altsd->bAlternateSetting;


		chconfig = 0;
		/* get audio formats */
		switch (protocol) {
		
		case UAC_VERSION_1: {
			//接下来获取音频相关的AS接口描述符
			struct uac1_as_header_descriptor *as =
				snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL);
			struct uac_input_terminal_descriptor *iterm;

			if (as->bLength < sizeof(*as)) {

			//传输使用的格式
			format = le16_to_cpu(as->wFormatTag); /* remember the format value */

			//bTerminalLink保存了跟这个接口相关的Terminal ID
			iterm = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf,
								       as->bTerminalLink);
			if (iterm) {
				num_channels = iterm->bNrChannels;
				chconfig = le16_to_cpu(iterm->wChannelConfig);
			}

			break;
		}

		case UAC_VERSION_2: {
			//省略,不懂
		}
		}

		//FORMAT_TYPE描述符
		fmt = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_FORMAT_TYPE);
		

		fp = kzalloc(sizeof(*fp), GFP_KERNEL);
		if (! fp) {
			dev_err(&dev->dev, "cannot malloc\n");
			return -ENOMEM;
		}

		//初始化fp对象,该对象专门用于保存格式信息。
		fp->iface = iface_no;
		fp->altsetting = altno;
		fp->altset_idx = i;
		fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress;
		fp->ep_attr = get_endpoint(alts, 0)->bmAttributes;
		fp->datainterval = snd_usb_parse_datainterval(chip, alts);
		fp->protocol = protocol;
		fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
		fp->channels = num_channels;
		if (snd_usb_get_speed(dev) == USB_SPEED_HIGH)
			fp->maxpacksize = (((fp->maxpacksize >> 11) & 3) + 1)
					* (fp->maxpacksize & 0x7ff);
		fp->attributes = parse_uac_endpoint_attributes(chip, alts, protocol, iface_no);
		fp->clock = clock;
		INIT_LIST_HEAD(&fp->list);


		//调用snd_usb_parse_audio_format进一步解析,它的功效依然是对fp对象的进一步填充,看过《usb声卡驱动(二)》的这个就没有什么可叙述的了
		if (snd_usb_parse_audio_format(chip, fp, format, fmt, stream) < 0) {
			kfree(fp->rate_table);
			kfree(fp);
			fp = NULL;
			continue;
		}

		/* Create chmap */
		if (fp->channels != num_channels)
			chconfig = 0;
		fp->chmap = convert_chmap(fp->channels, chconfig, protocol);

		dev_dbg(&dev->dev, "%u:%d: add audio endpoint %#x\n", iface_no, altno, fp->endpoint);
		//调用snd_usb_add_audio_stream创建各种对象,并初始化,并添加好
		err = snd_usb_add_audio_stream(chip, stream, fp);
		
		//一切OK之后,将当前的可选设置选中
		//然后初始化pitch
		//然后初始化采样率
		usb_set_interface(chip->dev, iface_no, altno);
		snd_usb_init_pitch(chip, iface_no, alts, fp);
		snd_usb_init_sample_rate(chip, iface_no, alts, fp, fp->rate_max);
	}
	return 0;
}

上面的函数,主要功能是解析描述符,它遍历所有的可选设置,选中audio 流的可选设置
分如下几步

  1. 首先跳过,不是我们关心的一些可选设置。
  2. 然后,填充audioformat对象,进行进一步的解析
  3. 当解析完成,调用snd_usb_add_audio_stream将各种创建好的对象放入规定的位置
  4. 一切,就进行一些初始化,包括接口的设置,pitch的初始化,采样率的初始化。

到现在为止,依然,还没有创建pcm对象。我们继续snd_usb_add_audio_stream函数

int snd_usb_add_audio_stream(struct snd_usb_audio *chip,
			     int stream,
			     struct audioformat *fp)
{
	struct snd_usb_stream *as;
	struct snd_usb_substream *subs;
	struct snd_pcm *pcm;
	int err;

	//遍历chip对象的pcm链表,如果存在一个空的stream,则使用这个stream
	list_for_each_entry(as, &chip->pcm_list, list) {
		if (as->fmt_type != fp->fmt_type)
			continue;
		subs = &as->substream[stream];
		if (subs->ep_num)
			continue;
		//创建stream
		err = snd_pcm_new_stream(as->pcm, stream, 1);
		if (err < 0)
			return err;
		//初始化
		snd_usb_init_substream(as, stream, fp);
		return add_chmap(as->pcm, stream, subs);
	}

	
	//遍历之后没有找到,则创建新的,并加入链表中
	as = kzalloc(sizeof(*as), GFP_KERNEL);
	if (!as)
		return -ENOMEM;
	as->pcm_index = chip->pcm_devs;
	as->chip = chip;
	as->fmt_type = fp->fmt_type;
	//啦啦啦,终于找到,我们辛辛苦苦期待的创建pcm对象啦
	err = snd_pcm_new(chip->card, "USB Audio", chip->pcm_devs,
			  stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0,
			  stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1,
			  &pcm);
	if (err < 0) {
		kfree(as);
		return err;
	}
	as->pcm = pcm;
	pcm->private_data = as;
	pcm->private_free = snd_usb_audio_pcm_free;
	pcm->info_flags = 0;
	if (chip->pcm_devs > 0)
		sprintf(pcm->name, "USB Audio #%d", chip->pcm_devs);
	else
		strcpy(pcm->name, "USB Audio");

	//然后初始化substream
	snd_usb_init_substream(as, stream, fp);

	list_add(&as->list, &chip->pcm_list);
	chip->pcm_devs++;

	snd_usb_proc_pcm_format_add(as);

	return add_chmap(pcm, stream, &as->substream[stream]);
}

上面的函数,也太不复杂了吧。看看它的主要逻辑:

  1. 先遍历chip对象,看看有没有空闲的stream,如果有,就用它
  2. 如果没有就重新创建一个,stream对象,包含有alsa对象中的pcm对象(一种特殊的component)。
  3. 然后初始化这个stream对象。

接下来,看看这个stream对象的初始化

static void snd_usb_init_substream(struct snd_usb_stream *as,
				   int stream,
				   struct audioformat *fp)
{
	struct snd_usb_substream *subs = &as->substream[stream];

	INIT_LIST_HEAD(&subs->fmt_list);
	spin_lock_init(&subs->lock);

	subs->stream = as;
	subs->direction = stream;
	subs->dev = as->chip->dev;
	subs->txfr_quirk = as->chip->txfr_quirk;
	subs->speed = snd_usb_get_speed(subs->dev);
	subs->pkt_offset_adj = 0;

	snd_usb_set_pcm_ops(as->pcm, stream);

	list_add_tail(&fp->list, &subs->fmt_list);
	subs->formats |= fp->formats;
	subs->num_formats++;
	subs->fmt_type = fp->fmt_type;
	subs->ep_num = fp->endpoint;
	if (fp->channels > subs->channels_max)
		subs->channels_max = fp->channels;
}

上面的代码那么的显眼,snd_usb_set_pcm_ops(as->pcm, stream);不用看也知道,是为stream对象里面的pcm对象,设置相应的回调接口。来看看

void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream)
{
	snd_pcm_set_ops(pcm, stream,
			stream == SNDRV_PCM_STREAM_PLAYBACK ?
			&snd_usb_playback_ops : &snd_usb_capture_ops);
}

果然,使用标准的alsa函数来注册相应的回调接口。

至此,整个alsa驱动都创建完成了。

现在来总结一下整个usb 音频驱动的数据结构:

  1. chip对象为:struct snd_usb_audio.
    它持有,snd_card对象,这个对象是alsa世界里面的声卡对象。

  2. chip对象有一个链表pcm_list,它是所有snd_usb_stream对象的一个链表。

  3. 而snd_usb_stream对象,持有一个alsa世界的snd_pcm对象,即表示alsa世界里面的pcm

  4. snd_usb_stream对象,还有一个数组substream[2]。它的类型为snd_usb_substream。表示的是打开的子流。在打开的时候,进行赋值,详见后面的pcm的打开和关闭。

在《usb声卡驱动二》中可以知道,一个流对应一个接口(输出一个interface,输入一个interface)。而在snd_usb_add_audio_stream函数中,已经将相同格式的接口统一在了一个snd_usb_stream下面了。

现在已经知道了,整个usb声卡驱动的主要数据结构,以及alsa世界里面的card对象,pcm对象的创建和赋值。接下来就是看它支持的各种功能啦。

下一篇见

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值