【javascript】录音可视化
文章目录
- 前言
- 一、音频数据格式
- 1. 常见的数据格式
- 二、获取音频方式
- 1. 用FileReader对象读取音频文件
- 2. 通过url请求获取音频数据
- 3. 录音获取音频流
- 三、音频格式转换
- 1. buffer和blob互相转换
- 2. blob转base64
- 3. base64转Uint8Array
- 四、音频可视化
- 五、大文件上传
- 1. 固定大小分片
- 2. 按时间戳分片
- 总结
前言
分享一些在开发语音识别功能遇到的问题和解决方法,包含音频数据格式转换,大文件上传,录音权限获取,音频可视化
一、音频数据格式
1. 常见的数据格式
从音质,文件大小,应用场景三方面对比如下,文件大小无损(pcm,wav,flac)大于有损(mp3,ogg,opus),实时语音需要低延迟opus,而高进度语音识别需要高质格式wav或pcm
数据格式 | 音质 | 文件大小 | 应用场景 |
---|---|---|---|
MP3 (MPEG-1 Audio Layer III) | 有损压缩,支持多种比特率(如128/192/320kbps) | 较小 | 适合网络传输和移动设备,音质在较高比特率下接近无损,但不适合高精度语音识别 |
OGG (Ogg Vorbis) | 有损压缩,支持多种比特率,类似mps3,但通常提供更好的压缩效率和音质 | 较小 | 适合网络传输和移动设备,不适合高精度语音识别 |
Opus | 高效的有损压缩,音质在低比特率下表现良好,支持多种比特率和采样率 | 较小 | 适合实时语音通信和语音识别系统,尤其是需要低延迟的场景 |
WMA (Windows Media Audio) | 有损压缩,支持多种比特率,类似mps3,但通常提供更好的压缩效率和音质,专为Windows平台优化 | 较小 | 适合Windows平台的音频传输和存储,不适合高精度语音识别 |
AMR (Adaptive Multi-Rate) | 有损未压缩,专为移动通信设计,能够根据网络条件动态调整比特率 | 较小 | 广泛用于移动设备的语音通话和语音识别 |
WAV (Waveform Audio File Format) | 无损压缩,支持多种采样率(8/16/44.1khz)和位深(8/6位) | 较大 | 广泛用于语音识别、音频编辑和存储 |
FLAC (Free Lossless Audio Codec) | 无损压缩,能够将音频压缩到大约原始大小的一半,同时保持音质不变 | 比wav小 | 适合需要高质量音频但又希望减小文件大小的场景 |
PCM (Pulse Code Modulation) | 无损未压缩,直接将模拟音频信号转换为数字信号,提供最高的音质,支持多种采样率和位深 | 较大 | 常用于语音识别系统中的原始音频数据处理 |
二、获取音频方式
1. 用FileReader对象读取音频文件
FileReader 只能访问用户明确选择的文件内容,可以从input标签选择文件,或者从拖放对象里获取,可以将文件读取以二进制和文本输出,reader.readAsArrayBuffer(data)//data是 Blob 或 File 对象
,reader.readAsDataURL(data)
,reader.readAsText(data)
,以下是通过input方式:
<input ref="fileInput" type="file" @change="getFile" />
const getFile = async (event: any) => {const file = event.target.files[0];const reader = new FileReader();reader.onload = (e: any) => {const arrayBuffer = e.target.result;};reader.readAsArrayBuffer(file);
};
2. 通过url请求获取音频数据
以下是通过fetch获取本地音频资源:
import testurl from "@/assets/test.mp3"
async function fetchAndConvertToBlob(url: string) {const response = await fetch(url)const blob = await response.blob()return blob
}
3. 录音获取音频流
(1)设置触发事件
Pointer Events 提供了一种统一的方式来处理不同类型的输入设备,包括鼠标、触摸屏和手写笔。可能会存在一些旧版本兼容问题,(推荐polyfill和shim处理兼容)
触摸事件防抖处理
const debounce = (fn: any, delay: any) => {let timer: anyreturn (...args: any[]) => {if (timer) {clearTimeout(timer) // 清除前一个定时器}timer = setTimeout(() => {fn(...args) // 调用函数,传递参数}, delay)}
}const debounceStartRecord = debounce(startRecord,500)
const debounceEndRecord = debounce(endRecord,500)
// startRecord和endRecord实现见下,音频格式转换处理
(2)判断应用设备麦克风授权,支持录音API
const handlePermissionDenied = async (fn: () => void) => {const checkPermissionSupport = 'permissions' in navigatorconst checkMediaDevicesSupport = async () => {if ('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices) {try {// 使用getUserMedia方法获取麦克风权限const stream = await navigator.mediaDevices.getUserMedia({ audio: true });// 录音权限已授权,去释放资源stream.getTracks().forEach((track) => track.stop());return true} catch (error) {console.error("navigator getUserMedia failed:", error);return false}} else {return false}}const permissionDeniedMessage = "您已拒绝麦克风权限。请在设置中授权麦克风权限,以便使用录音功能。";const permissionPromptMessage = "浏览器将提示您授权麦克风权限。请在设置中授权麦克风权限,以便使用录音功能。";const permissionQueryFailedMessage = "无法查询麦克风权限状态。请在设置中授权麦克风权限,以便使用录音功能。";const deviceNotSupportedMessage = "您的设备不支持录音功能。建议您使用支持该功能的浏览器或更新系统版本";if (checkPermissionSupport) {console.log("navigator.permissions is supported");try {const permissionStatus = await navigator.permissions.query({ name: "microphone" as PermissionName });console.log('permissionStatus', permissionStatus);if (permissionStatus.state === "granted") {// 录音权限已授权fn();} else if (permissionStatus.state === "prompt") {// 浏览器将提示用户进行授权,部分浏览器没有提示,需直接请求媒体设备const mediaDevicesPermission = await checkMediaDevicesSupport();if (mediaDevicesPermission) {// 录音权限已授权fn();} else {alert(permissionPromptMessage);}} else if (permissionStatus.state === "denied") {// 录音权限被拒绝alert(permissionDeniedMessage);}} catch (error) {console.error("Permission query failed:", error);alert(permissionQueryFailedMessage);}} else if (await checkMediaDevicesSupport()) {console.log("navigator.permissions is not supported, but navigator.mediaDevices.getUserMedia is supported");fn();} else {alert(deviceNotSupportedMessage);}
};
(3)使用audio-recorder-polyfill库处理音频文件转换
满足兼容不同系统和设备的WebRTC录音api支持,并提供释放麦克风方法,可以转换wav文件,自定义采样位数 (sampleBits)16,采样率(sampleRate)16000Hz,声道(numChannels)1
开始录音
const startRecord = (event?: any) => {event?.stopPropagation()handlePermissionDenied(() => {console.log("开始录音", isRecording.value)isPermissioning = false // 避免重复点击if (!isRecording.value) {try {navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {pcmRecorder.value = new AudioRecorder(stream, {sampleBits: 16, // 采样位数,支持 8 或 16,默认是16sampleRate: 16000, // 采样率,支持 11025、16000、22050、24000、44100、48000,根据浏览器默认值,我的chrome是48000numChannels: 1 // 声道,支持 1 或 2, 默认是1})pcmRecorder.value.start()changeRecordState("start")})} catch (error) {console.error("开始录音失败", error)}}})
}
结束录音
const endRecord = (e?: any) => {e?.preventDefault()console.log("结束录音", isRecording.value, isPermissioning)if (isPermissioning) {isRecording.value = false}if (isFouceEnd || !isRecording.value) {return}try {if (pcmRecorder.value) {// console.log(pcmRecorder.value)pcmRecorder.value.stop()pcmRecorder.value.addEventListener("dataavailable", async (e: any) => {pcmData.value = e.data// console.log("pcmData.value", pcmData.value)let base64String: any = await blobToBase64(pcmData.value)// console.log("pcmstr", base64String)base64String = base64String.split(",")[1]audioToText({ voice: base64String }).then((res: any) => {console.log("res", res)if (res.text) {curText.value = res.textsendMsg({ type: "model" })} else {// 判断当前识别成功的文字,如果第一次对话res.text为空,仍然展示关键字跳转if (msgList.value.length == 1) {isShowLink.value = true}}})})if (pcmRecorder.value && pcmRecorder.value.stream) {pcmRecorder.value.stream.getTracks().forEach((track: any) => {track.stop()track.applyConstraints({ echoCancellation: false }).catch((error: any) => {console.error("更新轨道状态失败2:", error)})})pcmRecorder.value.stream = null // 释放 MediaStream 对象}changeRecordState("end")}} catch (error) {changeRecordState("end")console.error("停止录音失败:", error)}
}
@vitejs/plugin-legacy 插件的主要作用是为旧版浏览器提供兼容性支持,通过 Babel 转译代码,使其可以在旧版浏览器中运行。它主要处理 JavaScript 语法的兼容性问题,而不是特定的 Web API(如 navigator.mediaDevices.getUserMedia 或 navigator.permissions)。这里推荐webrtc-adapter 和 permissions-api ,webrtc-adapter 是一个 Polyfill,用于解决 WebRTC API 的兼容性问题,特别是 navigator.mediaDevices.getUserMedia 方法的兼容性问题;permissions-api 是一个 Polyfill,用于解决 navigator.permissions API 的兼容性问题。navigator.permissions 是一个较新的 API,用于查询和监听权限状态。
Recorder.js 提供了更底层的控制,适合需要自定义音频处理的场景。
AudioRecorder 是一个更简单的封装,适合快速实现录音功能。
https://blog.csdn.net/gitblog_00046/article/details/137811883
两者都能满足你的需求(16 位采样位数、16000Hz 采样率、单声道),并在录音结束后释放麦克风资源。
三、音频格式转换
二进制数据转换
数据格式 | 概念 | 场景 |
---|---|---|
Data URL | 是一种将文件内容直接嵌入到页面中的方式,通常以 data: 协议开头。它将文件内容编码为 Base64 字符串 | 适合嵌入到 HTML 或 CSS 中,便于显示和传输,适合小文件,会增加页面大小,可能受浏览器存储限制 |
Blob URL | 是一种指向 Blob 对象的 URL,通常以 blob: 协议开头 | 临时的url,适用于大文件(如音频,视频),便于在 或 标签中直接播放,不增加页面大小 |
Blob(Binary Large Object) | 表示了一个不可变、原始数据的类文件对象。Blob 可以包含任意类型的数据,如文本、二进制数据、JSON 等 | 常用于处理文件上传、下载等操作。Blob 的大小可以从 0 到浏览器所允许的最大值不等 |
ArrayBuffer | 表示通用的、固定长度的原始二进制数据缓冲区。ArrayBuffer 的内容不能直接操作,只能通过 DataView 对象或 TypedArrray 对象来访问 | 适合底层处理,如音频分析或使用 Web Audio AP |
备注: Blob.arrayBuffer() 方法是一种较新的基于 Promise 的 API,用于将文件读取为数组缓冲区。
const encodedData = window.btoa(“Hello, world”); // 编码字符串
const decodedData = window.atob(encodedData); // 解码字符串
1. buffer和blob互相转换
const fileToBlob = (file: File) => {return new Promise((resolve, reject) => {const reader = new FileReader();reader.onload = function (e: any) {const blob = new Blob([e.target.result], { type: file.type });// buffer转blobconst blobUrl = URL.createObjectURL(blob);//创建blob urlresolve(blobUrl)};reader.onerror = (error) => {reject(error);};reader.readAsArrayBuffer(file); // 读取为 ArrayBuffer})
}const bufferToAudioUrl = (buffer: any) => {/*MIME 类型:1、 mp3:audio/mpeg 或 audio/mp3(所有主流浏览器均支持)2、 wav:/wav、audio/wave 或 audio/x-wav(所有主流浏览器均支持)3、 mp4:audio/mp4 或 audio/x-m4a(所有主流浏览器均支持)4、 ogg:audio/ogg(Chrome、Firefox、Edge 支持,但 Safari 不支持)5、 webm:audio/webm(Chrome、Firefox、Edge 支持,但 Safari 支持有限)*/const blob = new Blob([buffer], { type: "audio/mpeg" });return URL.createObjectURL(blob); //创建blob url
}export const blobToBuffer = (blob: Blob) => {return new Promise((resolve, reject) => {const reader = new FileReader();reader.onload = () => {resolve(reader.result); // reader.result 是 ArrayBuffer};reader.onerror = (error) => {reject(error);};reader.readAsArrayBuffer(blob);});
}
2. blob转base64
const blobToBase64 = (blob: Blob) => {return new Promise((resolve, reject) => {const reader = new FileReader()reader.onload = () => {resolve(reader.result)}reader.onerror = (error) => {reject(error)}reader.readAsDataURL(blob) // 将 Blob 转换为 Base64 字符串})
}
3. base64转Uint8Array
export function base64ToUint8Array(base64: string) {// 解码Base64字符串const byteCharacters = atob(base64.trim());// 将解码后的二进制字符串转换为Uint8Arrayconst byteNumbers = new Uint8Array(byteCharacters.length);for (let i = 0; i < byteCharacters.length; i++) {byteNumbers[i] = byteCharacters.charCodeAt(i);}return byteNumbers;//byteNumbers.buffer是arraybuffer类型
};
四、音频可视化
主要是通过arraybuffer进行音频可视化将音频文件读取为 ArrayBuffer 是一种更底层的方法,适用于需要对音频数据进行进一步处理的场景,例如使用 Web Audio API 进行音频分析或处理。以下是实现思路和部分代码:
- 使用AudioContext创建音频上下文,获取音频节点source
创建音频节点source可以3种方式:audiodom获取audCtx.createMediaElementSource(data)
,MediaStream媒体流audCtx.createMediaStreamSource(data)
,音频转成AudioBufferaudCtx.createBufferSource()
- 创建音频分析对象
audCtx.createAnalyser()
,并将音频源(source)连接到音频分析器source.connect(analyser)
- 启动音频源播放
source.start(0)
- 绘制平滑动画使用
canvas
和requestAnimationFrame
- 从分析对象获取当前音频的频率数据,并将其填充到指定的 Uint8Array 数组
analyser.getByteFrequencyData(dataArray)
- 使用dataArray分析数据进行音频条纹绘制
// 音频分析器
const initAudioAnalyser = async (data: any,type: "audiodom" | "mediastream" | "arraybuffer"
) => {// console.log(data, type);if (data) {// 创建音频节点source可以3种方式:audiodom获取,MediaStream媒体流,音频转成AudioBufferif (type == "audiodom") {isRecording.value = true;animationLoop();source = audCtx.createMediaElementSource(data);} else if (type == "mediastream") {source = audCtx.createMediaStreamSource(data);} else if (type == "arraybuffer") {isRecording.value = true;animationLoop();source = audCtx.createBufferSource();const uint8Array = base64ToUint8Array(data);dataArray = uint8Array;const arrayBuffer = uint8Array.buffer;/*返回audiodom实现思路const audioUrl = bufferToAudioUrl(arrayBuffer);const audioPlayer: any = document.getElementById("audioPlayer");audioPlayer.src = audioUrl;audioPlayer.play();source = audCtx.createMediaElementSource(audioPlayer);*/if (arrayBuffer instanceof ArrayBuffer) {try {const audioBuffer = await audCtx.decodeAudioData(arrayBuffer);console.log("audioBuffer 解码成功:", audioBuffer);source.buffer = audioBuffer;} catch (error: any) {isRecording.value = false;console.error("解码音频数据时出错:", error);// 报解码音频数据时出错: DOMException: Failed to execute 'decodeAudioData' on 'BaseAudioContext': Unable to decode audio data// 这是因为 ArrayBuffer 中的数据不是有效的音频文件格式}}}analyser = audCtx.createAnalyser();analyser.fftSize = 256; // 设置 FFT 大小,影响频率分析的精度,常见的值有 128, 256, 512, 1024, 2048console.log("Audio stream connected to AnalyserNode:",source.numberOfOutputs > 0); //为true表明已经连接到一个源//创建数组,用于接收分析器节点的分析数据dataArray = new Uint8Array(analyser.frequencyBinCount);//analyser.frequencyBinCount表示当前频率的数组长度source.connect(analyser);// analyser.connect(audCtx.destination); //输出设备 播放声音(如果需要将音频输出到扬声器,可以再连接到 audCtx.destination:)if (audCtx.state === "suspended") {// 如果你的代码在没有用户交互的情况下执行,AudioContext 可能不会从 suspended 状态变为 runningsource.start(0);source.onended = function () {console.log("音频播放结束");isRecording.value = false;};}console.log("initAudioAnalyser",audCtx.state,audCtx.sampleRate,dataArray);}
};
// 绘制音频波动的函数
const draw = () => {if (!analyser) {return;}analyser.getByteFrequencyData(dataArray); //让分析器节点分析出数据到数组中const volumeThreshold = 40; // 设置音量阈值,只有当音量高于这个值时才更新可视化// 计算平均振幅let sum = 0;for (let i = 0; i < dataArray.length; i++) {sum += dataArray[i];}const averageAmplitude = sum / dataArray.length;// 计算平均振幅console.log(audCtx.state, dataArray, averageAmplitude, volumeThreshold);// 根据平均振幅决定是否更新可视化if (averageAmplitude > volumeThreshold) {// 更新可视化效果// console.log("音量足够,更新可视化")/*dataArray一直为0,可能导致的原因(1)音频未正确播放,audCtx.state不是running(2)source是否正确连接analyser,source.connect(analyser)(3)getByteFrequencyData调用时机不正确,是否音频播放后调用,需要持续调用,volumeThreshold设置音量(4)采样率不匹配(audCtx.sampleRate和audiobuffer.sampleRate)(5)调试时的时间延迟问题:AnalyserNode必须在音频播放一定时间后才能提供有效的数据如果音频源被静音或音量为0*/// console.log("音乐节点", dataArray)const bufferLength = dataArray.length / 2.5; //一般两半波幅const barWidth = width / bufferLength;// 清空画布ctx.clearRect(0, 0, width, height);ctx.fillStyle = "#000000";for (let i = 0; i < bufferLength; i++) {const data = dataArray[i]; //<256const barHeight = (data / 255) * height; // 乘以height放大波幅// console.log(barHeight)// const x = i * barWidthconst x1 = i * barWidth + width / 2;const x2 = width / 2 - (i + 1) * barWidth;// const y = height - barHeight //底部对齐const y = (height - barHeight) / 2; //中心对其// ctx?.fillRect(x, y, barWidth - 3, barHeight)ctx?.fillRect(x1, y, barWidth - 4, barHeight);ctx?.fillRect(x2, y, barWidth - 4, barHeight);}} else {// 音量较低,不更新可视化// console.log("音量较低,跳过可视化更新")ctx.clearRect(0, 0, width, height);}
};const animationLoop = () => {if (!isRecording.value) {return;}draw();requestAnimationFrame(animationLoop);
};
五、大文件上传
语音识别为了保证识别准确性,选用高质量音频数据wav格式。由于WAV格式文件较大,直接上传可能导致网络传输效率低下或失败。因此,采用分片上传的方式将大文件分割成多个小片段依次上传。常见的分片上传方式有两种:
1. 固定大小分片
将文件按照固定的字节大小(如1MB)进行切分。这种方式实现简单,但可能会导致最后一个分片过小。这里提供两种传输方式 http 和 websocket,分片上传包含前端分片(遍历分割),分片上传(接口上传),分片合并(服务端),这里主要展示前两部分。
特性 | 数据类型 | 传输效率 | 适用场景 | 内存占用 | 实现复杂度 |
---|---|---|---|---|---|
HTTP application/octet-stream | 二进制(ArrayBuffer、Blob) | 高 | 文件上传、下载 | 低 | 中等 |
WebSocket 二进制帧 | 二进制(ArrayBuffer) | 高 | 实时文件传输 | 低 | 高 |
Base64 编码 | 文本字符串 | 低(体积增加 33%) | 小文件嵌入 | 高 | 低 |
(1)http
export const sendFile1 = (blob: any, sliceSize: any) => {let resstext = ''if (!blob) {return} else {const sliceNum = Math.ceil(blob.size / sliceSize)const fun: any = async () => {for (let i = 0; i < sliceNum; i++) {const start = i * sliceSizeconst end = Math.min(start + sliceSize, blob.size)const chunk = blob.slice(start, end) // 获取切片const isLastChunk = i === sliceNum - 1await new Promise((r) => {//audioToText参数分别:文件切片,是否最后一片,第几片audioToText(chunk, isLastChunk ? "1" : "0", i + 1).then((res: any) => {for (const item of res.body) {console.log(item)if (item?.ansStr) {resstext = resstext + JSON.parse(item.ansStr).ws_s}if (item.endFlag) {console.log('结束发送',resstext);break}}r(res)})})}}fun()}
}
(2)websocket
export const sendFile2 = (blob: Blob, sliceSize: number) => {let resstext = '';if (!blob) return;const sliceNum = Math.ceil(blob.size / sliceSize);const socket = new WebSocket('wss://example.com/socket'); // 替换为实际的 WebSocket 地址socket.onopen = () => {console.log('WebSocket connection established');const uploadChunks = async () => {for (let i = 0; i < sliceNum; i++) {const start = i * sliceSize;const end = Math.min(start + sliceSize, blob.size);const chunk = blob.slice(start, end); // 获取切片const isLastChunk = i === sliceNum - 1;const message = {chunk: chunk,isLastChunk: isLastChunk,chunkIndex: i + 1};socket.send(JSON.stringify(message));//实时发送}};uploadChunks();};socket.onmessage = (event) => {const response = JSON.parse(event.data);console.log('Received message from server:', response);if (response.body) {for (const item of response.body) {if (item?.ansStr) {resstext += JSON.parse(item.ansStr).ws_s;}if (item.endFlag) {console.log('获取识别后的文字', resstext);socket.close();break;}}}};socket.onerror = (error) => {console.error('WebSocket error:', error);};socket.onclose = () => {console.log('WebSocket connection closed');};
};
2. 按时间戳分片
根据录音的时间戳来划分片段,例如每秒生成一个分片。这种方式更适用于流式传输场景,能够更好地适应不同长度的音频文件。
//...此处省去录音开始和结束方法
let chunks = []; // 存储录音数据
mediaRecorder.ondataavailable = (event) => {
// 监听录音结束拿到录音数据if (event.data.size > 0) {chunks.push(event.data);// 按时间戳分片(例如每秒生成一个分片)const currentTime = Date.now();if (currentTime - startTime >= 1000) {const blob = new Blob(chunks, { type: 'audio/webm' });uploadChunk(blob); // 上传分片chunks = []; // 清空当前分片数据startTime = currentTime; // 重置开始时间}}};// 上传分片
function uploadChunk(blob) {const formData = new FormData();formData.append('file', blob, `chunk_${Date.now()}.webm`);fetch('/upload', {method: 'POST',body: formData,}).then(response => response.json()).then(data => {console.log('分片上传成功:', data);}).catch(error => {console.error('分片上传失败:', error);});
}
总结
以上内容主要是围绕录音api拓展的功能和知识点,有FileReader文件读取,canvas绘制,文件分片上传都可以单独了解,希望可以帮助做为一些实现方案的参考,有错误遗漏希望指出,后续还会补充完善~
相关文章:
【javascript】录音可视化
文章目录 前言一、音频数据格式1. 常见的数据格式 二、获取音频方式1. 用FileReader对象读取音频文件2. 通过url请求获取音频数据3. 录音获取音频流 三、音频格式转换1. buffer和blob互相转换2. blob转base643. base64转Uint8Array 四、音频可视化五、大文件上传1. 固定大小分片…...
解锁机器学习核心算法 | K-平均:揭开K-平均算法的神秘面纱
一、引言 机器学习算法种类繁多,它们各自有着独特的优势和应用场景。前面我们学习了线性回归算法、逻辑回归算法、决策树算法。而今天,我们要深入探讨的是其中一种经典且广泛应用的聚类算法 —— K - 平均算法(K-Means Algorithm)…...
Go入门之语言变量 常量介绍
func main(){var a int8 10var b int 5var c int 6fmt.Println("a", a, "b", b, "c", c)d : 10fmt.Printf("a%v leixing%T\n", d, d) } main函数是入口函数,fmt包有三个打印的函数Println,Print,Printf。第…...
音频采集(VUE3+JAVA)
vue部分代码 xx.vue import Recorder from ./Recorder.js; export default {data() {return {mediaStream: null,recorder: null,isRecording: false,audioChunks: [],vadInterval: null // 新增:用于存储声音活动检测的间隔 ID};},async mounted() {this.mediaSt…...
Linux运维篇-存储基础知识
什么是存储 用于存放数据信息的设备和介质,等同于计算机系统中的外部存储,是一个完整的系统。 存储的结构和趋势 存储的体系结构 当前存储的主要体系结构有三种: DASNASSAN 存储的发展趋势 ssd固态硬盘云存储一体化应用存储设备非结构…...
【git】已上传虚拟环境的项目更改成不再上传虚拟环境
虽然git用了很长时间,但是距离精通还是太远了。注意到虚拟环境是因为上传项目时用到的系统是macOS,而拉取项目时用到的系统是win,意识到是时候学习知识了(好懒啊)。 头一次上传:使用.gitignore避免虚拟环境…...
【Linux网络编程】应用层协议HTTP(请求方法,状态码,重定向,cookie,session)
🎁个人主页:我们的五年 🔍系列专栏:Linux网络编程 🌷追光的人,终会万丈光芒 🎉欢迎大家点赞👍评论📝收藏⭐文章 Linux网络编程笔记: https://blog.cs…...
Android GreenDAO 适配 AGP 8.0+
在 Android 中使用 GreenDao,由于 GreenDao 现在不维护,所以更新到新版本的 Gradle 经常出问题,在这记录一些升级遇到的问题,并且记录解决方案。 博主博客 https://blog.uso6.comhttps://blog.csdn.net/dxk539687357 一、‘:app…...
使用html css js 来实现一个服装行业的企业站源码-静态网站模板
最近在练习 前端基础,html css 和js 为了加强 代码的 熟悉程序,就使用 前端 写了一个个服装行业的企业站。把使用的技术 和 页面效果分享给大家。 应用场景 该制衣服装工厂官网前端静态网站模板主要用于前端练习和编程练习,适合初学者进行 HT…...
小胡说技书博客分类(部分目录):服务治理、数据治理与安全治理对比表格
文章目录 一、对比表格二、目录2.1 服务2.2 数据2.3 安全 一、对比表格 下表从多个维度对服务治理、数据治理和安全治理进行详细对比,为读者提供一个直观而全面的参考框架。 维度服务治理数据治理安全治理定义对软件开发全流程、应用交付及API和接口管理进行规范化…...
SpringBoot3.x整合WebSocket
SpringBoot3.x整合WebSocket 本文主要介绍最新springboot3.x下如何整合WebSocket. WebSocket简述 WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它允许在浏览器和服务器之间进行实时的、双向的通信。相对于传统的基于请求和响应的 HTTP 协议ÿ…...
SpringBoot中自动装配机制的原理
SpringBoot中的自动装配机制是其核心特性之一,其原理主要基于一系列约定和配置,能够根据项目的依赖和配置自动为应用程序加载和配置需要的Spring组件。以下是SpringBoot自动装配机制原理的详细解释: 一、启动类和注解 SpringBootApplicatio…...
STM32外设SPI FLASH应用实例
STM32外设SPI FLASH应用实例 1. 前言1.1 硬件准备1.2 软件准备 2. 硬件连接3. 软件实现3.1 SPI 初始化3.2 QW128 SPI FLASH 驱动3.3 乒乓存储实现 4. 测试与验证4.1 数据备份测试4.2 数据恢复测试 5 实例5.1 参数结构体定义5.2 存储参数到 SPI FLASH5.3 从 SPI FLASH 读取参数5…...
PHP支付宝--转账到支付宝账户
官方参考文档: https://opendocs.alipay.com/open/62987723_alipay.fund.trans.uni.transfer?sceneca56bca529e64125a2786703c6192d41&pathHash66064890 可以使用默认应用,也可以自建新应用,此处以默认应用来讲解【默认应用默认支持…...
太空飞船任务,生成一个地球发射、火星着陆以及下一次发射窗口返回地球的动画3D代码
import numpy as np import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation from mpl_toolkits.mplot3d import Axes3D# 天体参数设置(简化模型) AU 1.5e8 # 天文单位(公里) earth_orbital_radius …...
埃拉托斯特尼筛法来生成素数表【C语言】
代码: char *prime(int MAX) {char *a (char*)malloc(MAX * sizeof(char));if (a NULL) {fprintf(stderr, "Memory allocation failed\n");exit(EXIT_FAILURE);}memset(a, 1, MAX * sizeof(char));a[0] 0;a[1] 0;for (int i 2; i * i < MAX; i) …...
VSCode 实用快捷键
前文 VSCode 作为文本编辑神器, 熟练使用其快捷键更是效率翻倍, 本文介绍 VSCode 常用的实用的快捷键 实用快捷键 涉及到文本操作, 搜索定位, 多光标, 面板打开等快捷键 功能快捷键复制光标当前行 (不需要鼠标选中) Ctrl C 剪切光标当前行 (不需要鼠标选中) Ctrl X 当前行下…...
Ubuntu24.04无脑安装docker(含图例)
centos系统请看这篇 Linux安装Docker教程(详解) 一. ubuntu更换软件源 请看这篇:Ubuntu24.04更新国内源 二. docker安装 卸载老版docker(可忽略) sudo apt-get remove docker docker-engine docker.io containerd runc更新软件库 sudo a…...
近地面无人机植被定量遥感与生理参数反演
近地面无人机植被遥感是指利用无人机(UAV)搭载传感器,在低空(通常低于 100 米)对植被进行高分辨率遥感观测和数据采集的技术。这种技术结合了无人机的高灵活性和遥感的高精度,广泛应用于农业、生态学、林业…...
如何创建自定义权限的kubeconfig
如何创建自定义权限的kubeconfig 有些小伙伴问如何做自定义权限的kubeconfig首先看下我们怎么了解我们控制的权限的api以及涉及的资源和动作权限从哪里可以轻松查看了解了上面的,接下来就简单了,和简单的授权流程一致1、创建一个账户2、创建想要的角色或…...
使用 pjsua2 开发呼叫机器人,批量拨打号码并播放固定音频
如何使用 pjsua2 开发呼叫机器人,批量拨打号码并播放固定音频 声明 该播客仅提供实现思路,并非实际的方案记录,不要盲目照搬。 pjsua2库的安装会有较多问题,请参考本人之前的播客进行安装 pjsua2。 pjsua2 库具体的 api 说明请参考开源库内的 范例代码。 引言 在今天的…...
使用nvm管理node.js版本,方便vue2,vue3开发
在Vue项目开发过程中,我们常常会遇到同时维护Vue2和Vue3项目的情况。由于不同版本的Vue对Node.js 版本的要求有所差异,这就使得Node.js 版本管理成为了一个关键问题。NVM(Node Version Manager)作为一款强大的Node.js 版本管理工具…...
Breakout Tool
思科 CML 使用起来还是很麻烦的,很多操作对于习惯了 secure crt 或者 putty 等工具的网络工程师都不友好。 Breakout Tool 提供对远程实验室中虚拟机控制台与图形界面的本地化接入能力,其核心特性如下: Console 访问:基于 Telnet…...
网络安全-攻击流程-用户层
用户层攻击主要针对操作系统中的用户空间应用程序及用户权限,利用软件漏洞、配置错误或用户行为弱点进行攻击。以下是常见的用户层攻击类型及其流程,以及防御措施: 1. 缓冲区溢出攻击 攻击流程: 目标识别:确定存在漏…...
内网下,Ubuntu (24.10) 离线安装docker最新版教程
一般在数据比较敏感的情况下,是无法使用网络的,而对于Ubuntu系统来说,怎么离线安装docker呢? 下面我给大家来讲一下: 采用二进制安装: 1.下载docker离线包 官网下载: Index of linux/static…...
用deepseek学大模型08-卷积神经网络(CNN)
yuanbao.tencent.com 从入门到精通卷积神经网络(CNN),着重介绍的目标函数,损失函数,梯度下降 标量和矩阵形式的数学推导,pytorch真实能跑的代码案例以及模型,数据,预测结果的可视化展示, 模型应用场景和优缺点…...
6.【线性代数】—— 列空间和零空间
六 列空间和零空间 1. 列空间 C(A)2. 零空间 N(A)2.1 定义2.2 为什么零空间是一个子空间?2.3 Axb的解空间,是一个子空间吗? 1. 列空间 C(A) [ c o l 11 c o l 21 c o l 31 c o l 12 c o l 22 c o l 32 c o l 13 c o l 23 c o l 33 ] ⏟ A [ a…...
Spring SmartLifecycle:精准控制Bean的生命周期
一、核心作用 SmartLifecycle 是 Spring 框架中用于 精确控制组件生命周期阶段 的高级接口,主要解决三类问题: 有序启停:控制多个组件启动/关闭顺序阶段化处理:将初始化/销毁操作划分为不同阶段上下文感知:获取应用上…...
【ISO 14229-1:2023 UDS诊断(会话控制0x10服务)测试用例CAPL代码全解析②】
ISO 14229-1:2023 UDS诊断【会话控制0x10服务】_TestCase02 作者:车端域控测试工程师 更新日期:2025年02月15日 关键词:UDS诊断、0x10服务、诊断会话控制、ECU测试、ISO 14229-1:2023 TC10-002测试用例 用例ID测试场景验证要点参考条款预期…...
gitee SSH 公钥设置教程
Gitee 提供了基于 SSH 协议的 Git 服务,在使用 SSH 协议访问仓库仓库之前,需要先配置好账户 SSH 公钥。 1、生成秘钥 Windows 用户建议使用 Windows PowerShell 或者 Git Bash,在 命令提示符 下无 cat 和 ls 命令。 ssh-keygen -t ed25519 -C "Gitee SSH Key"中间…...
Wireshark 输出 数据包列表本身的值
在 Wireshark 中,如果你想输出数据包列表本身的值(例如,将数据包的摘要信息、时间戳、源地址、目的地址等导出为文本格式),可以使用 导出为纯文本文件 的功能。以下是详细步骤: 步骤 1:打开 Wir…...
electron 学习
文章目录 1.注意项1.1 安装前最好设置一下代理 官网 tutorial https://www.electronjs.org/docs/latest/tutorial/tutorial-prerequisites 1.注意项 1.1 安装前最好设置一下代理 npm config set registry https://registry.npmmirror.com/...
Asp.Net Core MVC 中级开发教程
Asp.Net Core MVC 中级开发教程 一、Asp.Net Core Mvc 区域使用 ASP.NET Core MVC的Areas使用整理 - 天马3798 - 博客园 二、Asp.Net Core 路径处理 Asp.Net Core Web相对路径、绝对路径整理 Asp.Net Core获取当前上下文对象 三、Asp.Net Core 服务使用和封装 四、Asp.Net …...
DeepSeek与ChatGPT的全面对比
在人工智能(AI)领域,生成式预训练模型(GPT)已成为推动技术革新的核心力量。OpenAI的ChatGPT自发布以来,凭借其卓越的自然语言处理能力,迅速占据市场主导地位。然而,近期中国AI初创公…...
什么是网络安全?网络安全防范技术包括哪些?
伴随着互联网的发展,它已经成为我们生活中不可或缺的存在,无论是个人还是企业,都离不开互联网。正因为互联网得到了重视,网络安全问题也随之加剧,给我们的信息安全造成严重威胁,而想要有效规避这些风险&…...
使用Java爬虫获取1688按图搜索商品(拍立淘API接口)
在电商领域,按图搜索商品(拍立淘)是一种非常实用的功能,尤其适合用户通过图片快速查找相似商品。1688开放平台提供了按图搜索商品的API接口,允许开发者通过图片获取相关的商品信息。本文将详细介绍如何使用Java爬虫技术…...
物联网技术赋能预测性维护的深度剖析与前景展望
一、引言 1.1 研究背景与意义 随着信息技术的飞速发展,物联网技术已逐渐渗透到各个行业领域,成为推动产业变革和创新的重要力量。物联网通过将各种设备、物品与互联网连接,实现数据的采集、传输和交互,为各行业带来了前所未有的智能化和自动化水平提升。在工业领域,设备…...
前端工程化的具体实现细节
🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…...
Dav_笔记14:优化程序提示 HINTs -4
指定全局表提示 指定表的提示通常是指发生提示的DELETE,SELECT或UPDATE查询块中的表,而不是指语句引用的任何视图中的表。 如果要为显示在视图中的表指定提示,Oracle建议使用全局提示,而不是在视图中嵌入提示。 您可以使用包含具…...
解锁享元模式:内存优化与性能提升的关键密码
系列文章目录 待后续补充~~~ 文章目录 一、享元模式初相识二、享元模式的核心概念2.1 内部状态与外部状态2.2 享元角色剖析 三、Java 代码中的享元模式3.1 简单示例代码实现3.2 代码解析与关键步骤 四、实际应用场景探秘4.1 文本编辑器中的享元模式4.2 游戏开发中的享元模式4.3…...
负载均衡 方式
DNS 软件负载均衡 Nginx 也是 软件负载均衡 各种策略 1、轮询(默认) 2、weight(权重) 3、IP Hash (会话粘滞) 4、fair 5、UrlHash...
CAS单点登录(第7版)18.日志和审计
如有疑问,请看视频:CAS单点登录(第7版) 日志和审计 Logging 概述 Logging CAS 提供了一个日志记录工具,用于记录重要信息事件,如身份验证成功和失败;可以对其进行自定义以生成用于故障排除的其他信息。…...
Linux多版本管理工具介绍
一、update-alternatives工具 1. 简介 update-alternatives是Linux系统自带的一个用于管理多个版本命令的工具。它允许用户在不同的软件版本之间进行切换,而不需要手动修改环境变量或者链接文件。 2. 基本使用 查看已安装的alternatives 使用命令update-alterna…...
DeepSeek笔记(二):DeepSeek局域网访问
如果有多台电脑,可以通过远程访问,实现在局域网环境下多台电脑共享使用DeepSeek模型。在本笔记中,首先介绍设置局域网多台电脑访问DeepSeek-R1模型。 一、启动Ollama局域网访问 1.配置环境变量 此处本人的操作系统是Windows11,…...
摄像头畸变矫正
简单介绍 所谓畸变其实就是由摄像头引起的图片失真, 一般在广角摄像头表现明显, 原本平整的桌面通过镜头看像个球面, 直观的解释直线被拍成了曲线, 这让我想起来了一个表情包. 去畸变的办法 首先我们需要一个标准棋盘(印有特定的标定图案), 如图: 把它摊平放在桌子上, 然后用…...
EasyRTC:智能硬件适配,实现多端音视频互动新突破
一、智能硬件全面支持,轻松跨越平台障碍 EasyRTC 采用前沿的智能硬件适配技术,无缝对接 Windows、macOS、Linux、Android、iOS 等主流操作系统,并全面拥抱 WebRTC 标准。这一特性确保了“一次开发,多端运行”的便捷性,…...
机器视觉--图像的运算(乘法)
一、引言 在图像处理领域,Halcon 是一款功能强大且广泛应用的机器视觉软件库。它提供了丰富的算子和工具,能够满足各种复杂的图像处理需求。图像的乘法运算作为其中一种基础操作,虽然不像一些边缘检测、形态学处理等操作那样被频繁提及&…...
蓝桥杯 Java B 组之哈希表应用(两数之和、重复元素判断)
Day 5:哈希表应用(两数之和、重复元素判断) 一、哈希表(Hash Table)基础 1. 什么是哈希表? 哈希表(Hash Table) 是一种键值对(key-value)存储的数据结构&…...
Kafka分区管理大师指南:扩容、均衡、迁移与限流全解析
#作者:孙德新 文章目录 分区分配操作(kafka-reassign-partitions.sh)1.1 分区扩容、数据均衡、迁移(kafka-reassign-partitions.sh)1.2、修改topic分区partition的副本数(扩缩容副本)1.3、Partition Reassign场景限流1.4、节点内副本移动到不…...
vue 接口传formdata
在Vue中,如果你需要向服务器发送FormData对象,通常是为了上传文件或者需要发送表单数据。FormData是一个非常有用的工具,因为它可以直接使用表单元素的值以及文件内容,并以一种浏览器兼容的方式来发送这些数据。下面是如何在Vue中…...