音频录制实现 绘制频谱

思路

获取设备信息

获取录音的频谱数据

绘制频谱图

具体实现

封装 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();}
};

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

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

相关文章

国外客户发开发信怎么发?写外贸邮件方法?

如何给国外客户发开发信&#xff1f;给老外发外贸开发信的技巧&#xff1f; 与国外客户保持联系并积极开展业务合作变得至关重要。一种有效的方式是通过发展信件&#xff0c;这可以帮助建立关系、传递信息和促成商机。蜂邮将介绍如何向国外客户发开发信&#xff0c;以确保您的…

python3:split()分割字符串为字符/字符串列表 2023-11-20

split() 方法通过指定分隔符对字符串进行切片&#xff0c;该方法将字符串分割成子字符串并返回一个由这些子字符串组成的列表。 #split("分隔符","分割次数") str_test"this is test" print (str_test.split()) # split()默认以空格为分…

2023大厂高频面试题之CSS篇(2)

系列文章: 2023前端大厂面试题之JavaScript篇(1) 2023前端大厂面试题之JavaScript篇(2) 2023前端大厂面试题之JavaScript篇(3) 2023前端大厂面试题之JavaScript篇(4) 2022大厂高频面试题之HTML篇 2023前端大厂高频面试题之CSS篇 2023前端大厂高频面试题之Vue篇(1) 2023前端大厂…

金融市场数据至上:QuestDB 为您的数据提供最优解 | 开源日报 No.81

vlang/v Stars: 34.7k License: MIT V 是一个开源项目&#xff0c;它是一种简单、易于学习的编程语言。该项目具有以下核心优势和主要功能&#xff1a; 简洁性&#xff1a;可以在周末内掌握这门语言。快速编译&#xff1a;使用 Clang 后端约为 110k loc/s&#xff0c;本地和…

MR素数测试及 pycryptodome库下 已知MR伪素数以及强伪证 生成指定伪随机数生成器绕过素性检测

MR素数测试在密码学库中应用广泛&#xff0c;通常作为BSPW的一部分来进行素数测试&#xff0c;由于在其算法中&#xff0c;有随机数的使用&#xff08;选择一个随机的base&#xff09;&#xff0c;若一个MR伪素数 n n n&#xff0c;已知其在某一个强伪证 a a a&#xff08;随机…

Leetcode—3.无重复字符的最长子串【中等】

2023每日刷题&#xff08;三十二&#xff09; Leetcode—3.无重复字符的最长子串 实现代码 class Solution { public:int lengthOfLongestSubstring(string s) {unordered_set<char> smap;int maxlen 0;int left 0;for(int i 0; i < s.size(); i) {while(smap.fi…

【精选】JSP技术知识点大合集

JSP简介 JSP介绍 JSP(全称Java Server Pages)Java服务端页面技术&#xff0c;是JavaEE平台下的技术规范。它允许使用特定的标签在HTML网页中插入Java代码&#xff0c;实现动态页面处理&#xff0c;所以JSP就是HTML与Java代码的复合体。JSP技术可以快速的实现一个页面的开发&am…

C++大神之路——环境篇

序 在我还在做后端的时候&#xff0c;当时程序员圈里就有个梗很火&#xff0c;说的是当时几种常用编程语言的鄙视链&#xff1a;做C的鄙视做Java的&#xff0c;做Java的鄙视做C#的&#xff0c;而我很不幸&#xff0c;当时在鄙视链最底层。一开始只是当个笑话听听就算了&#x…

MIB 6.1810实验Xv6 and Unix utilities(3)pingpong

Mit6.S081-实验1-Xv6 and Unix utilities-pingpong问题_Isana_Yashiro的博客-CSDN博客 Write a user-level program that uses xv6 system calls to ping-pong a byte between two processes over a pair of pipes, one for each direction. The parent should send a byte to…

00后如何组织双十一大促看这一篇就够了! | 京东云技术团队

引言 大家好&#xff0c;我是王蒙恩&#xff0c;一名“整顿职场”的00后。作为一名去年刚刚加入京东的校招生&#xff0c;我有幸成为本次CDP平台的11.11备战负责人。虽然早在实习的时候就经历过大促&#xff0c;但是真正组织整个部门的备战还是很难忘的。于是提起笔&#xff0…

深信服AC流量管理技术

拓扑图 一.保证通道针对修仙部&#xff0c;访问网站&#xff0c;邮件&#xff0c;DNS&#xff0c;IM&#xff0c;办工 OA&#xff0c;微博论坛网上银行等常见应用保证带宽最低 50%&#xff0c;最高 100% 1. 先新建线路带宽 2.新增流量管理通道&#xff08;保证关键应用&#x…

PPT基础:表格

目录 表格基本功能底纹框线单元格表格美化 表格基本功能 底纹 所在位置&#xff1a;插入图表>>>表设计>>>底纹 底纹&#xff1a;相当于每个单元格的“颜色填充”如何用表格拆分图片 &#xff08;1&#xff09;生成一个表格>>>插入一张图片>>…

我的创作纪念日——365天

机缘 最开始我写博客没有什么特别的原因&#xff0c;主要是因为以下几点&#xff1a; 练习自己的语言组织能力 记录自己学习生活中学到的知识 主要还是想找一个好的保存 Markdown 笔记的平台。 最终我选择了 CSDN&#xff0c;一来是因为 CSDN 对 Markdown 语法的支持较为全面…

计算机视觉的应用17-利用CrowdCountNet模型解决人群数量计算问题(pytorch搭建模型)

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下计算机视觉的应用17-利用CrowdCountNet模型解决人群数量计算问题(pytorch搭建模型)。本篇文章&#xff0c;我将向大家展示如何使用CrowdCountNet这个神奇的工具&#xff0c;以及它是如何利用深度学习技术来解决复杂…

Taro.navigateTo 使用URL传参数和目标页面参数获取

文章目录 1. Taro.navigateTo 简介2. 通过 URL 传递参数3. 目标页面参数获取4. 拓展与分析4.1 拓展4.2 URL参数的类型4.3 页面间通信 5. 总结 &#x1f389;欢迎来到Java学习路线专栏~Taro.navigateTo 使用URL传参数和目标页面参数获取 ☆* o(≧▽≦)o *☆嗨~我是IT陈寒&#x…

OpenAI前CEO萨姆·阿尔特曼可能重返CEO职位;用LoRA微调LLM的实用技巧

&#x1f989; AI新闻 &#x1f680; OpenAI前CEO萨姆阿尔特曼可能重返CEO职位 摘要&#xff1a;据报道&#xff0c;OpenAI前CEO萨姆阿尔特曼有望重新担任CEO职位&#xff0c;并对公司董事会进行重大改变。微软等投资人正努力恢复阿尔特曼的职位&#xff0c;尽管董事会仍然是…

基于Springboot+Vue的校园在线打印预约系统

基于SpringbootVue的校园在线打印预约系统的设计与实现 (1) 注册功能&#xff1a;允许学生、教职员工注册账户&#xff0c;并提供安全的身份验证机制&#xff0c;确保只有授权用户可以使用系统。 (2) 登录功能&#xff1a;店家或学生可以使用各自账号登录。登录后允许修改用户…

Sql Server 2017主从配置之:发布订阅

使用发布订阅模式搭建Sql Server 2017主从同步&#xff0c;类似事件通知机制&#xff0c;基本可以做到准实时同步&#xff0c;可以同时做到一对多的数据同步。 不过发布订阅模式&#xff0c;只能同时数据&#xff0c;不能同步表结构。在创建发布的时候&#xff0c;需要选择需要…

亚马逊卖家参与活动:提升产品排名的神秘法宝?

对于许多亚马逊卖家来说&#xff0c;提升产品排名是一项持续且充满挑战的任务。有很多方法可以帮助提升产品在亚马逊平台上的排名&#xff0c;其中之一就是参与亚马逊的Woot活动。那么&#xff0c;通过Woot活动真的可以提升产品排名吗&#xff1f;让我们深入了解。 什么是Woot活…

数字IC前端学习笔记:时钟切换电路

相关阅读 数字IC前端https://blog.csdn.net/weixin_45791458/category_12173698.html?spm1001.2014.3001.5482 有些时候我们需要在系统运行时切换系统时钟&#xff0c;最简单的方法就是使用一个MUX&#xff08;数据选择器&#xff09;选择输出的时钟&#xff0c;如下代码片所…