【音视频|ALSA】SS528开发板编译Linux内核ALSA驱动、移植alsa-lib、采集与播放usb耳机声音

news2024/11/22 5:26:45

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭SS528开发板编译Linux内核ALSA驱动、移植alsa-lib、采集与播放usb耳机声音🍭
😎金句分享😎:🍭来忘掉错对,来怀念过去,曾共度患难日子总有乐趣 ——《友情岁月》🍭

目录

  • 一、概述
  • 二、编译ALSA驱动
    • 2.1 配置和编译ALSA驱动模块
    • 2.2 在ss528开发板插入ALSA驱动
  • 三、交叉编译 alsa-lib
    • 3.1 下载 alsa-lib
    • 3.2 交叉编译 alsa-lib
  • 四、开发ALSA应用层程序
    • 4.1 编写应用层代码 alsa-capture-playback.c
    • 4.2 运行程序 alsa-capture-playback
  • 五、总结


在这里插入图片描述

一、概述

上篇文章【音视频|ALSA】ALSA是什么?ALSA框架详细介绍 简单地介绍了ALSA驱动;这篇文章主要是一篇实战文章,通过ALSA架构,在SS528开发板实现播放usb耳机声音。整个操作大概分为三步:

  • 编译ALSA驱动
  • 交叉编译alsa-lib
  • 基于alsa-lib库开发采集、播放音频程序

本文使用开发环境:

  • 开发主机:Ubuntu18.04LTS
  • 交叉编译工具:aarch64-mix210-linux-gcc
  • 使用的Linux内核源码:ss528sdk自带的,SS528V100_SDK_V2.0.0.3/open_source/linux/linux-4.19.y
  • 使用的alsa-lib库:ALSA官网下载的 alsa-lib-1.2.10

在这里插入图片描述

二、编译ALSA驱动

在Linux系统中,要播放usb接口的音频,首先需要先有一个可以识别到这个usb耳机的驱动,然后再针对这个驱动编写应用层代码。自己写的话,耗时耗力不讨好,而ALSA架构就提供了这样的驱动和应用层的库,下面介绍怎样在Linux源码编译ALSA驱动,并将其编译成模块,插入到板子的Linux系统。

2.1 配置和编译ALSA驱动模块

首先进入Linux源码目录,配置启用ALSA驱动:

cd linux-4.19.y
make ARCH=arm64 CROSS_COMPILE=aarch64-mix210-linux- menuconfig

参考下面语句配置:

	Device Drivers  --->
		<M> Sound card support  --->
			<M>   Advanced Linux Sound Architecture  --->
				[*]   PCM timer interface (NEW)
				[*]   Support old ALSA API (NEW)
				[*]   Sound Proc FS Support (NEW)
				[*]     Verbose procfs contents (NEW)
				[*]   Generic sound devices (NEW)  ---> 
				[*]   PCI sound devices (NEW)  ---> 
				(2048) Pre-allocated buffer size for HD-audio driver
				[*]   SPI sound devices (NEW)  ----
				[*]   USB sound devices (NEW)  --->
					<M>   USB Audio/MIDI driver

配置完成后,保存退出,执行下面语句编译模块:

make ARCH=arm64 CROSS_COMPILE=aarch64-mix210-linux- modules

编译完成后,在sound目录下,有8个ko生成,将它们复制到开发板的文件系统即可:

$ find ./sound/ -name "*.ko"
./sound/core/snd.ko
./sound/core/snd-hwdep.ko
./sound/core/snd-rawmidi.ko
./sound/core/snd-timer.ko
./sound/core/snd-pcm.ko
./sound/usb/snd-usbmidi-lib.ko
./sound/usb/snd-usb-audio.ko
./sound/soundcore.ko
$ mkdir /nfsroot/sound
$ cd sound/
$ cp soundcore.ko core/*.ko usb/*.ko /nfsroot/sound -far

2.2 在ss528开发板插入ALSA驱动

说明:下面的打印都是在开发板文件系统的操作打印。

开发板在没用插入ALSA驱动之前,是没有/dev/snd目录、/prc/asound目录的:

/nfsroot/sound # ls /dev/snd
ls: /dev/snd: No such file or directory
/nfsroot/sound # ls /proc/asound
ls: /prc/asound: No such file or directory
/nfsroot/sound # 

插入ALSA驱动,参考下面命令:

insmod soundcore.ko
insmod snd.ko         
insmod snd-hwdep.ko   
insmod snd-timer.ko     
insmod snd-rawmidi.ko   
insmod snd-pcm.ko       
insmod snd-usbmidi-lib.ko
insmod snd-usb-audio.ko 

在这里插入图片描述
注意insmod的顺序,避免报错:insmod: can’t insert ‘snd.ko’: unknown symbol in module, or unknown parameter

插入成功后,可以看到/dev/snd目录、/prc/asound目录:
在这里插入图片描述

卸载ALSA驱动,参考下面语句:

rmmod snd-usb-audio
rmmod snd-usbmidi-lib
rmmod snd-pcm
rmmod snd-rawmidi
rmmod snd-timer
rmmod snd-hwdep
rmmod snd
rmmod soundcore

注意rmmod的顺序,避免报错:rmmod: can’t unload module ‘soundcore’: Resource temporarily unavailable

在这里插入图片描述

三、交叉编译 alsa-lib

3.1 下载 alsa-lib

在官网 https://www.alsa-project.org/files/pub/lib/ 可以下载到历史版本;
在Github https://github.com/alsa-project/alsa-lib/tags 可以下载到最新发布版本
本文下载的是 alsa-lib-1.2.10.tar.gz
在这里插入图片描述

3.2 交叉编译 alsa-lib

解压源码,如果里面没有configure文件,需要执行下面命令生成:

libtoolize --force --copy --automake
aclocal
autoheader
automake --foreign --copy --add-missing
autoconf

编译过程参考下面命令:

sudo mkdir /usr/lib/alsa-lib-1.2.10
sudo chown wkd:wkd /usr/lib/alsa-lib-1.2.10/ -R
tar zxf alsa-lib-1.2.10.tar.gz
cd alsa-lib-1.2.10/
./configure --prefix=/usr/lib/alsa-lib-1.2.10/ CC=aarch64-mix210-linux-gcc --host=aarch64-mix210-linux --enable-static=yes --enable-shared=no
make && make install

这里首先创建了/usr/lib/alsa-lib-1.2.10目录,因为链接了libasound.a后,程序需要到安装目录寻找配置文件share/alsa/alsa.conf,所以这里安装的目录需要和开发板存放的目录一致。

如果指定了--prefix为其他目录,就需要将share/alsa/alsa.conf复制到开发板同样的目录,否则运行程序会报错:Cannot access file /usr/lib/alsa-lib-1.2.10/share/alsa/alsa.conf,也可以通过选项--with-configdir来指定配置文件目录。

在这里插入图片描述

四、开发ALSA应用层程序

4.1 编写应用层代码 alsa-capture-playback.c

代码参考:https://blog.csdn.net/u014056414/article/details/120989131

// alsa-capture-playback.c
// aarch64-mix210-linux-gcc alsa-capture-playback.c -I /usr/lib/alsa-lib-1.2.10/include/ -L /usr/lib/alsa-lib-1.2.10/lib/ -l asound -lpthread -ldl -lm -o alsa-capture-playback

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <alsa/asoundlib.h>


#define PLAYBACK_FILE "48000Hz-16bit-2ch-ChengDu.pcm"  // 采样率:48000 位深度:16bit 双通道
#define CAPTURE_FINE "capture.pcm"

//#define PCM_NAME	"hw:CARD=mycodec,DEV=0"
#define PCM_NAME	"hw:0,0"
#define RATE 		48000
#define FORMAT		SND_PCM_FORMAT_S16_LE
#define CHANNELS	1

snd_pcm_hw_params_t *hw_params;

int print_all_pcm_name(void) {
	char **hints;
	/* Enumerate sound devices */
	int err = snd_device_name_hint(-1, "pcm", (void***)&hints);
	if(err != 0)
	   return -1;

	char** n = hints;
	while(*n != NULL) {
		char *name = snd_device_name_get_hint(*n, "NAME");

		if(name != NULL && 0 != strcmp("null", name)) {
			printf("pcm name : %s\n",name);
			free(name);
		}
		n++;
	}

	snd_device_name_free_hint((void**)hints);
	return 0;
}

snd_pcm_t *open_sound_dev(snd_pcm_stream_t type,const char *name, unsigned int rate, int format,int channels,snd_pcm_uframes_t *period_frames) {
	int err;
	snd_pcm_t *handle;
	int dir = 0;
	
	printf("rate=%d, format=%d, channels=%d\n",rate,format,channels);

	if((err = snd_pcm_open(&handle, name, type, 0)) < 0) {
		printf("cannot snd_pcm_open (%s)\n",
			 snd_strerror(err));
		return NULL;
	}
	   
	if((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
		printf("cannot allocate hardware parameter structure (%s)\n",
			 snd_strerror(err));
		return NULL;
	}
			 
	if((err = snd_pcm_hw_params_any(handle, hw_params)) < 0) {
		printf("cannot initialize hardware parameter structure (%s)\n",
			 snd_strerror(err));
		return NULL;
	}

	if((err = snd_pcm_hw_params_set_access(handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
		printf("cannot set access type (%s)\n",
			 snd_strerror(err));
		return NULL;
	}

	if((err = snd_pcm_hw_params_set_format(handle, hw_params, format)) < 0) {
		printf("cannot set sample format (%s)\n",
			 snd_strerror(err));
		return NULL;
	}

	if((err = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rate, 0)) < 0) {
		printf("cannot set sample rate (%s)\n",
			 snd_strerror(err));
		return NULL;
	}

	if((err = snd_pcm_hw_params_set_channels(handle, hw_params, channels)) < 0) {
		printf("cannot set channel count (%s) channels=%d\n",snd_strerror(err),channels);
		if(channels==2)
		{
			channels = 1;
		}
		if(channels==1)
		{
			channels = 2;
		}
		
		if((err = snd_pcm_hw_params_set_channels(handle, hw_params, channels)) < 0) {
			printf("cannot set channel count (%s)\n",snd_strerror(err));
			return NULL;
		}
	}
	
#if 1
	unsigned int buffer_time, period_time;
	if (snd_pcm_hw_params_get_buffer_time_max(hw_params, &buffer_time, 0) < 0) {
		fprintf(stderr, "Error snd_pcm_hw_params_get_buffer_time_max\n");
		return NULL;
	}
	if(snd_pcm_stream(handle) == SND_PCM_STREAM_PLAYBACK)
	{
		if (buffer_time > 50000) 
			buffer_time = 50000;	//50ms 这个数越小数据取的越快
	}
	period_time = buffer_time / 4;
	if (snd_pcm_hw_params_set_buffer_time_near(handle, hw_params, &buffer_time, 0) < 0) {
		fprintf(stderr, "Error snd_pcm_hw_params_set_buffer_time_near\n");
		return NULL;
	}
 
	if (snd_pcm_hw_params_set_period_time_near(handle, hw_params, &period_time, 0) < 0) {
		fprintf(stderr, "Error snd_pcm_hw_params_set_period_time_near\n");
		return NULL;
	}

#endif

	if((err = snd_pcm_hw_params(handle, hw_params)) < 0) {
		printf("cannot set parameters (%s)\n",
			 snd_strerror(err));
		return NULL;
	}
	
	if(period_frames != NULL) {
		//获取一个周期有多少帧数据
		if((err =snd_pcm_hw_params_get_period_size(hw_params, period_frames, &dir)) < 0){
			printf("cannot get period size (%s)\n",
				snd_strerror(err));
			return NULL;
		}
	}
	
	snd_pcm_hw_params_free(hw_params);
	return handle;
}

int main(void) {
	int p,err;
	snd_pcm_t *playback_handle;
	snd_pcm_t *capture_handle;
	int pfd,cfd;
	snd_pcm_uframes_t period_frames;
	int size2frames;
	
	printf("program running ...\n");
	
	//查看所有pcm的name
	print_all_pcm_name();
	
	playback_handle = open_sound_dev(SND_PCM_STREAM_PLAYBACK,
                                     PCM_NAME,RATE,FORMAT,CHANNELS,&period_frames);
	if(!playback_handle) {
		printf("cannot open for playback\n");
        return -1;
    }
	
	usleep(5);
	
	capture_handle = open_sound_dev(SND_PCM_STREAM_CAPTURE,PCM_NAME,RATE,FORMAT,CHANNELS,NULL);
	if(!capture_handle) {
		printf("cannot open for capuure\n");
		snd_pcm_close(playback_handle);
        return -1;
    }
	
	if((err = snd_pcm_prepare(playback_handle)) < 0) {
		printf("cannot prepare audio interface for use (%s)\n",
			 snd_strerror(err));
		goto out;
	}

	if((err = snd_pcm_prepare(capture_handle)) < 0) {
		printf("cannot prepare audio interface for use (%s)\n",
			 snd_strerror(err));
		goto out;
	}
	
	//打开要播放的PCM文件
	pfd = open(PLAYBACK_FILE,O_RDONLY,0644);
	if(pfd < 0){
		printf("open %s error!!!\n",PLAYBACK_FILE);
		goto out;
	}
	
	//新建一个进程, 子进程播放, 父进程录音
	p = fork();
	if(p < 0) {
		printf("fork error!!!\n");
		goto out;
	}
	
	if(p==0) {
		char *pbuf;
		int size,period_bytes;
		period_bytes = snd_pcm_frames_to_bytes(playback_handle,period_frames);
		pbuf = malloc(period_bytes);
		int i = 0;
		for(; i<100; i++)
		{
			printf("[%s %d]times = %d\n",__FILE__,__LINE__,i);
			lseek(pfd, 0, SEEK_SET);
			while( size = read(pfd, pbuf, period_bytes)) {
				//解决最后一个周期数据问题
				if(size < period_bytes) {
					memset(pbuf+size, 0, period_bytes-size);
				}
				
				//size2frames = snd_pcm_bytes_to_frames(playback_handle,size);
				size2frames = period_frames;
				
				//向PCM写入数据,播放
				err = snd_pcm_writei(playback_handle, pbuf, size2frames);
				if(err == -EPIPE) {
					snd_pcm_prepare(playback_handle);
					fprintf(stderr, "<<< snd_pcm_writei --> Buffer Underrun >>> \n");
					err = snd_pcm_writei(playback_handle, pbuf, size2frames);
					if(err != size2frames) {
						printf("write to audio interface failede err:%d (size2frames:%d)\n",err,size2frames);
						free(pbuf);
						close(pfd);
						exit(-1);
					}
				}
				else if(err != size2frames) {
					printf("write to audio interface failede err:%d (size2frames:%d)\n",err,size2frames);
					free(pbuf);
					close(pfd);
					exit(-1);
				}
				//printf("process:playback wrote %d frames\n",size2frames);
				usleep(100);
			}
		}
		
		free(pbuf);
		close(pfd);
		sleep(1);	//等待一下,给点时间父进程录音
		exit(0);
	}
	
	char *cbuf;
	const int frames_size = snd_pcm_frames_to_bytes(capture_handle,period_frames);
	cbuf = malloc(frames_size);
	memset(cbuf, 0, frames_size);
	
	//打开录音的保存文件
	cfd = open(CAPTURE_FINE,O_RDWR | O_TRUNC | O_CREAT,0644);
	if(cfd < 0){
		printf("open %s error!!!\n",CAPTURE_FINE);
		goto out;
	}
	
	while(waitpid(p, NULL, WNOHANG) == 0) {	//查看一下子进程是否已经退出
		//向PCM读一周期数据
		if((size2frames = snd_pcm_readi(capture_handle, cbuf, period_frames)) < 0) {
			printf("read from audio interface failed (%d)\n",size2frames);
			free(cbuf);
			close(cfd);
			goto out;
		}
		//printf("--process:capture read %d frames\n",size2frames);
		write(cfd,cbuf,snd_pcm_frames_to_bytes(capture_handle,size2frames));
		memset(cbuf,0,frames_size);
		usleep(100);
	}
	free(cbuf);
	close(cfd);

out:
	snd_pcm_close(playback_handle);
	snd_pcm_close(capture_handle);
	printf("program finish ...\n");
	return 0;
}

复制上面代码保存为alsa-capture-playback.c,然后参考下面命令编译:

aarch64-mix210-linux-gcc alsa-capture-playback.c -I /usr/lib/alsa-lib-1.2.10/include/ -L /usr/lib/alsa-lib-1.2.10/lib/ -l asound -lpthread -ldl -lm -o alsa-capture-playback

4.2 运行程序 alsa-capture-playback

首先,在开发板插入驱动,参考上面 2.2 节;

其次,复制alsa-lib交叉编译生成的 /usr/lib/alsa-lib-1.2.3.2/share/alsa/alsa.conf 文件到开发板同样的路径;

最后,复制应用程序 alsa-capture-playback 和任意 48KHZ的双通道16bit的pcm文件 48000Hz-16bit-2ch-ChengDu.pcm到开发板任一目录,执行alsa-capture-playback

顺利执行的话,可以在耳机听到播放的pcm文件声音,并且执行的目录下会生成record.pcm。我使用的pcm文件是48KHZ的双通道16bit的,点击 下载链接 可以下载,音频打开如下图:
在这里插入图片描述

在这里插入图片描述

五、总结

本篇文章介绍了在Linux开发板使用ALSA架构播放usb耳机声音的实例,包括了:编译安装ALSA驱动、交叉编译alsa-lib、实现ALSA应用层程序。
在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1092468.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ArcGIS笔记4_水动力模型验证不理想时如何修改局部水深地形

本文目录 前言Step 1 模型验证不理想的情况Step 2 修改确值点并重新插值 前言 本章主要服务于MIKE水动力模型的调整修改工作。水动力模型跑完之后&#xff0c;常常会出现验证结果不理想的情况&#xff0c;比如潮位验证中&#xff0c;实测站点数据与模拟数据相差很大&#xff0…

【计算机组成体系结构】移码 | 定点小数的表示和运算

一、移码 上篇我们提到了原码&#xff0c;反码和补码的表示形式和如何转换。这篇我们会提到一个新的概念—移码。移码也很简单&#xff0c;其实就是在补码的基础上把符号取反即可。 值得注意的是&#xff0c;移码只能表示整数。而原码&#xff0c;反码和补码既可以表示整数又…

软件工程与计算总结(十一)人机交互设计

目录 ​编辑 一.引例 二.目标 三.人类因素 1.精神模型 2.差异性 四.计算机因素 1.可视化设计 2.常见界面类型 五.人机交互设计的交互性 1.导航 2.反馈 3.设计原则 六.设计过程 1.基本过程 2.界面原型化 一.引例 无论软件功能多么出色&#xff0c;亦或内部的构造…

具有标记和笔记功能的文件管理器TagSpaces

什么是 TagSpaces &#xff1f; TagSpaces 是一款免费、无供应商锁定的开源应用程序&#xff0c;用于借助标签组织、注释和管理本地文件。它具有高级笔记功能和待办事项应用程序的一些功能。该应用程序适用于 Windows、Linux、Mac OS 和 Android。并已经为 Firefox、Edge 和 Ch…

【Android知识笔记】图片专题(BitmapDrawable)

如何计算一张图片的占用内存大小? 注意是占用内存,不是文件大小可以运行时获取重要的是能直接掌握计算方法基础知识 Android 屏幕像素密度分类: (其实还有一种 ldpi = 120,不过这个已经绝种了,所以最低的只需关心mdpi即可) 上表中的比例为:m : h : xh : xxh: xxxh = …

java springboot 通过ConfigurationProperties给第三方bean注入属性

之前我们的文章 java boot将一组yml配置信息装配在一个对象中 讲过了 通过ConfigurationProperties将配置文件中的内容默认装配进属性类 但 这建立在 bean是自己定义的 如果 这是个第三方的类呢&#xff1f; 就比如 我们在 application 中写了一套数据源的加载规则 但需要用第…

无法启动此程序,因为计算机中丢失MSVCR71.dll的详细解决修复方法

大家好&#xff01;今天我来给大家分享一下msvcp71.dll丢失的修复方法。 首先&#xff0c;让我们来了解一下msvcp71.dll文件。msvcp71.dll是一个动态链接库文件&#xff0c;它是Microsoft Visual C 2010 Redistributable Package所包含的一个文件。这个文件被许多软件和游戏需…

Web攻防01-ASP应用相关漏洞-HTTP.SYSIIS短文件文件解析ACCESS注入

文章目录 ASP-默认安装-MDB数据库泄漏下载漏洞漏洞描述 ASP-中间件 HTTP.SYS&#xff08;CVE-2015-1635&#xff09;1、漏洞描述2、影响版本3、漏洞利用条件4、漏洞复现 ASP-中间件 IIS短文件漏洞1、漏洞描述2、漏洞成因:3、应用场景&#xff1a;4、利用工具&#xff1a;5、漏洞…

计算机毕业设计选什么题目好?springboot 研究生管理系统

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

普冉PY32系列(八) GPIO模拟和硬件SPI方式驱动无线收发芯片XN297LBW

目录 普冉PY32系列(一) PY32F0系列32位Cortex M0 MCU简介普冉PY32系列(二) Ubuntu GCC Toolchain和VSCode开发环境普冉PY32系列(三) PY32F002A资源实测 - 这个型号不简单普冉PY32系列(四) PY32F002A/003/030的时钟设置普冉PY32系列(五) 使用JLink RTT代替串口输出日志普冉PY32…

java气候分析平台天气预报系统springboot+vue

保护措施 (自动编号、图片、措施简介、措施地点、措施时间、创建时间、措施详情、措施名称)&#xff1b; 报名信息 (自动编号、活动地点、图片、活动名称、活动时间、参与人数、活动详情、审核回复、创建时间、报名时间、手机、活动简介、是否审核、姓名、账号)&#xff1b; 配…

在Node.js项目中使用node-postgres连接postgres以及报错指南

什么是node-postgres 官方文档 nodepostgres是node.js模块的集合&#xff0c;用于与PostgreSQL数据库接口。它支持回调、promise、async/await、连接池、准备好的语句、游标、流式结果、C/C绑定、富类型解析等等&#xff01;就像PostgreSQL本身一样&#xff0c;它有很多功能&a…

下载Python的不同版本在同一台电脑上如何共存

1. 下载安装不同版本的Python 官网下载&#xff1a;https://www.python.org/downloads/安装自己需要的版本&#xff08;我这里以Python3.6和Python3.9为例&#xff0c;下载安装细节不过多赘述&#xff09; &#xff08;这里的安装路径自己设定&#xff0c;命名最好是根据下载…

【MySQL】事务四大特性ACID、并发事务问题、事务隔离级别

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 MySQL 一、事务四大特性ACID1.1 原子性1.2 …

深入探究基于发布/订阅模式的轻量级消息传输协议 MQTT

目录 1、什么是 MQTT&#xff1f; 1.1、MQTT 与 HTTP 比较 1.2、 MQTT 与 XMPP 比较 2、MQTT 可以干啥&#xff1f; 3、 MQTT 协议特性详解 3.1、轻量高效&#xff0c;节省带宽 3.2、可靠的消息传递 3.3、海量连接支持 3.4、安全的双向通信 3.5、在线状态感知 4、MQ…

【数据库系统概论】第四章数据库安全性

数据库的安全性&#xff1a;保护数据库以防止不合法使用所造成的数据泄露、更改或破坏 grant和revoke语法

如何定制化跑腿小程序源码

跑腿小程序源码为您提供了一个强大的起点&#xff0c;但要创建一个成功的本地服务平台&#xff0c;您通常需要对源码进行定制化。这篇文章将介绍如何定制化跑腿小程序源码&#xff0c;包括添加新功能、修改界面和优化用户体验。 选择合适的跑腿小程序源码 首先&#xff0c;您…

边坡监测系统:全天监测、智能预警

边坡指的是为保证路基稳定,在路基两侧做成的具有一定坡度的坡面。边坡工程稳定性会被很多因素应用&#xff0c;具体可分为内在因素和外在因素进行分析。组成边坡的岩土体类型及性质、边坡地质构造、边坡形态、地下水等&#xff1a;外部因素包括&#xff1a;振动作用、气候条件、…

新式茶饮品牌如何写出生活感软文

居民消费水平的提升使新式茶饮品牌的市场不断扩张&#xff0c;在竞争激烈的茶饮市场中&#xff0c;品牌提高知名度的主要方式之一就是软文营销&#xff0c;而生活感软文是茶饮软文中较为常见的类型&#xff0c;它能有效拉进品牌与消费者之间的距离&#xff0c;那么新式茶饮品牌…

0:node的安装与环境配置

转载&#xff1a;https://blog.csdn.net/liu_1823/article/details/132987003 ** node.js安装 ** 下载地址&#xff1a;https://nodejs.org/en 下载第一个18.18.2 接下来的安装步骤直接都安装到C盘下面&#xff0c;一直点next就行。最后finish。 ** 配置环境变量 ** 完成了…