Linux驱动开发之音频驱动与基础应用编程

目录

CODEC芯片

音频编码

I2S总线接口

数字音频接口(DAI)

设备树配置

ALSA

音频相关概念

应用程序编写

运行测试

CODEC芯片

音频若想被CPU“听到”,就必须转换为CPU能够“听懂”的语言,即二进制数据的0和1。在信号处理领域,声音是模拟信号而二进制数据是数字信号,因此需要一个将模拟信号转化为数字信号的器件,而完成这个功能的就是ADC芯片。同理,CPU若想向外传达声音,就需要将数字信号转化为模拟信号,而完成这个功能的则是 DAC 芯片。将ADC和DAC芯片以及一系列其他单元叠加到一起就形成了专门用于音频处理的芯片,即音频编解码芯片(Audio CODEC),如ES8388,ES7202和rt5640等。不同CODEC芯片支持的录音/放音功能有所不同,其对应的引脚接口也会不同,但总体上CODEC对外接口可分为控制接口和数据接口两大部分,一般对应I2C和I2S。荣品RK3588开发板上使用的是rt5640,其原理图对应部分如下:

可以看出该CODEC通过AUDIO_SCL和AUDIO_SDA与CPU的I2C相连,通过I2C对其进行配置,并通过I2S接口进行数据传输。具有两路输入,两路扬声器输出以及一路耳机输出。

音频编码

音频编码一般采用脉冲编码调制(PCM)的方法,PCM是Pulse Code Modulation的缩写,该编码方法是对语音信号进行采样,然后对每个采样值进行量化编码,我们所熟知的CD音频即是使用PCM编码。使用PCM编码的音频文件在windows下的保存格式也是我们所熟知的WAV格式。

在PCM基础上发展起来的还有自适应差分脉冲编码调制ADPCM,其编码的方法是对输入样值进行自适应预测,然后对预测误差进行量化编码。除此之外,其他编码方式还有线性预测编码LPC,低延迟码激励线性预测编码LD-CELP等。而除了WAV格式,还有MP3,WMA和RAW等编码格式。

I2S总线接口

I2S(Inter-IC Sound)总线有时候也写作IIS,是飞利浦公司提出的一种用于数字音频设备之间进行音频数据传输的总线。和I2C、SPI这些常见的通信协议一样,I2S总线用于主控制器和音频CODEC芯片之间传输音频数据。因此,要想使用I2S协议,主控制器和音频CODEC 都得支持I2S协议。作为一种数字音频设备之间进行音频数据传输的总线标准,I2S一般有3~5根物理连接线:

  • SCK:串行时钟信号,为串行数据提供位时钟(BCLK),音频数据的每一位数据都对应一个SCK,而立体声都是双声道的,因此 SCK=2×采样率×采样位数。
  • WS:字段(声道)选择信号,也叫做LRCK,用于切换左右声道数据帧,WS为“1”表示正在传输左声道的数据,WS为“0”表示正在传输右声道的数据,其频率等于采样率。
  • SD:串行数据信号,也就是实际传输的音频数据,如果要同时实现放音和录音,那么就需要2根数据线IISDI和IISDO,分别用于录音和放音。
  • MCLK:为了使音频 CODEC 芯片与主控制器之间能够更好的同步而引入的信号,也叫做同步时钟或编码器时钟,一般是采样率的256倍或384倍。

数据的发送方和接收方需要有一个时钟信号来控制数据的传输,因此数据发送方(主设备)必须提供字段选择信号、时钟信号和数据信号。当有多个发送方和接收方,系统需引入控制模块,用于控制数字音频数据在不同设备之间的传输。传输模式示意图如下:

I2S总线协议也即音频数据时序图如下:

随着技术的发展,在统一的I2S接口下,出现了不同的数据格式,根据音频数据相对于LRCK 和SCK位置的不同,可分为LeftJustified(左对齐)和 RightJustified(右对齐)两种格式

数字音频接口(DAI)

音频CODEC支持I2S协议,那么主控制器也必须支持I2S协议,而主控制器则是通过音频接口来连接CODEC,这个接口在RK平台上的外设则为数字音频接口(DAI)。RK平台有两种I2S控制器:I2S和I2S-TDM。I2S控制器支持I2S, PCM协议;I2S-TDM控制器支持 I2S,PCM, TDM 协议。除此之外,RK平台还具有PDM控制器,支持PDM协议的数字麦或者ADC;支持数字CODEC接口,可对接支持该协议的模拟CODEC组合成完整CODEC;支持语音活性检测 (Voice Activity Detection),VAD接收来自DAI的数据,处理统计分析,达到预设阈值时,触发中断,唤醒系统;还支持支持 SPDIF Transmitter 接口协议等。并且RK 平台支持任意 DAI 的组合使用,重组 DAI 生成 Combo DAI,如下图所示:

一般来说,RK发布的SDK中已经完成了DAI驱动的编写,开发者只需要根据应用场景配置属性启用相应功能即可。相关驱动文件基本都在目录kernel/sound/soc/rockchip下。而machine驱动部分主要涉及声卡的添加,其中Simple Card是ASoC通用的machine driver,可支持大部分标准声卡的添加。

设备树配置

荣品RK3588开发板上使用的是rt5640,其相关设备树配置如下:

/ {rt5640-sound {compatible = "simple-audio-card";simple-audio-card,format = "i2s";simple-audio-card,name = "rockchip,rt5640-codec";simple-audio-card,mclk-fs = <256>;simple-audio-card,widgets ="Microphone", "Mic Jack","Headphone", "Headphone Jack";simple-audio-card,routing ="Mic Jack", "MICBIAS1","IN1P", "Mic Jack","Headphone Jack", "HPOL","Headphone Jack", "HPOR";simple-audio-card,cpu {sound-dai = <&i2s0_8ch>;};simple-audio-card,codec {sound-dai = <&rt5640>;};};rk_headset: rk-headset {status = "okay";compatible = "rockchip_headset";headset_gpio = <&gpio1 RK_PC4 GPIO_ACTIVE_HIGH>;pinctrl-names = "default";pinctrl-0 = <&hp_det>;};
};&i2s0_8ch {status = "okay";
};&i2c7 {status = "okay";pinctrl-names = "default";pinctrl-0 = <&i2c7m0_xfer>;rt5640: rt5640@1c {#sound-dai-cells = <0>;compatible = "realtek,rt5640";reg = <0x1c>;clocks = <&mclkout_i2s0>;clock-names = "mclk";realtek,in1-differential;pinctrl-names = "default";pinctrl-0 = <&i2s0_mclk>;io-channels = <&saradc 4>;hp-det-adc-value = <500>;spk-play-volume = <7>;       63-0 min-maxhp-play-volume = <15>;       63-0 min-maxcapture-volume = <127>; //0-127 min-max};
};&pinctrl {rt5640_pinctrl {hp_det:hp_det {rockchip,pins = <1 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>;};};
};

结合原理图可知rt5640挂载在i2c7下,也即其codec节点添加在i2c7节点之下。驱动文件在目录kernel/sound/soc/codecs下,为rt5640.c,该驱动文件为厂家所编写,一般直接移植即可。在rt5640该codec节点添加完成后,需添加并使能sound节点,这里为sound-rt5640节点,其与Simple Card该通用machine driver相配对。最后根据硬件连接情况使能对应的i2s节点,这里为i2s0,与原理图一致。其余配置为开发板针对耳机的相关节点配置以及耳机插入检测所需的gpio资源配置等,在驱动中实现耳机插入检测后从扬声器切换为耳机的功能。最后通过图形化配置界面使能相关配置,如I2S,新添加的rt5640驱动等。

重新编译并烧写内核,使用cat /proc/asound/cards命令查看系统声卡,确认驱动是否移植成功。

ALSA

ALSA是Advanced Linux Sound Architecture(高级的 Linux 声音体系)的缩写,目前已经成为了linux下的主流音频体系架构,采用分离、分层思想设计,提供了音频和MIDI的支持,替代了原先旧版本中的OSS(开发声音系统)。在应用层,ALSA提供了一套标准的API,应用程序只需要调用这些API就可完成对底层音频硬件设备的控制,如播放、录音等,这一套 API称为alsa-lib,关系示意图如下。在Linux内核设备驱动层,基于ALSA音频驱动框架注册的sound设备会在/dev/snd 目录下生成相应的设备节点文件。

由于alsa-lib是ALSA提供的一套在Linux下的C语言函数库,因此需要将alsa-lib交叉编译移植到开发板上,这样基于alsa-lib编写的应用程序才能成功运行。除了移植alsa-lib库之外,通常还需要移植alsa-utils,alsa-utils包含了一些用于测试、配置声卡的命令和工具,譬如aplay、arecord、alsactl、alsaloop、alsamixer、amixer等。其中,aplay命令用于播放.wav格式的音频文件;arecord命令用于录音测试,其生成.wav格式的音频文件;alsaloop命令用于回环测试,可实现边录音边播放;alsamixer和amixer用于配置声卡的混音器,区别在于前者是图形化界面后者是命令行形式;alsactl则用来保存对声卡的配置。在ALSA官网有这些工具的详细介绍及大量资料参考。

音频相关概念

基于alsa-lib的应用编程中会涉及一系列音频相关概念,如样本长度,声道数和采样率等。

  • 样本(sample)是记录音频数据最基本的单元,样本长度就是采样位数,也称为位深度(Bit Depth、Sample Size、Sample Width)。指计算机在采集和播放声音文件时,所使用数字声音信号的二进制位数,或者说每个采样样本所包含的位数(计算机对每个通道采样量化时数字比特位数),通常有8bit、16bit、24bit等。
  • 声道数(channel)分为单声道(Mono)和双声道/立体声(Stereo)1 表示单声道、2 表示立体声。
  • 帧(frame)表示一个声音单元,其长度为样本长度与声道数的乘积,一段音频数据就是由苦干帧组成的。把所有声道中的数据加在一起叫做一帧,对于单声道:一帧 = 样本长度 * 1;双声道:一帧 = 样本长度 * 2。譬如对于样本长度为 16bit 的双声道来说,一帧的大小等于:16 * 2 / 8 = 4 个字节。
  • 采样率(sample rate)指每秒钟采样次数,该次数是针对而言。常见的采样率有:8KHz,44.1KHz等。
  • 交错模式(interleaved)是一种音频数据的记录方式,分为交错模式和非交错模式。在交错模式下,数据以连续桢的形式存放,即首先记录完桢 1 的左声道样本和右声道样本(假设为立体声格式),再记录桢 2 的左声道样本和右声道样本。而在非交错模式下,首先记录的是一个周期内所有桢的左声道样本,再记录右声道样本,数据是以连续通道的方式存储。多数情况下都是使用交错模式。
  • 周期(period)是音频设备处理(读、写)数据的单位,每一次读或写一个周期的数据,一个周期包含若干个帧;譬如周期的大小为 1024 帧,则表示音频设备进行一次读或写操作的数据量大小为 1024 帧,假设一帧为 4 个字节,那么也就是 1024*4=4096 个字节数据。一个周期其实就是两次硬件中断之间的帧数,音频设备每处理(读或写)完一个周期的数据就会产生一个中断,所以两个中断之间相差一个周期
  • 数据缓冲区(buffer),一个缓冲区包含若干个周期,所以buffer是由若干个周期所组成的一块空间。

它们之间的关系如下:

音频设备底层驱动程序使用DMA来搬运数据,buffer中有4个period,每当DMA搬运完一个period的数据就会触发一次中断,因此搬运整个buffer中的数据将产生4次中断。为了延迟问题,ALSA把缓存区拆分成多个周期,以周期为传输单元进行传输数据。所以,周期不宜设置过大,周期过大会导致延迟过高;但周期也不能太小,周期太小会导致频繁触发中断,这样会使得CPU被频繁中断而无法执行其它的任务,使得效率降低。

应用程序编写

基于alsa-lib编写简单的音频播放程序,通过WAV音频文件相关数据结构实现对音频文件的信息解析并进行打印显示。

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <alsa/asoundlib.h>#define PCM_PLAYBACK_DEV "hw:0,0"//WAV 音频文件解析相关数据结构申明typedef struct WAV_RIFF {char ChunkID[4]; /* "RIFF" */u_int32_t ChunkSize; /* 从下一个地址开始到文件末尾的总字节数 */char Format[4]; /* "WAVE" */
} __attribute__ ((packed)) RIFF_t;typedef struct WAV_FMT {char Subchunk1ID[4]; /* "fmt " */u_int32_t Subchunk1Size; /* 16 for PCM */u_int16_t AudioFormat; /* PCM = 1*/u_int16_t NumChannels; /* Mono = 1, Stereo = 2, etc. */u_int32_t SampleRate; /* 8000, 44100, etc. */u_int32_t ByteRate; /* = SampleRate * NumChannels * BitsPerSample/8 */u_int16_t BlockAlign; /* = NumChannels * BitsPerSample/8 */u_int16_t BitsPerSample; /* 8bits, 16bits, etc. */
} __attribute__ ((packed)) FMT_t;static FMT_t wav_fmt;typedef struct WAV_DATA {char Subchunk2ID[4]; /* "data" */u_int32_t Subchunk2Size; /* data size */
} __attribute__ ((packed)) DATA_t;static snd_pcm_t *pcm = NULL; //pcm 句柄
static unsigned int buf_bytes; //应用程序缓冲区的大小(字节为单位)
static void *buf = NULL; //指向应用程序缓冲区的指针
static int fd = -1; //指向 WAV 音频文件的文件描述符
static snd_pcm_uframes_t period_size = 1024; //周期大小(单位: 帧)
static unsigned int periods = 16; //周期数(设备驱动层 buffer 的大小)static int snd_pcm_init(void)
{snd_pcm_hw_params_t *hwparams = NULL;int ret;/* 打开 PCM 设备 */ret = snd_pcm_open(&pcm, PCM_PLAYBACK_DEV, SND_PCM_STREAM_PLAYBACK, 0);if (0 > ret) {fprintf(stderr, "snd_pcm_open error: %s: %s\n",PCM_PLAYBACK_DEV, snd_strerror(ret));return -1;}/* 申请hwparams */snd_pcm_hw_params_malloc(&hwparams);/* 初始化hwparams */ret = snd_pcm_hw_params_any(pcm, hwparams);if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params_any error: %s\n", snd_strerror(ret));goto err2;}/* 设交错模式 */ret = snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params_set_access error: %s\n", snd_strerror(ret));goto err2;}/* 设置数据格式: 有符号 16 位、小端模式 */ret = snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16_LE);if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params_set_format error: %s\n", snd_strerror(ret));goto err2;}/* 设置采样率 */ret = snd_pcm_hw_params_set_rate(pcm, hwparams, wav_fmt.SampleRate, 0);if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params_set_rate error: %s\n", snd_strerror(ret));goto err2;}/* 设置声道数: 双声道 */ret = snd_pcm_hw_params_set_channels(pcm, hwparams, wav_fmt.NumChannels);if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params_set_channels error: %s\n", snd_strerror(ret));goto err2;}/* 设置周期大小 */ret = snd_pcm_hw_params_set_period_size(pcm, hwparams, period_size, 0);if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params_set_period_size error: %s\n", snd_strerror(ret));goto err2;}/* 设置周期数(驱动层 buffer 的大小) */ret = snd_pcm_hw_params_set_periods(pcm, hwparams, periods, 0);if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params_set_periods error: %s\n", snd_strerror(ret));goto err2;}/* 使配置生效 */ret = snd_pcm_hw_params(pcm, hwparams);snd_pcm_hw_params_free(hwparams); //释放 hwparams 对象占用的内存if (0 > ret) {fprintf(stderr, "snd_pcm_hw_params error: %s\n", snd_strerror(ret));goto err1;}buf_bytes = period_size * wav_fmt.BlockAlign; //变量赋值,一个周期的字节大小return 0;
err2:snd_pcm_hw_params_free(hwparams); //释放内存
err1:snd_pcm_close(pcm); //关闭 pcm 设备return -1;
}
static int open_wav_file(const char *file)
{RIFF_t wav_riff;DATA_t wav_data;int ret;fd = open(file, O_RDONLY);if (0 > fd) {fprintf(stderr, "open error: %s: %s\n", file, strerror(errno));return -1;}/* 读取 RIFF chunk */ret = read(fd, &wav_riff, sizeof(RIFF_t));if (sizeof(RIFF_t) != ret) {if (0 > ret)perror("read error");elsefprintf(stderr, "check error: %s\n", file);close(fd);return -1;}if (strncmp("RIFF", wav_riff.ChunkID, 4) ||//校验strncmp("WAVE", wav_riff.Format, 4)) {fprintf(stderr, "check error: %s\n", file);close(fd);return -1;}/* 读取 sub-chunk-fmt */ret = read(fd, &wav_fmt, sizeof(FMT_t));if (sizeof(FMT_t) != ret) {if (0 > ret)perror("read error");elsefprintf(stderr, "check error: %s\n", file);close(fd);return -1;}if (strncmp("fmt ", wav_fmt.Subchunk1ID, 4)) {//校验fprintf(stderr, "check error: %s\n", file);close(fd);return -1;}/* 打印音频文件的信息 */printf("<<<<音频文件格式信息>>>>\n\n");printf(" file name: %s\n", file);printf(" Subchunk1Size: %u\n", wav_fmt.Subchunk1Size);printf(" AudioFormat: %u\n", wav_fmt.AudioFormat);printf(" NumChannels: %u\n", wav_fmt.NumChannels);printf(" SampleRate: %u\n", wav_fmt.SampleRate);printf(" ByteRate: %u\n", wav_fmt.ByteRate);printf(" BlockAlign: %u\n", wav_fmt.BlockAlign);printf(" BitsPerSample: %u\n\n", wav_fmt.BitsPerSample);/* sub-chunk-data */if (0 > lseek(fd, sizeof(RIFF_t) + 8 + wav_fmt.Subchunk1Size,SEEK_SET)) {perror("lseek error");close(fd);return -1;}while(sizeof(DATA_t) == read(fd, &wav_data, sizeof(DATA_t))) {/* 找到 sub-chunk-data */if (!strncmp("data", wav_data.Subchunk2ID, 4))//校验return 0;if (0 > lseek(fd, wav_data.Subchunk2Size, SEEK_CUR)) {perror("lseek error");close(fd);return -1;}}fprintf(stderr, "check error: %s\n", file);return -1;
}int main(int argc, char *argv[])
{int ret;if (2 != argc) {fprintf(stderr, "Usage: %s <audio_file>\n", argv[0]);exit(EXIT_FAILURE);}/* 打开 WAV 音频文件 */if (open_wav_file(argv[1]))exit(EXIT_FAILURE);/* 初始化 PCM Playback 设备 */if (snd_pcm_init())goto err1;/* 申请读缓冲区 */buf = malloc(buf_bytes);if (NULL == buf) {perror("malloc error");goto err2;}/* 播放 */for ( ; ; ) {memset(buf, 0x00, buf_bytes); ret = read(fd, buf, buf_bytes); if (0 >= ret) goto err3;ret = snd_pcm_writei(pcm, buf, period_size);if (0 > ret) {fprintf(stderr, "snd_pcm_writei error: %s\n", snd_strerror(ret));goto err3;}else if (ret < period_size) {if (0 > lseek(fd, (ret-period_size) * wav_fmt.BlockAlign, SEEK_CUR)) {perror("lseek error");goto err3;}}}
err3:free(buf); 
err2:snd_pcm_close(pcm); 
err1:close(fd); exit(EXIT_FAILURE);
}

运行测试

这里采用龙芯2K0300久久派开发板进行测试,同时也是验证只要开发板移植了alsa-lib就能运行基于alsa-lib所编写的音频应用程序。交叉编译得到pcm_playback,拷入开发板运行播放音频文件,插入耳机能听到声音,并将程序运行的打印信息与文件信息作对比。

总结:本篇详细介绍了音频驱动相关概念,RK平台音频相关配置以及ALSA编程相关概念等,最后基于alsa-lib编写了简单的音频播放程序并在开发板上进行了验证。

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

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

相关文章

在 Java 中解析 JSON 数据

例子解析以下JSON数据 {"code":0,"msg":"成功","data": [{ "host":"1068222.com", "port":"", "m_token":"490e20e70e7de5f21a24b14c12a393f6", "categ…

python——集合(一)

文章目录 集合 set创建集合访问集合项in关键字添加集合元素删除集合元素复制集合使用操作符对集合进行交集、并集、差集、对称差集使用方法对集合进行交集、并集、差集、对称差集子集和超集 frozenset 冻结集合&#xff1f; 不可变集合&#xff01; 集合 set 什么是集合&#…

DeepSeek 与网络安全:AI 在网络安全领域的应用与挑战

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 1. 引言 在当今数字化时代&#xff0c;网络安全已成为国家、企业和个人面临的重要挑战。从传统的病毒、木马攻击&#xff0c;到高…

【Blender】二、建模篇--05,阵列修改器与晶格形变

阵列修改器是bender里面一个比较常用的修改器,所以我们单独开口来讲,我们会先从几片树叶出发,然后我们用阵列修改器把这几片树叶变成这样的造型和这样的造型。这两个造型分别就代表着阵列修改器最常用的两种偏移方法,我们现在就开始我们先来做几个树叶。 1.树叶建模 首先…

【Python 专题】数据结构 树

LeetCode 题目104. 二叉树的最大深度(gif 图解)方法一:后序遍历(DFS)方法二:层序遍历(BFS)872. 叶子相似的树(DFS 遍历)1448. 统计二叉树中好节点的数目(DFS 遍历)437. 路径总和 III(前缀和 + DFS 回溯)1372. 二叉树中的最长交错路径(DFS)236. 二叉树的最近公共…

Linux下基本指令(4)

Linux权限的概念 Linux下有两种用户&#xff1a;超级用户&#xff08;root&#xff09;、普通用户。 超级用户&#xff1a;可以再linux系统下做任何事情&#xff0c;不受限制 普通用户&#xff1a;在linux下做有限的事情。 超级用户的命令提示符是“#”&#xff0c;普通用户…

ubuntu部署小笔记-采坑

ubuntu部署小笔记 搭建前端控制端后端前端nginx反向代理使用ubuntu部署nextjs项目问题一 如何访问端口号配置后台运行该进程pm2 问题二 包体过大生产环境下所需文件 问题三 部署在vercel时出现的问题需要魔法访问后端api时&#xff0c;必须使用https协议电脑端访问正常&#xf…

【联盛德 W803-Pico 试用】简介、工程测试

【联盛德 W803-Pico 试用】简介、工程测试 本文介绍了联盛德微电子 W803-Pico 开发板的基本信息、环境搭建、工程测试等内容。简介包含开发板功能、主控参数及特点、开发板原理图等信息&#xff0c;工程测试包括 Blink、串口打印等方案的演示。 活动详情&#xff1a;联盛德问答…

cursor使用记录

一、如何查看自己登录的是哪个账号 操作路径&#xff1a;Cursor -- 首选项 -- Cursor Setting &#xff08;有快捷键&#xff09; 二、状态修改为竖排&#xff08;默认是横排&#xff09; 默认如图展示&#xff0c;想要像vscode、idea等等在左侧竖着展示 操作路径&#xff1…

gitlab 解决双重认证无法登录remote: HTTP Basic: Access denied.

问题&#xff1a;gitlab开启了双因素认证 如进行了 OAuth configuration 在进行git操作时如下提示 remote: HTTP Basic: Access denied. The provided password or token is incorrect or your account has 2FA enabled and you must use a personal access token instead o…

C语言基础学习指南:从零入门到实战应用——适合零基础学习者与进阶巩固

目录 一、C语言概述与开发环境搭建 二、核心语法与数据类型 三、控制结构与运算符 四、函数与模块化编程 五、指针与内存管理 六、实践建议与资源推荐 结语 一、C语言概述与开发环境搭建 C语言是一种高效、灵活的通用编程语言&#xff0c;广泛应用于系统开发、嵌入式系…

C# 委托——lambda

lambda表达式不简化写起来和匿名函数很像&#xff0c;而匿名函数通常赋值给委托&#xff0c;通过委托进行调用。以下我们对lambda和委托的基本规则与使用进行整理&#xff0c;同时为了加深理解和记忆&#xff0c;我们整理了委托是如何一步步演化到lambda。 1. 委托 委托是一个…

【每日论文】TESS 2: A Large-Scale Generalist Diffusion Language Model

下载PDF或阅读论文&#xff0c;请点击&#xff1a;LlamaFactory - huggingface daily paper - 每日论文解读 | LlamaFactory | LlamaFactory 摘要 我们推出了TESS 2&#xff0c;这是一种通用的指令跟随扩散语言模型&#xff0c;其性能优于当代的指令调整扩散模型&#xff0c;有…

conda 配置源

无论是Anaconda vs Miniconda vs Miniforge 中的哪个&#xff0c;只要使用conda就涉及源&#xff0c;换源的目的是为了加速包的获取 修改配置文件 通过修改用户目录下的 .condarc 文件来使用 不同系统下的 .condarc 目录如下&#xff1a; Linux: ${HOME}/.condarcmacOS: ${…

AI大模型发展对语音直播交友系统源码开发搭建的影响

近年来&#xff0c;AI大模型技术突飞猛进&#xff0c;为语音直播交友系统的源码开发搭建带来了深远影响。本文将从技术发展层面&#xff0c;探讨AI大模型如何赋能语音直播交友系统&#xff0c;并分析其对开发流程、功能实现和用户体验等方面带来的变革。 一、技术赋能&#xff…

C++面试题,TCP和UDP方面(1)

个人主页 &#xff1a; 个人主页 个人专栏 &#xff1a; 《数据结构》 《C语言》《C》《Linux》《网络》 《redis学习笔记》 文章目录 前言TCP和UDP的区别UDP如何实现可靠TCP滑动窗口原理TCP流量控制TCP超时重传总结 前言 这是个人总结的C方向的面试题,TCP和UDP方面&#xff0…

Huatuo热更新--如何使用

在安装完huatuo热更新插件后就要开始学习如何使用了。 1.创建主框渐Main 新建文件夹Main&#xff08;可自定义&#xff09;&#xff0c;然后按下图创建文件&#xff0c;注意名称与文件夹名称保持一致 然后新建场景&#xff08;Init场景&#xff09;&#xff0c;添加3个空物体…

Springboot + Ollama + IDEA + DeepSeek 搭建本地deepseek简单调用示例

1. 版本说明 springboot 版本 3.3.8 Java 版本 17 spring-ai 版本 1.0.0-M5 deepseek 模型 deepseek-r1:7b 需要注意一下Ollama的使用版本&#xff1a; 2. springboot项目搭建 可以集成在自己的项目里&#xff0c;也可以到 spring.io 生成一个项目 生成的话&#xff0c;如下…

如何在 macOS 上配置 MySQL 环境变量

如何在 macOS 上配置 MySQL 环境变量 步骤 1: 查找 MySQL 安装路径 打开终端&#xff0c;使用以下命令查找 mysql 的可执行文件路径&#xff1a; which mysql如果该命令没有返回结果&#xff0c;可以使用 find 命令&#xff1a; sudo find / -name "mysql" 2>/de…

Unity Excel导表工具转Lua文件

思路介绍 借助EPPlus读取Excel文件中的配置数据&#xff0c;根据指定的不同类型的数据配置规则来解析成对应的代码文本&#xff0c;将解析出的字符串内容写入到XXX.lua.txt文件中即可 EPPlus常用API //命名空间 using OfficeOpenXml;//Excel文件路径 var fileExcel new File…