10.WebRTC实现网页录音功能

article/2025/7/22 5:56:07

一、概述

通过WebRTC,实现在页面上进行录音,并将录音结果转换为.wav格式进行播放

二、录音实现

  1. 检测是否有麦克风权限
navigator.mediaDevices.enumerateDevices().then(function(devices) {// 检测视频输入let video = devices.find((device) => {return device.kind === 'videoinput'})// 检测音频输入let audio = devices.find((device) => {return device.kind === 'audioinput'})// getUserMedia的参数let mediaConstraints = {video: !!video,audio: !!audio}let constraints = { video: video && mediaConstraints.video, audio: audio && mediaConstraints.audio }return navigator.mediaDevices.getUserMedia(constraints).then(mediaStream => {stream = mediaStream // stream 记录音频流,在停止录音等时使用// 开始录音...beginRecord(mediaStream )})})
  1. 调用getUserMedia方法,获取音频流,并开始录音
// 录音
function beginRecord(mediaStream) {let audioContext = new (window.AudioContext || window.webkitAudioContext)let mediaNode = audioContext.createMediaStreamSource(mediaStream)// 创建一个jsNodelet jsNode = createJSNode(audioContext)// 需要连接扬声器消费到outputBuffer, process回调才能出发,且不给outputBuffer设置内容扬声器不会播出声音jsNode.connect(audioContext.destination)jsNode.onaudioprocess = (audioProcessingEvent) => {let audioBuffer = audioProcessingEvent.inputBufferlet leftChannelData = audioBuffer.getChannelData(0) // 左声道let rightChannelData = audioBuffer.getChannelData(1) // 右声道// 将音频数据存入dataListdataList.left.push(leftChannelData.slice(0))dataList.right.push(rightChannelData.slice(0))}// 把mediaNode连接到jsNodemediaNode.connect(jsNode)
}

以上步骤完成,便开始录音,并且将音频数据存入了dataList数组中。

到这一步,其实录音的工作已经完成了。下面的工作是将录音数据保存成特定格式(如.mp3),并插入到页面的audio元素中进行播放。

三、停止录音

停止录音需要做三个工作:

  1. 合并数据
  2. 创建指定格式的文件
  3. 录音播放

停止录音的总体方法为:

function stopRecord() {let leftData = mergeArray(dataList.left)let rightData = mergeArray(dataList.right)let allData = intervalLeftAndRight(leftData, rightData) // 合并数据let wavBuffer = createWavFile(allData) // 创建指定格式的数据playRecord(wavBuffer) // 播放录音}

下面按照具体步骤实现。

  1. 合并数据
// 交叉合并左右声道的数据
function intervalLeftAndRight(left, right) {let totalLength = left.length + right.lengthlet data = new Float32Array(totalLength)for(let i = 0; i < left.length; i++) {let k = i * 2data[k] = left[i]data[k + 1] = right[i]}return data
}function mergeArray(list) {let length = list.length * list[0].lengthlet data = new Float32Array(length)let offset = 0for(let i = 0; i < list.length; i++) {data.set(list[i], offset)offset += list[i].length}return data
}
  1. 创建指定格式的文件,这一块的代码是完全从网上找的资料,具体出处地址不记得了。此处是创建.wav文件
function createWavFile(audioData) {const WAV_HEAD_SIZE = 44let buffer = new ArrayBuffer(audioData.length * 2 + WAV_HEAD_SIZE)let view = new DataView(buffer)// 写入wav头部信息writeUTFBytes(view, 0, 'RIFF')view.setUint32(4, 44 + audioData.length * 2, true)writeUTFBytes(view, 8, 'WAVE')writeUTFBytes(view, 12, 'fmt ');view.setUint32(16, 16, true);view.setUint16(20, 1, true);view.setUint16(22, 2, true);view.setUint32(24, 44100, true);view.setUint32(28, 44100 * 2, true);view.setUint16(32, 2 * 2, true);view.setUint16(34, 16, true);writeUTFBytes(view, 36, 'data');view.setUint32(40, audioData.length * 2, true);// 写入wav头部,代码同上let length = audioData.length;let index = 44;let volume = 1;for (let i = 0; i < length; i++) {view.setInt16(index, audioData[i] * (0x7FFF * volume), true);index += 2;}return buffer;
}function writeUTFBytes(view, offset, string) {var lng = string.length;for (var i = 0; i < lng; i++) { view.setUint8(offset + i, string.charCodeAt(i));}
}
  1. 录音播放
function playRecord(arrayBuffer) {let blob = new Blob([new Uint8Array(arrayBuffer)])let blobUrl = URL.createObjectURL(blob); // 创建BlobURLdocument.querySelector('.audio-node').src = blobUrl // 插入到页面的音频播放组件
}

四、关闭录音功能

在停止录音的时候,浏览器的录音功能是需要手动关闭的,否则会在浏览器顶部出现录音的红色点点,下图这样的:

在这里插入图片描述

关闭录音功能的方法:

stream.getTracks().forEach(track => {if(track.readyState === 'live') {track.stop()}
})

以上,就是一个完整的web端录音功能的实现。


http://chatgpt.dhexx.cn/article/FbkbVHis.shtml

相关文章

uniapp中使用网页录音并上传声音文件(发语音)——js-audio-recorder的使用【伸手党福利】

uniapp中上传音频只能在app或小程序当中实现&#xff0c;如何使用网页完成语音的录制和上传则成为了困扰前端童鞋的重点。 本文着重解决&#xff1a; js-audio-recorder报 error:浏览器不支持getUserMedia ! 的问题。 js-audio-recorder报 NotFoundError : Requested device no…

前端实现在浏览器网页中录音

一、整体实现的思想 页面中实现录音需要使用浏览器提供的MediaRecorder API,所以要实现页面录音就需要浏览器支持MediaStream Recording相关的功能&#xff0c;即浏览器能够获取浏览器的录音权限。 页面内容&#xff0c;需要一个记录录音开始和结束的按钮&#xff0c;以及一个用…

基于神经网络的自适应最优控制

《 N e u r a l n e t w o r k a p p r o a c h t o c o n t i n u o u s − t i m e d i r e c t a d a p t i v e o p t i m a l c o n t r o l f o r p a r t i a l l y u n k n o w n n o n l i n e a r s y s t e m s 》 D r a g u n a V r a b i e ∗ , F r a n k L e w …

堆排序(排升序为啥建大堆,排降序为啥建小堆)

简介&#xff1a; 之前对堆排序认识的不是很透彻&#xff0c;今天回过头来再把堆排序的知识整理整理&#xff01;以及排升序为什么要建大堆&#xff0c;排降序要建小堆。 正文&#xff1a; 首先我们要知道&#xff1a;   ①堆的逻辑是一颗完全二叉树&#xff1b;   ②它使…

残差网络Residual Networks-残差网络的创建、训练、测试、调参

残差网络的创建、训练、测试、调参加粗样式 在Keras中实现残差网络模型的创建&#xff0c;并通过模型来实现对图片的分类。 残差网络的预备知识 网络越深越好&#xff1f; 随着网络层级的不断增加&#xff0c;模型精度不断得到提升&#xff0c;而当网络层级增加到一定的数目…

堆排序,为什么升序排列要建大堆,降序排列要建小堆

堆排序中用到了建立大小堆和向下调整的内容&#xff0c;对这些内容有些不了解的同学可以去补一补专门写堆的博客&#xff0c;方便更好的理解堆排序数据结构之堆&#xff08;Heap&#xff09;&#xff0c;堆的相关操作&#xff0c;用堆模拟优先级队列。 如果把待排序序列分为未排…

操作系统——动态分配算法(首次适应算法,最佳适应算法,最坏适应算法及回收)

操作系统——动态分配 写的时间早了&#xff0c;有些许漏洞和不足&#xff0c;请大家不要介意 分配方式可分为四类:单一连续分配、固定分区分配、动态分区分配以及动态可重定位分区分配算法四种方式&#xff0c;其中动态分区分配算法就是此实验的实验对象。动态分区分配又称为…

pandas中对列进行排序(单列/多列)/(升序/降序)/(多列升序,降序控制)

前言 我想把数据分析刻进DNA里 如下面的数据,对price,要进行最简单的升序操作: 这个好整: import pandas as pdshop pd.read_csv("data/shop.csv", index_colid)shop.sort_values(byprice, inplaceTrue)结果: 如果你想整活(被迫)要把数据进行降序排列: 按照降序,传…

最先适应法、最佳适应法、下次适配法、最差适配法

题&#xff1a; 分析&#xff1a; 1. 首先分析是不是最差适配法&#xff0c;最差适配法意思是首先找到最大的内存空间进行分配&#xff0c; 对于请求的5K存储空间&#xff0c;首先找到地址200K容量为56K的地方进行分配&#xff0c;然后剩余51K。 再对请求的15K进行分配&…

自适应滤波器更新算法-EP1

自适应滤波器更新算法-EP1 自适应滤波器是回声消除系统中非常重要的一个功能模块&#xff0c;而对于自适应滤波器来说&#xff0c;如果更新滤波器系数则是关键所在。本文将介绍几种现有的滤波器更新算法&#xff0c;并附上Matlab测试代码。 1、LMS算法 1.1算法原理 LMS算法即…

自适应神经网络算法原理,单神经元自适应控制

关于神经网络自适应控制求助 这句话你可以直接用&#xff0c;不用加引用。因为这句话是很容易验证的。在网络层数、隐含层节点数逐渐增加&#xff0c;训练次数增加之后&#xff0c;他的拟合能力也是不断增加的&#xff0c;所以说&#xff0c;他可以以任意精度逼近任何非线性连…

【转载】梯度下降算法的参数更新公式

NN这块的公式&#xff0c;前馈网络是矩阵乘法。损失函数的定义也是一定的。 但是如何更新参数看了不少描述&#xff0c;下面的叙述比较易懂的&#xff1a; 1、在吴恩达的CS229的讲义的第四页直接给出参数迭代公式 在UFLDL中反向传导算法一节也是直接给出的公式 2、例子&#x…

Java中Comparator的个人简单理解(升序降序)与使用

目录 Java自定义排序返回值简单记忆理解实践LInkedList升序&#xff08;默认情况&#xff09;降序 PriorityQueue升序下的小顶堆&#xff08;默认情况&#xff09;降序下的大顶堆 总结补充数组类型自定义排序降序排序 数组 Java自定义排序返回值简单记忆理解 默认情况下&#…

深度残差收缩网络(从信号降噪的角度进行理解)

本文探讨了深度残差收缩网络的另一种理解方式。 传统信号降噪算法的常见步骤是&#xff1a; ① 采用某种信号变换方法&#xff08;例如小波、经验模态分解&#xff09;&#xff0c;将含噪信号变换到另外一种形态&#xff08;例如小波系数、本征模态分量等&#xff09;。在这些…

NIPS 2016 深度学习 迁移学习 ---残差转移网络用于无监督领域自适应

深度学习的成功得益于大量的标注数据&#xff0c;而数据标注是非常消耗资源的。当一个问题中缺少标注数据时&#xff0c;可以从另一个源中所学知识迁移过来&#xff0c;并且用于新问题中。 清华大学的学者提出了一种新的方法&#xff08;https://arxiv.org/pdf/1602.04433.pdf&…

深度残差网络+自适应参数化ReLU激活函数(调参记录21)Cifar10~95.12%

本文在调参记录20的基础上,将残差模块的个数,从27个增加到60个,继续测试深度残差网络ResNet+自适应参数化ReLU激活函数在Cifar10数据集上的表现。 自适应参数化ReLU函数被放在了残差模块的第二个卷积层之后,这与Squeeze-and-Excitation Networks或者深度残差收缩网络是相似…

已知两个长度分别为m 和 n 的升序链表,合并降序链表,求时间复杂度

王道数据结构上一道题&#xff1a; 之前我看到一个电子版的书&#xff0c;上边答案解析写的有点错误&#xff0c; 听说有些同学买的实体书上&#xff0c;答案解析也是这样写的 这个是刊印错误&#xff0c;很显然2Max( m , n )大于等于 m n 而且这个解析也不够清晰。 解析&a…

波束形成 常见自适应波束形成算法信(干)噪比增益影响因素

0、其他补充 均匀线阵波束形成器的信噪比增益上确界可由下式表示&#xff1a; 其中为阵元数&#xff0c;所以为了方便起见&#xff0c;一般的稳健自适应波束形成算法在仿真过程中的阵元数量均设置为10。 阵列的导向矢量可由下式表示&#xff1a; 以首个阵元为参考阵元&#xff…

两个升序链表合并成一个降序链表的时间复杂度

王道考研P7 第六题 【2013年统考真题】已知两个长度分别为m和n的升序链表&#xff0c;若将它们合并为长度为mn的一个降序链表&#xff0c;则最坏情况下的时间复杂度是&#xff08;&#xff09; A. O(n) B. O(mn) C. O(min(m,n)) D. O(max(m,n)) 答案是D 注意&#xff0c;此题…

无线传感器网络路由优化中的能量均衡LEACH改进算法

文章目录 一、理论基础1、LEACH算法概述2、改进的LEACH算法 二、算法流程图三、仿真实验与分析四、参考文献 一、理论基础 1、LEACH算法概述 请参考这里。 2、改进的LEACH算法 改进的LEACH算法&#xff08;LEACH-N&#xff09;主要针对LEACH算法分簇阶段的缺陷而改进的&…