采集PCM,将base64片段转换为wav音频文件

需求

开始录音——监听录音数据——结束录音

在监听录音数据过程中:客户端每100ms给前端传输一次数据(pcm数据转成base64),前端需要将base64片段解码、合并、添加WAV头、转成File、上传到 OSS之后将 url 给到服务端处理。

{numberOfChannels: 1, // 声道数// sampleRate: 16000, // 采样率sampleRate: 44100, // 更改采样率为 44100 HzbitsPerChannel: 16, // 位深format: 'PCM',
}

概念

pcm是原始音频,mac上可以使用audacity软件播放pcm原始音频文件;
👇
base64编码:将二进制编码成文本格式
👇
atob 将二进制转为 unicode 字符序列,charCodeAt 获取每个字符的unicode编码
👇
Uint8Array 是包含8位(一个字节)的无符号整数序列,用于处理二进制数据
👇
ArrayBuffer 在内存中分配一段连续的空间,存储二进制数据,如数字、图像、音频文件等
👇
new Blob([wavHeader, pcmData], { type: ‘audio/wav’ }); 给PCM数据添加wav头信息
👇
Blob 是浏览器内部生成的二进制数据,包括数据和类型信息
👇
File 是 Blob 的子类,除了数据和类型信息,还包括文件名和最后修改时间,通常表示用户从本地文件系统选择的文件

将base64片段转为WAV文件

/*** 将base64片段转为WAV文件* @param base64Segments* @returns*/
export function base64ToAudio(base64Segments) {// 合并PCM数据const pcmData = mergeBase64SegmentsIntoPCM(base64Segments);// 创建WAV头const dataLength = pcmData.length;const wavHeader = createWavHeader(dataLength, 44100);// 合并WAV文件头和PCM数据const blob = new Blob([wavHeader, pcmData], { type: 'audio/wav' });const file = new File([blob], 'output.wav', { type: 'audio/wav' });return file;
}

将一系列Base64编码的音频段合并成一个PCM数据流

/*** 将一系列Base64编码的音频段合并成一个PCM数据流* @param segments 包含Base64编码音频段的数组* @returns*/
function mergeBase64SegmentsIntoPCM(segments) {let mergedData = new Uint8Array();segments.forEach((base64Segment) => {const binarySegment = atob(base64Segment);const binaryArray = new Uint8Array(binarySegment.length);for (let i = 0; i < binarySegment.length; i++) {binaryArray[i] = binarySegment.charCodeAt(i);}mergedData = mergeArrays(mergedData, binaryArray);});// 合并后的PCM数据return mergedData;
}

合并两个TypedArray(类型化数组)


/*** 合并两个TypedArray(类型化数组)* @param segments* @returns*/
function mergeArrays(a, b) {// 类型化数组,确保类型一致const c = new a.constructor(a.length + b.length);// 类型化数组的set方法直接在底层内存中操作,不需要逐个元素拷贝,效率高c.set(a, 0);// 保障合并后的数组在内存中是连续的,提高访问速度c.set(b, a.length);return c;
}

创建一个WAV文件的头部信息

/*** 创建一个WAV文件的头部信息* 包含了RIFF格式标识、文件大小、WAVE标识、格式子块fmt的ID和大小、音频格式、* 声道数、采样率、字节率、块对齐、每样本位数以及数据子块data的ID和大小* @param dataSize 文件大小* @param sampleRate 采样率* @returns*/
function createWavHeader(dataSize, sampleRate) {// 创建一个大小为44字节的ArrayBuffer,用于存储WAV文件头const buffer = new ArrayBuffer(44);// 创建一个DataView,用于操作buffer中的数据const view = new DataView(buffer);view.setUint32(0, 0x52494646, false); // 设置Chunk ID为"RIFF"view.setUint32(4, dataSize + 36, true); // 设置文件大小(不包括前8个字节)view.setUint32(8, 0x57415645, false); // 设置格式标识为"WAVE"view.setUint32(12, 0x666d7420, false); // 设置第一个子块ID为"fmt "view.setUint32(16, 16, true); // 设置第一个子块大小为16字节view.setUint16(20, 1, true); // 设置音频格式为PCM(1表示PCM)view.setUint16(22, 1, true); // 设置声道数(单声道为1)view.setUint32(24, sampleRate, true); // 设置采样率view.setUint32(28, sampleRate * 2, true); // 设置字节率(采样率 * 每帧字节数)view.setUint16(32, 2, true); // 设置每帧字节数(块对齐)view.setUint16(34, 16, true); // 设置每样本位数view.setUint32(36, 0x64617461, false); // 设置第二个子块ID为"data"view.setUint32(40, dataSize, true); // 设置第二个子块大小(即音频数据大小)// 返回填充了WAV文件头信息的bufferreturn buffer;
}

异步获取音频文件的时长

/*** 异步获取音频文件的时长* @param file 音频文件* @returns 返回音频的时长(秒)*/
export const getAudioDuration = async (file) => {try {const audio = new Audio(URL.createObjectURL(file));await new Promise((resolve) => (audio.onloadedmetadata = resolve));const { duration } = audio;return duration;} catch (error) {console.error('获取音频时长时发生错误:', error);return 0;}
};

将文件上传到oss

export const uploadFile = (data: UploadTokenData, file: File) => {console.log('uploadFile开始了', data, '====', file);const bodyFormData = new FormData();const url = `${data.host}/${data.dir}${file.name}`;bodyFormData.append('OSSAccessKeyId', data.accessId);bodyFormData.append('policy', data.policy);bodyFormData.append('signature', data.signature);bodyFormData.append('key', `${data.dir}${file.name}`);bodyFormData.append('dir', data.dir);bodyFormData.append('success_action_status', '200');bodyFormData.append('file', file);console.log('uploadFile上传的url: ', url);return new Promise((resolve, reject) => {const xhr = new XMLHttpRequest();xhr.onerror = function error(e) {console.log('upload error', e);reject(e);};xhr.onload = async () => {// allow success when 2xx status see https://github.com/react-component/upload/issues/34if (xhr.status < 200 || xhr.status >= 300) {reject('上传异常');}console.log('upload success');resolve({...data,ossUrl: url,});};xhr.open('post', data.host, true);xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');xhr.send(bodyFormData);});
};

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

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

相关文章

Redis分布式系统中的主从复制

本篇文章主要对Redis的主从复制进行讲解。主要分析复制的原理&#xff0c;包括:建立复制、全量复制、部分复制、全量复制、心跳检测等。希望本篇文章会对你有所帮助。 文章目录 一、主从复制简介 二、配置主从复制模式 断开主从复制 安全性 只读 传输延迟 三、拓扑结构 四、主…

【git】太大了失败: fatal: fetch-pack: invalid index-pack output

#‘’ Git仓库过大致使clone失败的解决方法 上述大神的方法&#xff0c;亲测有效 中途失败: 太大了 fetch-pack: unexpected disconnect while reading sideband packet fatal: early EOF fatal: fetch-pack: invalid index-pack output关闭压缩 git config --global core.…

如何利用VPN和NAT技术实现高效安全的网络连接

文章目录 **什么是VPN&#xff1f;****专用地址的使用****VPN的工作原理****远程接入VPN****VPN的应用实例****什么是NAT&#xff1f;****NAT的工作原理****NAPT&#xff08;网络地址与端口号转换&#xff09;****NAT的优势和局限****NAT的应用实例****VPN和NAT的结合****常见问…

C++ | Leetcode C++题解之第279题完全平方数

题目&#xff1a; 题解&#xff1a; class Solution { public:// 判断是否为完全平方数bool isPerfectSquare(int x) {int y sqrt(x);return y * y x;}// 判断是否能表示为 4^k*(8m7)bool checkAnswer4(int x) {while (x % 4 0) {x / 4;}return x % 8 7;}int numSquares(i…

详细带你彻底搞懂 Spring Security 6.0 的实现原理

​ 博客主页: 南来_北往 系列专栏&#xff1a;Spring Boot实战 前言 Spring Security 6.0是一个功能强大且可扩展的身份验证和访问控制框架&#xff0c;它用于保护基于Java的应用程序。其主要目标是提供一个全面的安全解决方案&#xff0c;包括身份验证、授权、防止跨站请…

IPv6过渡技术之网络工程师软考中级

IPv6过渡技术 IPv4/IPv6双栈 1.设备支持IPV4/IPv6&#xff0c;IPv4及IPv6在网络中独立部署&#xff0c;在一段时间内并存。对现有IPv4业务影响较小。 2.演进方案相对简单、易理解。网络规划设计工作量相对更少 3.现有软硬件(网络设备、终端、操作系统等)已经有很大一部分支持…

IDEA-安装插件 驼峰下划线转换

第一步&#xff1a;安装 file-settings-plugins-在marketplace搜索“CamelCase”-点击安装 第二步&#xff1a;设置 file-settings-editor-camel_case 第三步&#xff1a;使用 选中想转换的遍历 使用快捷键 Alt Shift U

用excel能做出这些报表吗?

用excel能做出这些报表吗&#xff1f; 有什么办法不安装OFFICE也能显示出来&#xff1f;

SQLLIte [SQLITE_BUSY] The database file is locked (database is locked) 问题

SqlLite是一个嵌入式轻量级文件数据库 侵入式 。 方便&#xff0c;不需要安装数据库&#xff0c;仅需要一个目录轻量级。支持的数据量不大。单文件。有文件锁。标题出现的问题就是触发了锁。 所以&#xff0c;sqlLite的锁是库锁&#xff0c;因为sqlLite的数据库就是一个后缀为…

解释Spring Data中的PagingAndSortingRepository

PagingAndSortingRepository是Spring Data仓库层次结构中的一个接口&#xff0c;它扩展自CrudRepository接口。正如其名称所暗示的&#xff0c;PagingAndSortingRepository为实现分页和排序的数据访问提供了额外的方法。通过使用这个接口&#xff0c;你可以更容易地管理和访问大…

ARM体系结构和接口技术(十)按键中断实验

文章目录 一、按键中断实验&#xff08;一&#xff09;分析按键电路图&#xff08;二&#xff09;芯片手册 二、按键中断实验分析&#xff08;一&#xff09;查看所有外设的总线以及寄存器基地址&#xff08;二&#xff09;RCC章节&#xff08;三&#xff09;GPIO章节&#xff…

Known框架实战演练——进销存业务单据

本文介绍如何实现进销存管理系统的业务单据模块&#xff0c;业务单据模块包括采购进货单、采购退货单、销售出货单、销售退货单4个菜单页面。由于进销单据字段大同小异&#xff0c;因此设计共用一个页面组件类。 项目代码&#xff1a;JxcLite开源地址&#xff1a; https://git…

2024最新Uniapp的H5网页版添加谷歌授权验证

现在教程不少&#xff0c;但是自从谷歌升级验证之后&#xff0c;以前的老教程就失效了&#xff0c;现在写一个新教程以备不时之需。 由于众所周知的特殊原因&#xff0c;开发的时候一定注意网络环境&#xff0c;如果没有梯子是无法进行开发的哦~ clientID的申请方式我就不再进…

HTML开发笔记:3.图形化开发软件与模版网站

一、Google Web Designer 下载网址&#xff1a;webdesigner.withgoogle.com&#xff08;得挂梯子&#xff09; 可以编辑文字 可以插入图片&#xff0c;快捷键是ctrlshiftI 右侧“大纲”属性中可以调节大小 可以点击右上角在浏览器中预览效果&#xff1a; 二、模版网站 https://…

数据库编程中游标 连接 commit 字符集

在数据库编程中&#xff0c;关闭游标和连接是一个重要的步骤&#xff0c;确保资源正确地释放和数据库操作的完整性。 游标&#xff08;Cursor&#xff09;和连接&#xff08;Connection&#xff09;是数据库编程中两个关键但不同的概念。它们在数据库操作中的作用和功能有所不…

Python Flask入门到精通:详细教程和实战案例

前言 Flask是一个轻量级的Web框架&#xff0c;用于快速开发Web应用程序。它的设计理念是简洁、灵活和易于扩展&#xff0c;非常适合于从简单的单页应用到复杂的大型项目。通过Flask&#xff0c;可以创建各种Web应用程序&#xff0c;比如博客、电子商务网站、RESTful API等。 …

紫霞仙子和至尊宝的对白

背景介绍 至尊宝和紫霞仙子是电影《大话西游》中的人物。该电影讲述了至尊宝放弃戴上金箍成为盖世英雄&#xff0c;选择当一个凡人&#xff0c;与‌紫霞仙子长相厮守的故事。这部电影通过奇幻、冒险、喜剧和悲剧的元素&#xff0c;展现了一段跨越时空的爱情故事&#xff0c;充…

ODBC+FreeTDS从Linux访问Windows SqlServer数据库

提示 \color{red}{提示} 提示&#xff1a; 《Linux系统上编译安装FreeTDS库文件》中讲述了如何编译FreeTDS源码&#xff0c;并安装。 本文部分内容会在上述文章的基础上深入。 本文内容所使用的环境 Windows系统&#xff1a;Windows 10 企业版 64位操作系统&#xff1b;IP&a…

WordPress主题追格企业官网主题免费开源版V1.1.6

追格企业官网主题免费开源版由追格开发的一款开源wordpress主题&#xff0c;专为企业建站和追格企业官网小程序&#xff08;开源版&#xff09;PC配套而设计&#xff0c;功能集新闻动态、留言反馈、产品与服务、公司简介、联系我们等模块。

如何通过集成软件授权管理系统推动企业业务增长?

软件货币化已经成为许多企业商业成功的关键&#xff0c;随着全球数字化进程不断深入&#xff0c;其重要性也在不断增加。将许可解决方案优化集成到现有系统中&#xff0c;已成为从接收到订单到交付和激活许可的任何高效流程的基本要素。 软件货币化无处不在 无论是传统的软件企…