ALSA声卡驱动中的DAPM详解之七:dapm事件机制(dapm event)

5.10.6 kernel sound code学习 由结构体snd_soc_dapm_widget描述。2.dapm_seq_check_event:遍历down_list/up_list链表,向其中的widget发出SND_SOC_DAPM_WILL_PMD/SND_SOC_DAPM_WILL_PMU事件,感兴趣该事件的widget的event回调会被调用。asoc_rtd_to_cpu/snd_soc_rtd_add_component:把snd_soc_card中的dai_link配置复制到相应的snd_soc_pcm_runtime中。 阅读详情
AI摘要

本文深入探讨了DAPM框架中的事件机制,包括不同类型的DAPM事件、widget的事件回调函数、触发事件的方式以及DAPM如何通过事件机制管理音频路径上的电源状态。

前面的六篇文章,我们已经讨论了dapm关于动态电源管理的有关知识,包括widget的创建和初始化,widget之间的连接以及widget的上下电顺序等等。本章我们准备讨论dapm框架中的另一个机制:事件机制。通过dapm事件机制,widget可以对它所关心的dapm事件做出反应,这种机制对于扩充widget的能力非常有用,例如,对于那些位于codec之外的widget,好像喇叭功放、外部的前置放大器等等,由于不是使用codec内部的寄存器进行电源控制,我们就必须利用dapm的事件机制,获得相应的上下电事件,从而可以定制widget自身的电源控制功能。

/*****************************************************************************************************/
声明:本博内容均由http://blog.youkuaiyun.com/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/

dapm event的种类

dapm目前为我们定义了9种dapm event,他们分别是:

事件类型说明
SND_SOC_DAPM_PRE_PMUwidget要上电前发出的事件
SND_SOC_DAPM_POST_PMUwidget要上电后发出的事件
SND_SOC_DAPM_PRE_PMDwidget要下电前发出的事件
SND_SOC_DAPM_POST_PMDwidget要下电后发出的事件
SND_SOC_DAPM_PRE_REG音频路径设置之前发出的事件
SND_SOC_DAPM_POST_REG音频路径设置之后发出的事件
SND_SOC_DAPM_WILL_PMU在处理up_list链表之前发出的事件
SND_SOC_DAPM_WILL_PMD在处理down_list链表之前发出的事件
SND_SOC_DAPM_PRE_POST_PMDSND_SOC_DAPM_PRE_PMD和
SND_SOC_DAPM_POST_PMD的合并
前8种每种占据一个位,所以,我们可以在一个整数中表达多个我们需要关心的dapm事件,只要把它们按位或进行合并即可。

widget的event回调函数

ALSA声卡驱动中的DAPM详解之二:widget-具备路径和电源管理信息的kcontrol中,我们已经介绍过代表widget的snd_soc_widget结构,在这个结构体中,有一个event字段用于保存该widget的事件回调函数,同时,event_flags字段用于保存该widget需要关心的dapm事件种类,只有event_flags字段中相应的事件位被设置了的事件才会发到event回调函数中进行处理。

我们知道,dapm为我们提供了常用widget的定义辅助宏,使用以下这几种辅助宏定义widget时,默认需要我们提供dapm event回调函数

  • SND_SOC_DAPM_MIC
  • SND_SOC_DAPM_HP
  • SND_SOC_DAPM_SPK
  • SND_SOC_DAPM_LINE
这些widget都是位于codec外部的器件,它们无法使用通用的寄存器操作来控制widget的电源状态,所以需要我们提供event回调函数。以下的例子来自dapm的内核文档,外部的喇叭功放通过CORGI_GPIO_APM_ON这个gpio来控制它的电源状态:
/* turn speaker amplifier on/off depending on use */
static int corgi_amp_event(struct snd_soc_dapm_widget *w, int event)
{
	gpio_set_value(CORGI_GPIO_APM_ON, SND_SOC_DAPM_EVENT_ON(event));
	return 0;
}

/* corgi machine dapm widgets */
static const struct snd_soc_dapm_widget wm8731_dapm_widgets =
	SND_SOC_DAPM_SPK("Ext Spk", corgi_amp_event);
另外,我们也可以通过以下这些带"_E"后缀的辅助宏版本来定义需要dapm事件的widget:
  • SND_SOC_DAPM_PGA_E
  • SND_SOC_DAPM_OUT_DRV_E
  • SND_SOC_DAPM_MIXER_E
  • SND_SOC_DAPM_MIXER_NAMED_CTL_E
  • SND_SOC_DAPM_SWITCH_E
  • SND_SOC_DAPM_MUX_E
  • SND_SOC_DAPM_VIRT_MUX_E

触发dapm event

我们已经定义好了带有event回调的widget,那么,在那里触发这些dapm event?答案是:在dapm_power_widgets函数的处理过程中,dapm_power_widgets函数我们已经在ALSA声卡驱动中的DAPM详解之六:精髓所在,牵一发而动全身中做了详细的分析,其中,在所有需要处理电源变化的widget被分别放入up_list和down_list链表后,会相应地发出各种dapm事件:

static int dapm_power_widgets(struct snd_soc_card *card, int event)
{
        ......
        list_for_each_entry(w, &down_list, power_list) {
                dapm_seq_check_event(card, w, SND_SOC_DAPM_WILL_PMD);
        }

        list_for_each_entry(w, &up_list, power_list) {
                dapm_seq_check_event(card, w, SND_SOC_DAPM_WILL_PMU);
        }

        /* Power down widgets first; try to avoid amplifying pops. */
        dapm_seq_run(card, &down_list, event, false);

        dapm_widget_update(card);

        /* Now power up. */
        dapm_seq_run(card, &up_list, event, true);
        ......
}
可见,在真正地进行上电和下电之前,dapm向down_list链表中的每个widget发出SND_SOC_DAPM_WILL_PMD事件,而向up_list链表中的每个widget发出SND_SOC_DAPM_WILL_PMU事件。在处理上下电的函数dapm_seq_run中,会调用dapm_seq_run_coalesced函数执行真正的寄存器操作,进行widget的电源控制,dapm_seq_run_coalesced也会发出另外几种dapm事件:

static void dapm_seq_run_coalesced(struct snd_soc_card *card,
                                   struct list_head *pending)
{
        ......
        list_for_each_entry(w, pending, power_list) {
                ......
                /* Check for events */
                dapm_seq_check_event(card, w, SND_SOC_DAPM_PRE_PMU);
                dapm_seq_check_event(card, w, SND_SOC_DAPM_PRE_PMD);
        }

        if (reg >= 0) {
                ......
                pop_wait(card->pop_time);
                soc_widget_update_bits_locked(w, reg, mask, value);
        }

        list_for_each_entry(w, pending, power_list) {
                dapm_seq_check_event(card, w, SND_SOC_DAPM_POST_PMU);
                dapm_seq_check_event(card, w, SND_SOC_DAPM_POST_PMD);
        }
}
另外,负责更新音频路径的dapm_widget_update函数中也会发出dapm事件:

static void dapm_widget_update(struct snd_soc_card *card)
{
        struct snd_soc_dapm_update *update = card->update;
        struct snd_soc_dapm_widget_list *wlist;
        struct snd_soc_dapm_widget *w = NULL;
        unsigned int wi;
        int ret;

        if (!update || !dapm_kcontrol_is_powered(update->kcontrol))
                return;

        wlist = dapm_kcontrol_get_wlist(update->kcontrol);

        for (wi = 0; wi < wlist->num_widgets; wi++) {
                w = wlist->widgets[wi];

                if (w->event && (w->event_flags & SND_SOC_DAPM_PRE_REG)) {
                        ret = w->event(w, update->kcontrol, SND_SOC_DAPM_PRE_REG);
                        ......
                }
        }

        ......
        /* 更新kcontrol的值,改变音频路径 */
        ret = soc_widget_update_bits_locked(w, update->reg, update->mask,
                                  update->val);
        ......

        for (wi = 0; wi < wlist->num_widgets; wi++) {
                w = wlist->widgets[wi];

                if (w->event && (w->event_flags & SND_SOC_DAPM_POST_REG)) {
                        ret = w->event(w, update->kcontrol, SND_SOC_DAPM_POST_REG);
                        ......
                }
        }
}
可见,改变路径的前后,分别发出了SND_SOC_DAPM_PRE_REG事件和SND_SOC_DAPM_POST_REG事件。

dai widget与stream widget

dai widget    在ALSA声卡驱动中的DAPM详解之四:在驱动程序中初始化并注册widget和route一文中,我们已经讨论过dai widget,dai widget又分为cpu dai widget和codec dai widget,它们在machine驱动分别匹配上相应的codec和platform后,由soc_probe_platform和soc_probe_codec这两个函数通过调用dapm的api函数:

  • snd_soc_dapm_new_dai_widgets
来创建的,通常会为playback和capture各自创建一个dai widget,他们的类型分别是:
  • snd_soc_dapm_dai_in      对应playback dai
  • snd_soc_dapm_dai_out    对应capture dai
另外,dai widget的名字是使用stream name来命名的,他通常来自snd_soc_dai_driver中的stream_name字段。dai widget的sname字段也使用同样的名字。
stream widget    stream widget通常是指那些要处理音频流数据的widget,它们包含以下这几种类型:
  • snd_soc_dapm_aif_in                 用SND_SOC_DAPM_AIF_IN辅助宏定义
  • snd_soc_dapm_aif_out               用SND_SOC_DAPM_AIF_OUT辅助宏定义
  • snd_soc_dapm_dac                    用SND_SOC_DAPM_AIF_DAC辅助宏定义
  • snd_soc_dapm_adc                    用SND_SOC_DAPM_AIF_ADC辅助宏定义
对于这几种widget,我们除了要指定widget的名字外,还要指定他对应的stream的名字,保存在widget的sname字段中。
连接dai widget和stream widget
默认情况下,驱动不会通过snd_soc_route来主动定义dai widget和stream widget之间的连接关系,实际上,他们之间的连接关系是由ASoc负责的,在声卡的初始化函数中,使用snd_soc_dapm_link_dai_widgets函数来建立他们之间的连接关系:
static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
        ......
        /* card bind complete so register a sound card */
        ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
                        card->owner, 0, &card->snd_card);
        ......
        if (card->dapm_widgets)
                snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
                                          card->num_dapm_widgets);
        /*  建立dai widget和stream widget之间的连接关系  */
        snd_soc_dapm_link_dai_widgets(card);
        ......
        if (card->controls)
                snd_soc_add_card_controls(card, card->controls, card->num_controls);
        ......
        if (card->dapm_routes)
                snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
                                        card->num_dapm_routes);
        ......
        if (card->fully_routed)
                list_for_each_entry(codec, &card->codec_dev_list, card_list)
                        snd_soc_dapm_auto_nc_codec_pins(codec);

        snd_soc_dapm_new_widgets(card);

        ret = snd_card_register(card->snd_card);
        ......
        return 0;
}
我们再来分析一下snd_soc_dapm_link_dai_widgets函数,看看它是如何连接这两种widget的,它先是遍历声卡中所有的widget,找出类型为snd_soc_dapm_dai_in和snd_soc_dapm_dai_out的widget,通过widget的priv字段,取出widget对应的snd_soc_dai结构指针:
int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card)
{               
        struct snd_soc_dapm_widget *dai_w, *w;
        struct snd_soc_dai *dai;
        
        /* For each DAI widget... */
        list_for_each_entry(dai_w, &card->widgets, list) {
                switch (dai_w->id) {
                case snd_soc_dapm_dai_in:
                case snd_soc_dapm_dai_out:
                        break;
                default:
                        continue;
                }       
                                
                dai = dai_w->priv;
接着,再次从头遍历声卡中所有的widget,找出能与dai widget相连接的stream widget,第一个前提条件是这两个widget必须位于同一个dapm context中:
                /* ...find all widgets with the same stream and link them */
                list_for_each_entry(w, &card->widgets, list) {
                        if (w->dapm != dai_w->dapm)
                                continue;
dai widget不会与dai widget相连,所以跳过它们:
                        switch (w->id) {
                        case snd_soc_dapm_dai_in:
                        case snd_soc_dapm_dai_out:
                                continue;
                        default:
                                break;
                        }
dai widget的名字没有出现在要连接的widget的stream name中,跳过这个widget:
                        if (!w->sname || !strstr(w->sname, dai_w->name))
                                continue;
如果 widget的stream name包含了 dai的stream name,则匹配成功,连接这两个widget:
                        if (dai->driver->playback.stream_name &&
                            strstr(w->sname,
                                   dai->driver->playback.stream_name)) {
                                dev_dbg(dai->dev, "%s -> %s\n",
                                         dai->playback_widget->name, w->name);

                                snd_soc_dapm_add_path(w->dapm,
                                        dai->playback_widget, w, NULL, NULL);
                        }

                        if (dai->driver->capture.stream_name &&
                            strstr(w->sname,
                                   dai->driver->capture.stream_name)) {
                                dev_dbg(dai->dev, "%s -> %s\n",
                                        w->name, dai->capture_widget->name);

                                snd_soc_dapm_add_path(w->dapm, w,
                                        dai->capture_widget, NULL, NULL);
                        }
                }
        }

        return 0;
}
由此可见,dai widget和stream widget是通过stream name进行匹配的,所以,我们在定义codec的stream widget时,它们的stream name必须要包含dai的stream name,这样才能让ASoc自动把这两种widget连接在一起,只有把它们连接在一起,ASoc中的播放、录音和停止等事件,才能通过dai widget传递到codec中,使得codec中的widget能根据目前的播放状态,动态地开启或关闭音频路径上所有widget的电源。我们看看wm8993中的例子:
SND_SOC_DAPM_AIF_OUT("AIFOUTL", "Capture", 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("AIFOUTR", "Capture", 1, SND_SOC_NOPM, 0, 0),

SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0),
分别定义了左右声道两个stream name为Capture和Playback的stream widget。对应的dai driver结构定义如下:
static struct snd_soc_dai_driver wm8993_dai = {
        .name = "wm8993-hifi",
        .playback = {
                .stream_name = "Playback",
                .channels_min = 1,
                .channels_max = 2,
                .rates = WM8993_RATES,
                .formats = WM8993_FORMATS,
                .sig_bits = 24,
        },
        .capture = {
                 .stream_name = "Capture",
                 .channels_min = 1,
                 .channels_max = 2,
                 .rates = WM8993_RATES,
                 .formats = WM8993_FORMATS,
                 .sig_bits = 24,
         },
        .ops = &wm8993_ops,
        .symmetric_rates = 1,
};
可见,它们的stream name是一样的,声卡初始化阶段会把它们连接在一起。需要注意的是,如果我们定义了snd_soc_dapm_aif_in和snd_soc_dapm_aif_out类型的stream widget,并指定了他们的stream name,在定义DAC或ADC对应的widget时,它们的stream name最好不要也使用相同的名字,否则,dai widget即会连接上AIF,也会连接上DAC/ADC,造成音频路径的混乱:
SND_SOC_DAPM_ADC("ADCL", NULL, WM8993_POWER_MANAGEMENT_2, 1, 0),
SND_SOC_DAPM_ADC("ADCR", NULL, WM8993_POWER_MANAGEMENT_2, 0, 0),

SND_SOC_DAPM_DAC("DACL", NULL, WM8993_POWER_MANAGEMENT_3, 1, 0),
SND_SOC_DAPM_DAC("DACR", NULL, WM8993_POWER_MANAGEMENT_3, 0, 0),

stream event

把dai widget和stream widget连接在一起,就是为了能把ASoc中的pcm处理部分和dapm进行关联,pcm的处理过程中,会通过发出stream event来通知dapm系统,重新扫描并调整音频路径上各个widget的电源状态,目前dapm提供了以下几种stream event:

/* dapm stream operations */
#define SND_SOC_DAPM_STREAM_NOP                 0x0
#define SND_SOC_DAPM_STREAM_START               0x1
#define SND_SOC_DAPM_STREAM_STOP                0x2
#define SND_SOC_DAPM_STREAM_SUSPEND             0x4
#define SND_SOC_DAPM_STREAM_RESUME              0x8
#define SND_SOC_DAPM_STREAM_PAUSE_PUSH  0x10
#define SND_SOC_DAPM_STREAM_PAUSE_RELEASE       0x20
比如,在soc_pcm_prepare函数中,会发出SND_SOC_DAPM_STREAM_START事件:

        snd_soc_dapm_stream_event(rtd, substream->stream,
                        SND_SOC_DAPM_STREAM_START);
而在soc_pcm_close函数中,会发出SND_SOC_DAPM_STREAM_STOP事件:

                        snd_soc_dapm_stream_event(rtd,
                                                  SNDRV_PCM_STREAM_PLAYBACK,
                                                  SND_SOC_DAPM_STREAM_STOP);
snd_soc_dapm_stream_event函数最终会使用soc_dapm_stream_event函数来完成具体的工作:
static void soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, int stream,
        int event)
{

        struct snd_soc_dapm_widget *w_cpu, *w_codec;
        struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
        struct snd_soc_dai *codec_dai = rtd->codec_dai;

        if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
                w_cpu = cpu_dai->playback_widget;
                w_codec = codec_dai->playback_widget;
        } else {
                w_cpu = cpu_dai->capture_widget;
                w_codec = codec_dai->capture_widget;
        }
该函数首先从snd_soc_pcm_runtime结构中取出cpu dai widget和codec dai widget,接下来:

        if (w_cpu) {

                dapm_mark_dirty(w_cpu, "stream event");

                switch (event) {
                case SND_SOC_DAPM_STREAM_START:
                        w_cpu->active = 1;
                        break;
                case SND_SOC_DAPM_STREAM_STOP:
                        w_cpu->active = 0;
                        break;
                case SND_SOC_DAPM_STREAM_SUSPEND:
                case SND_SOC_DAPM_STREAM_RESUME:
                case SND_SOC_DAPM_STREAM_PAUSE_PUSH:
                case SND_SOC_DAPM_STREAM_PAUSE_RELEASE:
                        break;
                }
        }
把cpu dai widget加入到dapm_dirty链表中,根据stream event的类型,把cpu dai widget设定为激活状态或非激活状态,接下来,对codec dai widget做出同样的处理:

        if (w_codec) {

                dapm_mark_dirty(w_codec, "stream event");

                switch (event) {
                case SND_SOC_DAPM_STREAM_START:
                        w_codec->active = 1;
                        break;
                case SND_SOC_DAPM_STREAM_STOP:
                        w_codec->active = 0;
                        break;
                case SND_SOC_DAPM_STREAM_SUSPEND:
                case SND_SOC_DAPM_STREAM_RESUME:
                case SND_SOC_DAPM_STREAM_PAUSE_PUSH:
                case SND_SOC_DAPM_STREAM_PAUSE_RELEASE:
                        break;
                }
        }
最后,它调用了我们熟悉的dapm_power_widgets函数:

        dapm_power_widgets(rtd->card, event);
}
因为dai widget和codec上的stream widget是相连的,所以,dai widget的激活状态改变,会沿着音频路径传递到路径上的所有widget,等dapm_power_widgets返回后,如果发出的是SND_SOC_DAPM_STREAM_START事件,路径上的所有widget会处于上电状态,保证音频数据流的顺利播放,如果发出的是SND_SOC_DAPM_STREAM_STOP 事件,路径上的所有widget会处于下电状态,保证最小的功耗水平。

Linux alsa音频框架DAPM介绍 而DAPM(Dynamic Audio Power Management,动态音频电源管理)是ALSA系统中的一个关键组件,主要负责在音频播放和录制过程中动态控制音频部件的电源状态,以实现节能。DAPM分为多个组件,每个组件代表音频传输路径中的一个节点,例如麦克风、放大器、DAC(数模转换器)、ADC(模数转换器)等。总结来说,DAPM是一个功能强大且复杂的组件,通过动态管理音频路径和节点的电源状态,显著降低了系统的整体功耗,对于在ALSA框架下开发音频应用的开发者来说非常重要。节点之间通过路径连接。 阅读详情

相关推荐

alsa(asoc dapm)框架分析

alsa(asoc dapm)框架分析,描述了alsa 音频驱动开发过程中一些工具的使用和驱动的分析框架

ALSA学习(8)——DAPM详解之一:kcontrol

参考博客:https://blog.youkuaiyun.com/DroidPhone/article/details/12793293

永不秃头的程序员的博客 2231

Burp靶场实战:JWT签名绕过漏洞分析与利用

本文通过Burp Suite靶场实战,深入剖析了JWT签名绕过漏洞的多种攻击手法,包括签名未验证、alg:none攻击、弱密钥爆破、JWK/JKU头注入、KID路径遍历及算法混淆攻击。文章详细演示了利用过程,并提供了关键的防御指南,帮助开发和安全人员加固JWT实现,确保认证安全。

weixin_29325515的博客 593

DAPM之五:dapm机制深入分析(上)

哲学里有三个终极命题:你是谁?从何处来?到何处去?而对于dapm机制,我们可以这样提问:dapm是什么,起到何作用?dapm是如何建立的?dapm又是如何触发的?下面会就这三个问题进行分析。首先是dapm是什么?

AZURE 2万+

DSPF28335:从零到一,实战GPIO与外部中断的嵌入式系统交互设计

本文以德州仪器DSPF28335芯片为例,详细讲解了嵌入式系统中GPIO与外部中断(EXTI)的实战配置方法。通过一个完整的按键控制LED并触发ADC采样的项目,深入剖析了GPIO寄存器配置、三级中断机制(外设-PIE-CPU)以及中断服务函数编写的核心要点,帮助开发者掌握嵌入式交互设计的基石,并提供了关键的避坑指南与调试清单。

weixin_29169899的博客 115

晶振(晶体振荡器)测试方法详解

晶振测试方法分为无源和有源两种类型。无源晶振(两脚)需用示波器测试波形,重点关注正弦波形状、200mV-1.5Vpp幅值和频率精度;有源晶振(四脚)通电后直接输出方波。测试要点包括:示波器使用10X探头、AC耦合,带宽需5倍于晶振频率;万用表可初筛短路/开路;替换法是工程最有效方法。常见故障排查顺序:供电→复位→焊接→型号→负载电容→波形观察→更换晶振→MCU配置。负载电容需按公式计算匹配,示波器观察是核心测试手段。

jiujiujinyuxi的博客 923

Linux ALSA 之十三:ASOC DAPM 简介 & Widget/Kcontrol 定义

DAPM 是 的缩写,即,旨在允许便携式 Linux 设备在任何时候使用音频子系统中的最小电量。它独立于其他内核 Power Manager,故可以很容易地与其他 PM 系统共存。DAPM 对所有用户空间应用程序来说也是完全透明的,因为所有电源切换都是在 ASoC 核心内完成。对于用户空间应用程序,不需要更改代码或重新编译。DAPM 根据当前激活的音频流(playback/capture)和声卡中的 mixer 等的配置来决定哪些音频控件的电源开关被打开或关闭。在前面 SoC Kcontrol 中知道,利

年少生而为人 3917

Codex辅助小说创作:零成本AI写作指南与实战技巧

自然语言处理技术通过深度学习模型理解语义上下文,其核心原理是基于大规模文本训练的语言模型预测能力。在创意写作领域,这项技术的价值在于突破传统写作工具的功能局限,实现从内容生成到风格模仿的智能化辅助。特别是提示词工程的应用,让创作者能够精准控制AI输出的内容和风格。在实际应用场景中,AI写作工具显著降低了小说创作门槛,适用于网络小说、奇幻科幻等多种文学类型。本文以OpenAI Codex为例,详细解析如何利用AI技术完成从故事大纲设计、章节生成到平台发布的全流程创作,其中提示词优化和内容质量把控是提升生成效果

weixin_32678995的博客 557

ALSA声卡驱动中的DAPM详解之七:dapm事件机制(dapm event)

前面的六篇文章,我们已经讨论了dapm关于动态电源管理的有关知识,包括widget的创建和初始化,widget之间的连接以及widget的上下电顺序等等。本章我们准备讨论dapm框架中的另一个机制:事件机制。通过dapm事件机制,widget可以对它所关心的dapm事件做出反应,这种机制对于扩充widget的能力非常有用,例如,对于那些位于codec之外的widget,好像喇叭功放、外部的前置放大

蓝精灵的专栏 1122

OpenClaw Windows 安装指南

好的,我来一步一步带你在 Windows 上安装 OpenClaw。

m0_60605453的博客 548

前端使用二开Thingsboard一、二、三事(三)-规则链

以项目实战为基础,逐步介绍前端在thingsboard系统二次开发中的一些注意事项以及一些thingsboard一些页面功能讲解,为前端人员开发使用thingsboard提供一种可靠思路,打造一个完美的物联网系统。

weixin_43163457的博客 862
上一篇: ALSA声卡驱动中的DAPM详解之六:精髓所在,牵一发而动全身
下一篇: Linux SPI总线和设备驱动架构之一:系统概述
DroidPhone
博客等级 码龄16年 3750粉丝 51原创
评论 10
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值