音频录制实现 绘制频谱
思路
获取设备信息
获取录音的频谱数据
绘制频谱图
具体实现
封装 loadDevices.js
/*** 是否支持录音*/
const recordingSupport = () => {const scope = navigator.mediaDevices || {};if (!scope.getUserMedia) {scope = navigatorscope.getUserMedia || (scope.getUserMedia = scope.webkitGetUserMedia || scope.mozGetUserMedia || scope.msGetUserMedia);}if (!scope.getUserMedia) {return false}return scope
}// 获取麦克风权限
export const getUserMediaPermission = () => {return new Promise((resolve, reject) => {const mediaDevices = recordingSupport()if (mediaDevices.getUserMedia) {let constraints = { audio: true }mediaDevices.getUserMedia(constraints).then(resolve, reject);} else { reject(false) } // 浏览器不支持录音})
}function checkMime() {var types = ["audio/mpeg","audio/webm","audio/mp4","audio/wav","audio/ogg","audio/flac","audio/m4a","audio/mp3","audio/mpga","audio/oga",];let first;for (var i in types) {// 判断当前浏览器支持哪种let supported = MediaRecorder.isTypeSupported(types[i]);if (supported && !first) {console.log("Is " + types[i] + " supported? " + (supported ? "Yes!" : "Nope :("));first = types[i];}}return first;
}let streams = []
let stopDraw = false/*** 释放资源*/
export const devicesDispose = () => {console.log('devicesDispose-释放资源');stopDraw = truestreams.forEach(e => {e.getTracks().forEach(track => track.stop());})
}export const getAudioContext = () => window.AudioContext ||window.webkitAudioContext ||window.mozAudioContext ||window.msAudioContext;export default function loadDevices(options = {}) {const { readover = () => { }, change = () => { }, stop = () => { } } = optionslet analyser;let mediaRecorder;let dataArray;let audioChunks = [];try {const draw = () => {if (stopDraw) returnrequestAnimationFrame(draw);analyser.getByteTimeDomainData(dataArray);change(dataArray);};let mimeType = checkMime();getUserMediaPermission().then((stream) => {streams.push(streams)// 创建记录器mediaRecorder = new MediaRecorder(stream, { mimeType });// 音频数据发生变化时收集音频片段,用于合成音频文件mediaRecorder.addEventListener("dataavailable", (event) => {console.log("mediaRecorder-dataavailable:", event);audioChunks.push(event.data);});// // 监听音频开始录制// mediaRecorder.addEventListener('start', () => {// console.log("mediaRecorder-start:");// audioChunks = []// })// 音频录制结束回调mediaRecorder.addEventListener("stop", () => {console.log("mediaRecorder-end:", audioChunks);const audioBlob = new Blob(audioChunks, { type: "audio/mp4" }); // wav webm mp4 stop(audioBlob);// 清空 chunks 以便下一次录音 audioChunks = []});// 获取音频数据const audioContext = new getAudioContext()();const source = audioContext.createMediaStreamSource(stream);// 通过AnalyserNode对象的getByteTimeDomainData方法来获取音频数据的波形形式:// 获取音频时间和频率数据analyser = audioContext.createAnalyser();// 定义长度analyser.fftSize = 2048; // 可以调整这个值来改变细节const bufferLength = analyser.frequencyBinCount;dataArray = new Uint8Array(bufferLength);// 合并流数据source.connect(analyser);draw()readover(mediaRecorder)}).catch((err) => {console.log("stream-errr", err);});} catch (err) {console.log("mediaDevices-errr", err);}
}
示例
import { onMounted, onUnmounted } from "vue";
import loadDevices, {devicesDispose,getAudioContext,
} from "../compositions/VerbalChat/loadDevices";let mediaRecorder;
const speak = ref(false);// 停止录制
const uploadAudio = (blob) => {// others 获取录音数据之后后续处理 上传// const formData = new FormData();// formData.append("file", blob);// 接口formData上传
};// 绘制方法
const draw = ({ data }) => {// 调用子组件的绘制方法,传递数据// verCanvas.value && verCanvas.value.draw({ data });
};const btnClick = () => {if (!speak.value) {console.log("开始录制"); speak.value = true;mediaRecorder && mediaRecorder.start();} else {console.log("停止录制");speak.value = false;mediaRecorder && mediaRecorder.stop();}
};onMounted(() => {loadDevices({readover: (r) => (mediaRecorder = r),change: (dataArray) => {if (speak.value) {// 处于录制中draw({ data: dataArray });}},stop: (blob) => uploadAudio(blob),});
});onUnmounted(()=>devicesDispose())
绘制频谱图
<template><canvas class="VerbalCanvas" ref="canvasRef"></canvas>
</template><script setup>
import { onMounted, ref, watch } from "vue";let ctx, canvas;
const canvasRef = ref();const draw = ({ data }) => {if (!canvasRef.value) return;canvas = canvasRef.value;canvas.height = parseFloat(getComputedStyle(canvas)["height"]);canvas.width = parseFloat(getComputedStyle(canvas)["width"]);ctx = canvas.getContext("2d");// drawWave(ctx, canvas, type, data);// drawLoop(ctx, canvas, type, data);drawCircle(ctx, canvas, type, data);
}const clear = () => {try {ctx.clearRect(0, 0, canvas.width, canvas.height);} catch (er) {console.log("er", er);}
}defineExpose({ draw, clear });
绘制曲线
const waveH = 150; // 波区域高度
const obj = {top: 0,center: canvas.height / 2,bottom: canvas.height - waveH,
};
const initY = obj[type];
const dataArray = data || []; // 模拟数据 随机生成一个数组,值随机ctx.fillStyle = "rgba(200, 200, 200, 0)";
ctx.fillRect(0, 0, canvas.width, canvas.height);ctx.lineWidth = 1;
ctx.strokeStyle = "#0077FF"; //"rgb(0, 0, 0)";ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();const sliceWidth = (canvas.width * 1.0) / dataArray.length;
let x = 0;for (let i = 0; i < dataArray.length; i++) {const v = dataArray[i] / 128.0;// const y = (v * canvas.height) / 2 ;const y = (v * waveH) / 2 + initY;if (i === 0) {ctx.moveTo(x, y);} else {ctx.lineTo(x, y);}x += sliceWidth;
}// ctx.lineTo(canvas.width, canvas.height / 2);
ctx.lineTo(canvas.width, waveH / 2) + initY;
ctx.stroke();
绘制音频环
ctx.clearRect(0, 0, canvas.width, canvas.height);
const cX = canvas.width / 2;
const cY = canvas.height / 2;
const r = 100;
const basel = Math.floor(data.length / 360);
for (var i = 0; i < 360; i++) {var value = (data[i * basel] / 60) * 8; //8; // 模拟数据 value = Math.random() * 100ctx.beginPath();ctx.lineWidth = 2;ctx.strokeStyle = "#08a3ef";ctx.moveTo(cX, cY);//R * cos (PI/180*一次旋转的角度数) ,-R * sin (PI/180*一次旋转的角度数)ctx.lineTo(Math.cos(((i * 1) / 180) * Math.PI) * (r + value) + cX,-Math.sin(((i * 1) / 180) * Math.PI) * (r + value) + cY);ctx.stroke();
}
//画一个小圆,将线条覆盖
ctx.beginPath();
ctx.lineWidth = 1;
ctx.arc(cX, cY, r, 0, 2 * Math.PI, false);
ctx.fillStyle = "#000";
ctx.stroke();ctx.fill();
绘制圆
/** 绘制圆 */
const drawCircle = (ctx, canvas, type, data) => {ctx.clearRect(0, 0, canvas.width, canvas.height);const cX = canvas.width / 2;const cY = canvas.height / 2;const r = 100;for (var i = 0; i < data.length; i += 4) {const v = (data[i] + data[i + 1] + data[i + 2] + data[i + 3]) / 4;const r = v * 0.5;// for (var i = 0; i < 254; i += 4) {// const r = Math.random() * 100;ctx.beginPath();ctx.lineWidth = 1;ctx.arc(cX, cY, r, 0, 2 * Math.PI, false);ctx.strokeStyle = "#c46868";ctx.stroke();}
};
相关文章:
音频录制实现 绘制频谱
思路 获取设备信息 获取录音的频谱数据 绘制频谱图 具体实现 封装 loadDevices.js /*** 是否支持录音*/ const recordingSupport () > {const scope navigator.mediaDevices || {};if (!scope.getUserMedia) {scope navigatorscope.getUserMedia || (scope.getUserM…...

nginx代理本地服务请求,避免跨域;前端图片压缩并上传
痛点 有时用vscode进行一些测试 请求不同端口服务、或者其他服务接口时时,老是会报跨域,非常的烦 所有就想用 nginx 进行请求代理,来解决这个痛点 nginx 下载地址:nginx: download 下载到某一目录: window下nginx相关…...

Vue3-readonly(深只读) 与 shallowReadonly(浅只读)
Vue3-readonly(深只读) 与 shallowReadonly(浅只读) readonly(深只读):具有响应式对象中所有的属性,其所有值都是只读且不可修改的。shallowReadonly(浅只读):具有响应式对象的第一层属性值是只读且不可修改的&#x…...

中小企业怎么实现数字化转型?有什么实用的工单管理系统?
当前,世界经济数字化转型已是大势所趋。在这个数字化转型的大潮中,如果企业仍然逆水而行,不随大流,那么,企业将有可能会被抛弃,被对手超越,甚至被市场边缘化,导致最终的结果是&#…...
vue3.x中父组件添加自定义参数后,如何获取子组件$emit传递过来的参数
之前写过一篇文章,vue中父组件添加自定义参数后,如何获取子组件$emit传递过来的参数 现在已经进入vue3.x开发的时代了,那么vue3.x中父组件添加自定义参数后,如何获取子组件$emit传递过来的参数? 1、子组件使用emit传…...

【Machine Learning in R - Next Generation • mlr3】
本篇主要介绍mlr3包的基本使用。 一个简单的机器学习流程在mlr3中可被分解为以下几个部分: 创建任务 比如回归、分裂、生存分析、降维、密度任务等等挑选学习器(算法/模型) 比如随机森林、决策树、SVM、KNN等等训练和预测 创建任务 本次示…...

CorelDraw2024(CDR)- 矢量图制作软件介绍
在当今数字化时代,平面设计已成为营销、品牌推广和创意表达中不可或缺的元素。平面设计必备三大软件Adebo PhotoShop、CorelDraw、Adobe illustrator, 今天小编就详细介绍其中之一的CorelDraw软件。为什么这款软件在设计界赢得了声誉,并成为了设计师的无…...
RT-DETR优化改进:轻量级Backbone改进 | VanillaNet极简神经网络模型 | 华为诺亚2023
🚀🚀🚀本文改进:一种极简的神经网络模型 VanillaNet,支持vanillanet_5, vanillanet_6, vanillanet_7, vanillanet_8, vanillanet_9, vanillanet_10, vanillanet_11等版本,相对于自带的rtdetr-l、rtdetr-x参数量如下: layersparametersgradientsvanillanet_5338277174…...

本地部署 EmotiVoice易魔声 多音色提示控制TTS
本地部署 EmotiVoice易魔声 多音色提示控制TTS EmotiVoice易魔声 介绍ChatGLM3 Github 地址部署 EmotiVoice准备模型文件准备预训练模型推理 EmotiVoice易魔声 介绍 EmotiVoice是一个强大的开源TTS引擎,支持中英文双语,包含2000多种不同的音色ÿ…...

5g路由器赋能园区无人配送车联网应用方案
随着人工智能、无人驾驶技术和自动化技术的不断进步,无人配送技术得到了极大的发展。园区内的物流配送任务通常是繁琐的,需要大量的人力资源和时间。无人配送技术能够提高配送效率并减少人力成本。无人配送车辆和机器人能够根据预定的路线和计划自动完成…...
ARTS 打卡第一周
ARTS AlgorithmReviewTipShare Algorithm 题目 class Solution {func mergeAlternately(_ word1: String, _ word2: String) -> String {var ans ""var idx1 word1.startIndexvar inx2 word2.startIndexwhile idx1 < word1.endIndex || idx2 < word2.e…...

第八部分:JSP
目录 JSP概述 8.1:什么是JSP,它有什么作用? 8.2:JSP的本质是什么? 8.3:JSP的三种语法 8.3.1:jsp头部的page指令 8.3.2:jsp中的常用脚本 ①声明脚本(极少使用…...

Github小彩蛋显示自己的README,git 个人首页的 README,readme基本语法
先上效果👇 代码在下面,流程我放最下面了,思路就是创建一个和自己同名的仓库,要公开,创建的时候会提示小彩蛋你的reademe会展示在你的首页,或许你在这个readme里面的修改都会在你的主页上看到了ὄ…...

dxva2+ffmpeg硬件解码(Windows)终结发布
《dxva2超低延迟视频播放器》演示demo下载URL: 【免费】dxva2硬解码超低延迟网络本地播放器资源-CSDN文库 本地播放 截图: rtsp播放截图(推送内容为本地桌面,所以是这样的) OK,进入主题: 前前…...
C#密封类、偏类
C#密封类 在C#中,密封类(Sealed Class)是一种特殊的类,它阻止其他类继承它。你可以通过在类定义前面加上 sealed 关键字来创建一个密封类。 以下是一个密封类的例子: public sealed class MyClass {// Class member…...
C++菱形继承问题
总结: 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义利用虚继承 virtual 可以解决菱形继承问题 #include <iostream> #include <string> using namespace std; class Animal { public:int m_Age; };//继承前加…...

第20章 数据库编程
通过本章需要理解JDBC的核心设计思想以及4种数据库访问机制,理解数据库连接处理流程,并且可以使用JDBC进行Oracle数据库的连接,理解工厂设计模式在JDBC中的应用,清楚地理解DriverManager类的作用,掌握Connection、Prep…...

PS学习笔记——初识PS界面
文章目录 PS界面 PS界面 我使用的是PS2021,可能不同版本界面有所不同,但大体来说没有太多差异 可以看到下面这个图就是ps的主界面,大体分为菜单栏、选项栏、工具栏、面板、以及最中央的工作区。 ps中的操作基本都能在菜单栏中找到 可以从菜…...

JDBC,Java连接数据库
下载 JDBC https://mvnrepository.com/ 创建项目,然后创建一个目录并将下载好的 jar 包拷贝进去 选择 Add as Library,让这个目录能被项目识别 连接数据库服务器 在 JDBC 里面,使用 DataSource 类来描述数据库的位置 import com.mysql.cj.…...

java智慧校园信息管理系统源码带微信小程序
一、智慧校园的定义 智慧校园指的是以云计算和物联网为基础的智慧化的校园工作、学习和生活一体化环境。以各种应用服务系统为载体,将教学、科研、管理和校园生活进行充分融合,让校园实现无处不在的网络学习、融合创新的网络科研、透明高效的校务治理、…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...

CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。
1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj,再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...
MFE(微前端) Module Federation:Webpack.config.js文件中每个属性的含义解释
以Module Federation 插件详为例,Webpack.config.js它可能的配置和含义如下: 前言 Module Federation 的Webpack.config.js核心配置包括: name filename(定义应用标识) remotes(引用远程模块࿰…...

VisualXML全新升级 | 新增数据库编辑功能
VisualXML是一个功能强大的网络总线设计工具,专注于简化汽车电子系统中复杂的网络数据设计操作。它支持多种主流总线网络格式的数据编辑(如DBC、LDF、ARXML、HEX等),并能够基于Excel表格的方式生成和转换多种数据库文件。由此&…...

Python环境安装与虚拟环境配置详解
本文档旨在为Python开发者提供一站式的环境安装与虚拟环境配置指南,适用于Windows、macOS和Linux系统。无论你是初学者还是有经验的开发者,都能在此找到适合自己的环境搭建方法和常见问题的解决方案。 快速开始 一分钟快速安装与虚拟环境配置 # macOS/…...
用鸿蒙HarmonyOS5实现国际象棋小游戏的过程
下面是一个基于鸿蒙OS (HarmonyOS) 的国际象棋小游戏的完整实现代码,使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├── …...