Android LAME原生音频

前言

我想大家都做过录音的功能吧,首先想到的是不是MediaRecorder?今天我们不用MediaRecorder,而是使用LAME库自己编译音频编码模块,很明显,这个需要用到NDK。凡是涉及到音视频编解码这块的,都需要用到Android NDK(Native Development Kit),原生开发工具包。即使用C/C++代码实现音频的采样和编码,然后使用Java去调用原生模块实现录音功能。

音视频相关基础知识

我来简单过一下基础的音视频相关知识。

Audio Sample:音频采样,通常指录制音频采样文件PCM的过程,PCM(脉冲编码调制)是一种音频流,称为裸流。人耳听到的是模拟信号,PCM是把声音从模拟信号转化为数字信号的技术。

Audio Track:音轨,封装格式的音频文件通常由多个音轨组成,比如MP3文件就是一种封装格式的音频文件,与之相对的就是音频原始采样数据PCM。比如一首歌,歌声是一个音轨,吉他声、鼓声等一些混合在其中的声音各自也是一个音轨。

Audio Channel:声道,比如左声道、右声道和环绕立体声。

Bitrate:比特率,俗称码率。它直接决定声音的清晰度即声音特征的详细程度,SQ、HQ音质是通过它来判断的,码率越高,音频文件越大,质量也越高。

Sample Rate:采样率,大多数沿用国际通用的标准采样率,即44.100kHZ或者48.000kHZ,肯定是不能录制超声波和次声波的,因为人耳感知不到。

录音和播放声音的详细过程。

录音:音频采样编码->音频封装

播放声音:音频解封装->音频解码播放

录音首先采样并编码得到PCM文件,然后封装PCM文件为MP3、WAV、FLAC等文件,播放声音首先也要解封装成PCM文件,然后对PCM文件解码播放。

编译共享库so

编译共享库so的过程有两种方式,一种是使用ndk-build+Android.mk+Application.mk,一种是CMake+CMakeLists.txt。今天我们采用最原始的方式,mk文件。其实Android.mk和CMakeLists.txt很多东西是一一对应的。

Android.mkCMakeLists.txt
LOCAL_MODULE、LOCAL_SRC_FILESadd_library
LOCAL_CFLAGSadd_definitions
LOCAL_C_INCLUDESinclude_directories
LOCAL_STATIC_LIBRARIES、LOCAL_SHARED_LIBRARIESadd_library + set_target_properties
LOCAL_LDLIBSfind_library
C文件

截屏2023-09-13 14.40.51.png

Android.mk
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LAME_LIBMP3_DIR := lame-3.100_libmp3lameLOCAL_MODULE    := mp3lameLOCAL_SRC_FILES :=\
$(LAME_LIBMP3_DIR)/bitstream.c \
$(LAME_LIBMP3_DIR)/fft.c \
$(LAME_LIBMP3_DIR)/id3tag.c \
$(LAME_LIBMP3_DIR)/mpglib_interface.c \
$(LAME_LIBMP3_DIR)/presets.c \
$(LAME_LIBMP3_DIR)/quantize.c \
$(LAME_LIBMP3_DIR)/reservoir.c \
$(LAME_LIBMP3_DIR)/tables.c  \
$(LAME_LIBMP3_DIR)/util.c \
$(LAME_LIBMP3_DIR)/VbrTag.c \
$(LAME_LIBMP3_DIR)/encoder.c \
$(LAME_LIBMP3_DIR)/gain_analysis.c \
$(LAME_LIBMP3_DIR)/lame.c \
$(LAME_LIBMP3_DIR)/newmdct.c \
$(LAME_LIBMP3_DIR)/psymodel.c \
$(LAME_LIBMP3_DIR)/quantize_pvt.c \
$(LAME_LIBMP3_DIR)/set_get.c \
$(LAME_LIBMP3_DIR)/takehiro.c \
$(LAME_LIBMP3_DIR)/vbrquantize.c \
$(LAME_LIBMP3_DIR)/version.c \
MP3Encoder.cinclude $(BUILD_SHARED_LIBRARY)
  • LOCAL_PATH :=$(call my-dir)
    当前文件在系统中的路径,必须为Android.mk文件的第一行。
  • include $(CLEAR_VARS)
    清除上一次构建中的全局变量,开始一次新的编译。
  • LOCAL_MODULE
    生成的模块的名称,这里是动态库so文件的名称,so文件的名称拼接为lib「模块名」.so
    该模块的编译的目标名,用于区分各个模块,名字必须是唯一并不包含空格的,如果编译目标是 so 库,那么该 so 库的名称就是 lib 项目名 .so。
  • LOCAL_SRC_FILES
    要编译的.c或.cpp文件,.h和.hpp文件可以不用出现在这里,系统会自动包含。
  • include $(BUILD_SHARED_LIBRARY)
    include开头的是构建系统的内置变量,此行代码的意思是构建动态库,也称共享库,还有以下几种取值。
    BUILD_STATIC_LIBRARY: 构建静态库。
    PREBUILT_STATIC_LIBRARY: 将静态库包装成一个模块。
    PREBUILT_SHARED_LIBRARY: 将静态库包装成一个模块。
    BUILD_EXECUTABLE: 构建可执行文件。
Application.mk
APP_ABI := armeabi  armeabi-v7a  arm64-v8a  x86  x86_64  mips  mips64
APP_MODULES := mp3lame
APP_CFLAGS += -DSTDC_HEADERS
APP_PLATFORM := android-21
  • APP_ABI ABI(Application Binary Interface)应用二级制接口,这是一种计算机科学中的概念,用于描述软件库或操作系统与应用程序之间的二进制通信方式。它跟CPU指令集对应。
  • APP_MODULES 指定模块
  • APP_FLAGS 指定编译过程的flag,“DSTDC_HEADERS” 是一个编程中常见的宏定义,通常用于检查标准库头文件是否已经包含。这个宏定义通常在C/C++代码中使用,用于确保标准库的头文件已经正确包含,以便程序可以正常编译和运行。如果没有正确包含标准库头文件,编译器可能会报错或者出现未定义的行为。
  • APP_PLATFORM 指定创建的动态库的平台。

与JNI相关的文件

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>#ifndef _Included_Mp3Encoder
#define _Included_Mp3Encoder
#ifdef __cplusplus
extern "C" {
#endifJNIEXPORT void JNICALL Java_com_dorachat_dorachat_recorder_mp3_Mp3Encoder_init(JNIEnv *, jclass, jint, jint, jint, jint, jint);JNIEXPORT jint JNICALL Java_com_dorachat_dorachat_recorder_mp3_Mp3Encoder_encode(JNIEnv *, jclass, jshortArray, jshortArray, jint, jbyteArray);JNIEXPORT jint JNICALL Java_com_dorachat_dorachat_recorder_mp3_Mp3Encoder_flush(JNIEnv *, jclass, jbyteArray);JNIEXPORT void JNICALL Java_com_dorachat_dorachat_recorder_mp3_Mp3Encoder_close(JNIEnv *, jclass);#ifdef __cplusplus
}
#endif
#endif

lame的jni层主要定义4个方法init、encode、flush和close。

#include "lame-3.100_libmp3lame/lame.h"
#include "Mp3Encoder.h"static lame_global_flags *glf = NULL;JNIEXPORT void JNICALL Java_com_dorachat_dorachat_recorder_mp3_Mp3Encoder_init(JNIEnv *env, jclass cls, jint inSamplerate, jint outChannel,jint outSamplerate, jint outBitrate, jint quality) {if (glf != NULL) {lame_close(glf);glf = NULL;}glf = lame_init();lame_set_in_samplerate(glf, inSamplerate);lame_set_num_channels(glf, outChannel);lame_set_out_samplerate(glf, outSamplerate);lame_set_brate(glf, outBitrate);lame_set_quality(glf, quality);lame_init_params(glf);
}JNIEXPORT jint JNICALL
Java_com_dorachat_dorachat_recorder_mp3_Mp3Encoder_encode(JNIEnv *env, jclass cls, jshortArray buffer_l, jshortArray buffer_r,jint samples, jbyteArray mp3buf) {jshort* j_buffer_l = (*env)->GetShortArrayElements(env, buffer_l, NULL);jshort* j_buffer_r = (*env)->GetShortArrayElements(env, buffer_r, NULL);const jsize mp3buf_size = (*env)->GetArrayLength(env, mp3buf);jbyte* j_mp3buf = (*env)->GetByteArrayElements(env, mp3buf, NULL);int result = lame_encode_buffer(glf, j_buffer_l, j_buffer_r,samples, j_mp3buf, mp3buf_size);(*env)->ReleaseShortArrayElements(env, buffer_l, j_buffer_l, 0);(*env)->ReleaseShortArrayElements(env, buffer_r, j_buffer_r, 0);(*env)->ReleaseByteArrayElements(env, mp3buf, j_mp3buf, 0);return result;
}JNIEXPORT jint JNICALL
Java_com_dorachat_dorachat_recorder_mp3_Mp3Encoder_flush(JNIEnv *env, jclass cls, jbyteArray mp3buf) {const jsize mp3buf_size = (*env)->GetArrayLength(env, mp3buf);jbyte* j_mp3buf = (*env)->GetByteArrayElements(env, mp3buf, NULL);int result = lame_encode_flush(glf, j_mp3buf, mp3buf_size);(*env)->ReleaseByteArrayElements(env, mp3buf, j_mp3buf, 0);return result;
}JNIEXPORT void JNICALL Java_com_dorachat_dorachat_recorder_mp3_Mp3Encoder_close(JNIEnv *env, jclass cls) {lame_close(glf);glf = NULL;
}

这个需要你会一点C语言的基础,然后就可以轻松调用lame库的函数了。

package com.dorachat.dorachat.recorder.mp3;public class Mp3Encoder {static {System.loadLibrary("mp3lame");}public native static void close();public native static int encode(short[] buffer_l, short[] buffer_r, int samples, byte[] mp3buf);public native static int flush(byte[] mp3buf);public native static void init(int inSampleRate, int outChannel, int outSampleRate, int outBitrate, int quality);public static void init(int inSampleRate, int outChannel, int outSampleRate, int outBitrate) {init(inSampleRate, outChannel, outSampleRate, outBitrate, 7);}
}

我们再看一下java层。

Java层大致实现

我们录音肯定是要在后台Service中进行的,可以使用onStartCommand进行调用,这里只是一个简单的功能,就不使用aidl跨进程了。开始录音时我们需要开一个线程去采样音频数据。

public void start(String filePath, RecordConfig config) {this.currentConfig = config;if (state != RecordState.IDLE && state != RecordState.STOP) {Logger.e(TAG, "状态异常当前状态: %s", state.name());return;}resultFile = new File(filePath);String tempFilePath = getTempFilePath();LogUtils.dformat(TAG, "----------------开始录制 %s------------------------", currentConfig.getFormat().name());LogUtils.dformat(TAG, "参数: %s", currentConfig.toString());LogUtils.iformat(TAG, "pcm缓存 tmpFile: %s", tempFilePath);LogUtils.iformat(TAG, "录音文件 resultFile: %s", filePath);tmpFile = new File(tempFilePath);audioRecordThread = new AudioRecordThread();audioRecordThread.start();
}

private class AudioRecordThread extends Thread {private AudioRecord audioRecord;private int bufferSize;AudioRecordThread() {bufferSize = AudioRecord.getMinBufferSize(currentConfig.getSampleRate(),currentConfig.getChannelConfig(), currentConfig.getEncodingConfig()) * RECORD_AUDIO_BUFFER_TIMES;Logger.d(TAG, "record buffer size = %s", bufferSize);audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, currentConfig.getSampleRate(),currentConfig.getChannelConfig(), currentConfig.getEncodingConfig(), bufferSize);if (currentConfig.getFormat() == RecordConfig.RecordFormat.MP3) {if (mp3EncodeThread == null) {initMp3EncoderThread(bufferSize);} else {LogUtils.e("mp3EncodeThread != null, 请检查代码");}}}@Overridepublic void run() {super.run();switch (currentConfig.getFormat()) {case MP3:startMp3Recorder();break;default:startPcmRecorder();break;}}private void startPcmRecorder() {state = RecordState.RECORDING;notifyState();LogUtils.d("开始录制PCM");FileOutputStream fos = null;try {fos = new FileOutputStream(tmpFile);audioRecord.startRecording();byte[] byteBuffer = new byte[bufferSize];while (state == RecordState.RECORDING) {int end = audioRecord.read(byteBuffer, 0, byteBuffer.length);notifyData(byteBuffer);fos.write(byteBuffer, 0, end);fos.flush();}audioRecord.stop();files.add(tmpFile);if (state == RecordState.STOP) {makeFile();} else {LogUtils.i("暂停");}} catch (Exception e) {LogUtils.e(e.getMessage());notifyError("录音失败");} finally {try {if (fos != null) {fos.close();}} catch (IOException e) {e.printStackTrace();}}if (state != RecordState.PAUSE) {state = RecordState.IDLE;notifyState();LogUtils.d("录音结束");}}private void startMp3Recorder() {state = RecordState.RECORDING;notifyState();try {audioRecord.startRecording();short[] byteBuffer = new short[bufferSize];while (state == RecordState.RECORDING) {int end = audioRecord.read(byteBuffer, 0, byteBuffer.length);if (mp3EncodeThread != null) {mp3EncodeThread.addChangeBuffer(new Mp3EncodeThread.ChangeBuffer(byteBuffer, end));}notifyData(ByteUtils.toBytes(byteBuffer));}audioRecord.stop();} catch (Exception e) {LogUtils.e(e.getMessage());notifyError("录音失败");}if (state != RecordState.PAUSE) {state = RecordState.IDLE;notifyState();stopMp3Encoded();} else {LogUtils.d("暂停");}}
}private void stopMp3Encoded() {if (mp3EncodeThread != null) {mp3EncodeThread.stopSafe(new Mp3EncodeThread.EncordFinishListener() {@Overridepublic void onFinish() {notifyFinish();mp3EncodeThread = null;}});} else {LogUtils.e("mp3EncodeThread is null, 代码业务流程有误,请检查!");}
}private void makeFile() {switch (currentConfig.getFormat()) {case MP3:return;case WAV:mergePcmFile();makeWav();break;case PCM:mergePcmFile();break;default:break;}notifyFinish();LogUtils.i("录音完成! path: %s ; 大小:%s", resultFile.getAbsoluteFile(), resultFile.length());
}/*** 添加Wav头文件。*/
private void makeWav() {if (!FileUtils.isFile(resultFile) || resultFile.length() == 0) {return;}byte[] header = WavUtils.generateWavFileHeader((int) resultFile.length(), currentConfig.getSampleRate(), currentConfig.getChannelCount(), currentConfig.getEncoding());WavUtils.writeHeader(resultFile, header);
}/*** 合并文件。*/
private void mergePcmFile() {boolean mergeSuccess = mergePcmFiles(resultFile, files);if (!mergeSuccess) {notifyError("合并失败");}
}/*** 合并PCM文件。** @param recordFile 输出文件* @param files      多个文件源* @return 是否成功*/
private boolean mergePcmFiles(File recordFile, List<File> files) {if (recordFile == null || files == null || files.size() <= 0) {return false;}FileOutputStream fos = null;BufferedOutputStream outputStream = null;byte[] buffer = new byte[1024];try {fos = new FileOutputStream(recordFile);outputStream = new BufferedOutputStream(fos);for (int i = 0; i < files.size(); i++) {BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(files.get(i)));int readCount;while ((readCount = inputStream.read(buffer)) > 0) {outputStream.write(buffer, 0, readCount);}inputStream.close();}} catch (Exception e) {LogUtils.e(e.getMessage());return false;} finally {try {if (outputStream != null) {outputStream.close();}if (fos != null) {fos.close();}} catch (IOException e) {e.printStackTrace();}}for (int i = 0; i < files.size(); i++) {files.get(i).delete();}files.clear();return true;
}/*** 根据当前的时间生成相应的文件名。*/
private String getTempFilePath() {String fileDir = String.format(Locale.getDefault(), "%s/Record/", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getAbsolutePath());if (!FileUtils.createOrExistsDir(fileDir)) {LogUtils.e("文件夹创建失败:" + fileDir);}String fileName = String.format(Locale.getDefault(), "record_tmp_%s", FileUtils.getNowString(new SimpleDateFormat("yyyyMMdd_HH_mm_ss", Locale.SIMPLIFIED_CHINESE)));return String.format(Locale.getDefault(), "%s%s.pcm", fileDir, fileName);
}/*** 表示当前录制状态。*/
public enum RecordState {/*** 空闲状态。*/IDLE,/*** 录音中。*/RECORDING,/*** 暂停中。*/PAUSE,/*** 正在停止。*/STOP,/*** 录音流程结束(转换结束)。*/FINISH
}

在Mp3EncodeThread中init初始化录制参数,然后调用encode进行编码录制,录制完成调用flush把缓冲区冲一下水,清洗一下。最后调用一下close,完美。
最后附上录制流程细节的代码。

package com.dorachat.dorachat.recorder.mp3;import com.dorachat.dorachat.recorder.RecordConfig;
import com.dorachat.dorachat.recorder.RecordService;import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;public class Mp3EncodeThread extends Thread {private static final String TAG = Mp3EncodeThread.class.getSimpleName();private List<ChangeBuffer> cacheBufferList = Collections.synchronizedList(new LinkedList<ChangeBuffer>());private File file;private FileOutputStream os;private byte[] mp3Buffer;private EncodeFinishListener encodeFinishListener;/*** 是否已停止录音。*/private volatile boolean isOver = false;/*** 是否继续轮询数据队列。*/private volatile boolean start = true;public Mp3EncodeThread(File file, int bufferSize) {this.file = file;mp3Buffer = new byte[(int) (7200 + (bufferSize * 2 * 1.25))];RecordConfig currentConfig = RecordService.getCurrentConfig();int sampleRate = currentConfig.getSampleRate();Mp3Encoder.init(sampleRate, currentConfig.getChannelCount(), sampleRate, currentConfig.getRealEncoding());}@Overridepublic void run() {try {this.os = new FileOutputStream(file);} catch (FileNotFoundException e) {return;}while (start) {ChangeBuffer next = next();lameData(next);}}public void addChangeBuffer(ChangeBuffer changeBuffer) {if (changeBuffer != null) {cacheBufferList.add(changeBuffer);synchronized (this) {notify();}}}public void stopSafe(EncodeFinishListener encodeFinishListener) {this.encodeFinishListener = encodeFinishListener;isOver = true;synchronized (this) {notify();}}private ChangeBuffer next() {for (;;) {if (cacheBufferList == null || cacheBufferList.size() == 0) {try {if (isOver) {finish();}synchronized (this) {wait();}} catch (Exception e) {}} else {return cacheBufferList.remove(0);}}}private void lameData(ChangeBuffer changeBuffer) {if (changeBuffer == null) {return;}short[] buffer = changeBuffer.getData();int readSize = changeBuffer.getReadSize();if (readSize > 0) {int encodedSize = Mp3Encoder.encode(buffer, buffer, readSize, mp3Buffer);try {os.write(mp3Buffer, 0, encodedSize);} catch (IOException e) {}}}private void finish() {start = false;final int flushResult = Mp3Encoder.flush(mp3Buffer);if (flushResult > 0) {try {os.write(mp3Buffer, 0, flushResult);os.close();} catch (final IOException e) {}}if (encodeFinishListener != null) {encodeFinishListener.onFinish();}}public static class ChangeBuffer {private short[] rawData;private int readSize;public ChangeBuffer(short[] rawData, int readSize) {this.rawData = rawData.clone();this.readSize = readSize;}short[] getData() {return rawData;}int getReadSize() {return readSize;}}public interface EncodeFinishListener {/*** 格式转换完毕。*/void onFinish();}
}

总结

这只是Android音视频开发的冰山一角,主要是为了演示大概的开发流程,如果需要深入研究Android NDK,一定需要先把C/C++语言学好,这样才能走得更远。

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

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

相关文章

驱动开发:内核MDL读写进程内存

100编程书屋_孔夫子旧书网 MDL内存读写是最常用的一种读写模式,通常需要附加到指定进程空间内然后调用内存拷贝得到对端内存中的数据,在调用结束后再将其空间释放掉,通过这种方式实现内存读写操作,此种模式的读写操作也是最推荐使用的相比于CR3切换来说,此方式更稳定并不会…

ios:Command PhaseScriptExecution failed with a nonzero exit code

问题 使用 xcode 跑项目真机调试的时候&#xff0c;一直报错 Command PhaseScriptExecution failed with a nonzero exit code。 解决 最终靠以下方法解决 删除Podfile.lock文件删除Pods文件删除.xcworkspace文件Pod installCommandShiftK 清理一下缓存 亲测有效

滑动窗口-java

主要通过单调队列来解决滑动窗口问题&#xff0c;得到滑动窗口中元素的最大值和最小值。 目录 前言 一、滑动窗口 二、算法思路 1.滑动窗口 2.算法思路 3.代码详解 三、代码如下 1.代码如下 2.读入数据 3.代码运行结果 总结 前言 主要通过单调队列来解决滑动窗口问题&#xff…

Leetcode刷题笔记5

76. 最小覆盖子串 76. 最小覆盖子串 - 力扣&#xff08;LeetCode&#xff09; 解法一&#xff1a; 暴力枚举 哈希表 先定义left和right&#xff0c;可以在随机位置 枚举一个位置向后找&#xff0c;找到一个位置之后&#xff0c;发现这段区间是一个最小的区间之后&#xff0c…

【探索数据之美】“从基础到精通——深入解析数据结构与二叉树的秘密“

gitee代码获取链接&#xff1a;https://gitee.com/flying-wolf-loves-learning/data-structure.git 一、树的概念 1.1 概念简述 数据结构中的树是一种层次结构&#xff0c;它由节点&#xff08;node&#xff09;和边&#xff08;edge&#xff09;组成。每个节点都有零个或多…

解决鼠标滚动时element-ui下拉框错位的问题

问题描述&#xff1a;elementUi的el-select下拉选择框,打开之后,直到失去焦点才会自动关闭。 在有滚动条的弹窗中使用时就会出现打开下拉框,滚动弹窗,el-select下拉框会超出弹窗范围的问题. 解决方案&#xff1a; 1、先在util文件夹下创建个hideSelect.js文件&#xff0c;代码…

数据结构(六)队列

文章目录 一、概念二、逻辑结构&#xff1a;线性结构三、存储结构&#xff08;一&#xff09;顺序队列&#xff08;二&#xff09;循环队列1. 结构体定义2. 创建队列&#xff08;1&#xff09;函数定义&#xff08;2&#xff09;注意点&#xff08;3&#xff09;代码实现 3. 入…

改进rust代码的35种具体方法-类型(十九)-避免使用反射

上一篇文章 从其他语言来到Rust的程序员通常习惯于将反思作为工具箱中的工具。他们可能会浪费很多时间试图在Rust中实现基于反射的设计&#xff0c;却发现他们所尝试的事情只能做得不好&#xff0c;如果有的话。这个项目希望通过描述Rust在反思方面做什么和不做什么&#xff0c…

C语言例题47、从键盘输入一个正整数n,计算1+1/(1+2)+1/(1+2+3)+…+1/(1+2+3+…+n) 的值

#include <stdio.h>void main() {int x;int fm 0;//分母double sum 0;printf("请输入一个正整数&#xff1a;");scanf("%d", &x);for (int i 1; i < x; i) {fm i;//分母变化sum sum 1.0 / fm;if (i ! x) {printf("1/%d ", f…

【Linux】升级GCC(版本9.3),补充:binutils

GCC&#xff1a;GNU Compiler Collection 。编译器&#xff0c;几乎Linux中所有程序&#xff08;包括内核&#xff09;都是gcc编译的&#xff0c;包括libc。 gcc不仅仅是编译器&#xff0c;gcc也有很多库&#xff0c;依赖libc。gcc和libc互相依赖。 GCC官网&#xff1a;GCC, …

【C++练级之路】【Lv.22】C++11——右值引用和移动语义

快乐的流畅&#xff1a;个人主页 个人专栏&#xff1a;《算法神殿》《数据结构世界》《进击的C》 远方有一堆篝火&#xff0c;在为久候之人燃烧&#xff01; 文章目录 引言一、右值引用1.1 左值和右值1.2 左值引用和右值引用的范围1.3 左值引用的意义 二、移动语义2.1 移动构造…

AI大模型探索之路-实战篇9:探究Agent智能数据分析平台的架构与功能

系列篇章&#x1f4a5; AI大模型探索之路-实战篇4&#xff1a;深入DB-GPT数据应用开发框架调研 AI大模型探索之路-实战篇5&#xff1a;探索Open Interpreter开放代码解释器调研 AI大模型探索之路-实战篇6&#xff1a;掌握Function Calling的详细流程 AI大模型探索之路-实战篇7…

中断处理过程介绍

概念 中断 中断源 分类 中断处理过程 中断请求 实现器件 中断判优 软件判优 过程 器件实现 程序实现 硬件判优 链路判优 器件实现 控制器判优 中断响应 中断服务 中断返回

STM32H750外设之ADC通道选择

目录 概述 1 通道选择功能介绍 2 通道选择&#xff08; SQRx、 JSQRx&#xff09; 2.1 通道复用 2.1.1 通道介绍 2.1.2 通道框图 2.2 转换分组 2.3 内部专用通道 3 通道预选寄存器 (ADCx_PCSEL) 3.1 功能介绍 3.2 预选通道寄存器 概述 本位主要介绍STM32H750外设之…

栈 队列

目录 1.1栈的基本概念 1.1.1栈的定义 1.1.2栈的基本操作 1.2栈的顺序存储结构 1.2.1构造原理 1.2.2基本算法 1.3栈的链式存储结构 1.3.1构造原理 1.3.2基本算法 2.1队列的基本概念 2.1.1队列的定义 2.1.2队列的基本运算 2.2队列的顺序存储结构 2.2.1构造原理 2.2.1基…

CRLF注入漏洞

1.CRLF注入漏洞原理 Nginx会将 $uri进行解码&#xff0c;导致传入%0a%0d即可引入换行符&#xff0c;造成CRLF注入漏洞。 执行xss语句 2.漏洞扩展 CRLF 指的是回车符(CR&#xff0c;ASCII 13&#xff0c;\r&#xff0c;%0d) 和换行符(LF&#xff0c;ASCII 10&#xff0c;\n&am…

FTP协议——LightFTP安装(Linux)

1、简介 LightFTP是一个轻量级的FTP&#xff08;File Transfer Protocol&#xff0c;文件传输协议&#xff09;客户端软件。FTP是一种用于在网络上传输文件的标准协议&#xff0c;允许用户通过TCP/IP网络&#xff08;如互联网&#xff09;在计算机之间进行文件传输。 2、步骤…

在ARM开发板上,栈大小设置为2MB(常用设置)里面存放的数据

系列文章目录 在ARM开发板上&#xff0c;栈大小设置为2MB&#xff08;常用设置&#xff09;里面存放的数据 在ARM开发板上&#xff0c;栈大小设置为2MB&#xff08;常用设置&#xff09;里面存放的数据 系列文章目录 在ARM开发板上&#xff0c;栈&#xff08;Stack&#xff09;…

Thingsboard规则链:Message Type Filter节点详解

一、Message Type Filter节点概述 二、具体作用 三、使用教程 四、源码浅析 五、应用场景与案例 智能家居自动化 工业设备监控 智慧城市基础设施管理 六、结语 在物联网&#xff08;IoT&#xff09;领域&#xff0c;数据处理与自动化流程的实现是构建智能系统的关键。作…

创新实训2024.05.28日志:记忆化机制、基于MTPE与CoT技术的混合LLM对话机制

1. 带有记忆的会话 1.1. 查询会话历史记录 在利用大模型自身能力进行对话与解答时&#xff0c;最好对用户当前会话的历史记录进行还原&#xff0c;大模型能够更好地联系上下文进行解答。 在langchain chat chat的chat函数中&#xff0c;通过实现langchain框架提供的ChatMemo…