当前位置: 首页 > news >正文

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

😁博客主页😁:🚀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来指定配置文件目录,它将影响 include/config.h中的 ALSA_CONFIG_DIR 目录。

在这里插入图片描述

四、开发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	1snd_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 1unsigned 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;}#endifif((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的nameprint_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应用层程序。
在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

相关文章:

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

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…...

C/C++基础讲解(一百三十一)之经典篇(信息合并/平均分数存储)

C/C++基础讲解(一百三十一)之经典篇(信息合并/平均分数存储) 程序之美 前言 很多时候,特别是刚步入大学的学子们,对于刚刚开展的计算机课程基本上是一团迷雾,想要弄明白其中的奥秘,真的要花费一些功夫,我和大家一样都是这么啃过来的,从不知到知知,懵懂到入门,每一步…...

【ROS】使用vscode浏览navigation2源码时,提示:没有那个文件或目录

【ROS】郭老二博文之:ROS目录 1、问题描述 使用vscode浏览navigation2源码时,头文件下面有波浪线,并提示:没有那个文件或目录。比如没有:geometry_msgs/msg/polygon.h 错误信息: 无法打开源文件 “geometry_msgs/msg/polygon.h” (dependency of “nav2_controller/co…...

ARM-day9作业

main.c: #include "uart.h"#include "key_it.h"int main(){char c;char *s;uart4_init(); //串口初始化//中断初始化key_it_config();key3_it_config();//完成GPIO相关初始化all_led_init();//风扇初始化fs_init();//蜂鸣器初始化fmq_init();while(1){…...

ORA-00600: internal error code, arguments

通过rman将11g异机升级到19c时&#xff0c;应用归档时报错&#xff0c;报错如下 RMAN> recover database ; Starting recover at 2023-10-15 21:10:02 allocated channel: ORA_DISK_1 channel ORA_DISK_1: SID5776 device typeDISK starting media recovery media recove…...

C#里氏替换

在C#中&#xff0c;里氏替换原则是面向对象编程中的一个重要原则&#xff0c;它是关于继承和多态性的概念。 里氏替换原则的定义是&#xff1a;如果S是T的子类型&#xff08;或者T是S的基类型&#xff09;&#xff0c;那么程序中任意使用T类型的地方都可以替换为S类型而不会产…...

Java-使用sqlSessionTemplate实现批量更新-模拟mybatis 动态sql

环境准备&#xff08;非核心方法&#xff09; 创建表 创建表的sql(下表是基于Oracle创建的) CREATE TABLE "SYSTEM"."STUDENT" ("ID" NUMBER(10, 0),"NAME" VARCHAR2(20 BYTE),"ADDRES" CLOB,PRIMARY KEY ( …...

Eslint配置 Must use import to load ES Module(已解决)

最近在配置前端项目时&#xff0c;eslint经常会碰到各种报错&#xff08;灰常头疼~&#xff09; Syntax Error Error No ESLint configuration found.Syntax Error: Error: D:\dmq\dmq-ui.eslintrc.js: Environment key “es2021” is unknown at Array.forEach ()error in ./…...

正向代理(流量代理)

文章目录 正向代理与反向代理流量转发工具环境准备reGeorg 正向代理与反向代理 正向代理是客户端和其他所有服务器&#xff08;重点&#xff1a;所有&#xff09;的代理者 反向代理是客户端和所要代理的服务器之间的代理。 流量转发工具 需要放在拿到shell的服务器上可使用 …...

易天光通信推出100G BIDI ER光模块最新解决方案

随着数字信息时代的快速发展&#xff0c;网络通信技术的迅猛进步成为推动科技创新和产业升级的重要引擎之一。作为光通信行业的新秀&#xff0c;近期易天光通信推出了全新的100G BIDI ER1 Lite光模块和100G BIDI LR1 Lite光模块&#xff0c;助力崭新的未来网络建设。 易天光通…...

Flask框架配置celery-[1]:flask工厂模式集成使用celery,可在异步任务中使用flask应用上下文,即拿即用,无需更多配置

一、概述 1、celery框架和flask框架在运行时&#xff0c;是在不同的进程中&#xff0c;资源是独占的。 2、celery异步任务如果想使用flask中的功能&#xff0c;如orm&#xff0c;是需要在flask应用上下文管理器中执行orm操作的 3、使用celery是需要使用到中间件的&#xff0…...

合并二叉树

题目链接 合并二叉树 题目描述 注意点 如果两个节点重叠&#xff0c;那么将这两个节点的值相加作为合并后节点的新值&#xff1b;否则&#xff0c;不为 null 的节点将直接作为新二叉树的节点 解答思路 先序遍历二叉树&#xff0c;将重叠部分节点值相加作为新节点的值&…...

Sanic​——Python函数变成API的神器

今天给大家介绍一个超好用的框架&#xff0c;迅速将Python函数变成API&#xff0c;它就是最近越来越火的异步Web框架Sanic。 1. Sanic简介 Sanic 是 Python3.7 Web 服务器和 Web 框架&#xff0c;旨在提高性能。它允许使用 Python3.5 中添加的async/await语法&#xff0c;这使…...

Windows连接不上VMware,ping不通的问题

文章目录 防火墙问题Windows和虚拟机下的ip不一致导致的问题VMware Network Adapter (适配器)丢失的问题参考文档 防火墙问题 防火墙默认不会拦截ping命令&#xff0c;除非你个人设置了Linux防火墙Centos7的常用命令关闭防火墙 systemctl stop firewalld #停止Windows和虚拟机…...

24、Flink 的table api与sql之Catalogs(java api操作数据库、表)-2

Flink 系列文章 1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接 13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…...

【MySQL】深入了解索引的底层逻辑结构

文章目录 主键排序一. InnoDB的索引结构1. 单个page2. 多个page 二. 为什么选择B树三. 聚簇索引和非聚簇索引结束语 主键排序 我们创建一个user表&#xff0c;并乱序插入数据 mysql> create table if not exists user(-> id int primary key,-> age int not null,-&…...

Android之SpannableString使用

文章目录 前言一、效果图二、实现代码总结 前言 在开发中&#xff0c;往往有些需求是我们不愿意遇到的&#xff0c;但是也不得不处理的事情&#xff0c;比如一段文案&#xff0c;需要文案中某些文字变颜色或者点击跳转&#xff0c;所以简单写了几句代码实现&#xff0c;没什么…...

【Python】Python求均值、中值和众数

Python求均值、中值和众数 我们将讨论如何使用描述性统计数据进行数据分析&#xff0c;包括&#xff1a; 均值——一组值的平均值; 中值——当所有值按顺序排列时的中间值; 众数——最常出现的值。 以上这些都是集中趋势度量&#xff0c;每种都会产生一个值来表示一组值中的“…...

NPM 常用命令(十二)

目录 1、npm unpublish 1.1 使用语法 1.2 描述 2、npm unstar 2.1 使用语法 3、npm update 3.1 使用语法 3.2 描述 3.3 示例 插入符号依赖 波浪号依赖 低于 1.0.0 的插入符号依赖 子依赖 更新全局安装的包 4、npm version 4.1 使用语法 5、npm view 5.1 使用语…...

数据在内存中的存储(2)

文章目录 3. 浮点型在内存中的存储3.1 一个例子3.2 浮点数存储规则 3. 浮点型在内存中的存储 常见的浮点数&#xff1a; 3.14159 1E10 ------ 1.0 * 10^10 浮点数家族包括&#xff1a; float、double、long double 类型 浮点数表示的范围&#xff1a;float.h中定义 3.1 一个例…...

MCP Jenkins Intelligence:基于AI的Jenkins智能运维与效率提升实践

1. 项目概述&#xff1a;当Jenkins遇上AI&#xff0c;DevOps的“副驾驶”来了如果你和我一样&#xff0c;每天都要和Jenkins打交道&#xff0c;盯着那些流水线看构建状态、查失败日志、分析性能瓶颈&#xff0c;那你肯定也幻想过&#xff1a;要是能像聊天一样问它问题就好了。比…...

魔兽争霸3终极增强插件:5分钟快速解决游戏兼容性问题

魔兽争霸3终极增强插件&#xff1a;5分钟快速解决游戏兼容性问题 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper WarcraftHelper是一款专为魔兽争霸3设…...

月薪8K到年薪80万!这个AI职位一年暴涨985%,普通人如何抓住风口?2026年最火爆的5个岗位+3条入场路径全解析!

文章讲述了AI Agent开发工程师的兴起&#xff0c;年薪可达80万。文章以小李的真实故事为例&#xff0c;展示了通过主动学习AI技术&#xff0c;可以实现职业的巨大转变。文章还分析了Agentic AI的特点及其对就业市场的影响&#xff0c;指出40%的岗位将被重新定义。文章列举了AI …...

从零构建C++/CUDA推理引擎:深入解析yalm项目与LLM底层优化

1. 项目概述&#xff1a;从零构建一个高性能的C/CUDA推理引擎最近在深入研究大语言模型推理的性能优化&#xff0c;发现很多开源实现为了追求极致的性能&#xff0c;代码往往高度优化&#xff0c;甚至引入了动态并行等高级CUDA特性&#xff0c;这对想深入理解底层原理的开发者来…...

FreeRTOS和RT-Thread的内存管理实战:如何正确使用pvPortMalloc与rt_malloc替代C库malloc

FreeRTOS与RT-Thread内存管理实战&#xff1a;从标准库陷阱到RTOS最佳实践 在嵌入式实时操作系统开发中&#xff0c;动态内存分配就像高空走钢丝——一步失误可能导致系统崩溃。传统C库的malloc/free在RTOS环境中如同穿着拖鞋走钢丝&#xff0c;而pvPortMalloc和rt_malloc则是专…...

MySQL 安装后安全加固实操:从空密码警告到配置安全远程访问(Ubuntu 18.04 + MySQL 5.7)

MySQL 安全加固实战&#xff1a;从空密码警告到生产级配置 在Ubuntu服务器上部署MySQL数据库时&#xff0c;许多开发者会惊讶地发现安装后竟然可以直接用mysql -uroot无密码登录。这种默认配置在生产环境中无异于敞开大门邀请不速之客。本文将带你完成从基础安装到生产级安全配…...

流水线插件开发从3天缩短到10分钟:嘉为蓝鲸AI技能实战

流水线插件开发从3天缩短到10分钟&#xff1a;嘉为蓝鲸AI技能实战 在企业 DevOps 落地中&#xff0c;流水线插件是工具对接、流程沉淀、平台扩展的核心。但插件开发往往面临门槛高、周期长、质量不稳定等问题。一个简单插件&#xff0c;两三天就这么耗进去了。01 插件开发的真实…...

Loop:三步快速配置,让你的Mac窗口管理效率提升300%

Loop&#xff1a;三步快速配置&#xff0c;让你的Mac窗口管理效率提升300% 【免费下载链接】Loop Window management made elegant. 项目地址: https://gitcode.com/GitHub_Trending/lo/Loop 你是否经常在多个应用窗口间来回切换&#xff0c;感觉自己像个杂技演员&#…...

AI智能体技能库开发指南:模块化设计、安全实践与性能优化

1. 项目概述&#xff1a;一个面向AI智能体的技能库最近在折腾AI智能体&#xff08;Agent&#xff09;开发&#xff0c;发现一个挺有意思的项目&#xff1a;jdrhyne/agent-skills。这名字听起来就挺直白&#xff0c;一个“智能体技能库”。简单来说&#xff0c;它不是一个完整的…...

Steel:专为AI智能体设计的浏览器自动化API与部署实战

1. 项目概述&#xff1a;为AI应用赋能的浏览器自动化引擎 如果你正在构建一个需要与真实网页交互的AI智能体&#xff0c;或者开发一个复杂的浏览器自动化工具&#xff0c;那么你大概率会遇到一个共同的难题&#xff1a;如何稳定、高效地管理浏览器实例&#xff1f;从处理无头Ch…...