qcom 音频相关的dsp driver笔记(基于msm8996平台)

原址
0 前言
1 关于acdb
1.1 从audio_calibration.c说起
1.2 关于acdb配置的注册
1.3 关于acdb配置过程
2 关于dsp driver
3 关于asm
4 关于adm
5 关于afe
6 关于apr消息
7 关于channel map
附录 关于afe port id

关于音频框架的大致架构,高通文档原话:

这里其实就说明了,qcom的音频框架底层驱动主要有asoc driver(在这一篇笔记里面记录的)、slimbus、acdb和adsp组成,这篇笔记就主要记录一下acdb、adsp相关的,并且记录一下跟slimbus相关的channel map的内容。

1 关于acdb
目前来说,对于acdb文件,我的理解是一个dsp的参数配置文件。 
在audio这一块,dsp里面最主要分为了三个部分,asm、adm和afe。另外的lsm是干什么用的,目前暂时还没用上……也没看到手上的开发板有哪里用到了……所以暂时不管。

那么asm、adm及afe的管理工作或者说他们的driver都是在ap(cpu)上运行的,也就是对应的q6asm.c、q6adm.c、q6afe.c,这些driver负责提供对这些module的控制接口,以及配置接口,当有音频流需要播放时,platform driver或者dai driver里面调用对应的接口打开、配置正确的asm、adm及afe。

除此之外,android在打开一个linux音频逻辑设备进行播放时,还会把asm、adm及afe需要的相应配置从acdb文件中读出来,通过audio calibration这个逻辑设备配置给q6asm.c、q6adm.c、q6afe.c。这些module会将配置保存,并且在open相应module时使用这些配置。

详细过程在各个module里面记录。

启动音频时,acdb相关的过程如下图所示: 
audio_calibration是一个misc类型的逻辑设备,android侧就是通过该逻辑设备与linux侧进行acdb数据交互的。

libacdbloader.so是qcom的一个闭源库,里面主要是提供了对acdb文件的操作,以及与audio_calibration设备的交互,acdb文件配置的最终操作都是调用该库里面的方法完成。

关于acdb来看下高通的原话吧: 
……

上面的就不翻译了……

这里记录一下acdb文件里面记录的东西是怎么配置给dsp的,下面以asm为例来进行记录,所有使用到acdb配置的模块工作过程都一模一样。

1.1 从audio_calibration.c说起
audio_calibration是”msm_audio_cal”逻辑设备的driver,该设备其实就是一个接口类型的逻辑设备,提供一个kernel与userspace交互的通道,userspace通过”msm_audio_cal”设备来进行audio calibration。

该driver非常简单就不详细记录了,其中的一个关键函数是audio_cal_register(),该函数的作用简单来说就是让其他的模块给audio_calibration提供一组回调接口,比如set、get等,同时指明该回调接口处理的数据类型,所有能识别的数据类型qcom已经定义好了,第三方如果要增加新类型必须得修改qcom的audio_calibration.c代码……没必要……

1.2 关于acdb配置的注册
以asm为例,在module init(q6asm_init_cal_data()函数)的时候就进行注册,注册调用的函数为:

int cal_utils_create_cal_types(int num_cal_types,
            struct cal_type_data **cal_type,
            struct cal_type_info *info)
{undefined
    ……
    for (i = 0; i < num_cal_types; i++) {undefined
        ……
        cal_type[i] = create_cal_type_data(&info[i]);
        ……
        ret = audio_cal_register(1, &info[i].reg);
        ……
    }
done:
    return ret;
}

其中,info的内容是在q6asm_init_cal_data()函数中写死了的,这个是根据各模块自己的需求来决定。 
这里主要就是做了两件事,1、创建一个cal_type_data,创建该结构所需要的参数由info提供;2、向audio_calibration注册一个数据侦听,也就是当audio_calibration收到该模块想要的数据类型后调用一下info[i].reg中提供的回调函数。

回过头来,cal_type_data这个东西对于asm来说是个啥…… 
无论是asm、adm还是afe,都定义了一个这个:static struct cal_type_data *cal_data[ASM_MAX_CAL_TYPES];,这个东西其实就是保存acdb或者其他调音参数的数据结构。

struct audio_cal_callbacks {undefined
    int (*alloc) (int32_t cal_type, size_t data_size, void *data);
    int (*dealloc) (int32_t cal_type, size_t data_size, void *data);
    int (*pre_cal) (int32_t cal_type, size_t data_size, void *data);
    int (*set_cal) (int32_t cal_type, size_t data_size, void *data);
    int (*get_cal) (int32_t cal_type, size_t data_size, void *data);
    int (*post_cal) (int32_t cal_type, size_t data_size, void *data);
};

struct audio_cal_reg {undefined
    int32_t             cal_type;
    struct audio_cal_callbacks  callbacks;
};

struct cal_util_callbacks {undefined
    int (*map_cal)
        (int32_t cal_type, struct cal_block_data *cal_block);
    int (*unmap_cal)
        (int32_t cal_type, struct cal_block_data *cal_block);
    bool (*match_block)
        (struct cal_block_data *cal_block, void *user_data);
};

struct cal_type_info {undefined
    struct audio_cal_reg        reg;
    struct cal_util_callbacks   cal_util_callbacks;
};

struct cal_type_data {undefined
    struct cal_type_info        info;
    struct mutex            lock;
    struct list_head        cal_blocks;
};

struct cal_block_data {undefined
    size_t          client_info_size;
    void            *client_info;
    void            *cal_info;
    struct list_head    list;
    struct cal_data     cal_data;
    struct mem_map_data map_data;
    int32_t         buffer_number;
};

struct cal_type_data嵌套了一大堆,其实归结来说:

info:里面存放了该参数的类型信息以及一些操作函数,类型信息就是指的该参数属于什么类型,例如:ASM_TOPOLOGY_CAL_TYPE。操作函数就是该参数的回调操作方法,struct audio_cal_callbacks里面的方法其实都已经注册到了audio calibration里面去了,由audio calibration负责调用,这里其实没什么作用……struct cal_util_callbacks的方法由audio_cal_utils.c里面的函数调用,audio_cal_utils.c里面是直接调用的保存在info里面的方法,其中match_block方法是必须存在的,用于判断同一条参数是否已经保存过了。(audio_cal_utils.c里面属于一个audio calibration框架辅助工具库,把所有calibration中所用到的共性行为抽象出来写成了辅助工具函数,每个具体的calibration模块自己写该模块的特性函数,把特性方法提供给audio_cal_utils.c,这也是一种常用的软件模式)
lock:锁……
cal_blocks:实际定义为:struct cal_block_data,其中void *cal_info;是存放实际参数的地方。int32_t buffer_number;是asm用来判断参数是否重复的标志。(见match函数)
1.3 关于acdb配置过程
配置过程简单来说如下:

1、audio calibration收到ioctrl
2、根据参数类型调用注册到该参数类型下的所有模块的set回调函数
3、各模块回调函数被调用后把参数保存在对应参数类型下的cal_data[type]的cal_blocks->cal_info中。

2 关于dsp driver
在记录asm、adm和afe之前先简单记录一下整个dsp driver的构成:

3 关于asm
asm是干什么的: 
在cpu中,asm对应的其实就是stream,或者说就是一条音频数据流,asm负责完成把音频数据流写给dsp的任务以及对dsp asm的配置工作。 
在dsp中,asm负责音频的编解码以及部分的音效处理。

asm 怎么工作的: 
这里简单记录一下,在音频流开始播放的时候,会调用open函数:

static int msm_pcm_open(struct snd_pcm_substream *substream)
{undefined
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct snd_soc_pcm_runtime *soc_prtd = substream->private_data;
    struct msm_audio *prtd;
    int ret = 0;

prtd = kzalloc(sizeof(struct msm_audio), GFP_KERNEL);
    ……
    prtd->substream = substream;
    prtd->audio_client = q6asm_audio_client_alloc(
                (app_cb)event_handler, prtd);
    ……
    prtd->enabled = IDLE;
    prtd->dsp_cnt = 0;
    prtd->set_channel_map = false;
    prtd->reset_event = false;
    runtime->private_data = prtd;
    ……
}

这里跟asm相关的主要是q6asm_audio_client_alloc()函数,简单来说就是向asm注册了一个client,建立了一个该substream与dsp的asm通信的通道,这个通道由q6asm.c来管理。

struct audio_client *q6asm_audio_client_alloc(app_cb cb, void *priv)
{undefined
    struct audio_client *ac;
    int n;
    int lcnt = 0;
    int rc = 0;

ac = kzalloc(sizeof(struct audio_client), GFP_KERNEL);
    ……
    /* 申请一个stream session,qcom规定同时最多有8个session,
     * 这里qcom就是用了一个全局数组来存当前存在stream,然后如果哪个元素空着
     * 就把哪个元素的数组下标返回回来作为该session的id */
    n = q6asm_session_alloc(ac);
    ……
    ac->session = n;
    ac->cb = cb;
    ac->path_delay = UINT_MAX;
    ac->priv = priv;
    ac->io_mode = SYNC_IO_MODE;
    ac->perf_mode = LEGACY_PCM_MODE;
    ac->fptr_cache_ops = NULL;
    /* DSP expects stream id from 1 */
    ac->stream_id = 1;
    ……
    /* 想apr框架申请一个apr通道 */
    ac->apr = apr_register("ADSP", "ASM", \
            (apr_fn)q6asm_callback,\
            ((ac->session) << 8 | 0x0001),\
            ac);
    ……
    ac->mmap_apr = q6asm_mmap_apr_reg();
    if (ac->mmap_apr == NULL) {undefined
        mutex_unlock(&session_lock);
        goto fail_mmap;
    }
    ……
    rc = send_asm_custom_topology(ac);
    if (rc < 0) {undefined
        mutex_unlock(&session_lock);
        goto fail_mmap;
    }
    ……
}

关于q6asm_mmap_apr_reg();这里单独说一下,其实里面也是注册了一个apr的通道,这里为什么要单独注册一个……我觉得这里主要是为了处理asm模块级的消息,详细的在后面记录,这里简单说一下。 
ac->apr = apr_register()是注册了一个到dsp中asm模块下面的对应stream的通信通道,但是这里还需要一个cpu asm到dsp asm模块的通信通道,用于cpu去控制dsp的asm模块,主要体现在两边的smmu后的共享内存上,也就是mmap,所以这里主要是为了两边协商共享内存的地址用。

这之后,系统会调prepare函数:

static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream)
{undefined
    ……
    /* 按照一定的格式打开一个asm,也就是告诉dsp的asm现在的这条流的格式,下面好做准备解析 */
    ret = q6asm_open_write_v3(prtd->audio_client,
                  FORMAT_LINEAR_PCM, bits_per_sample);
    /* 简单来说就是吧acdb配下来的参数发给asm模块 */
    ret = q6asm_send_cal(prtd->audio_client);
    ……
    /* 这个session id前面已经记录过是怎么来的了 */
    prtd->session_id = prtd->audio_client->session;
    /* 这个函数的作用在记录adm时来说 */
    ret = msm_pcm_routing_reg_phy_stream(soc_prtd->dai_link->be_id,
            prtd->audio_client->perf_mode,
            prtd->session_id, substream->stream);
    ……
    /* 配置采样率、声道信息 */
    ret = q6asm_media_format_block_multi_ch_pcm_v3(
                prtd->audio_client, runtime->rate,
                runtime->channels, !prtd->set_channel_map,
                prtd->channel_map, bits_per_sample,
                sample_word_size);
    ……
}

最后trigger和write就不记录了……

其实asm里面最麻烦的地方就在于共享内存的管理这块,这里要是有机会的话就单独记录一下……

4 关于adm
adm:audio device management 
adm设备包括路由矩阵和acdb device两个东西。 
acdb device是个什么东西呢,其实这个东西就是QACT里面的Tools->DeviceDesigner里面的一个device…… 
所以,这个adm管理的东西就是QACT里面的这个device,对应到dsp里面,就是链接copp和audio font port的东西,关于这个qcom的音频框架overview文档中有对应的示意图,这里就不贴了。

device : 
把他叫做一个设备也没错,可以理解为这个设备有n个输入(最大8),每个输入都是一个音频流,还有一个输出,这个输出也是一个音频流,每个输入都是一个asm的数据,每个输出都是把数据给afe。这个里面我觉得最主要就是做了两件事,混音和调音。因为输入可能是多个,但输出只有一个,所以这里面必须根据输出的channel数、采样率(可能需要重采样)、位宽进行混音,合并成一个与afe匹配的输出流。再就是调音,或者说是音效(effect),这里各个音效公司可以把自己需要的音效处理进行集成。

routing: 
所谓routing,就是指的路由,因为adm主要两个作用,一个是路由矩阵,一个是音频处理。音频处理这一块主要依赖于acdb文件,因为这一块只能配置dsp中已有的模块和拓扑的行为,所以不做重点记录,主要记录一下关于路由矩阵这块。因为asoc中是采用的dynamic pcm,既然是dynamic pcm,那么fe和be具体的连接工作由dsp完成,所以dsp的driver必须告诉dsp他需要怎么连接,那么这个工作在msm8996平台上就是由be platform driver完成的,也就是msm-pcm-routing-v2.c这个文件,这个文件操作dsp中的adm模块,来实现数据的路由,所以,adm模块才是最终数据路由的执行者。那么既然要进行路由,有几个东西就必须知道:1、dsp接入了那些数据;2、dsp要把这些数据从哪些端口送出去;3、每条数据需要用什么处理算法。其实整个be platform driver就是围绕这三个问题展开的,这也是为什么这个dirver的命名中有routing这个词。

在这里面有几个关键的全局变量:msm_bedais,fe_dai_map,fe_dai_app_type_cfg和cal_data。 
其中cal_data变量在前面已经说了,这里就不详细记录。 
msm_bedais:记录了be dai的相关信息,其中主要包括那些fe的session会送到该be dai的afe port中。 
fe_dai_map:记录了当前所有工作的fe session的信息 
fe_dai_app_type_cfg: 
……

上面是qcom的文档中写的,其实这个app type就是选择copp的标志,这里结合qact中的device来一起看就比较好理解,不然估计是理解不能……

session_copp_map:至于这个全局变量,不算特别重要就不记录了,简单来说就是保存了每个session要经过的copp,这个东西的作用就是给dsp发参数配置时,会从里面读取一下数据。后面分析代码的时候稍微会提一下这个东西的。

所以说,上面的三个问题分别由上述的三个全局变量解答了,那么再就是这几个变量的值是哪里来的,这里结合打印信息来看:

[   85.351447] gift_dsp : kctl name = SLIM RX0 MUX 
[   85.353027] gift_dsp : kctl name = SLIM RX1 MUX 
[   85.355048] gift_dsp : kctl name = SLIM_0_RX Channels 
[   85.355908] gift_dsp : kctl name = RX INT7_1 MIX1 INP0 
[   85.368966] gift_dsp : kctl name = RX INT8_1 MIX1 INP0 
[   85.369722] gift_dsp : kctl name = SpkrLeft COMP Switch 
[   85.369782] gift_dsp : kctl name = SpkrRight COMP Switch 
[   85.369831] gift_dsp : kctl name = SpkrLeft BOOST Switch 
[   85.369881] gift_dsp : kctl name = SpkrRight BOOST Switch 
[   85.369930] gift_dsp : kctl name = SpkrLeft VISENSE Switch 
[   85.369979] gift_dsp : kctl name = SpkrRight VISENSE Switch 
[   85.370079] gift_dsp : kctl name = SpkrLeft SWR DAC_Port Switch 
[   85.370867] gift_dsp : kctl name = SpkrRight SWR DAC_Port Switch 
[   85.372261] gift_dsp : kctl name = Audio Stream 15 App Type Cfg 
[   85.372277] gift_dsp msm_pcm_routing_reg_stream_app_type_cfg: fedai_id 4, session_type 0, app_type 69937, acdb_dev_id 15, sample_rate 48000
[   85.372454] gift_dsp msm_routing_set_cal topology (0x00010314), acdb_id (0x0000000f), path (0), app_type (0x00011131), sample_rate (0), 
[   85.372836] gift_dsp : kctl name = SLIMBUS_0_RX Audio Mixer MultiMedia5 
[   85.372842] gift_dsp : msm_pcm_routing_process_audio: reg 2 val 4 set 1
[   85.374045] gift_dsp msm_pcm_routing_hw_params: BE Sample Rate (48000) format (2) be_id 2, channel (2)
[   85.374994] gift_dsp : kctl name = Playback Channel Map15 
[   85.406330] gift_dsp : !!!!!!!!!!! 
[   85.421707] gift_dsp : adm_open:port 0x4000 path:1 rate:48000 mode:2 perf_mode:1,topo_id 66324
[   85.424604] gift_dsp : msm_pcm_routing_reg_phy_stream: setting idx bit of fe:4, type: 0, be:2
[   85.642493] gift_dsp : kctl name = Audio Stream 0 App Type Cfg 
[   85.642510] gift_dsp msm_pcm_routing_reg_stream_app_type_cfg: fedai_id 0, session_type 0, app_type 69936, acdb_dev_id 15, sample_rate 48000
[   85.642697] gift_dsp msm_routing_set_cal topology (0x00010314), acdb_id (0x0000000f), path (0), app_type (0x00011130), sample_rate (-319468453), 
[   85.643140] gift_dsp : kctl name = SLIMBUS_0_RX Audio Mixer MultiMedia1 
[   85.643146] gift_dsp : msm_pcm_routing_process_audio: reg 2 val 0 set 1
[   85.644683] gift_dsp : kctl name = Playback Channel Map0 
[   85.652305] gift_dsp : adm_open:port 0x4000 path:1 rate:48000 mode:2 perf_mode:0,topo_id 66324
[   85.655030] gift_dsp : msm_pcm_routing_reg_phy_stream: setting idx bit of fe:0, type: 0, be:2

所以:

msm_bedais在hw parameters里面赋值
fe_dai_app_type_cfg在Audio Stream %xx App Type Cfg的control里面赋值
cal_data(topology相关)通过acdb loader把acdb里面的内容发下来。在adm init时会向audio calibration注册cal_data,并且提供set函数:msm_routing_set_cal,当acdb set参数时回调该函数,完成对cal_data的写操作
fe_dai_map在msm_pcm_routing_reg_phy_stream函数中赋值,该函数是msm_pcm_prepare时调用的,也就是fe的platform在prepare时调用
adm_open是在msm_pcm_routing_reg_phy_stream函数中创建的,在be platform prepare时由于fe_dai_map里面的stream id还没有获取(msm_pcm_prepare时才能获取),所以往往adm_open并不是在be platform的prepare中创建的,也就是不是在msm_pcm_routing_prepare中创建的
msm_bedais[reg].fe_sessions在msm_pcm_routing_process_audio()里面被设置的,msm_pcm_routing_process_audio()函数是在SLIMBUS_0_RX Audio Mixer MultiMedia5控件被control写后触发调用的。这个控件的定义:
static const struct snd_kcontrol_new slimbus_rx_mixer_controls[] = {undefined
    ……
    SOC_SINGLE_EXT("MultiMedia5", MSM_BACKEND_DAI_SLIMBUS_0_RX,
        MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, msm_routing_get_audio_mixer,
        msm_routing_put_audio_mixer),
    ……
}

static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = {undefined
    ……
    SND_SOC_DAPM_MIXER("SLIMBUS_0_RX Audio Mixer", SND_SOC_NOPM, 0, 0,
        slimbus_rx_mixer_controls, ARRAY_SIZE(slimbus_rx_mixer_controls)),
    ……
}

以上基本上就把adm这块给解释清楚了,最后再来看一下具体的路由矩阵:

/* multiple copp per stream. */
struct route_payload {undefined
    /* copp_idx与port_id其实是一一对应的关系,这里就是定义出了
     * copp与afe port的对应关系 */
    unsigned int copp_idx[MAX_COPPS_PER_PORT];
    unsigned int port_id[MAX_COPPS_PER_PORT];
    int app_type;
    int acdb_dev_id;
    int sample_rate;
    unsigned short num_copps;
    unsigned int session_id;
};

int msm_pcm_routing_reg_phy_stream(int fedai_id, int perf_mode,
                    int dspst_id, int stream_type)
{undefined
    ……
    struct route_payload payload;
    ……
    for (i = 0; i < MSM_BACKEND_DAI_MAX; i++) {undefined
        if (!is_be_dai_extproc(i) &&
               (afe_get_port_type(msm_bedais[i].port_id) == port_type) &&
               (msm_bedais[i].active) &&
               (test_bit(fedai_id, &msm_bedais[i].fe_sessions))) {undefined
            ……
            for (j = 0; j < MAX_COPPS_PER_PORT; j++) {undefined
                unsigned long copp =
                    session_copp_map[fedai_id][session_type][i];
                if (test_bit(j, &copp)) {undefined
                    payload.port_id[num_copps] =
                            msm_bedais[i].port_id;
                    payload.copp_idx[num_copps] = j;
                    num_copps++;
                }
            }
            ……
        }
    }
    ……
    if (num_copps) {undefined
        payload.num_copps = num_copps;
        payload.session_id = fe_dai_map[fedai_id][session_type].strm_id;
        payload.app_type =
            fe_dai_app_type_cfg[fedai_id][session_type].app_type;
        payload.acdb_dev_id =
            fe_dai_app_type_cfg[fedai_id][session_type].acdb_dev_id;
        payload.sample_rate =
            fe_dai_app_type_cfg[fedai_id][session_type].sample_rate;
        /* 把这一条stream与对应adm device的绑定消息发送给dsp */
        adm_matrix_map(path_type, payload, perf_mode);
        msm_pcm_routng_cfg_matrix_map_pp(payload, path_type, perf_mode);
    }
    ……
}

那么acdb device在这里面到底是个什样的存在?这里凭自己的理解稍微画一下,这里qcom好像也没有明确的说,所以暂且这样认为:

5 关于afe
afe的作用:把adm输出的音频数据发送给codec,同时还可以进行一些音频处理。

本来关于afe port的内容是准备稍微记录一下的,后来越来越发现这里有很大的迷惑性,而这迷惑性完全来自于qcom自身编码的不严谨,所以还是单独抽出来记录一下关于afe port的相关内容。

afe是在be dai prepare的时候被打开的,比如:msm-dai-q6-v2.c里面,调用了函数:

int afe_port_start(u16 port_id, union afe_port_config *afe_config,
    u32 rate) /* This function is no blocking */
{undefined
    /* 校验参数有效性 */
    ……
    /* 检查afe的apr是否注册了,如果没注册,则给afe注册一个apr */
    ret = afe_q6_interface_prepare();
    ……
    /* Also send the topology id here: */
    port_index = afe_get_port_index(port_id);
    if (!(this_afe.afe_cal_mode[port_index] == AFE_CAL_MODE_NONE)) {undefined
        /* One time call: only for first time */
        /* 都是在发apr消息,配置afe的参数,有些参数是通过acdb文件拿到的,例如topology的值 */
        afe_send_custom_topology();
        afe_send_port_topology_id(port_id);
        afe_send_cal(port_id);
        afe_send_hw_delay(port_id, rate);
    }

/* Start SW MAD module */
    /* 这里的MAD有什么用暂时不清楚……好像是一种数据类型? */
    mad_type = afe_port_get_mad_type(port_id);
    if (mad_type != MAD_HW_NONE && mad_type != MAD_SW_AUDIO) {undefined
        ……
        ret = afe_turn_onoff_hw_mad(mad_type, true);
        ……
    }

/* aanc 相关 */
    if ((this_afe.aanc_info.aanc_active) &&
        (this_afe.aanc_info.aanc_tx_port == port_id)) {undefined
        ……
        ret = afe_aanc_start(this_afe.aanc_info.aanc_tx_port,
                this_afe.aanc_info.aanc_rx_port);
        ……
    }
    /* 给afe配置参数,包括channel map信息,详见:
     * struct afe_audioif_config_command config 结构 */
    config.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
                APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER);
    ……
    config.hdr.opcode = AFE_PORT_CMD_SET_PARAM_V2;
    ……
    config.port = *afe_config;

ret = afe_apr_send_pkt(&config, &this_afe.wait[index]);
    ……

/* 发送AFE_PORT_CMD_DEVICE_START消息 */
    ret = afe_send_cmd_port_start(port_id);
    ……
}

对于afe,这里有一个有趣的地方,整个q6afe向apr只注册了一个src port为0xFFFFFFFF的apr svc,这样一来,所有跟afe模块相关的apr消息其实都是q6afe来统一管理了,这个地方个人觉得qcom没有处理好……

afe有一个很关键的地方,就是channel map,这一块单独在后面记录。

6 关于apr消息
apr是基于smd和smmu映射的,这里的记录不去分析smd和smmu,这两个东西不是几句话能说清楚的……这里只用明白一个概念,smd可以提供若干个通道,让不同设备进行数据交换;smmu可以把外部设备的访问映射为对一段地址的访问,也就是说如果外部设备有一段可以访问的内存,那么可以直接通过smmu把这段内存映射给cpu,cpu可以直接去访问这段内存。

首先说下几个结构……

struct apr_client {undefined
    uint8_t id;/* 非APR_CLIENT_AUDIO即APR_CLIENT_VOICE */
    uint8_t svc_cnt;/* 使用了的svc的个数 */
    uint8_t rvd;
    struct mutex m_lock;
    struct apr_svc_ch_dev *handle;/* 通信操作的实体接口,这里其实就是smd的handle,只是被封装过一次 */
    struct apr_svc svc[APR_SVC_MAX];/* apr server,每个server的基本信息已经写死了,例如“svc_tbl_qdsp6” */
};

#define APR_DEST_MODEM 0
#define APR_DEST_QDSP6 1
#define APR_DEST_MAX   2

#define APR_CLIENT_AUDIO    0x0
#define APR_CLIENT_VOICE    0x1
#define APR_CLIENT_MAX      0x2

/* 全局变量,一个2×2的数组,保存了所有apr通信的参数 */
static struct apr_client client[APR_DEST_MAX][APR_CLIENT_MAX];

这个地方为什么要叫client,说实话不是特别清楚,这个client的server是谁?也不是特别清楚……但是他的作用倒是很明显,所有需要通过apr通信的模块都会把自己的一些信息保存进这个client中,然后通过handle与dsp交互数据,这些后面具体记录。

这里再稍微记录一下这个结构:

typedef int32_t (*apr_fn)(struct apr_client_data *data, void *priv);

struct apr_svc {undefined
    uint16_t id;/* server id,例如:APR_SVC_AFE */
    uint16_t dest_id;/* 目的器件的id,例如:APR_DEST_QDSP6 */
    uint16_t client_id;/* server所属的client的id 例如:APR_CLIENT_AUDIO */
    uint16_t dest_domain;/* 域id,跟dest id可以对应,例如:APR_DOMAIN_ADSP */
    uint8_t rvd;
    uint8_t port_cnt;/* 向该server注册的的port数量 */
    uint8_t svc_cnt;
    uint8_t need_reset;
    apr_fn port_fn[APR_MAX_PORTS];/* 每个port的数据接收回调函数 */
    void *port_priv[APR_MAX_PORTS];/* 回调函数的参数 */
    apr_fn fn;/* 整个server接收数据的回调函数,只有在找不到对应port的回调函数时才会被调用 */
    void *priv;/* server回调函数的参数 */
    struct mutex m_lock;
    spinlock_t w_lock;
    uint8_t pkt_owner;
};

关于apr的行为,这里就记录两点:注册和接收回调

struct apr_svc *apr_register(char *dest, char *svc_name, apr_fn svc_fn,
                uint32_t src_port, void *priv)
{undefined
    ……
    dest_id = apr_get_dest_id(dest);

if (dest_id == APR_DEST_QDSP6) {undefined
        /* 检查dsp状态 */
        ……
    } else if (dest_id == APR_DEST_MODEM) {undefined
        /* 检查modem状态 */
        ……
    }
    /* 根据sve name和domain id获取该server对应的client id、service序列号和service id
     * 这些东西都在apr.c里面写死了,见:svc_tbl_qdsp6和svc_tbl_voice两个全局数组 */
    if (apr_get_svc(svc_name, domain_id, &client_id, &svc_idx, &svc_id)) {undefined
        ……
    }

clnt = &client[dest_id][client_id];
    ……
    if (!clnt->handle && can_open_channel) {undefined
        /* 实际打开通信channel,adsp这里就是smd的一个channel,并返回该channel的一个handle */
        clnt->handle = apr_tal_open(client_id, dest_id,
                APR_DL_SMD, apr_cb_func, NULL);
        ……
    }
    ……
    svc = &clnt->svc[svc_idx];
    ……
    svc->id = svc_id;
    svc->dest_id = dest_id;
    svc->client_id = client_id;
    svc->dest_domain = domain_id;
    svc->pkt_owner = APR_PKT_OWNER_DRIVER;

/* 这里如果源端口为0xFFFFFFFF,则表示设置该service的接收回调函数,其余值(若有效)
     * 则为设置对应port的接收回调函数 */
    if (src_port != 0xFFFFFFFF) {undefined
        temp_port = ((src_port >> 8) * 8) + (src_port & 0xFF);
        ……
        if (!svc->port_cnt && !svc->svc_cnt)
            clnt->svc_cnt++;
        svc->port_cnt++;
        svc->port_fn[temp_port] = svc_fn;
        svc->port_priv[temp_port] = priv;
    } else {undefined
        if (!svc->fn) {undefined
            if (!svc->port_cnt && !svc->svc_cnt)
                clnt->svc_cnt++;
            svc->fn = svc_fn;
            if (svc->port_cnt)
                svc->svc_cnt++;
            svc->priv = priv;
        }
    }
    ……
}

注册函数就记录这么多,关于apr_tal_open()背后的操作今后有机会再记录。

接着是接收回调函数,这个接收回调是指的clnt->handle的回调,也就是当smd收到数据后调用apr模块的函数,然后apr再在该回调中去调用service或者port的回调函数,过程如下:

void apr_cb_func(void *buf, int len, void *priv)
{undefined
    /* 一直在校验接收参数的有效性 */
    ……
    /* 根据接收的参数找到client */
    apr_client = &client[src][clnt];
    for (i = 0; i < APR_SVC_MAX; i++)
        /* 从client中找到对应的service */
        if (apr_client->svc[i].id == svc) {undefined
            pr_debug("%d\n", apr_client->svc[i].id);
            c_svc = &apr_client->svc[i];
            break;
        }
    ……
    temp_port = ((data.dest_port >> 8) * 8) + (data.dest_port & 0xFF);
    ……
    /* 如果有port能处理该数据则让该port的回调函数处理,
     * 否则让service的回调函数处理该数据(src port为0xFFFFFFFF的那一条注册命令) */
    if (c_svc->port_cnt && c_svc->port_fn[temp_port])
        c_svc->port_fn[temp_port](&data,  c_svc->port_priv[temp_port]);
    else if (c_svc->fn)
        c_svc->fn(&data, c_svc->priv);
    else
        pr_err("APR: Rxed a packet for NULL callback\n");
}

其实如果不算通信channel相关的内容的话到这里apr的最进本的工作原理就说的差不多了,接下来主要记录一下apr的消息怎么发送。 
首先是apr消息的hdr:

struct apr_hdr {undefined
    uint16_t hdr_field;
    uint16_t pkt_size;
    uint8_t src_svc;
    uint8_t src_domain;
    uint16_t src_port;
    uint8_t dest_svc;
    uint8_t dest_domain;
    uint16_t dest_port;
    uint32_t token;
    uint32_t opcode;
};

给一个最简单的示例:

static int xxxxxxx_fill_apr_hdr(struct apr_hdr *apr_hdr, uint32_t port,
             uint32_t opcode, uint32_t apr_msg_size)
{undefined
    if (apr_hdr == NULL) {undefined
        pr_err("[  err][%s] %s: invalid APR pointer \r\n", LOG_FLAG, __func__);
        return -EINVAL;
    }

apr_hdr->hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
        APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER);
    apr_hdr->pkt_size = apr_msg_size; /* total len, include the hdr */
    apr_hdr->src_svc = APR_SVC_XXX;
    apr_hdr->src_domain = APR_DOMAIN_APPS;
    apr_hdr->src_port = port;/* apr port id, dsp will use this value as dest_port when response this cmd */
    apr_hdr->dest_svc = APR_SVC_XXX;
    apr_hdr->dest_domain = APR_DOMAIN_ADSP;
    apr_hdr->dest_port = 0;
    apr_hdr->token = port;
    apr_hdr->opcode = opcode;

return 0;
}

如果了解一般通信协议的设计的话,这里就很好理解,无非就是要告诉对方,我是谁,从我的哪个service的哪个port发出去的什么类型的消息,该消息要给谁,给到哪个service的哪个port。 
这里dest port一般填0,为什么还真不太清楚……src port填我们期望接收到dsp回复的port,简单来说就是dsp在应答这条消息时会把接收到的src port作为应答消息的dest port,然后我们在注册apr service的时候填的src port如果与这里应答消息的dest port匹配上了,则该消息将调用注册apr时提供的src port对应的回调函数处理此应答消息。

一般的apr消息格式如下: 
hdr + 对应消息数据0 + ... + 对应消息数据n + 附加内容 
例如:

struct adm_cmd_set_pspd_mtmx_strtr_params_v5 {undefined
    struct apr_hdr hdr;
    /* LSW of parameter data payload address.*/
    u32     payload_addr_lsw;
    /* MSW of parameter data payload address.*/
    u32     payload_addr_msw;
    /* Memory map handle returned by ADM_CMD_SHARED_MEM_MAP_REGIONS */
    /* command. If mem_map_handle is zero implies the message is in */
    /* the payload */
    u32     mem_map_handle;
    /* Size in bytes of the variable payload accompanying this */
    /* message or in shared memory. This is used for parsing the */
    /* parameter payload. */
    u32     payload_size;
    u16     direction;
    u16     sessionid;
    u16     deviceid;
    u16     reserved;
} __packed;

struct apr_hdr hdr;就是hdr,其余所有都为“对应消息数据”,这里最麻烦的就是“附加内容”。 
因为smd只能提供一个比较小的内存(好像是因为这个),所以,如果两边需要交互一个比较大的消息内容时往往会重新映射一段内存(利用smmu),然后发送消息的那一方把数据写到映射的内存中,并把内存地址在apr消息中告诉给对端,对端利用收到的apr消息,解析出附加内容的内存地址,然后读出附加内容。在上面代码中:

u32     payload_addr_lsw;
    u32     payload_addr_msw;
1
2
为cpu侧拿到的内存的物理地址。

u32     mem_map_handle;
1
为dsp那边对这段地址的映射句柄,这个值是dsp发送过来的,我估计是dsp侧对于这一段内存的记录信息的一个标志,dsp侧依靠这个值就可以找到cpu侧映射的内存在dsp侧所看到的地址。那么这些内存是怎么来的,这里稍微记录一下:

首先,cpu通过msm_audio_ion_alloc()函数去申请一段内存,这段内存可以是利用smmu映射出来的一段dsp的内存;
接着,cpu向dsp发送AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS类型的apr消息,把刚刚得到的那一段内存的物理地址告诉dsp
最后,dsp通过发送AFE_SERVICE_CMDRSP_SHARED_MEM_MAP_REGIONS类型的apr消息,把dsp这边映射这段内存后的一个句柄告诉cpu,cpu把这个句柄保存下来。
当cpu要通过上述内存与dsp交互数据时,cpu首先把数据写入这段内存中,并把cpu侧的内存物理地址填入payload_addr_lsw和payload_addr_msw中,同时,把dsp发送过来的内存映射句柄填入mem_map_handle中,最后发送该apr消息,即可完成数据交互。
7 关于channel map
channel map,跟物理链路控制器相关。怎么理解这个问题呢……首先看下linux官方文档的一段话:

PCM is another 4 wire interface, very similar to I2S, which can support a more flexible protocol. It has bit clock (BCLK) and sync (SYNC) lines that are used to synchronise the link whilst the Tx and Rx lines are used to transmit and receive the audio data. Bit clock usually varies depending on sample rate whilst sync runs at the sample rate. PCM also supports Time Division Multiplexing (TDM) in that several devices can use the bus simultaneously (this is sometimes referred to as network mode).

简单来说就是linux音频系统支持TDM特性,也就是pcm一个接口传输n个channel的数据,然后要让codec知道那个slot是传输的哪个channel的数据,channel map最主要的工作就在于此。(从文档描述和代码上来看是这样的)由于手上的硬件是qcom msm8996平台,该平台使用的不是pcm接口也不是i2s接口,所以这里没法进行实际的测试,对于pcm的channel map作用只能推断。

在msm8996平台上,qcom用来他们自己的slimbus的方式来链接dsp和codec(还有一些其他的设备),那么这里的channel map是干了什么,这一节主要记录下与之相关的内容。

slimbus:qcom的一个链接dsp、codec的总线,具体总线的工作时序和协议不清楚,但是从driver里面来看,该总线设计了若干个channel(好像是32个:slim-msm-ngd.c dev->ctrl.nchans = MSM_SLIM_NCHANS;),codec和dsp之间占用了ch_num为:

unsigned int rx_ch[TASHA_RX_MAX] = {144, 145, 146, 147, 148, 149, 150,
                        151, 152, 153, 154, 155, 156};
    unsigned int tx_ch[TASHA_TX_MAX] = {128, 129, 130, 131, 132, 133,
                        134, 135, 136, 137, 138, 139,
                        140, 141, 142, 143};

的channel,这里的ch_num感觉没有实际上的意义,仅仅是该channel的一个名称,可以用来唯一确定这个channel,但是有一点,就是不能跟其他使用slimbus的地方重复,所有qcom把tx的ch定义为128~143,rx的定义为144~156。

上述定义是在声卡初始化dai link被创建时通过dai link的init来实现的,相关代码见:msm8996.c中的msm_audrx_init()函数。该函数中调用了:

snd_soc_dai_set_channel_map(codec_dai, ARRAY_SIZE(tx_ch),
                    tx_ch, ARRAY_SIZE(rx_ch), rx_ch);

来实现对codec的channel进行初始化:

static int tasha_set_channel_map(struct snd_soc_dai *dai,
                 unsigned int tx_num, unsigned int *tx_slot,
                 unsigned int rx_num, unsigned int *rx_slot)
{undefined
    ……
    if (tasha->intf_type == WCD9XXX_INTERFACE_TYPE_SLIMBUS) {undefined
        wcd9xxx_init_slimslave(core, core->slim->laddr,
                       tx_num, tx_slot, rx_num, rx_slot);
        ……
    }
    return 0;
}

int wcd9xxx_init_slimslave(struct wcd9xxx *wcd9xxx, u8 wcd9xxx_pgd_la,
               unsigned int tx_num, unsigned int *tx_slot,
               unsigned int rx_num, unsigned int *rx_slot)
{undefined
    …… 
    ret = wcd9xxx_configure_ports(wcd9xxx);
    ……
    if (wcd9xxx->rx_chs) {undefined
        wcd9xxx->num_rx_port = rx_num;
        for (i = 0; i < rx_num; i++) {undefined
            wcd9xxx->rx_chs[i].ch_num = rx_slot[i];
            INIT_LIST_HEAD(&wcd9xxx->rx_chs[i].list);
        }
        ret = wcd9xxx_alloc_slim_sh_ch(wcd9xxx, wcd9xxx_pgd_la,
                        wcd9xxx->num_rx_port,
                        wcd9xxx->rx_chs,
                        SLIM_SINK);
        ……
    } 
    ……
}

如果一直tracewcd9xxx_alloc_slim_sh_ch()函数下去,会发现其实就是再向slimbus配置channel的占用情况。 
这样一来,wcd9xxx->rx_chs这一个成员的内容就全部填好了,这个成员在后面很重要,所以这里特别说一声,该参数的channel相关的内容都是在这里填写的。 
但是,wcd9xxx->rx_chs在wcd9xxx中是顶一个的一个指针,那么到底有多少个rx_chs,每个rx_chs中的其他成员的内容在哪里填的呢?这个问题其实直觉告诉我应该在probe之类的初始化的函数中找答案:

static int tasha_codec_probe(struct snd_soc_codec *codec)
{undefined
    struct wcd9xxx *control;
    ……
    ptr = devm_kzalloc(codec->dev, (sizeof(tasha_rx_chs) +
               sizeof(tasha_tx_chs)), GFP_KERNEL);
    ……         
    control->rx_chs = ptr;
    memcpy(control->rx_chs, tasha_rx_chs, sizeof(tasha_rx_chs));
    ……
}

其中tasha_rx_chs是一个全局变量,已经把每个port定义好了,这样依赖rx_chs的port和channel都定义好了。

以上只是把port和channel定义好了,但是实际上在什么时候使用哪个port、哪个channel,这些channel和port又是怎么跟具体的aif关联的,这些才是最关键的。 
当在播放音频之前,上层(android)会根据配置文件(xxx.xml)来配置一些control(通过control逻辑设备)以打开物理音频链路。这一块另外一篇笔记高通msm8996平台的ASOC音频路径分析中已经记录过了,这里就不再记录。下面直接列举control的控制过程:

[   42.123093] gift_dsp : kctl name = SLIM RX0 MUX 
[   42.123110] gift_dsp slim_rx_mux_put: wname SLIM RX0 MUX cname SLIM RX0 MUX value 5 shift 0 item 5
[   42.123975] gift_dsp : kctl name = SLIM RX1 MUX 
[   42.123988] gift_dsp slim_rx_mux_put: wname SLIM RX1 MUX cname SLIM RX1 MUX value 5 shift 1 item 5
[   42.125526] gift_dsp : kctl name = SLIM_0_RX Channels 
[   42.125539] gift_dsp msm_slim_0_rx_ch_put: msm_slim_0_rx_ch = 2
[   42.126302] gift_dsp : kctl name = RX INT7_1 MIX1 INP0 
[   42.127509] gift_dsp : kctl name = RX INT8_1 MIX1 INP0 
[   42.128286] gift_dsp : kctl name = SpkrLeft COMP Switch 
[   42.128338] gift_dsp : kctl name = SpkrRight COMP Switch 
[   42.128384] gift_dsp : kctl name = SpkrLeft BOOST Switch 
[   42.128428] gift_dsp : kctl name = SpkrRight BOOST Switch 
[   42.128471] gift_dsp : kctl name = SpkrLeft VISENSE Switch 
[   42.128516] gift_dsp : kctl name = SpkrRight VISENSE Switch 
[   42.128562] gift_dsp : kctl name = SpkrLeft SWR DAC_Port Switch 
[   42.129477] gift_dsp : kctl name = SpkrRight SWR DAC_Port Switch 
[   42.131947] gift_dsp : kctl name = Audio Stream 15 App Type Cfg 
[   42.132909] gift_dsp : kctl name = SLIMBUS_0_RX Audio Mixer MultiMedia5 
[   42.134902] gift_dsp msm_slim_0_rx_be_hw_params_fixup: format = 2, rate = 48000, channels = 2
[   42.134917] gift_dsp tasha_get_channel_map: dai->id 7, rx_num 2
[   42.134922] gift_dsp msm_snd_hw_params: rx_0_ch=2, rx_ch_count=0, rx_ch_cnt=2
[   42.134943] gift_dsp msm_dai_q6_set_channel_map: SLIMBUS_0_RX cnt[2] ch[144 145]
[   42.136845] gift_dsp : kctl name = Playback Channel Map15 
[   42.178973] gift_dsp tasha_codec_enable_slimrx: event called! codec name tasha_codec num_dai 11
[   42.208103] gift_dsp gift_pcm : msm_pcm_prepare : name = pcm15p!!! 
[   42.209709] gift_dsp : adm_open:port 0x4000 path:1 rate:48000 mode:2 perf_mode:1,topo_id 66324

硬件是双声道,所以这里一开始就会去控制SLIM RX0 MUX和SLIM RX1 MUX两个control,在这两个control的put函数:

static int slim_rx_mux_put(struct snd_kcontrol *kcontrol,
               struct snd_ctl_elem_value *ucontrol)
{undefined
    ……
    /* value need to match the Virtual port and AIF number */
    switch (rx_port_value) {undefined
    ……
    case 5:
        ……
        list_add_tail(&core->rx_chs[port_id].list,
                  &tasha_p->dai[AIF_MIX1_PB].wcd9xxx_ch_list);
        break;
    ……
    }
rtn:
    mutex_unlock(&codec->mutex);
    snd_soc_dapm_mux_update_power(widget->dapm, kcontrol,
                    rx_port_value, e, update);

return 0;
    ……
}

可以看到这里会把这两个声道(一个声道是一个port)的rx_chs信息加入tasha_p->dai[AIF_MIX1_PB].wcd9xxx_ch_list中,tasha_p->dai[AIF_MIX1_PB]表示当前数据传输的codec侧的dai,至于为什么是AIF_MIX1_PB,这个也是在上面提到的笔记里面有记录。所以经过SLIM RX0 MUX和SLIM RX1 MUX的配置后,tasha_p->dai[AIF_MIX1_PB].wcd9xxx_ch_list中就应该挂接了2个rx_chs了。把链路物理信息放到了codec dai中只是开始,还并没有真正去配置硬件的寄存器,让codec芯片知道究竟该怎么配置dai。 
在上面那个函数中,设置完rx_chs后立马调用了snd_soc_dapm_mux_update_power(),这个函数就是在音频链路中让自己上电,当音频链路全部上电后会触发”AIF MIX1 PB”widget的事件,该widget的回调函数:tasha_codec_enable_slimrx()。这个函数里面看上去应该是真正的配置codec和slimbus的地方,让slimbus的channel和codec的port在物理上对应起来。这里之所以是“应该”,是因为这一块还没完全弄清楚,目前只是从代码上看起来比较像,但是由于没有datasheet和相关文档,所以不知道他底下到底配置的那些是什么……所以只能猜测一下……

这里基本上把codec这边的channel map说清楚了,还有dsp那边的channel map没说…… 
dsp这里的channel map的操作在msm8996.c里面,也就是machine driver里面:

static int msm_snd_hw_params(struct snd_pcm_substream *substream,
                 struct snd_pcm_hw_params *params)
{undefined
    ……
    if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {undefined
        ret = snd_soc_dai_get_channel_map(codec_dai,
                    &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch);
        ……
        if (dai_link->be_id == MSM_BACKEND_DAI_SLIMBUS_5_RX) {undefined
            ……
        } else {undefined
            pr_debug("gift_dsp %s: rx_0_ch=%d, rx_ch_count=%d, rx_ch_cnt=%u\n", __func__,
                  msm_slim_0_rx_ch, rx_ch_count, rx_ch_cnt);
            rx_ch_count = msm_slim_0_rx_ch;
        }
        ret = snd_soc_dai_set_channel_map(cpu_dai, 0, 0,
                          rx_ch_count, rx_ch);
        ……
        }
    }
    ……
}

上面这段代码,简单来说就做了两件事,从codec dai里面把channel map读出来(其实就是这里:tasha_p->dai[AIF_MIX1_PB].wcd9xxx_ch_list),然后再把这个channel map写给cpu dai。至于里面的:rx_ch_count = msm_slim_0_rx_ch;就是单独配置了channel数,以单独配置的channel数为准,这里其实msm_slim_0_rx_ch和rx_ch_cnt都是2…… 
msm_snd_hw_params()这个函数的调用位置:

static struct snd_soc_ops msm8996_be_ops = {undefined
    .hw_params = msm_snd_hw_params,
};

其实就是dai link的opt,所以在hw_params时就会自动调过来。 
这里的cpu dai set channel map函数为:

static int msm_dai_q6_set_channel_map(struct snd_soc_dai *dai,
                unsigned int tx_num, unsigned int *tx_slot,
                unsigned int rx_num, unsigned int *rx_slot)

{undefined
    ……
    switch (dai->id) {undefined
    case SLIMBUS_0_RX:
    case SLIMBUS_1_RX:
    case SLIMBUS_2_RX:
    case SLIMBUS_3_RX:
    case SLIMBUS_4_RX:
    case SLIMBUS_5_RX:
    case SLIMBUS_6_RX:
        ……
        for (i = 0; i < rx_num; i++) {undefined
            dai_data->port_config.slim_sch.shared_ch_mapping[i] =
                rx_slot[i];
            pr_debug("%s: find number of channels[%d] ch[%d]\n",
                   __func__, i, rx_slot[i]);
        }
        dai_data->port_config.slim_sch.num_channels = rx_num;
        ……
        break;
        ……
    }
    ……
}

可以看到,这里其实是把channel信息写入了port_config结构中,这里也仅仅是软件层面上的操作,并没有触发实际的硬件行为,在cpu侧真正触发硬件行为的是在msm_dai_q6_prepare()里面调用afe_port_start()函数打开afe时,配置给dsp的afe模块,然后afe模块再去控制dsp的硬件完成的,关于afe在前面进行了记录。

至此,msm8996平台的channel map这一块基本上可以理解清楚了,唯一的悬念就是slimbus那里,codec到底配置了一些什么,以使得codec的port和slimbus的channel对应上的。这里今后如果能找到相关文档再回来记录……

附录 关于afe port id
afe里面有个afe port,这个东西在打开afe(afe_port_start())时是dai->id确定的,在打开open adm时也会用到,open adm时的这个port id是prepare时,根据:

be_id = rtd->dai_link->be_id;
bedai = &msm_bedais[be_id];
bedai->active = 1;

去激活了dai link里面定义的be id对应的msm_bedais[],然后再在msm_pcm_routing_reg_phy_stream()函数调用时去查询所有active为1的msm_bedais[],然后进行open adm。 
这里的dai link中be dai赋的值要与msm_bedais[]中的顺序完全一致,msm_bedais[]为一个adm中的全局变量。

总觉得这里这样做有点危险,不符合同一参数唯一性……如果不注意可能导致afe里面和adm里面还有dai link里面以及dai里面的定义有出入……

再来说下afe port的定义: 
apr_audio-v2.h里面定义了所有的afe port,但是有个有趣的地方:

/* Slimbus Multi channel port id pool  */
#define SLIMBUS_0_RX        0x4000
#define SLIMBUS_0_TX        0x4001
#define SLIMBUS_1_RX        0x4002
……

/*  Start of the range of port IDs for SLIMbus devices. */
#define AFE_PORT_ID_SLIMBUS_RANGE_START 0x4000

/*  End of the range of port IDs for SLIMbus devices. */
#define AFE_PORT_ID_SLIMBUS_RANGE_END \
    (AFE_PORT_ID_SLIMBUS_RANGE_START +\
    AFE_PORT_ID_SLIMBUS_RANGE_SIZE-1)
……

这里一样写的是有缺陷的……而且容易给人造成误解,这里的SLIMBUS_0_RX就是AFE_PORT_ID_SLIMBUS_RANGE_START,即使SLIMBUS_0_RX不是起始的slimbus的值,那么这里的AFE_PORT_ID_SLIMBUS_RANGE_START肯定不能用一个立即数,而必须用已经定义好的slimbus的编号的宏。或者AFE_PORT_ID_SLIMBUS_RANGE_START用立即数定义,但是slimbus的定义应该都是基于AFE_PORT_ID_SLIMBUS_RANGE_START的,这里qcom的写法是非常不严谨的编码方式!!!

同样,由于这个地方定义的不严谨,又导致了另外一个地方的定义看上去很不统一:

struct msm_pcm_routing_bdai_data {undefined
    u16 port_id; /* AFE port ID */
    u8 active; /* track if this backend is enabled */
    unsigned long fe_sessions; /* Front-end sessions */
    u64 port_sessions; /* track Tx BE ports -> Rx BE
                * number of BE should not exceed
                * the size of this field
                */
    unsigned int  sample_rate;
    unsigned int  channel;
    unsigned int  format;
    u32 compr_passthr_mode;
    char *name;
};

struct msm_pcm_routing_bdai_data msm_bedais[MSM_BACKEND_DAI_MAX] = {undefined
    { PRIMARY_I2S_RX, 0, 0, 0, 0, 0, 0, 0, LPASS_BE_PRI_I2S_RX},
    { PRIMARY_I2S_TX, 0, 0, 0, 0, 0, 0, 0, LPASS_BE_PRI_I2S_TX},
    { SLIMBUS_0_RX, 0, 0, 0, 0, 0, 0, 0, LPASS_BE_SLIMBUS_0_RX},
    { SLIMBUS_0_TX, 0, 0, 0, 0, 0, 0, 0, LPASS_BE_SLIMBUS_0_TX},
    { HDMI_RX, 0, 0, 0, 0, 0, 0, 0, LPASS_BE_HDMI},
    { INT_BT_SCO_RX, 0, 0, 0, 0, 0, 0, 0, LPASS_BE_INT_BT_SCO_RX},
    { INT_BT_SCO_TX, 0, 0, 0, 0, 0, 0, 0, LPASS_BE_INT_BT_SCO_TX},
    { INT_FM_RX, 0, 0, 0, 0, 0, 0, 0, LPASS_BE_INT_FM_RX},
    { INT_FM_TX, 0, 0, 0, 0, 0, 0, 0, LPASS_BE_INT_FM_TX},
    { RT_PROXY_PORT_001_RX, 0, 0, 0, 0, 0, 0, 0, LPASS_BE_AFE_PCM_RX},
    { RT_PROXY_PORT_001_TX, 0, 0, 0, 0, 0, 0, 0, LPASS_BE_AFE_PCM_TX},
    { AFE_PORT_ID_PRIMARY_PCM_RX, 0, 0, 0, 0, 0, 0, 0, LPASS_BE_AUXPCM_RX},
    { AFE_PORT_ID_PRIMARY_PCM_TX, 0, 0, 0, 0, 0, 0, 0, LPASS_BE_AUXPCM_TX},
    { VOICE_PLAYBACK_TX, 0, 0, 0, 0, 0, 0, 0, LPASS_BE_VOICE_PLAYBACK_TX},
    { VOICE2_PLAYBACK_TX, 0, 0, 0, 0, 0, 0, 0, LPASS_BE_VOICE2_PLAYBACK_TX},
    ……
};

可以看到,这里面的每行第一个元素应该填写afe port id的,结果这里port id的命名乱七八糟……一会是AFE_PORT_ID_XXX开头,一会是没有前缀的,很容易给人造成困惑,这个afe port到底是以什么原则来定义的……

# 欢迎使用Markdown编辑器

你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

新的改变

我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:

  1. 全新的界面设计 ,将会带来全新的写作体验;
  2. 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
  3. 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
  4. 全新的 KaTeX数学公式 语法;
  5. 增加了支持甘特图的mermaid语法1 功能;
  6. 增加了 多屏幕编辑 Markdown文章功能;
  7. 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
  8. 增加了 检查列表 功能。

功能快捷键

撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G
查找:Ctrl/Command + F
替换:Ctrl/Command + G

合理的创建标题,有助于目录的生成

直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

如何改变文本的样式

强调文本 强调文本

加粗文本 加粗文本

标记文本

删除文本

引用文本

H2O is是液体。

210 运算结果是 1024.

插入链接与图片

链接: link.

图片:

带尺寸的图片:

居中的图片:

居中并且带尺寸的图片:

当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

如何插入一段漂亮的代码片

去博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.

// An highlighted block
var foo = 'bar';

生成一个适合你的列表

  • 项目

    • 项目

      • 项目
  1. 项目1
  2. 项目2
  3. 项目3
  • 计划任务
  • 完成任务

创建一个表格

一个简单的表格是这么创建的:

项目 Value
电脑 $1600
手机 $12
导管 $1

设定内容居中、居左、居右

使用:---------:居中
使用:----------居左
使用----------:居右

第一列 第二列 第三列
第一列文本居中 第二列文本居右 第三列文本居左

SmartyPants

SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:

TYPE ASCII HTML
Single backticks 'Isn't this fun?' ‘Isn’t this fun?’
Quotes "Isn't this fun?" “Isn’t this fun?”
Dashes -- is en-dash, --- is em-dash – is en-dash, — is em-dash

创建一个自定义列表

Markdown
Text-to-HTML conversion tool
Authors
John
Luke

如何创建一个注脚

一个具有注脚的文本。2

注释也是必不可少的

Markdown将文本转换为 HTML

KaTeX数学公式

您可以使用渲染LaTeX数学表达式 KaTeX:

Gamma公式展示 Γ(n)=(n−1)!∀n∈N\Gamma(n) = (n-1)!\quad\forall n\in\mathbb NΓ(n)=(n−1)!∀n∈N 是通过欧拉积分

Γ(z)=∫0∞tz−1e−tdt.\Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=∫0∞​tz−1e−tdt.

你可以找到更多关于的信息 LaTeX 数学表达式here.

新的甘特图功能,丰富你的文章

Mon 06Mon 13Mon 20已完成 进行中 计划一 计划二 现有任务Adding GANTT diagram functionality to mermaid
  • 关于 甘特图 语法,参考 这儿,

UML 图表

可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图:

#mermaid-svg-7tLyhEjDhP6mkBrI .label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);fill:#333;color:#333}#mermaid-svg-7tLyhEjDhP6mkBrI .label text{fill:#333}#mermaid-svg-7tLyhEjDhP6mkBrI .node rect,#mermaid-svg-7tLyhEjDhP6mkBrI .node circle,#mermaid-svg-7tLyhEjDhP6mkBrI .node ellipse,#mermaid-svg-7tLyhEjDhP6mkBrI .node polygon,#mermaid-svg-7tLyhEjDhP6mkBrI .node path{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-7tLyhEjDhP6mkBrI .node .label{text-align:center;fill:#333}#mermaid-svg-7tLyhEjDhP6mkBrI .node.clickable{cursor:pointer}#mermaid-svg-7tLyhEjDhP6mkBrI .arrowheadPath{fill:#333}#mermaid-svg-7tLyhEjDhP6mkBrI .edgePath .path{stroke:#333;stroke-width:1.5px}#mermaid-svg-7tLyhEjDhP6mkBrI .flowchart-link{stroke:#333;fill:none}#mermaid-svg-7tLyhEjDhP6mkBrI .edgeLabel{background-color:#e8e8e8;text-align:center}#mermaid-svg-7tLyhEjDhP6mkBrI .edgeLabel rect{opacity:0.9}#mermaid-svg-7tLyhEjDhP6mkBrI .edgeLabel span{color:#333}#mermaid-svg-7tLyhEjDhP6mkBrI .cluster rect{fill:#ffffde;stroke:#aa3;stroke-width:1px}#mermaid-svg-7tLyhEjDhP6mkBrI .cluster text{fill:#333}#mermaid-svg-7tLyhEjDhP6mkBrI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#ffffde;border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}#mermaid-svg-7tLyhEjDhP6mkBrI .actor{stroke:#ccf;fill:#ECECFF}#mermaid-svg-7tLyhEjDhP6mkBrI text.actor>tspan{fill:#000;stroke:none}#mermaid-svg-7tLyhEjDhP6mkBrI .actor-line{stroke:grey}#mermaid-svg-7tLyhEjDhP6mkBrI .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333}#mermaid-svg-7tLyhEjDhP6mkBrI .messageLine1{stroke-width:1.5;stroke-dasharray:2, 2;stroke:#333}#mermaid-svg-7tLyhEjDhP6mkBrI #arrowhead path{fill:#333;stroke:#333}#mermaid-svg-7tLyhEjDhP6mkBrI .sequenceNumber{fill:#fff}#mermaid-svg-7tLyhEjDhP6mkBrI #sequencenumber{fill:#333}#mermaid-svg-7tLyhEjDhP6mkBrI #crosshead path{fill:#333;stroke:#333}#mermaid-svg-7tLyhEjDhP6mkBrI .messageText{fill:#333;stroke:#333}#mermaid-svg-7tLyhEjDhP6mkBrI .labelBox{stroke:#ccf;fill:#ECECFF}#mermaid-svg-7tLyhEjDhP6mkBrI .labelText,#mermaid-svg-7tLyhEjDhP6mkBrI .labelText>tspan{fill:#000;stroke:none}#mermaid-svg-7tLyhEjDhP6mkBrI .loopText,#mermaid-svg-7tLyhEjDhP6mkBrI .loopText>tspan{fill:#000;stroke:none}#mermaid-svg-7tLyhEjDhP6mkBrI .loopLine{stroke-width:2px;stroke-dasharray:2, 2;stroke:#ccf;fill:#ccf}#mermaid-svg-7tLyhEjDhP6mkBrI .note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-7tLyhEjDhP6mkBrI .noteText,#mermaid-svg-7tLyhEjDhP6mkBrI .noteText>tspan{fill:#000;stroke:none}#mermaid-svg-7tLyhEjDhP6mkBrI .activation0{fill:#f4f4f4;stroke:#666}#mermaid-svg-7tLyhEjDhP6mkBrI .activation1{fill:#f4f4f4;stroke:#666}#mermaid-svg-7tLyhEjDhP6mkBrI .activation2{fill:#f4f4f4;stroke:#666}#mermaid-svg-7tLyhEjDhP6mkBrI .mermaid-main-font{font-family:"trebuchet ms", verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7tLyhEjDhP6mkBrI .section{stroke:none;opacity:0.2}#mermaid-svg-7tLyhEjDhP6mkBrI .section0{fill:rgba(102,102,255,0.49)}#mermaid-svg-7tLyhEjDhP6mkBrI .section2{fill:#fff400}#mermaid-svg-7tLyhEjDhP6mkBrI .section1,#mermaid-svg-7tLyhEjDhP6mkBrI .section3{fill:#fff;opacity:0.2}#mermaid-svg-7tLyhEjDhP6mkBrI .sectionTitle0{fill:#333}#mermaid-svg-7tLyhEjDhP6mkBrI .sectionTitle1{fill:#333}#mermaid-svg-7tLyhEjDhP6mkBrI .sectionTitle2{fill:#333}#mermaid-svg-7tLyhEjDhP6mkBrI .sectionTitle3{fill:#333}#mermaid-svg-7tLyhEjDhP6mkBrI .sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7tLyhEjDhP6mkBrI .grid .tick{stroke:#d3d3d3;opacity:0.8;shape-rendering:crispEdges}#mermaid-svg-7tLyhEjDhP6mkBrI .grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7tLyhEjDhP6mkBrI .grid path{stroke-width:0}#mermaid-svg-7tLyhEjDhP6mkBrI .today{fill:none;stroke:red;stroke-width:2px}#mermaid-svg-7tLyhEjDhP6mkBrI .task{stroke-width:2}#mermaid-svg-7tLyhEjDhP6mkBrI .taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7tLyhEjDhP6mkBrI .taskText:not([font-size]){font-size:11px}#mermaid-svg-7tLyhEjDhP6mkBrI .taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7tLyhEjDhP6mkBrI .taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}#mermaid-svg-7tLyhEjDhP6mkBrI .task.clickable{cursor:pointer}#mermaid-svg-7tLyhEjDhP6mkBrI .taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-7tLyhEjDhP6mkBrI .taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-7tLyhEjDhP6mkBrI .taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-7tLyhEjDhP6mkBrI .taskText0,#mermaid-svg-7tLyhEjDhP6mkBrI .taskText1,#mermaid-svg-7tLyhEjDhP6mkBrI .taskText2,#mermaid-svg-7tLyhEjDhP6mkBrI .taskText3{fill:#fff}#mermaid-svg-7tLyhEjDhP6mkBrI .task0,#mermaid-svg-7tLyhEjDhP6mkBrI .task1,#mermaid-svg-7tLyhEjDhP6mkBrI .task2,#mermaid-svg-7tLyhEjDhP6mkBrI .task3{fill:#8a90dd;stroke:#534fbc}#mermaid-svg-7tLyhEjDhP6mkBrI .taskTextOutside0,#mermaid-svg-7tLyhEjDhP6mkBrI .taskTextOutside2{fill:#000}#mermaid-svg-7tLyhEjDhP6mkBrI .taskTextOutside1,#mermaid-svg-7tLyhEjDhP6mkBrI .taskTextOutside3{fill:#000}#mermaid-svg-7tLyhEjDhP6mkBrI .active0,#mermaid-svg-7tLyhEjDhP6mkBrI .active1,#mermaid-svg-7tLyhEjDhP6mkBrI .active2,#mermaid-svg-7tLyhEjDhP6mkBrI .active3{fill:#bfc7ff;stroke:#534fbc}#mermaid-svg-7tLyhEjDhP6mkBrI .activeText0,#mermaid-svg-7tLyhEjDhP6mkBrI .activeText1,#mermaid-svg-7tLyhEjDhP6mkBrI .activeText2,#mermaid-svg-7tLyhEjDhP6mkBrI .activeText3{fill:#000 !important}#mermaid-svg-7tLyhEjDhP6mkBrI .done0,#mermaid-svg-7tLyhEjDhP6mkBrI .done1,#mermaid-svg-7tLyhEjDhP6mkBrI .done2,#mermaid-svg-7tLyhEjDhP6mkBrI .done3{stroke:grey;fill:#d3d3d3;stroke-width:2}#mermaid-svg-7tLyhEjDhP6mkBrI .doneText0,#mermaid-svg-7tLyhEjDhP6mkBrI .doneText1,#mermaid-svg-7tLyhEjDhP6mkBrI .doneText2,#mermaid-svg-7tLyhEjDhP6mkBrI .doneText3{fill:#000 !important}#mermaid-svg-7tLyhEjDhP6mkBrI .crit0,#mermaid-svg-7tLyhEjDhP6mkBrI .crit1,#mermaid-svg-7tLyhEjDhP6mkBrI .crit2,#mermaid-svg-7tLyhEjDhP6mkBrI .crit3{stroke:#f88;fill:red;stroke-width:2}#mermaid-svg-7tLyhEjDhP6mkBrI .activeCrit0,#mermaid-svg-7tLyhEjDhP6mkBrI .activeCrit1,#mermaid-svg-7tLyhEjDhP6mkBrI .activeCrit2,#mermaid-svg-7tLyhEjDhP6mkBrI .activeCrit3{stroke:#f88;fill:#bfc7ff;stroke-width:2}#mermaid-svg-7tLyhEjDhP6mkBrI .doneCrit0,#mermaid-svg-7tLyhEjDhP6mkBrI .doneCrit1,#mermaid-svg-7tLyhEjDhP6mkBrI .doneCrit2,#mermaid-svg-7tLyhEjDhP6mkBrI .doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}#mermaid-svg-7tLyhEjDhP6mkBrI .milestone{transform:rotate(45deg) scale(0.8, 0.8)}#mermaid-svg-7tLyhEjDhP6mkBrI .milestoneText{font-style:italic}#mermaid-svg-7tLyhEjDhP6mkBrI .doneCritText0,#mermaid-svg-7tLyhEjDhP6mkBrI .doneCritText1,#mermaid-svg-7tLyhEjDhP6mkBrI .doneCritText2,#mermaid-svg-7tLyhEjDhP6mkBrI .doneCritText3{fill:#000 !important}#mermaid-svg-7tLyhEjDhP6mkBrI .activeCritText0,#mermaid-svg-7tLyhEjDhP6mkBrI .activeCritText1,#mermaid-svg-7tLyhEjDhP6mkBrI .activeCritText2,#mermaid-svg-7tLyhEjDhP6mkBrI .activeCritText3{fill:#000 !important}#mermaid-svg-7tLyhEjDhP6mkBrI .titleText{text-anchor:middle;font-size:18px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7tLyhEjDhP6mkBrI g.classGroup text{fill:#9370db;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}#mermaid-svg-7tLyhEjDhP6mkBrI g.classGroup text .title{font-weight:bolder}#mermaid-svg-7tLyhEjDhP6mkBrI g.clickable{cursor:pointer}#mermaid-svg-7tLyhEjDhP6mkBrI g.classGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-7tLyhEjDhP6mkBrI g.classGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-7tLyhEjDhP6mkBrI .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5}#mermaid-svg-7tLyhEjDhP6mkBrI .classLabel .label{fill:#9370db;font-size:10px}#mermaid-svg-7tLyhEjDhP6mkBrI .relation{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-7tLyhEjDhP6mkBrI .dashed-line{stroke-dasharray:3}#mermaid-svg-7tLyhEjDhP6mkBrI #compositionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-7tLyhEjDhP6mkBrI #compositionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-7tLyhEjDhP6mkBrI #aggregationStart{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-7tLyhEjDhP6mkBrI #aggregationEnd{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-7tLyhEjDhP6mkBrI #dependencyStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-7tLyhEjDhP6mkBrI #dependencyEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-7tLyhEjDhP6mkBrI #extensionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-7tLyhEjDhP6mkBrI #extensionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-7tLyhEjDhP6mkBrI .commit-id,#mermaid-svg-7tLyhEjDhP6mkBrI .commit-msg,#mermaid-svg-7tLyhEjDhP6mkBrI .branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7tLyhEjDhP6mkBrI .pieTitleText{text-anchor:middle;font-size:25px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7tLyhEjDhP6mkBrI .slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7tLyhEjDhP6mkBrI g.stateGroup text{fill:#9370db;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7tLyhEjDhP6mkBrI g.stateGroup text{fill:#9370db;fill:#333;stroke:none;font-size:10px}#mermaid-svg-7tLyhEjDhP6mkBrI g.statediagram-cluster .cluster-label text{fill:#333}#mermaid-svg-7tLyhEjDhP6mkBrI g.stateGroup .state-title{font-weight:bolder;fill:#000}#mermaid-svg-7tLyhEjDhP6mkBrI g.stateGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-7tLyhEjDhP6mkBrI g.stateGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-7tLyhEjDhP6mkBrI .transition{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-7tLyhEjDhP6mkBrI .stateGroup .composit{fill:white;border-bottom:1px}#mermaid-svg-7tLyhEjDhP6mkBrI .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px}#mermaid-svg-7tLyhEjDhP6mkBrI .state-note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-7tLyhEjDhP6mkBrI .state-note text{fill:black;stroke:none;font-size:10px}#mermaid-svg-7tLyhEjDhP6mkBrI .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.7}#mermaid-svg-7tLyhEjDhP6mkBrI .edgeLabel text{fill:#333}#mermaid-svg-7tLyhEjDhP6mkBrI .stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7tLyhEjDhP6mkBrI .node circle.state-start{fill:black;stroke:black}#mermaid-svg-7tLyhEjDhP6mkBrI .node circle.state-end{fill:black;stroke:white;stroke-width:1.5}#mermaid-svg-7tLyhEjDhP6mkBrI #statediagram-barbEnd{fill:#9370db}#mermaid-svg-7tLyhEjDhP6mkBrI .statediagram-cluster rect{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-7tLyhEjDhP6mkBrI .statediagram-cluster rect.outer{rx:5px;ry:5px}#mermaid-svg-7tLyhEjDhP6mkBrI .statediagram-state .divider{stroke:#9370db}#mermaid-svg-7tLyhEjDhP6mkBrI .statediagram-state .title-state{rx:5px;ry:5px}#mermaid-svg-7tLyhEjDhP6mkBrI .statediagram-cluster.statediagram-cluster .inner{fill:white}#mermaid-svg-7tLyhEjDhP6mkBrI .statediagram-cluster.statediagram-cluster-alt .inner{fill:#e0e0e0}#mermaid-svg-7tLyhEjDhP6mkBrI .statediagram-cluster .inner{rx:0;ry:0}#mermaid-svg-7tLyhEjDhP6mkBrI .statediagram-state rect.basic{rx:5px;ry:5px}#mermaid-svg-7tLyhEjDhP6mkBrI .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#efefef}#mermaid-svg-7tLyhEjDhP6mkBrI .note-edge{stroke-dasharray:5}#mermaid-svg-7tLyhEjDhP6mkBrI .statediagram-note rect{fill:#fff5ad;stroke:#aa3;stroke-width:1px;rx:0;ry:0}:root{--mermaid-font-family: '"trebuchet ms", verdana, arial';--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive}#mermaid-svg-7tLyhEjDhP6mkBrI .error-icon{fill:#522}#mermaid-svg-7tLyhEjDhP6mkBrI .error-text{fill:#522;stroke:#522}#mermaid-svg-7tLyhEjDhP6mkBrI .edge-thickness-normal{stroke-width:2px}#mermaid-svg-7tLyhEjDhP6mkBrI .edge-thickness-thick{stroke-width:3.5px}#mermaid-svg-7tLyhEjDhP6mkBrI .edge-pattern-solid{stroke-dasharray:0}#mermaid-svg-7tLyhEjDhP6mkBrI .edge-pattern-dashed{stroke-dasharray:3}#mermaid-svg-7tLyhEjDhP6mkBrI .edge-pattern-dotted{stroke-dasharray:2}#mermaid-svg-7tLyhEjDhP6mkBrI .marker{fill:#333}#mermaid-svg-7tLyhEjDhP6mkBrI .marker.cross{stroke:#333}:root { --mermaid-font-family: "trebuchet ms", verdana, arial;}#mermaid-svg-7tLyhEjDhP6mkBrI {color: rgba(0, 0, 0, 0.75);font: ;}张三李四王五你好!李四, 最近怎么样?你最近怎么样,王五?我很好,谢谢!我很好,谢谢!李四想了很长时间, 文字太长了不适合放在一行.打量着王五...很好... 王五, 你怎么样?张三李四王五

这将产生一个流程图。:

#mermaid-svg-ZSZv1OY9djlXveYq .label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);fill:#333;color:#333}#mermaid-svg-ZSZv1OY9djlXveYq .label text{fill:#333}#mermaid-svg-ZSZv1OY9djlXveYq .node rect,#mermaid-svg-ZSZv1OY9djlXveYq .node circle,#mermaid-svg-ZSZv1OY9djlXveYq .node ellipse,#mermaid-svg-ZSZv1OY9djlXveYq .node polygon,#mermaid-svg-ZSZv1OY9djlXveYq .node path{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-ZSZv1OY9djlXveYq .node .label{text-align:center;fill:#333}#mermaid-svg-ZSZv1OY9djlXveYq .node.clickable{cursor:pointer}#mermaid-svg-ZSZv1OY9djlXveYq .arrowheadPath{fill:#333}#mermaid-svg-ZSZv1OY9djlXveYq .edgePath .path{stroke:#333;stroke-width:1.5px}#mermaid-svg-ZSZv1OY9djlXveYq .flowchart-link{stroke:#333;fill:none}#mermaid-svg-ZSZv1OY9djlXveYq .edgeLabel{background-color:#e8e8e8;text-align:center}#mermaid-svg-ZSZv1OY9djlXveYq .edgeLabel rect{opacity:0.9}#mermaid-svg-ZSZv1OY9djlXveYq .edgeLabel span{color:#333}#mermaid-svg-ZSZv1OY9djlXveYq .cluster rect{fill:#ffffde;stroke:#aa3;stroke-width:1px}#mermaid-svg-ZSZv1OY9djlXveYq .cluster text{fill:#333}#mermaid-svg-ZSZv1OY9djlXveYq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#ffffde;border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}#mermaid-svg-ZSZv1OY9djlXveYq .actor{stroke:#ccf;fill:#ECECFF}#mermaid-svg-ZSZv1OY9djlXveYq text.actor>tspan{fill:#000;stroke:none}#mermaid-svg-ZSZv1OY9djlXveYq .actor-line{stroke:grey}#mermaid-svg-ZSZv1OY9djlXveYq .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333}#mermaid-svg-ZSZv1OY9djlXveYq .messageLine1{stroke-width:1.5;stroke-dasharray:2, 2;stroke:#333}#mermaid-svg-ZSZv1OY9djlXveYq #arrowhead path{fill:#333;stroke:#333}#mermaid-svg-ZSZv1OY9djlXveYq .sequenceNumber{fill:#fff}#mermaid-svg-ZSZv1OY9djlXveYq #sequencenumber{fill:#333}#mermaid-svg-ZSZv1OY9djlXveYq #crosshead path{fill:#333;stroke:#333}#mermaid-svg-ZSZv1OY9djlXveYq .messageText{fill:#333;stroke:#333}#mermaid-svg-ZSZv1OY9djlXveYq .labelBox{stroke:#ccf;fill:#ECECFF}#mermaid-svg-ZSZv1OY9djlXveYq .labelText,#mermaid-svg-ZSZv1OY9djlXveYq .labelText>tspan{fill:#000;stroke:none}#mermaid-svg-ZSZv1OY9djlXveYq .loopText,#mermaid-svg-ZSZv1OY9djlXveYq .loopText>tspan{fill:#000;stroke:none}#mermaid-svg-ZSZv1OY9djlXveYq .loopLine{stroke-width:2px;stroke-dasharray:2, 2;stroke:#ccf;fill:#ccf}#mermaid-svg-ZSZv1OY9djlXveYq .note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-ZSZv1OY9djlXveYq .noteText,#mermaid-svg-ZSZv1OY9djlXveYq .noteText>tspan{fill:#000;stroke:none}#mermaid-svg-ZSZv1OY9djlXveYq .activation0{fill:#f4f4f4;stroke:#666}#mermaid-svg-ZSZv1OY9djlXveYq .activation1{fill:#f4f4f4;stroke:#666}#mermaid-svg-ZSZv1OY9djlXveYq .activation2{fill:#f4f4f4;stroke:#666}#mermaid-svg-ZSZv1OY9djlXveYq .mermaid-main-font{font-family:"trebuchet ms", verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-ZSZv1OY9djlXveYq .section{stroke:none;opacity:0.2}#mermaid-svg-ZSZv1OY9djlXveYq .section0{fill:rgba(102,102,255,0.49)}#mermaid-svg-ZSZv1OY9djlXveYq .section2{fill:#fff400}#mermaid-svg-ZSZv1OY9djlXveYq .section1,#mermaid-svg-ZSZv1OY9djlXveYq .section3{fill:#fff;opacity:0.2}#mermaid-svg-ZSZv1OY9djlXveYq .sectionTitle0{fill:#333}#mermaid-svg-ZSZv1OY9djlXveYq .sectionTitle1{fill:#333}#mermaid-svg-ZSZv1OY9djlXveYq .sectionTitle2{fill:#333}#mermaid-svg-ZSZv1OY9djlXveYq .sectionTitle3{fill:#333}#mermaid-svg-ZSZv1OY9djlXveYq .sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-ZSZv1OY9djlXveYq .grid .tick{stroke:#d3d3d3;opacity:0.8;shape-rendering:crispEdges}#mermaid-svg-ZSZv1OY9djlXveYq .grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-ZSZv1OY9djlXveYq .grid path{stroke-width:0}#mermaid-svg-ZSZv1OY9djlXveYq .today{fill:none;stroke:red;stroke-width:2px}#mermaid-svg-ZSZv1OY9djlXveYq .task{stroke-width:2}#mermaid-svg-ZSZv1OY9djlXveYq .taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-ZSZv1OY9djlXveYq .taskText:not([font-size]){font-size:11px}#mermaid-svg-ZSZv1OY9djlXveYq .taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-ZSZv1OY9djlXveYq .taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}#mermaid-svg-ZSZv1OY9djlXveYq .task.clickable{cursor:pointer}#mermaid-svg-ZSZv1OY9djlXveYq .taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-ZSZv1OY9djlXveYq .taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-ZSZv1OY9djlXveYq .taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-ZSZv1OY9djlXveYq .taskText0,#mermaid-svg-ZSZv1OY9djlXveYq .taskText1,#mermaid-svg-ZSZv1OY9djlXveYq .taskText2,#mermaid-svg-ZSZv1OY9djlXveYq .taskText3{fill:#fff}#mermaid-svg-ZSZv1OY9djlXveYq .task0,#mermaid-svg-ZSZv1OY9djlXveYq .task1,#mermaid-svg-ZSZv1OY9djlXveYq .task2,#mermaid-svg-ZSZv1OY9djlXveYq .task3{fill:#8a90dd;stroke:#534fbc}#mermaid-svg-ZSZv1OY9djlXveYq .taskTextOutside0,#mermaid-svg-ZSZv1OY9djlXveYq .taskTextOutside2{fill:#000}#mermaid-svg-ZSZv1OY9djlXveYq .taskTextOutside1,#mermaid-svg-ZSZv1OY9djlXveYq .taskTextOutside3{fill:#000}#mermaid-svg-ZSZv1OY9djlXveYq .active0,#mermaid-svg-ZSZv1OY9djlXveYq .active1,#mermaid-svg-ZSZv1OY9djlXveYq .active2,#mermaid-svg-ZSZv1OY9djlXveYq .active3{fill:#bfc7ff;stroke:#534fbc}#mermaid-svg-ZSZv1OY9djlXveYq .activeText0,#mermaid-svg-ZSZv1OY9djlXveYq .activeText1,#mermaid-svg-ZSZv1OY9djlXveYq .activeText2,#mermaid-svg-ZSZv1OY9djlXveYq .activeText3{fill:#000 !important}#mermaid-svg-ZSZv1OY9djlXveYq .done0,#mermaid-svg-ZSZv1OY9djlXveYq .done1,#mermaid-svg-ZSZv1OY9djlXveYq .done2,#mermaid-svg-ZSZv1OY9djlXveYq .done3{stroke:grey;fill:#d3d3d3;stroke-width:2}#mermaid-svg-ZSZv1OY9djlXveYq .doneText0,#mermaid-svg-ZSZv1OY9djlXveYq .doneText1,#mermaid-svg-ZSZv1OY9djlXveYq .doneText2,#mermaid-svg-ZSZv1OY9djlXveYq .doneText3{fill:#000 !important}#mermaid-svg-ZSZv1OY9djlXveYq .crit0,#mermaid-svg-ZSZv1OY9djlXveYq .crit1,#mermaid-svg-ZSZv1OY9djlXveYq .crit2,#mermaid-svg-ZSZv1OY9djlXveYq .crit3{stroke:#f88;fill:red;stroke-width:2}#mermaid-svg-ZSZv1OY9djlXveYq .activeCrit0,#mermaid-svg-ZSZv1OY9djlXveYq .activeCrit1,#mermaid-svg-ZSZv1OY9djlXveYq .activeCrit2,#mermaid-svg-ZSZv1OY9djlXveYq .activeCrit3{stroke:#f88;fill:#bfc7ff;stroke-width:2}#mermaid-svg-ZSZv1OY9djlXveYq .doneCrit0,#mermaid-svg-ZSZv1OY9djlXveYq .doneCrit1,#mermaid-svg-ZSZv1OY9djlXveYq .doneCrit2,#mermaid-svg-ZSZv1OY9djlXveYq .doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}#mermaid-svg-ZSZv1OY9djlXveYq .milestone{transform:rotate(45deg) scale(0.8, 0.8)}#mermaid-svg-ZSZv1OY9djlXveYq .milestoneText{font-style:italic}#mermaid-svg-ZSZv1OY9djlXveYq .doneCritText0,#mermaid-svg-ZSZv1OY9djlXveYq .doneCritText1,#mermaid-svg-ZSZv1OY9djlXveYq .doneCritText2,#mermaid-svg-ZSZv1OY9djlXveYq .doneCritText3{fill:#000 !important}#mermaid-svg-ZSZv1OY9djlXveYq .activeCritText0,#mermaid-svg-ZSZv1OY9djlXveYq .activeCritText1,#mermaid-svg-ZSZv1OY9djlXveYq .activeCritText2,#mermaid-svg-ZSZv1OY9djlXveYq .activeCritText3{fill:#000 !important}#mermaid-svg-ZSZv1OY9djlXveYq .titleText{text-anchor:middle;font-size:18px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-ZSZv1OY9djlXveYq g.classGroup text{fill:#9370db;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}#mermaid-svg-ZSZv1OY9djlXveYq g.classGroup text .title{font-weight:bolder}#mermaid-svg-ZSZv1OY9djlXveYq g.clickable{cursor:pointer}#mermaid-svg-ZSZv1OY9djlXveYq g.classGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-ZSZv1OY9djlXveYq g.classGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-ZSZv1OY9djlXveYq .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5}#mermaid-svg-ZSZv1OY9djlXveYq .classLabel .label{fill:#9370db;font-size:10px}#mermaid-svg-ZSZv1OY9djlXveYq .relation{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-ZSZv1OY9djlXveYq .dashed-line{stroke-dasharray:3}#mermaid-svg-ZSZv1OY9djlXveYq #compositionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-ZSZv1OY9djlXveYq #compositionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-ZSZv1OY9djlXveYq #aggregationStart{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-ZSZv1OY9djlXveYq #aggregationEnd{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-ZSZv1OY9djlXveYq #dependencyStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-ZSZv1OY9djlXveYq #dependencyEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-ZSZv1OY9djlXveYq #extensionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-ZSZv1OY9djlXveYq #extensionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-ZSZv1OY9djlXveYq .commit-id,#mermaid-svg-ZSZv1OY9djlXveYq .commit-msg,#mermaid-svg-ZSZv1OY9djlXveYq .branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-ZSZv1OY9djlXveYq .pieTitleText{text-anchor:middle;font-size:25px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-ZSZv1OY9djlXveYq .slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-ZSZv1OY9djlXveYq g.stateGroup text{fill:#9370db;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-ZSZv1OY9djlXveYq g.stateGroup text{fill:#9370db;fill:#333;stroke:none;font-size:10px}#mermaid-svg-ZSZv1OY9djlXveYq g.statediagram-cluster .cluster-label text{fill:#333}#mermaid-svg-ZSZv1OY9djlXveYq g.stateGroup .state-title{font-weight:bolder;fill:#000}#mermaid-svg-ZSZv1OY9djlXveYq g.stateGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-ZSZv1OY9djlXveYq g.stateGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-ZSZv1OY9djlXveYq .transition{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-ZSZv1OY9djlXveYq .stateGroup .composit{fill:white;border-bottom:1px}#mermaid-svg-ZSZv1OY9djlXveYq .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px}#mermaid-svg-ZSZv1OY9djlXveYq .state-note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-ZSZv1OY9djlXveYq .state-note text{fill:black;stroke:none;font-size:10px}#mermaid-svg-ZSZv1OY9djlXveYq .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.7}#mermaid-svg-ZSZv1OY9djlXveYq .edgeLabel text{fill:#333}#mermaid-svg-ZSZv1OY9djlXveYq .stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-ZSZv1OY9djlXveYq .node circle.state-start{fill:black;stroke:black}#mermaid-svg-ZSZv1OY9djlXveYq .node circle.state-end{fill:black;stroke:white;stroke-width:1.5}#mermaid-svg-ZSZv1OY9djlXveYq #statediagram-barbEnd{fill:#9370db}#mermaid-svg-ZSZv1OY9djlXveYq .statediagram-cluster rect{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-ZSZv1OY9djlXveYq .statediagram-cluster rect.outer{rx:5px;ry:5px}#mermaid-svg-ZSZv1OY9djlXveYq .statediagram-state .divider{stroke:#9370db}#mermaid-svg-ZSZv1OY9djlXveYq .statediagram-state .title-state{rx:5px;ry:5px}#mermaid-svg-ZSZv1OY9djlXveYq .statediagram-cluster.statediagram-cluster .inner{fill:white}#mermaid-svg-ZSZv1OY9djlXveYq .statediagram-cluster.statediagram-cluster-alt .inner{fill:#e0e0e0}#mermaid-svg-ZSZv1OY9djlXveYq .statediagram-cluster .inner{rx:0;ry:0}#mermaid-svg-ZSZv1OY9djlXveYq .statediagram-state rect.basic{rx:5px;ry:5px}#mermaid-svg-ZSZv1OY9djlXveYq .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#efefef}#mermaid-svg-ZSZv1OY9djlXveYq .note-edge{stroke-dasharray:5}#mermaid-svg-ZSZv1OY9djlXveYq .statediagram-note rect{fill:#fff5ad;stroke:#aa3;stroke-width:1px;rx:0;ry:0}:root{--mermaid-font-family: '"trebuchet ms", verdana, arial';--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive}#mermaid-svg-ZSZv1OY9djlXveYq .error-icon{fill:#522}#mermaid-svg-ZSZv1OY9djlXveYq .error-text{fill:#522;stroke:#522}#mermaid-svg-ZSZv1OY9djlXveYq .edge-thickness-normal{stroke-width:2px}#mermaid-svg-ZSZv1OY9djlXveYq .edge-thickness-thick{stroke-width:3.5px}#mermaid-svg-ZSZv1OY9djlXveYq .edge-pattern-solid{stroke-dasharray:0}#mermaid-svg-ZSZv1OY9djlXveYq .edge-pattern-dashed{stroke-dasharray:3}#mermaid-svg-ZSZv1OY9djlXveYq .edge-pattern-dotted{stroke-dasharray:2}#mermaid-svg-ZSZv1OY9djlXveYq .marker{fill:#333}#mermaid-svg-ZSZv1OY9djlXveYq .marker.cross{stroke:#333}:root { --mermaid-font-family: "trebuchet ms", verdana, arial;}#mermaid-svg-ZSZv1OY9djlXveYq {color: rgba(0, 0, 0, 0.75);font: ;}

链接
长方形
圆角长方形
菱形
  • 关于 Mermaid 语法,参考 这儿,

FLowchart流程图

我们依旧会支持flowchart的流程图:

Created with Raphaël 2.3.0开始我的操作确认?结束yesno
  • 关于 Flowchart流程图 语法,参考 这儿.

导出与导入

导出

如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

导入

如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。


  1. mermaid语法说明 ↩︎

  2. 注脚的解释 ↩︎

qcom 音频相关的dsp driver笔记(基于msm8996平台)相关推荐

  1. qcom vfe笔记(msm8996平台)

    qcom vfe笔记(msm8996平台) tags : msm8996 camera 文章目录 qcom vfe笔记(msm8996平台) @[toc] 0 前言 1 camera smmu初始化 ...

  2. [DSP学习笔记]基于TMS320F28335的FFT及加窗函数实现

    一.新建工程 首先我们先将C2000 wave中我们所需要的函数库给导入到我们的新建工程中(可见于我实现FIR滤波文章中导入函数库的操作). 工程中可见有以下文件.(仅FFT可删除滤波有关文件) 接着 ...

  3. [DSP学习笔记]基于TMS320F28335的FIR滤波实现

    首先进入TI官网,搜索C2000 wave,进行下载安装. 安装完成后,在2000 wave的安装目录下,进入以下目录:C2000Ware_4_02_00_00\libraries\dsp\FPU\c ...

  4. 【转载】高通msm8996平台的ASOC音频路径分析(基于androidN及linux3.1x)

    高通msm8996平台的ASOC音频路径分析(基于androidN及linux3.1x) tags : msm8996 sound linux android 原文:高通msm8996平台的ASOC音 ...

  5. master节点重置后添加node报错_企业实战(23)基于Docker平台的Jenkins添加node节点与报错详解(2)...

    相关文章  企业实战(23)基于Docker平台部署Jenkins中国定制版(1) 添加node节点前配置 在这里插入图片描述 在这里插入图片描述 开始添加node节点 在这里插入图片描述 在这里插入 ...

  6. 安卓端gis_基于Android平台的移动GIS研究与实现

    基于 Android 平台的移动 GIS 研究与实现 张俊杰 ; 张海燕 ; 罗锐 [期刊名称] <计算机工程与设计> [年 ( 卷 ), 期] 2013(034)009 [摘要] 为满足 ...

  7. DSP学习笔记之EPWM

    DSP学习笔记之EWPM学习 \qquad 学习PWM模块的知识,最少需要掌握频率可调.占空比可调.互补死区可调.多个PWM相位差可调等.内容较多,主要是参照英文手册中几个模块的介绍,内容很多,最基本 ...

  8. # 蓝牙音频相关知识

    蓝牙音频相关知识 文章目录 蓝牙音频相关知识 1 音频源 2 蓝牙音频编解码器 3 一些标准 4 蓝牙音频其他相关知识 4.1 蓝牙版本 4.2 ANC(主动降噪) 4.3 音响相关参数 4.4 音质 ...

  9. 区块链技术背景下数字音频商业模式变革的逻辑 - 基于云听、喜马拉雅FM和CastBox的对比分析

    本文经过<传媒>杂志老师的授权.2022-08-24 ,我在微信公众号 乐生活与爱IT Plus上发表了原创文章<观念即商品.传播即分销.互动即迭代 |元宇宙和新媒体>,并提出 ...

最新文章

  1. Python字符串处理函数
  2. 皮一皮:为啥年轻人不生孩子?
  3. python 调用C++,传递int,char,char*,数组和多维数组
  4. jquery学习手记(3)属性
  5. Imageloader1-总体简介
  6. 蓝桥杯java a组_蓝桥杯十一届JavaA组-C++解题
  7. 使用SQL Server连接xml接口,读取并解析数据
  8. php cli swoole mysql_[了解实践]Swoole、PHP与MySQL:连接池,swoole扩展实现真正的PHP数据库连接池。...
  9. python读入txt数据,并转成矩阵
  10. 【Python】Python3.7.3 - memoization 结果缓存记忆程序设计优化技术
  11. 第十一天-linux的硬链接和软连接的区别
  12. 解决cdh4.5.0下 MAP任务看不到状态
  13. macOS Big Sur:快速入门的50个使用技巧
  14. 【通信总线】CAN 总线简介及应用
  15. rhino插件-创建犀牛软件皮肤-rhino皮肤-界面开发-犀牛插件
  16. 数字图像处理编程入门笔记
  17. 代码动态改变view的大小
  18. 如何将多张图片拼成一张图?
  19. VB集成无标题栏Form图片按钮Activex
  20. 七牛云发送短信验证码

热门文章

  1. java pmt以及ipmt计算
  2. 微信企业号开发-如何建立连接
  3. 微信企业号 sdk java_基于Java spring框架的微信企业号开发中关于js-sdk的配置
  4. Java中数字转中文数字
  5. SVG是什么?SVG有什么用途?
  6. Method_Confusion_Attack_on_Bluetooth_Pairing
  7. pandas合并excel
  8. attactEvent与addEventListener
  9. 无线网络性能测试 软件,WiFi性能测试
  10. 关于更佳学术搜索及Android SDK更新问题