当前位置: 首页 > news >正文

采集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);});
};

相关文章:

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

需求 开始录音——监听录音数据——结束录音 在监听录音数据过程中&#xff1a;客户端每100ms给前端传输一次数据&#xff08;pcm数据转成base64&#xff09;&#xff0c;前端需要将base64片段解码、合并、添加WAV头、转成File、上传到 OSS之后将 url 给到服务端处理。 {num…...

eclipse ui bug

eclipse ui bug界面缺陷&#xff0c;可能项目过多&#xff0c;特别maven项目过多&#xff0c;下载&#xff0c;自动编译&#xff0c;加载更新界面异常 所有窗口死活Restore不回去了 1&#xff09;尝试创建项目&#xff0c;还原界面&#xff0c;失败 2&#xff09;关闭所有窗口&…...

前端获取blob文件格式的两种格式

第一种,后台传递给前台是base64格式的JSON数据 这时候前台拿到base64格式的数据可以通过内置的atob解码方法结合new Uint8Array和new Blob方法转换成blob类型的数据格式,然后可以使用blob数据格式进行操作,虽然base64转换成blob要经过很多步骤,但幸运的是这些步骤都是固定的,因…...

向日葵RCE复现(CNVD-2022-10270/CNVD-2022-03672)

一、环境 1.1 网上下载低版本的向日葵<2022 二、开始复现 2.1 在目标主机上打开旧版向日葵 2.2 首先打开nmap扫描向日葵主机端口 2.3 在浏览器中访问ip端口号cgi-bin/rpc?actionverify-haras &#xff08;端口号&#xff1a;每一个都尝试&#xff0c;直到获取到session值…...

Postman中的负载均衡测试:确保API的高可用性

Postman中的负载均衡测试&#xff1a;确保API的高可用性 在微服务架构和分布式系统中&#xff0c;API的负载均衡是确保系统高可用性和可扩展性的关键技术之一。Postman作为一个多功能的API开发和测试平台&#xff0c;提供了多种工具来帮助测试人员模拟高负载情况下的API表现。…...

anaconda+tensorflow+keras+jupyter notebook搭建过程(CPU版)

AnacondaTensorFlowKeras 环境搭建教程...

LitCTF2024赛后web复现

复现要求&#xff1a;看wp做一遍&#xff0c;自己做一遍&#xff0c;第二天再做一遍。&#xff08;一眼看出来就跳过&#xff09; 目录 [LitCTF 2024]浏览器也能套娃&#xff1f; [LitCTF 2024]一个....池子&#xff1f; [LitCTF 2024]高亮主题(划掉)背景查看器 [LitCTF 2…...

Elasticsearch:跨集群使用 ES|QL

警告&#xff1a;ES|QL 的跨集群搜索目前处于技术预览阶段&#xff0c;可能会在未来版本中更改或删除。Elastic 将努力解决任何问题&#xff0c;但技术预览中的功能不受官方 GA 功能的支持 SLA 约束。 使用 ES|QL&#xff0c;你可以跨多个集群执行单个查询。 前提&#xff1a; …...

学习笔记4:docker和k8s选择简述

docker和 k8s 占用资源 使用客户体量Docker 和 Kubernetes&#xff08;K8s&#xff09;都是流行的容器化技术&#xff0c;但它们在资源管理和使用上有一些不同。以下是关于两者资源占用和使用客户体量的详细比较&#xff0c;基于具体数据和信息&#xff1a; Docker 资源占用…...

关于锁策略

在Java中对于多线程来说&#xff0c;锁是一种重要且必不可少的东西&#xff0c;那么我们将如何使用以及在什么时候使用什么样的锁呢&#xff1f;请各位往下看 悲观锁VS乐观锁 悲观锁&#xff1a; 在多线程环境中&#xff0c;冲突是非常常见的&#xff0c;所以在执行操作之前…...

昇思25天学习打卡营第3天|基础知识-数据集Dataset

目录 环境 环境 导包 数据集加载 数据集迭代 数据集常用操作 shuffle map batch 自定义数据集 可随机访问数据集 可迭代数据集 生成器 MindSpore提供基于Pipeline的数据引擎&#xff0c;通过数据集&#xff08;Dataset&#xff09;和数据变换&#xff08;Transfor…...

C++11新特性——智能指针——参考bibi《 原子之音》的视频以及ChatGpt

智能指针 一、内存泄露1.1 内存泄露常见原因1.2 如何避免内存泄露 二、实例Demo2.1 文件结构2.2 Dog.h2.3 Dog.cpp2.3 mian.cpp 三、独占式智能指针:unique _ptr3.1 创建方式3.1.1 ⭐从原始(裸)指针转换&#xff1a;3.1.2 ⭐⭐使用 new 关键字直接创建&#xff1a;3.1.3 ⭐⭐⭐…...

“微软蓝屏”全球宕机,敲响基础软件自主可控警钟

上周五&#xff0c;“微软蓝屏”“感谢微软 喜提假期”等词条冲上热搜&#xff0c;全球百万打工人受此影响&#xff0c;共同见证这一历史性事件。据微软方面发布消息称&#xff0c;旗下Microsoft 365系列服务出现访问中断。随后在全球范围内&#xff0c;包括企业、政府、个人在…...

【Linux C | 网络编程】进程间传递文件描述符socketpair、sendmsg、recvmsg详解

我们的目的是&#xff0c;实现进程间传递文件描述符&#xff0c;是指 A进程打开文件fileA,获得文件描述符为fdA&#xff0c;现在 A进程要通过某种方法&#xff0c;传递fdA&#xff0c;使得另一个进程B&#xff0c;获得一个新的文件描述符fdB&#xff0c;这个fdB在进程B中的作用…...

高并发内存池(六)Page Cache回收功能的实现

当Page Cache接收了一个来自Central Cache的Span&#xff0c;根据Span的起始页的_pageId来对前一页所对应的Span进行查找&#xff0c;并判断该Span&#xff0c;是否处于使用状态&#xff0c;从而看是否可以合并&#xff0c;如果可以合并继续向前寻找。 当该Span前的空闲Span查…...

浅析JWT原理及牛客出现过的相关面试题

原文链接&#xff1a;https://kixuan.github.io/posts/f568/ 对jwt总是一知半解&#xff0c;而且项目打算写个关于JWT登录的点&#xff0c;所以总结关于JWT的知识及网上面试考察过的点 参考资料&#xff1a; Cookie、Session、Token、JWT_通俗地讲就是验证当前用户的身份,证明-…...

Spring AI (五) Message 消息

5.Message 消息 在Spring AI提供的接口中&#xff0c;每条信息的角色总共分为三类&#xff1a; SystemMessage&#xff1a;系统限制信息&#xff0c;这种信息在对话中的权重很大&#xff0c;AI会优先依据SystemMessage里的内容进行回复&#xff1b; UserMessage&#xff1a;用…...

【windows Docker desktop】在git bash中报错 docker: command not found 解决办法

【windows Docker desktop】在git bash中报错 docker: command not found 解决办法 1. 首先检查在windows中环境变量是否设置成功2. 检查docker在git bash中环境变量是否配置3. 重新加载终端配置4. 最后在校验一下是否配置成功 1. 首先检查在windows中环境变量是否设置成功 启…...

02.FreeRTOS的移植

文章目录 FreeRTOS移植到STM32F103ZET6上的详细步骤1. 移植前的准备工作2. 添加FreeRTOS文件3. 修改SYSTEM文件4. 修改中断相关文件5. 修改FreeRTOSConfig.h文件6. 可选步骤 FreeRTOS移植到STM32F103ZET6上的详细步骤 1. 移植前的准备工作 **基础工程&#xff1a;**内存管理部…...

【个人笔记】一个例子理解工厂模式

工厂模式优点&#xff1a;创建时类名过长或者参数过多或者创建很麻烦等情况时用&#xff0c;可以减少重复代码&#xff0c;简化对象的创建过程&#xff0c;避免暴露创建逻辑&#xff0c;也适用于需要统一管理所有创建对象的情况&#xff0c;比如线程池的工厂类Executors 简单工…...

pam_env.so模块配置解析

在PAM&#xff08;Pluggable Authentication Modules&#xff09;配置中&#xff0c; /etc/pam.d/su 文件相关配置含义如下&#xff1a; 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块&#xff0c;负责验证用户身份&am…...

1688商品列表API与其他数据源的对接思路

将1688商品列表API与其他数据源对接时&#xff0c;需结合业务场景设计数据流转链路&#xff0c;重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点&#xff1a; 一、核心对接场景与目标 商品数据同步 场景&#xff1a;将1688商品信息…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

Module Federation 和 Native Federation 的比较

前言 Module Federation 是 Webpack 5 引入的微前端架构方案&#xff0c;允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

稳定币的深度剖析与展望

一、引言 在当今数字化浪潮席卷全球的时代&#xff0c;加密货币作为一种新兴的金融现象&#xff0c;正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而&#xff0c;加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下&#xff0c;稳定…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

iview框架主题色的应用

1.下载 less要使用3.0.0以下的版本 npm install less2.7.3 npm install less-loader4.0.52./src/config/theme.js文件 module.exports {yellow: {theme-color: #FDCE04},blue: {theme-color: #547CE7} }在sass中使用theme配置的颜色主题&#xff0c;无需引入&#xff0c;直接可…...

MySQL:分区的基本使用

目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区&#xff08;Partitioning&#xff09;是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分&#xff08;分区&#xff09;可以独立存储、管理和优化&#xff0c;…...

Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案

在大数据时代&#xff0c;海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构&#xff0c;在处理大规模数据抓取任务时展现出强大的能力。然而&#xff0c;随着业务规模的不断扩大和数据抓取需求的日益复杂&#xff0c;传统…...