音频录制实现 绘制频谱
思路
获取设备信息
获取录音的频谱数据
绘制频谱图
具体实现
封装 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智慧校园信息管理系统源码带微信小程序
一、智慧校园的定义 智慧校园指的是以云计算和物联网为基础的智慧化的校园工作、学习和生活一体化环境。以各种应用服务系统为载体,将教学、科研、管理和校园生活进行充分融合,让校园实现无处不在的网络学习、融合创新的网络科研、透明高效的校务治理、…...
深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...
Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
【C++进阶篇】智能指针
C内存管理终极指南:智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...
前端中slice和splic的区别
1. slice slice 用于从数组中提取一部分元素,返回一个新的数组。 特点: 不修改原数组:slice 不会改变原数组,而是返回一个新的数组。提取数组的部分:slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...
ubuntu系统文件误删(/lib/x86_64-linux-gnu/libc.so.6)修复方案 [成功解决]
报错信息:libc.so.6: cannot open shared object file: No such file or directory: #ls, ln, sudo...命令都不能用 error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory重启后报错信息&…...
区块链技术概述
区块链技术是一种去中心化、分布式账本技术,通过密码学、共识机制和智能合约等核心组件,实现数据不可篡改、透明可追溯的系统。 一、核心技术 1. 去中心化 特点:数据存储在网络中的多个节点(计算机),而非…...
