java 播放.pcm文件_如何在浏览器中播放pcm音频

本文记录一点工作经历,探讨音频文件的格式

更多访问我的博客

前言

最近在整理音视频编程的知识,回忆起半年多,有一次需求是在后台播放某来源的 pcm 文件,当时处理方法用了点技巧,记录下来

背景:业务需求,在web后台里播放 pcm 文件,文件不大(约300KB,已知 pcm 的参数采样率16000,采样位数16,声道数1

如何播放

浏览器是无法直接播放 pcm 音频的,因为 pcm 是比较原始的音频格式:

PCM(Puls Code Modulation)全称脉码调制录音,PCM录音就是将声音的模拟信号表示成0,1标识的数字信号,未经任何编码和压缩处理,所以可以认为PCM是未经压缩的音频原始格式。PCM格式文件中不包含头部信息,播放器无法知道采样率,声道数,采样位数,音频数据大小等信息,导致无法播放。

如何让浏览器识别 pcm

浏览器可以播放另一种音频格式:WAV格式全称为WAVE,前面提到只需要在PCM文件的前面添加WAV文件头,就可以生成WAV格式文件

所以我的解决方法是给 pcm 添加 wav header,接下来就是 browser javascript 的实践编码了

javascript 如何处理文件流

js 在处理文件流、网络数据,常用到 ArrayBuffer 类型,关于 ArrayBuffer 类型的API调用方法,需要事先多了解。

第一步,ajax异步获取网络 pcm 文件的 ArrayBuffer

const getWebFileArrayBuffer = async (url) => {

return await fetch(url).then(response => response.arrayBuffer())

}

第二步,对获取的 pcm 文件流 ArrayBuffer 添加 wav header,我们先弄明白 header 的构造:

e0b9e2a3dd52b861057eb73dd8ef9c02.gif

看以上图片,我们需要将获取到的 pcm data 添加 44 bytes 的 header,根据 header 的结构,对齐、紧凑地填充信息,在 javascript 中,需要使用 DataView 类型帮助我们进行字节填充的操作,注意DataView提供的 API 默认使用 little end 的数据格式,需要额外定义 big end 格式填充字节的方法。

以下以代码来说明如何一步一步填充字节信息:

const getWebPcm2WavArrayBuffer = async (url) => {

const bytes = await getWebFileArrayBuffer(url)

return addWavHeader(bytes, 16000, 16, 1) // 这里是当前业务需求,特定的参数,采样率16000,采样位数16,声道数1

}

const addWavHeader = function (samples, sampleRateTmp, sampleBits, channelCount) {

let dataLength = samples.byteLength

/* 新的buffer类,预留 44 bytes 的 heaer 空间 */

let buffer = new ArrayBuffer(44 + dataLength)

/* 转为 Dataview, 利用 API 来填充字节 */

let view = new DataView(buffer)

/* 定义一个内部函数,以 big end 数据格式填充字符串至 DataView */

function writeString (view, offset, string) {

for (let i = 0; i < string.length; i++) {

view.setUint8(offset + i, string.charCodeAt(i))

}

}

let offset = 0

/* ChunkID, 4 bytes, 资源交换文件标识符 */

writeString(view, offset, 'RIFF'); offset += 4

/* ChunkSize, 4 bytes, 下个地址开始到文件尾总字节数,即文件大小-8 */

view.setUint32(offset, /* 32 */ 36 + dataLength, true); offset += 4

/* Format, 4 bytes, WAV文件标志 */

writeString(view, offset, 'WAVE'); offset += 4

/* Subchunk1 ID, 4 bytes, 波形格式标志 */

writeString(view, offset, 'fmt '); offset += 4

/* Subchunk1 Size, 4 bytes, 过滤字节,一般为 0x10 = 16 */

view.setUint32(offset, 16, true); offset += 4

/* Audio Format, 2 bytes, 格式类别 (PCM形式采样数据) */

view.setUint16(offset, 1, true); offset += 2

/* Num Channels, 2 bytes, 通道数 */

view.setUint16(offset, channelCount, true); offset += 2

/* SampleRate, 4 bytes, 采样率,每秒样本数,表示每个通道的播放速度 */

view.setUint32(offset, sampleRateTmp, true); offset += 4

/* ByteRate, 4 bytes, 波形数据传输率 (每秒平均字节数) 通道数×每秒数据位数×每样本数据位/8 */

view.setUint32(offset, sampleRateTmp * channelCount * (sampleBits / 8), true); offset += 4

/* BlockAlign, 2 bytes, 快数据调整数 采样一次占用字节数 通道数×每样本的数据位数/8 */

view.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2

/* BitsPerSample, 2 bytes, 每样本数据位数 */

view.setUint16(offset, sampleBits, true); offset += 2

/* Subchunk2 ID, 4 bytes, 数据标识符 */

writeString(view, offset, 'data'); offset += 4

/* Subchunk2 Size, 4 bytes, 采样数据总数,即数据总大小-44 */

view.setUint32(offset, dataLength, true); offset += 4

/* 数据流需要以大端的方式存储,定义不同采样比特的 API */

function floatTo32BitPCM (output, offset, input) {

input = new Int32Array(input)

for (let i = 0; i < input.length; i++, offset += 4) {

output.setInt32(offset, input[i], true)

}

}

function floatTo16BitPCM (output, offset, input) {

input = new Int16Array(input)

for (let i = 0; i < input.length; i++, offset += 2) {

output.setInt16(offset, input[i], true)

}

}

function floatTo8BitPCM (output, offset, input) {

input = new Int8Array(input)

for (let i = 0; i < input.length; i++, offset++) {

output.setInt8(offset, input[i], true)

}

}

if (sampleBits == 16) {

floatTo16BitPCM(view, 44, samples)

} else if (sampleBits == 8) {

floatTo8BitPCM(view, 44, samples)

} else {

floatTo32BitPCM(view, 44, samples)

}

return view.buffer

}

第三步,在浏览器播放 pcm 转出的 wav 的文件流 ArrayBuffer

先转成 base64 格式

const getWebPcm2WavBase64 = async (url) => {

let bytes = await getWebPcm2WavArrayBuffer(url)

return `data:audio/wav;base64,${btoa(new Uint8Array(bytes).reduce((data, byte) => {

return data + String.fromCharCode(byte)

}, ''))}`

}

将 base64 字符串放入组件中,这里以 react/ant design 的组件为例,封装一个方法

const playWebPcm = async (url) => {

try {

let pcmBase64 = await fileServer.getWebPcm2WavBase64(url)

Modal.info({

title: '播放音频',

content: (

),

onOk () {},

okText: '关闭',

})

} catch (err) {

console.error(err)

message.error('预载音频文件失败')

}

}

Reference

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

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

相关文章

mysql 完全备份恢复吗_mysql完全备份与恢复

备份的重要性&#xff1a;在生产环境中我们数据库可能会遭遇各种各样的不测从而导致数据丢失, 大概分为以下几种.硬件故障软件故障自然灾害误操作 (占比最大)备份类型&#xff1a;物理和逻辑角度&#xff1a;物理备份---指对数据库操作系统的物理文件的备份逻辑备份---指对数据…

scala与java的区别_Scala学习笔记及与Java不同之处总结

Scala与Java具有许多相似之处&#xff0c;但又有许多不同。这里主要从一个Java开发人员的角度&#xff0c;总结在使用Scala的过程中所面临的一些思维转变。这里只是总结了部分两种语言在开发过程中的不同&#xff0c;以后会陆续更新一些切换后在开发过程中值得注意的地方。下面…

centos6.5搭建mysql主从_mysql 主从配置,主-》windows,从-》centos6.5

1.虚拟机配置的主从关系。win7 ip地址192.168.52.102&#xff0c;虚拟机ip 192.168.184.128。docs进入主服务器(master)mysql目录下&#xff0c;添加用户&#xff0c;然后执行mysql>create user dba192.168.184.128 identified by 123456 &#xff0c;然后grant replication…

java 定时关机_「window定时关机命令」电脑定时关机命令,这个方法适用于win7及以下系统 - seo实验室...

window定时关机命令上班族朋友们有没有碰到一种情况&#xff1a;临时需要外出&#xff0c;又不确定是否还要回来&#xff0c;因此办公室的电脑关与不关&#xff0c;小小的纠结了一下。以下方法或者可以帮你化解这个小纠结。定时关机且可以回来时取消(本法适用于 win7及以下系统…

彩票模拟选号程序java_java课程设计-彩票购买抽奖程序

彩票抽奖程序[TOC]###可实现功能&#xff1a;允许注册用户&#xff0c;用户信息包括用户id,用户名&#xff0c;密码&#xff0c;账户金额&#xff0c;电话号码等属性。允许注册用户购买彩票&#xff1a;手动选号、随机选号&#xff0c;并允许设置投注数。抽奖功能&#xff1a;要…

java给你的初步印象_Java之初印象

Java语言的跨平台性:Java语言的编程过程:只要实现了特定平台下的解释器程序(JVM),Java字节码就能通过解释器程序在该平台下运行,这是java跨平台的根本,当前并不是在所有平台下都有相应的Java解释器程序,这也是Java并不是在所有平台下都能运行的原因,它只能在实现了Java解释器程…

php js跨域上传文件,Jquery实现跨域异步上传文件步骤详解

这次给大家带来Jquery实现跨域异步上传文件步骤详解&#xff0c;Jquery实现跨域异步上传文件的注意事项有哪些&#xff0c;下面就是实战案例&#xff0c;一起来看一下。先说明白这个跨域异步上传功能我们借助了Jquery.form插件&#xff0c;它在异步表单方面很有成效&#xff0c…

php js offset,获取元素的偏移量offset实例详解

问题&#xff1a;如果获取元素距离文档顶部的距离?[javascript] view plain copyvar rect$(#elem)[0].getBoundingClientRect();//获取元素距离文档顶部的距离var toprect.top(window.pageYOffset||document.documentElement.scrollTop)-(document.documentElement.clientTop|…

php 删除上传文件,php实现文件上传、下载和删除的方法

这篇文章主要为大家详细介绍了php文件上传、下载和删除示例,具有一定的参考价值&#xff0c;感兴趣的小伙伴们可以参考一下php文件上传、下载和删除示例大体思路如下&#xff0c;具体内容如下一.文件上传1.把上传文件的区域做出来p12.把显示文件的区域做出来p23.提交表单&#…

软件工程详细设计说明书_软件工程导论知识点梳理之简答题

1. 软件危机的表现形式对软件开发成本和进度估计不准确已完成的软件不符合用户需求软件产品质量差&#xff0c;可靠性得不到保证软件产品可维护性差软件成本在计算机总成本中的比例逐渐变大软件开发生产率提高速度比不上计算机应用速度2. 产生软件危机的原因(1)软件是计算机系统…

php模板意思,php中的 是什么意思

php调用类的内部静态成员&#xff0c;或者是类之间调用就要用两个冒号(::)。说明&#xff1a;“::”符号可以认为是与C语言中的“.”相似的&#xff0c;而它更像C中(Perl)的::类范围操作符。示例&#xff1a;{$0;(){//}(){();$;}};/*C语言中的*/a::b::c();//C中的函数$a::b::c;…

程序员为什么老得快_这段 Python 代码让程序员赚 300W,公司已确认!网友:神操作!...

点击上方“Python大本营”&#xff0c;选择“置顶公众号”python大本营 IT人的职业提升平台Python到底还能给人多少惊喜&#xff1f;笔者最近看到了这两天关于Python最热门的话题&#xff0c;关于《地产大佬潘石屹学Python的原因》&#xff0c;结果被这个回答惊到了&#xff1…

Mercedes-Benz won’t start| Step by Step Troubleshooting Guide

Mercedes won’t start or turn over? Are you experiencing Mercedes-Benz no start problems? Key won’t turn at all? Engine turning over but the car will not start? Maybe it finally starts, runs for a few seconds and then dies. These are common Mercedes-…

php如何设置页面布局,excel页面布局怎么调整

excel页面布局调整的方法&#xff1a;首先点击菜单的页面布局&#xff0c;选择纸张大小&#xff1b;然后点击“纸张方向”&#xff0c;单击以选择横向或者纵向&#xff1b;最后点击“页边距”即可。点击菜单——页面布局&#xff0c;工具栏将出现页面布局的许多项目&#xff0c…

无法获取未定义或 null 引用的属性“value”_SpringBoot之Spring@Value属性注入使用详解

在使用Spring框架的项目中&#xff0c;Value是使用比较频繁的注解之一&#xff0c;它的作用是将配置文件中key对应的值赋值给它标注的属性。在日常使用中我们常用的功能都比较简单&#xff0c;本篇文章系统的带大家来了解一下Value的使用方法。Value注入支持形式Value属性注入功…

0x11 栈

【例题】Push,Pop,GetMin 手写一个栈 1 #include <iostream>2 #include <cstdio>3 #include <cmath>4 #include <cstring>5 #include <algorithm>6 #include <queue>7 using namespace std;8 const int maxn1000000;9 int stack[maxn], m[…

java 同类型转换失败,你们见过java类型转换,自己转自己失败的情况吗?很神奇的操作...

问题就是上面这个问题。List slaughterProducts slaughterForm.getSlaughterProductModelForm();for (SlaughterProductModelForm e : slaughterProducts) {....}居然运行到for的时候出现上面这个错误。很神奇吧&#xff0c;工作这么多年了第一次发现 JAVA自己转自己转不成功。…

用户管理与文件权限

一&#xff1a;用户管理 现代操作系统一般属于多用户的操作系统&#xff0c;也就是说&#xff0c;同一台机器可以为多个用户建立账户&#xff0c;一般这些用户都是为普通用户&#xff0c;这些普通用户能同时登录这台计算机&#xff0c;计算机对这些用户分配一定的资源。 普通用…

php中划线,html中下划线、删除线、上划线的样式与用法实例

这篇文章主要介绍了下划线、删除线、上划线等常用的实例&#xff0c;划线是非常常见的一种样式&#xff0c;为了网页中的视觉效果以及对文字的说明&#xff0c;我们经常对文体进行一些划线操作。下面文章就是对各种划线的详细介绍。一. 下划线的详细介绍在我们日常的Web的开发中…

php获取页面中的指定内容,php 获取页面中指定内容的实现类

[email protected]image&#xff1a;Grep.class.php/** grep class* Date: 2013-06-15* Author: fdipzone* Ver: 1.0** Func:** set: 设置内容* get: 返回指定的内容* replace: 返回替换后的内容* get_pattern 根据type返回pattern*/class Grep{ // class startprivate $_patte…