STM32MP157-Linux音频应用编程-简易语音助手
文章目录
- 前言
- STM32MP157简易语音助手
- alsa-lib简介:
- 移植alsa-lib库:
- libcurl库简介:
- 移植libcurl库:
- API调用
- 修改asrmain.c文件
- 修改token.c文件
- 录音
- 文件IO
- 打开音频文件
- 硬件控制
- sysfs文件系统
- 数据解析和控制
- 多线程
- 主循环
- 实现效果及注意事项
- 实现效果
- 注意事项
- 源代码(转载请注明出处)
前言
本篇分享:
Linux应用编程之音频编程,使用户用户可以使用语音控制开发板上的LED灯和蜂鸣器模块。
环境介绍:
系统:Linux
硬件:正点原子STM32MP157开发板
声卡:开发板自带
STM32MP157简易语音助手
实现目标 :用户可以使用语音控制开发板上的LED灯和蜂鸣器模块。
知识点 : C语言、文件IO、alsa-lib 库的使用、libcurl库、API调用、字符串解析、多线程。
在上一篇STM32MP157语音识别项目中,由于之前使用的交叉编译器无法正常编译使用了libcurl库的程序,导致不得不使用execl函数调用CURL指令实现(可能是由于交叉编译器的c库版本和libcurl库的c库版本不同导致)。这样的程序不够灵活(依赖操作系统提供的指令和参数格式)且性能低(调用指令时需要花费额外的系统开销和时间,包括切换上下文、启动子进程、进行系统调用等操作,而使用c库函数则避免了这些额外的开销 )。所以,这次继续沿用Linux语音识别项目中用到的libcurl库来实现。
alsa-lib简介:
alsa-lib 是一套 Linux 应用层的 C 语言函数库,为音频应用程序开发提供了一套统一、标准的接口,应用程序只需调用这一套 API 即可完成对底层声卡设备的操控,譬如播放与录音。
用户空间的 alsa-lib 对应用程序提供了统一的API 接口,这样可以隐藏驱动层的实现细节,简化了应用 程序的实现难度、无需应用程序开发人员直接去读写音频设备节点。所以,主要就是学习 alsa-lib 库函数的使用、如何基于 alsa-lib 库函数开发音频应用程序。
alsa-lib官方说明文档:https://www.alsa-project.org/alsa-doc/alsa-lib/
移植alsa-lib库:
正点STM32MP157开发板出厂已移植(非广告!),需要请参考其他教程。
要在嵌入式linux系统上运行使用alsa-lib库的程序,需要移植alsa-lib库,可以参考网上移植alsa-lib库的方法,或自行下载alsa-lib资源包,自行编译移植。
开源ALSA架构的官网地址:https://www.alsa-project.org/wiki/Main_Page
libcurl库简介:
libcurl是一个跨平台的网络协议库,支持http, https, ftp, gopher, telnet, dict, file, 和ldap 协议。libcurl同样支持HTTPS证书授权,HTTP POST, HTTP PUT, FTP 上传, HTTP基本表单上传,代理,cookies,和用户认证。
官网地址:http://curl.haxx.se/
移植libcurl库:
正点STM32MP157开发板出厂已移植(非广告!),需要请参考其他教程。
注意:curl
指令和libcurl
是两个不同的东西,虽然它们都用于处理HTTP请求,但是有以下区别:
curl
指令是一个命令行工具,而libcurl
是一个C语言的库,可以通过函数调用使用。curl
指令可以直接在终端中运行,而libcurl
需要在编程时使用。curl
指令的功能相对简单,主要用于从终端中发送HTTP请求并获取响应,而libcurl
功能更为强大,可以通过编程实现更多复杂的HTTP请求和响应处理操作。curl
指令可以在不同的操作系统和终端上运行,而libcurl
需要在特定的平台上进行编译和部署。
总之,curl
指令是一个简单、方便的工具,可以帮助开发人员快速进行HTTP请求和响应测试。而libcurl
是一个功能更为强大的库,适合于在编程时进行HTTP请求和响应处理,但需要进行编译和部署。
API调用
该程序使用的是百度语音识别API
注册后领取免费额度及创建中文普通话应用(创建前先领取免费额度(180 天免费额度,可调用约 5 万次左右) )
创建好应用后,可以得到API key和Secret Key(填写到程序中的相应位置)
调用API相关说明,Demo代码中有多种语言的调用示例可以参考,使用c语言的话也可以直接在本程序上面再次更改:
API相关c文件中 需要修改的有asrmain.c、token.c和相应的头文件:
修改asrmain.c文件
asrmain.c的fill_config函数中(该函数我已修改,原本无file参数,根据实际情况使用),需要修改的有:音频文件格式,API Key以及Secret Key:
RETURN_CODE fill_config(struct asr_config *config,char *file) {// 填写网页上申请的appkey 如 g_api_key="g8eBUMSokVB1BHGmgxxxxxx"char api_key[] = "填写网页上申请的API key";// 填写网页上申请的APP SECRET 如 $secretKey="94dc99566550d87f8fa8ece112xxxxx"char secret_key[] = "填写网页上申请的Secret Key";// 需要识别的文件char *filename = NULL;filename = file;// 文件后缀仅支持 pcm/wav/amr 格式,极速版额外支持m4a 格式char format[] = "pcm";char *url = "http://vop.baidu.com/server_api"; // 可改为https// 1537 表示识别普通话,使用输入法模型。其它语种参见文档int dev_pid = 1537;char *scope = "audio_voice_assistant_get"; // # 有此scope表示有asr能力,没有请在网页里勾选,非常旧的应用可能没有…………
结合音频录制的程序使用的话,还需要删除示例中的main函数,run函数中的相关初始化以及API调用函数需要根据实际情况重新调整调用位置。本项目总体按照:获取token(在程序开始时获取一次即可,根据官网可知获取的token有效期为30天,重新获取token则之前获取的token失效)->调用API->得到返回结果->解析结果->对硬件控制。
asrmain.c的run_asr函数中(该函数我已修改,原本无result_voice参数,根据实际情况使用),需要修改的有:禁用SSL证书验证(在文件中查到下面这个函数名即可找到需要修改的位置)和 延长连接超时时间(源代码中设定时间为5s,在开发板上连接时间更长,需要改为10s,可能是由于硬件性能较弱或者网络环境不稳定等原因导致 )。
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0)
是一个CURL库的选项设置,用于控制CURL库对SSL证书的验证行为。默认情况下,CURL库会验证SSL证书的有效性,以确保请求的安全性。如果SSL证书验证失败,CURL库将阻止请求的进一步处理并返回一个错误。
将CURLOPT_SSL_VERIFYPEER
选项设置为0将禁用SSL证书验证,从而允许不受信任的证书通过。这个设置通常用于调试和测试目的,不建议在生产环境中使用,因为它会降低请求的安全性。如果需要在生产环境中使用不受信任的证书,建议使用自签名证书或受信任的CA签名证书,并通过其他手段验证证书的有效性,而不是禁用SSL证书验证。
修改token.c文件
和上面部分相同,禁用SSL证书验证。
录音
查看Linux语音识别中的录音内容
文件IO
我们需要将录制的音频文件保存到本地,就需要用到文件IO相关知识,打开音频文件以及向音频文件写数据。
打开音频文件
函数:
函数原型:
FILE *fopen(const char *filename, const char *mode)参数:
filename -- 字符串,表示要打开的文件名称。
mode -- 字符串,表示文件的访问模式。作用:
以指定的方式打开文件。
代码:
/*创建一个保存PCM数据的文件*/
if ((pcm_data_file = fopen(argv[1], "wb")) == NULL)
{printf("无法创建%s音频文件.\n", argv[1]);exit(1);
}
printf("用于录制的音频文件已打开.\n");参数:
argv[1]:程序执行时传递的参数,例./voice record.cpm,则该参数为"record.cpm"
"wb":只写打开或新建一个二进制文件,只允许写数据。
硬件控制
sysfs文件系统
在 Linux 系统下,一切皆文件!应用层如何操控底层硬件,同样也是通过文件 I/O 的方式来实现。本项目的硬件控制都是通过sysfs文件系统实现对LED和蜂鸣器的控制。
在嵌入式Linux开发中,sysfs文件系统通常被用来访问硬件资源,例如GPIO、I2C、SPI等外设,可以通过sysfs文件系统的接口来控制和读取硬件设备的状态信息。因此,sysfs文件系统对于嵌入式Linux开发非常重要,几乎所有的嵌入式Linux系统都会支持sysfs文件系统。
sysfs 文件系统挂载在/sys 目录下,启动开发板后可以到/sys目录查看。/sys下不同的子目录。
/sys
目录下包含了很多子目录,每个子目录都代表一个系统设备或内核模块,其中常见的子目录包括:
- block:块设备相关的信息,例如硬盘、光驱等。
- bus:系统总线相关的信息,例如USB、PCI、I2C等。
- class:设备类型相关的信息,例如输入设备、网络设备等。
- dev:与设备文件相关的信息,例如设备号、设备名称等。
- firmware:硬件固件相关的信息,例如BIOS、驱动程序等。
- fs:文件系统相关的信息,例如文件系统挂载状态等。
- kernel:内核相关的信息,例如内核版本、内核命令行参数等。
- module:内核模块相关的信息,例如已加载的内核模块等。
- power:电源管理相关的信息,例如电量、电源状态等。
- sys:系统信息相关的信息,例如CPU信息、内存信息等。
这些子目录下包含了许多虚拟文件和目录,通过这些文件和目录可以方便地访问内核数据结构和设备信息,从而实现对设备的控制和监控。
在sysfs文件系统中,一个硬件设备为一个目录,设备的属性则为文件。
在正点原子STM32MP157中,LED和蜂鸣器对应的设备目录均为/sys/class/leds/。
LED的触发方式控制文件为/sys/class/leds/user-led/trigger,LED亮度控制文件为/sys/class/leds/user-led/brightness。
蜂鸣器的触发方式控制未见为/sys/class/leds/beep/trigger,蜂鸣器开关控制文件为/sys/class/leds/beep/brightness。
数据解析和控制
本项目使用strstr函数对识别的结果进行判断,判断识别结果中是否包含"灯"、“蜂鸣器"字符串,有的话再判断结果中包含"开"还是"关”。
如识别的结果为"灯打开"或"开灯",程序将修改向LED灯的触发方式文件写入none(无触发),向LED亮度控制文件写入"1",这样就实现了LED灯的点亮
函数:
函数原型:
char *strstr(const char *haystack, const char *needle) haystack -- 要被检索的 C 字符串.
needle -- 在 haystack 字符串内要搜索的子字符串。作用:
查看原字符串中是否存在子字符串,不存在返回NULL
部分代码:
void Voice_Controll(char result[])
{/*检测语音和灯的控制有关*/if(strstr(result,"灯")!=NULL){if(strstr(result,"开")!=NULL && strstr(result,"关")!=NULL)return;else if(strstr(result,"开")!=NULL) Led_Controll(1); else if(strstr(result,"关")!=NULL) Led_Controll(0); }/*检测语音和蜂鸣器的控制有关*/......
}void Led_Controll(int ONOFF)
{int fd1,fd2;/*打开LED触发文件*/fd1 = open(LED_TRIGGER, O_WRONLY);if (0 > fd1) {perror("open error");exit(-1);}/*打开LED开关文件*/fd2 = open(LED_BRIGHTNESS, O_WRONLY);if (0 > fd2) {perror("open error");exit(-1);}/*根据传递参数控制LED*/if(ONOFF == 1){write(fd1, "none", 4); //先将触发模式设置为 nonewrite(fd2, "1", 1); //点亮 LEDprintf("LED已打开!\n");}else{write(fd1, "none", 4); //先将触发模式设置为 nonewrite(fd2, "0", 1); //熄灭 LEDprintf("LED已关闭!\n");}/*关闭文件*/close(fd1);close(fd2);
}
多线程
函数:
函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg)thread -- 传出参数,保存系统为我们分配好的线程 ID
attr -- 通常传 NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
start_routine -- 函数指针,指向线程主函数,该函数运行结束,则线程结束。
arg -- 线程主函数执行期间所使用的参数。作用:
创建一个新线程。函数原型:
int int truncate(const char *path,off_t length);参数:
path -- 文件路径名。
length -- 截断长度,若文件大小>length大小,额外的数据丢失。若文件大小<length大小,那么,这个文件将会被扩展,扩展的部分将补以null,也就是‘\0’。作用:
截断或扩展文件。
代码:
/*创建子线程检测按键是否按下*/
pthread_t tid;
ret = pthread_create(&tid, NULL, button_tfn, NULL);
if (ret != 0) perror("pthread_create failed");void *button_tfn(void *arg)
{struct input_event in_ev = {0};int fd;int value = -1;/*打开按键事件对应的文件*/if (0 > (fd = open("/dev/input/event1", O_RDONLY))){perror("open error");exit(-1);}while(1){/*循环读取数据*/if (sizeof(struct input_event) != read(fd, &in_ev, sizeof(struct input_event))){perror("read error");exit(-1);}if (EV_KEY == in_ev.type && in_ev.code == 114)//114为KEY0 { /*按键事件*/switch (in_ev.value){/*KEY0松开*/case 0:/*** 1.更新按键状态为松开* 2.延时等待主循环判断,否则可能出现主循环先判断标志位为1而出现PCM设备停止还在继续读数据* 3.停止PCM设备*/key_flag_now = 0;sleep(1);snd_pcm_drop(capture_handle);break;/*KEY0按下*/case 1:/*** 1.清空文件,使文件从头开始写,等于重新录制音频* 2.同样注意顺序,先使设备恢复进入准备状态,避免出现主循环先检测到标志位为1而读取声卡设备* 3.更新按键状态为按下*/truncate(pcm_file_name,1);snd_pcm_prepare(capture_handle);key_flag_now = 1;break;}}else if(EV_KEY == in_ev.type && in_ev.code == 115)//115为KEY1{/*按键事件*/switch (in_ev.value){/*KEY1按下*/case 1:/*退出程序*/exit_program();break;}}}
}
主循环
主循环内判断声卡设备状态是否改变(按键状态决定),若当前声卡为运行状态则进行音频采集,若当前声卡为停止状态则调用API进行识别。
while (1)
{/*判断按键状态是否更新*/if(key_flag_now != key_flag_old){/*视当前状态为旧状态*/key_flag_old = key_flag_now;/*若按键按下*/if(key_flag_now == 1)printf("开始采集音频数据...\n");/*若按键松开*/else{printf("采集结束!\n");/*调用API进行识别*/run_asr(&config, token,result);/*对识别的结果进行处理*/Voice_Controll(result);printf("请长按KEY0按键开始采集音频数据!单击KEY1退出程序!\n");}}/*若按键按下*/if(key_flag_now == 1){/*从声卡设备读取一帧音频数据:2048字节*/ret = snd_pcm_readi(capture_handle, buffer, buffer_frames);if(0 > ret){printf("从音频接口读取失败(%s)\n", snd_strerror(ret));exit(1);}/*写数据到文件: 音频的每帧数据样本大小是16位=2个字节*/fwrite(buffer, (ret * AUDIO_CHANNEL_SET), frame_byte, pcm_data_file);}
}
实现效果及注意事项
实现效果
如图所示长按KEY0按键开始音频录制,松开即音频录制结束,再调用百度语言API进行识别,并向用户展示识别的结果以及实现对硬件的控制。之后用户可自行选择继续识别或退出程序。
注意事项
该程序在声卡不进行录音时是将声卡设备给停止工作了的,在停止声卡设备前需要加入一小段的延时等待,若不添加延时等待,可能会出现子线程使声卡设备停止的同时主线程在读取声卡设备,从而导致下图中出现的错误:
源代码(转载请注明出处)
相关文章:

STM32MP157-Linux音频应用编程-简易语音助手
文章目录前言STM32MP157简易语音助手alsa-lib简介:移植alsa-lib库:libcurl库简介:移植libcurl库:API调用修改asrmain.c文件修改token.c文件录音文件IO打开音频文件硬件控制sysfs文件系统数据解析和控制多线程主循环实现效果及注意…...

Python-OpenCV图像处理:学习图像算术运算,如加减法、图像混合、按位运算,以及如何实现它们
目录 目标 图像添加 图像混合算法 按位运算 目标 学习对图像的几种算术运算,如加法、减法、位运算等。了解这些功能:cv.add()、...

并发编程——ReentrantLock
如果有兴趣了解更多相关内容,欢迎来我的个人网站看看:耶瞳空间 一:基本介绍 从Java 5开始,引入了一个高级的处理并发的java.util.concurrent包,它提供了大量更高级的并发功能,能大大简化多线程程序的编写…...

English Learning - L2 第 3 次小组纠音 [ʌ] [ɒ] [ʊ] [ɪ] [ə] [e] 2023.3.4 周六
English Learning - L2 第 3 次小组纠音 [ʌ] [ɒ] [ʊ] [ɪ] [ə] [e] 2023.3.4 周六共性问题小元音 [ʌ]小元音 [ɒ]小元音 [ʊ]小元音 [ɪ]小元音 [ə]小元音 [e]我的发音问题纠音过程共性问题 小元音 [ʌ] 口型容易偏大 解决办法:因为嘴角没有放松,…...

STM32之关门狗
看门狗介绍在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造成程序的跑飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续工作,会造成整个系统的陷入…...

Apollo控制部分1-- ControlComponent组件介绍
Apollo控制部分1-- ControlComponent组件介绍摘要一、ControlComponent1、启动文件解析2、ControlComponent()组件函数解析1)ControlComponent::ControlComponent() 构造函数2)ControlComponent::Init() 初始化函数(执行一次)3&am…...

0626-0631韩顺平Java Buffered字节处理流 学习笔记
如何去构建字节流package com.hspedu.outputstream_;import java.io.*;/*** author abner* version 1.0*/ public class BufferedCopy02 {public static void main(String[] args) {String srcFilePath "D:\\Users\\Pictures\\Camera Roll\\Pierre-Auguste_Renoir,_Le_Mo…...

【网络】序列化和反序列化
🥁作者: 华丞臧. 📕专栏:【网络】 各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞收藏关注)。如果有错误的地方,欢迎在评论区指出。 推荐一款刷题网站 👉 LeetCode刷题网站 文章…...

【代码随想录训练营】【Day32】第八章|贪心算法|122.买卖股票的最佳时机II |55. 跳跃游戏|45.跳跃游戏II
买卖股票的最佳时机II 题目详细:LeetCode.122 买卖股票的最佳时机,怎么都能够想出来个思路,假如我们每天都能预知明天的股票是涨是降,那么贪心策略就是在涨之前买股票,在降的前一天卖掉,这就是买卖股票的…...

constexpr 和 常量表达式
👀👀常量表达式 常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。 字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。 那么是什么来就决定是不是常量表达式呢?一个对象是不是常量表达式主要…...

Vue响应式原理————Object.defineProperty()和proxy的用法分享
Vue框架一个比较核心的功能就是我们的数据是响应式的,这样我们在修改数据的时候,页面会自动帮我们更新,那么想要实现这个功能就要实现对一个数据的劫持,即在取值和设置值的同时我们能够检测到即数据劫持。vue2响应式的实现原理所依…...

CSDN 编程竞赛三十四期题解
竞赛总览 CSDN 编程竞赛三十四期:比赛详情 (csdn.net) 本期的题目和第三十一期竞赛的题目竟然高度重合,真不知道该写点什么了。 不过,上次那道测试数据有bug的题已经修复了,答题过程挺顺利的,没有遇到新的问题。 竞…...

C#教程06 运算符
文章目录 一、算术运算符加法运算符(+)减法运算符(-)乘法运算符(*)除法运算符(/)二、逻辑运算符与运算符(&&)或运算符(||)非运算符(!)三、比较运算符等于运算符(==)大于运算符(>)小于运算符(<)大于等于运算符(>=)小于等于运算符(<=…...

软测入门(六)pytest单元测试
pytest pytest是python的一种单元测试框架,同自带的unit test测试框架类似,但pytest更简洁高效。 单元测试: 测试 函数、类、方法能不能正常运行测试的结果是否符合我们的预期结果 安装 pip install -U pytest基本使用 通过pytest包使用…...

经典分类模型回顾5—DenseNet实现图像分类(matlab)
DenseNet,全称为Densely Connected Convolutional Networks,中文名为密集连接卷积网络,是由李沐等人在2017年提出的一种深度神经网络架构。 DenseNet旨在解决深度神经网络中的梯度消失问题和参数数量过多的问题,通过构建密集连接…...

基于flask+bootstrap+echarts+mysql的鱼村小馆订餐后台管理系统
📋 个人简介 💖 作者简介:大家好,我是阿牛,全栈领域优质创作者。😜📝 个人主页:馆主阿牛🔥🎉 支持我:点赞👍收藏⭐️留言Ὅ…...

Spark使用Log4j将日志发送到Kafka
文章目录自定义KafkaAppender修改log4j.properties配置启动命令配置添加参数启动之后可以在Kafka中查询发送数据时区问题-自定义实现JSONLayout解决自定义JSONLayout.java一键应用可能遇到的异常ClassNotFoundException: xxx.KafkaLog4jAppenderUnexpected problem occured dur…...

c++类与对象整理(上)
目录 1.类的引入 2.类的定义 3.类的访问限定符及封装 1)访问限定符 2)封装 4.类的作用域 5.类的实例化 6.类的对象大小的计算 1)类对象的存储方式 2)内存对齐和大小计算 编辑 7.类成员函数的this指针 1)…...

Docker学习(十九)什么是镜像的元数据?
在 Docker 中,镜像的元数据是指与镜像相关的所有信息,包括镜像的名称和标签、作者、描述、创建日期、环境变量、命令等。这些信息都是通过 Dockerfile 或命令行创建和指定的。 镜像的元数据被存储在 Docker Registry 中,并在使用 docker pull…...

Python如何获取弹幕?给你介绍两种方式
前言 弹幕可以给观众一种“实时互动”的错觉,虽然不同弹幕的发送时间有所区别,但是其只会在视频中特定的一个时间点出现,因此在相同时刻发送的弹幕基本上也具有相同的主题,在参与评论时就会有与其他观众同时评论的错觉。 在国内…...

JAVA- AOP 面向切面编程 Aspect切面工具类 记录特定方法执行时的入参、执行时间、返参等内容
背景:JAVA项目,使用AOP对指定函数进行切面。能够记录特定方法执行时的入参、执行时间、返参结果等内容。 文章目录1、自定义注解类1.1 Target1.2 Retention2、Aspect切面工具2.1 JointPoint2.2 Pointcut2.3 切面中的相关注解3、同一个类里调用AOP4、其他…...

「史上最全的 TCG 规范解读」TCG 规范架构概述(下)
可信计算组织(Ttrusted Computing Group,TCG)是一个非盈利的工业标准组织,它的宗旨是加强不同计算机平台上计算环境的安全性。TCG 于 2003 年春成立,并采纳了由可信计算平台联盟(the Trusted Computing Platform Allia…...

GDScript 导出变量 (4.0)
概述 导出变量的功能在3.x版本中也是有的,但是4.0版本对其进行了语法上的改进。 导出变量在日常的游戏制作中提供节点的自定义参数化调节功能时非常有用,除此之外还用于自定义资源。 本文是(Bilibili巽星石)在4.0官方文档《GDScr…...

JAVA知识点全面总结6:泛型反射和注解
六.JAVA知识点全面总结6泛型反射和注解 1.什么是泛型?可以用在哪里? 2.泛型擦除机制是什么?为什么擦除? 3.通配符是什么?作用是什么? 未更新 1.注解是什么?有什么用? 2.注解的自定义和实…...

死代码删除(DCE,Dead Code Elimination)和激进的死代码删除(ADCE,Aggressive DCE)
死代码删除(DCE,Dead Code Elimination)和激进的死代码删除(ADCE,Aggressive DCE)死代码删除(DCE,Dead Code Elimination)DCE简介DCE基本算法激进的死代码删除࿰…...

询问new bing关于android开发的15个问题(前景、未来、发展方向)
前言:new bing是基于chat-gpt的新搜索工具,可以采用对话方式进行问题搜索,经过排队等候终于可以使用new bing,询问了目前我最关心的关于android开发几个问题 文章目录1.如何学好android开发?2.android开发能做什么?3.…...

【C++】初识类和对象
🏖️作者:malloc不出对象 ⛺专栏:C的学习之路 👦个人简介:一名双非本科院校大二在读的科班编程菜鸟,努力编程只为赶上各位大佬的步伐🙈🙈 目录前言一、面向过程和面向对象初步认识二…...

EPICS S7nodave手册
第一章:介绍 本手册分为6章(不算次介绍部分)。第一章介绍s7nodave用于EPICS的设备支持的概念和特新。第二章描述启动一个使用s7nodave的IOC项目所需要的几步。第三章描述s7nodave支持的IOC shell命令。之后,第四章解释s7nodave支持的各种记录类型。最后…...

2023最新版本RabbitMQ的持久化和简单使用
上节讲了 RabbitMQ下载安装教程 , 本节主要介绍RabbitMQ的持久化和简单使用。 一、RabbitMQ消息持久化 当处理一个比较耗时得任务的时候,也许想知道消费者(consumers)是否运行到一半就挂掉。在当前的代码中,当RabbitM…...

函数式编程
函数式编程(一) 文章目录函数式编程(一)1. 前言1.1 概念2. Lambda 表达式2.1 概述2.2 基本的格式2.3 触发条件2.4 Lambda表达式2.4.1 无参无返回值2.4.2 有参无返回值2.4.3 无参数有返回值2.4.4 有参有返回值【重点】2.4.4.1 比较…...