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.osnd-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,创建mixerif (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_opserr = 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创建midiif ((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_interfaceif (! 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 IDiterm = 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,则使用这个streamlist_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;//创建streamerr = 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);elsestrcpy(pcm->name, "USB Audio");//然后初始化substreamsnd_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对象的创建和赋值。接下来就是看它支持的各种功能啦。

下一篇见

usb声卡驱动(五):声卡驱动的开始相关推荐

  1. ALSA声卡驱动五之移动设备中的ALSA(ASoC)

    1.  ASoC的由来 ASoC--ALSA System on Chip ,是建立在标准ALSA驱动层上,为了更好地支持嵌入式处理器和移动设备中的音频Codec的一套软件体系.在ASoc出现之前,内 ...

  2. beast软件linux用法,声卡驱动设置 - 黑苹果驱动高手篇 MultiBeast用法进阶_Linux教程_Linux公社-Linux系统门户网站...

    声卡驱动设置 Audio自然是声卡驱动设置项,这个选项包括众多板载声卡驱动,选择之前最好仔细阅读描述文件,并选择正确的声卡型号. ▲ Realtek ALC8xx 项(包括ALC8xxHDA和Appl ...

  3. win7(windows7旗舰版)声卡High Definition Audio驱动 (安装失败)解决方法

    win7(windows7旗舰版)声卡High Definition Audio驱动 (安装失败)解决方案 前几天装了一下windows7体验一下,结果声卡驱动安装有问题,这电脑没声音我可没法活啊. ...

  4. linux音频驱动修复工具,Linux声卡驱动(4)——音频驱动实战

    一.应用测试工具的使用 1.在external/tinyalsa下有以C语言实现的alsa的测试程序,编译后生成tinypcminfo tinyplay tinycap tinymix 四个elf格式 ...

  5. 安卓声卡驱动:2.Machine驱动与声卡

    一 声卡简介 一个音频系统由硬件和驱动这些硬件的软件组成. 在一台设备上,如果硬件完整,且驱动硬件的软件全部初始化成功后,ALSA系统就会注册一个声卡,通过这个声卡,应用程序可以控制硬件设备的链路联通 ...

  6. win7(windows7旗舰版)声卡High Definition Audio驱动(安装失败)解决方案(Thinkpad)

    申精, Definition, 声卡, 旗舰, Audio 你看到我这篇文章时候估计你快被windows7无法安装声卡驱动而崩溃了,呵呵,别急,这篇文章是你的救星,本人已经试验多台电脑,完美解决win ...

  7. USB总线驱动及鼠标驱动实例

    转自:https://blog.csdn.net/liangzc1124/article/details/119333357.https://www.cnblogs.com/lifexy/p/7631 ...

  8. USB总线-Linux内核USB设备驱动之UAC2驱动分析(十)

    1.概述 UVC(USB Audio Class)定义了使用USB协议播放或采集音频数据的设备应当遵循的规范.目前,UAC协议有UAC1.0和UAC2.0.UAC2.0协议相比UAC1.0协议,提供了 ...

  9. STC8H开发(十五): GPIO驱动Ci24R1无线模块

    目录 STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解) STC8H开发(二): 在Linux VSCode中配置和使用FwLib_STC8封装库(图文详解) ST ...

  10. Linux 音频驱动(五) ALSA音频驱动之PCM逻辑设备

    目录 1. 前言 2. PCM逻辑设备 2.1. 创建 PCM逻辑设备: 2.2. PCM逻辑设备文件操作函数集:snd_pcm_f_ops[] 2.3. Open PCM逻辑设备 2.4. Writ ...

最新文章

  1. python营销骗局_python案例:金融营销活动中欺诈用户行为分析
  2. 如何知道linux的ssh秘钥是否匹配,SSH密钥验证
  3. php bloginfo templatedirectory,PHP变量不显示使用bloginfo('template_directory')的图像
  4. listview 不可滑动问题解决
  5. 程序控制流——Python基础语法
  6. [4/10]指定结进程名称的命令taskkill.exe
  7. 利用bug来进行调试
  8. fht算法c语言源码,sklearn中LinearRegression使用及源码解读
  9. vb mysql 实例,vb数据库(vb编辑access数据库实例)
  10. 移动机顶盒migu-jt-u1 unt400c刷机 hi3798 root
  11. 201671030108 +词频统计软件项目报告
  12. 卡耐基梅隆大学教授邢波:Petuum,大数据分布式机器学习平台
  13. python运用ico图标_使用python将图片格式转换为ico格式的示例
  14. 三星s8升级到android9相机,三星S8拍照怎么样?S8相机官方详解
  15. 时间记录APP———Time Meter
  16. 应用实践 | Apache Doris 在网易互娱的应用实践
  17. print中的逗号“,”打印出来相当于空格
  18. NBUT - 1077 骨牌铺方格 【递推】
  19. Delphi2010设置成Delphi7风格
  20. cadence allegro 元器件定位

热门文章

  1. STM32应用文件系统--W25Q256(RTT系统)
  2. W25Q256编程时需要关注的器件特性
  3. [转载]我的PMP复习备考经验谈(上篇)—— 一本关于PMP备考的小指南
  4. Jsp内置对象实验报告
  5. java 程序员职业规划,详细说明
  6. 基于js利用经纬度进行两地的距离计算(转)
  7. C 使用拉依达准则(3σ准则)剔除异常数据( Net剔除一组数据中的奇异值)
  8. 远程连接树莓派桌面xrdp
  9. 免费的音视频格式转换网站-ncm, qmc, mflac, mgg转mp3
  10. 量子加密技术成功在人工智能产品上的应用落地