JS实现audio音频剪裁剪切复制播放与上传(步骤详解)


Posted in Javascript onJuly 28, 2020

JS实现audio音频剪裁剪切复制播放与上传(步骤详解) 

背景是这样的,用户上传音频文件,可能只需要几十秒就够了,但是常规的音乐都要3~5分钟,80%的流量都是不需要的,要是就这么传上去,其实是流量的浪费,如果可以在前端就进行剪裁,也就是只取前面一段时间的音频,岂不是可以给公司省很多流量费用,前端的业务价值就体现了。

关键如何实现呢?

下面,就以“截取用户上传音频前3秒内容”的需求示意下如何借助Web Audio API实现音频的部分复制与播放功能。

一、不哔哔,直接正题

实现步骤如下。

1. File对象转ArrayBuffer

在Web网页中,用户选择的文件是个file对象,我们可以将这个文件对象转换成Blob、ArrayBuffer或者Base64。

在音频处理这里,都是使用ArrayBuffer这个数据类型。

代码如下所示,假设file类型的文件选择框的id是 'file'

file.onchange = function (event) {
 var file = event.target.files[0];
 // 开始识别
 var reader = new FileReader();
 reader.onload = function (event) {
 var arrBuffer = event.target.result;
 // arrBuffer就是包含音频数据的ArrayBuffer对象
 });
 reader.readAsArrayBuffer(file);
};

使用的是 readAsArrayBuffer() 方法,无论是MP3格式、OGG格式还是WAV格式,都可以转换成ArrayBuffer类型。

2. ArrayBuffer转AudioBuffer

这里的ArrayBuffer相对于把音频文件数组化了,大家可以理解为把音频文件分解成一段一段的,塞进了一个一个有地址的小屋子里,在计算机领域称为“缓冲区”,就是单词Buffer的意思。

所谓音频的剪裁,其实就是希望可以复制音频前面一段时间的内容。

但是问题来了,ArrayBuffer里面的数据并没有分类,统一分解了,想要准确提取某一截音频数据,提取不出来。

所以,才需要转换成AudioBuffer,纯粹的音频数据,方便提取。

AudioBuffer是一个仅仅包含音频数据的数据对象,是Web Audio API中的一个概念。

既然说到了Web Audio API,那我们就顺便……顺便……,想了想,还是不展开,因为太庞杂了,这Web Audio API至少比Web Animation API复杂了10倍,API之多,体量之大,世间罕见,想要完全吃透了,没有三年五载,啃不下来。

如果大家不是想要立志成为音视频处理专家,仅仅是临时解决一点小毛小病的问题,则不必深入,否则脑坑疼,使用MDN文档中的一些案例东拼西凑,基本的效果也能弄出来。

扯远了,回到这里。

AudioBuffer大家可以理解为音乐数据,那为什么叫AudioBuffer,不叫AudioData呢?

因为Buffer是个专有名词,直译为缓冲区,大家可以理解为高速公路,AudioBuffer处理数据更快,而且还有很多延伸的API,就像是高速公路上的服务区,有吃有喝还有加油的地方。

AudioData一看名字就是乡下土鳖,虽然接地气,但是,处理好几兆的数据的时候,就有些带不动了,就好像骑小电驴,在公速公路和乡道县道没多大区别,但是如果是开跑车,啧啧,乡下路就带不动了。

如何才能转换成AudioBuffer呢?

使用AudioContext对象的 decodeAudioData() 方法,代码如下:

var audioCtx = new AudioContext();

audioCtx.decodeAudioData(arrBuffer, function(audioBuffer) {
 // audioBuffer就是AudioBuffer
});

3. 复制AudioBuffer前3秒数据

AudioBuffer对象是一个音频专用Buffer对象,包含很多音频信息,包括:

duration
numberOfChannels
sampleRate

等。

包括一些音频声道数据处理方法,例如:

getChannelData()
copyFromChannel()
copyToChannel()

文档见这里: https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer

所以,实现的原理很简单,创建一个空的AudioBuffer,复制现有的通道数据前3秒的数据,然后复制的内容写入到这个空的AudioBuffer,于是我们就得到了一个剪裁后的音频Buffer数据了。

代码如下:

// 声道数量和采样率
var channels = audioBuffer.numberOfChannels;
var rate = audioBuffer.sampleRate;

// 截取前3秒
var startOffset = 0;
var endOffset = rate * 3;
// 3秒对应的帧数
var frameCount = endOffset - startOffset;

// 创建同样采用率、同样声道数量,长度是前3秒的空的AudioBuffer
var newAudioBuffer = new AudioContext().createBuffer(channels, endOffset - startOffset, rate);
// 创建临时的Array存放复制的buffer数据
var anotherArray = new Float32Array(frameCount);
// 声道的数据的复制和写入
var offset = 0;
for (var channel = 0; channel < channels; channel++) {
 audioBuffer.copyFromChannel(anotherArray, channel, startOffset);
 newAudioBuffer.copyToChannel(anotherArray, channel, offset);
}

// newAudioBuffer就是全新的复制的3秒长度的AudioBuffer对象

上面JavaScript代码中的变量 newAudioBuffer 就是全新的复制的3秒长度的AudioBuffer对象。

4. 使用newAudioBuffer做点什么?

其实应该是有了AudioBuffer对象后我们可以做点什么。

能做很多事情。

1) 如果希望直接播放

我们可以直接把AudioBuffer的数据作为音频数据进行播放

// 创建AudioBufferSourceNode对象
var source = audioCtx.createBufferSource();
// 设置AudioBufferSourceNode对象的buffer为复制的3秒AudioBuffer对象
source.buffer = newAudioBuffer;
// 这一句是必须的,表示结束,没有这一句没法播放,没有声音
// 这里直接结束,实际上可以对结束做一些特效处理
source.connect(audioCtx.destination);
// 资源开始播放
source.start();

2) 如果希望在<audio>元素中播放

这个还挺麻烦的。

<audio> 的src属性获取音频资源,再进行处理是简单的,网上的案例也很多。

但是,想要处理后的AudioBuffer再变成src让 <audio> 元素播放,嘿嘿,就没那么容易了。

我 (张鑫旭) 找了一圈,没有看到Web Audio API中有专门的“逆转录”方法。

唯一可行的路数就是根据AudioBuffer数据,重新构建原始的音频数据。研究了一番,转成WAV格式相对容易,想要转换成MP3格式比较麻烦,这里有个项目: https://github.com/higuma/mp3-lame-encoder-js 不过自己没验证过,不过看代码量,还挺惊人的。

因此,我们的目标还是转到WAV音频文件生成上吧,下面这段方法是从网上找的AudioBuffer转WAV文件的方法,以Blob数据格式返回。

// Convert AudioBuffer to a Blob using WAVE representation
function bufferToWave(abuffer, len) {
 var numOfChan = abuffer.numberOfChannels,
 length = len * numOfChan * 2 + 44,
 buffer = new ArrayBuffer(length),
 view = new DataView(buffer),
 channels = [], i, sample,
 offset = 0,
 pos = 0;

 // write WAVE header
 // "RIFF"
 setUint32(0x46464952);
 // file length - 8   
 setUint32(length - 8);
 // "WAVE"   
 setUint32(0x45564157);
 // "fmt " chunk
 setUint32(0x20746d66); 
 // length = 16   
 setUint32(16); 
 // PCM (uncompressed)    
 setUint16(1); 
 setUint16(numOfChan);
 setUint32(abuffer.sampleRate);
 // avg. bytes/sec
 setUint32(abuffer.sampleRate * 2 * numOfChan);
 // block-align
 setUint16(numOfChan * 2);
 // 16-bit (hardcoded in this demo)
 setUint16(16);    
 // "data" - chunk
 setUint32(0x61746164); 
 // chunk length   
 setUint32(length - pos - 4);   

 // write interleaved data
 for(i = 0; i < abuffer.numberOfChannels; i++)
 channels.push(abuffer.getChannelData(i));

 while(pos < length) {
  // interleave channels
 for(i = 0; i < numOfChan; i++) {
  // clamp
  sample = Math.max(-1, Math.min(1, channels[i][offset])); 
  // scale to 16-bit signed int
  sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767)|0; 
  // write 16-bit sample
  view.setInt16(pos, sample, true);  
  pos += 2;
 }
 // next source sample
 offset++     
 }

 // create Blob
 return new Blob([buffer], {type: "audio/wav"});

 function setUint16(data) {
 view.setUint16(pos, data, true);
 pos += 2;
 }

 function setUint32(data) {
 view.setUint32(pos, data, true);
 pos += 4;
 }
}

WAV格式的兼容性还是很6的,如下图所示:

JS实现audio音频剪裁剪切复制播放与上传(步骤详解)

凡事支持Web Audio API的浏览器都支持WAV格式,所以,技术上完全可行。

下面这段JS可以得到剪裁后的WAV音频的Blob数据格式:

var blob = bufferToWave(newAudioBuffer, frameCount);

有了Blob数据,接下来事情就简单了。

我们可以直接把Blob数据转换成URL,可以使用 URL.createObjectURL() 生成一个Blob链接。

假设页面上有如下HTML代码:

<audio id="audio" controls=""></audio>

则如下设置,就可以点击上面的 <audio> 元素进行播放了。

audio.src = URL.createObjectURL(blob);

如果要转换成Base64地址,可以这么处理:

var reader2 = new FileReader();
reader2.onload = function(event){
 audio.src = event.target.result;
};
reader2.readAsDataURL(blob);

3) 如果希望上传剪裁的音频

有了Blob数据,上传还不是洒洒水的事情。

可以使用FormData进行传输,例如:

var formData = new FormData();
formData.append('audio', blob);
// 请求走起
var xhr = new XMLHttpRequest();
xhr.open('POST', this.cgiGetImg, true);
// 请求成功
xhr.onload = function () {
};
// 发送数据
xhr.send(formData);

有demo可以进行效果体验的,您可以狠狠地点击这里: 用户上传的MP3音频剪裁并播放demo

使用截图示意如下:

JS实现audio音频剪裁剪切复制播放与上传(步骤详解)

本文地址: https://www.zhangxinxu.com/wordpress/?p=9505

到此这篇关于JS实现audio音频剪裁剪切复制播放与上传的文章就介绍到这了,更多相关js audio音频剪裁内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
js select常用操作控制代码
Mar 16 Javascript
jQuery中的ready函数与window.onload谁先执行
Jun 21 Javascript
AngularJS验证信息框架的封装插件用法【w5cValidator扩展插件】
Nov 03 Javascript
详解网站中图片日常使用以及优化手法
Jan 09 Javascript
基于Vue2.0的分页组件
Mar 16 Javascript
jQuery实现点击下拉框中的值累加到文本框中的方法示例
Oct 28 jQuery
npm 下载指定版本的组件方法
May 17 Javascript
vue mounted组件的使用
Jun 18 Javascript
在Create React App中启用Sass和Less的方法示例
Jan 16 Javascript
vue实现标签云效果的方法详解
Aug 28 Javascript
js实现拖拽元素选择和删除
Aug 25 Javascript
vue点击弹窗自动触发点击事件的解决办法(模拟场景)
May 25 Vue.js
JavaScript中window和document用法详解
Jul 28 #Javascript
vue中echarts引入中国地图的案例
Jul 28 #Javascript
vue 监听窗口变化对页面部分元素重新渲染操作
Jul 28 #Javascript
JavaScript文档加载模式以及元素获取
Jul 28 #Javascript
javascript实现贪吃蛇小游戏
Jul 28 #Javascript
Element DateTimePicker日期时间选择器的使用示例
Jul 27 #Javascript
Js图片点击切换轮播实现代码
Jul 27 #Javascript
You might like
非常实用的php验证码类
2016/05/15 PHP
详解PHP swoole process的使用方法
2017/08/26 PHP
Linux下源码包安装Swoole及基本使用操作图文详解
2019/04/02 PHP
JavaScript 学习 - 提高篇
2007/02/02 Javascript
当前流行的JavaScript代码风格指南
2014/09/10 Javascript
jQuery模拟新浪微博首页滚动效果的方法
2015/03/11 Javascript
JS实现的网页背景闪电闪烁效果代码
2015/10/17 Javascript
jQuery实现的左右移动焦点图效果
2016/01/14 Javascript
jQuery Ajax使用FormData对象上传文件的方法
2016/09/07 Javascript
最原始的jQuery注册验证方式
2016/10/11 Javascript
微信小程序 websocket 实现SpringMVC+Spring+Mybatis
2017/08/04 Javascript
react中的ajax封装实例详解
2017/10/17 Javascript
vue实现类似淘宝商品评价页面星级评价及上传多张图片功能
2018/10/29 Javascript
Vue侦测相关api的实现方法
2019/05/22 Javascript
koa2 从入门到精通(小结)
2019/07/23 Javascript
微信小程序返回上一级页面的实现代码
2020/06/19 Javascript
Python+Selenium+PIL+Tesseract自动识别验证码进行一键登录
2017/09/20 Python
selenium+python 去除启动的黑色cmd窗口方法
2018/05/22 Python
python中利用h5py模块读取h5文件中的主键方法
2018/06/05 Python
python计算列表内各元素的个数实例
2018/06/29 Python
PyCharm鼠标右键不显示Run unittest的解决方法
2018/11/30 Python
PyCharm配置mongo插件的方法
2018/11/30 Python
Python的条件表达式和lambda表达式实例
2019/01/31 Python
Python Pandas分组聚合的实现方法
2019/07/02 Python
简单了解python反射机制的一些知识
2019/07/13 Python
python命名空间(namespace)简单介绍
2019/08/10 Python
Canvas引入跨域的图片导致toDataURL()报错的问题的解决
2018/09/19 HTML / CSS
世界上最伟大的马产品:Equiderma
2020/01/07 全球购物
捷克建筑材料网上商店:DEK.cz
2021/03/06 全球购物
小学教师读书活动总结
2014/07/08 职场文书
公司财务部岗位职责
2015/04/14 职场文书
毕业证明书
2015/06/19 职场文书
严以用权学习心得体会
2016/01/12 职场文书
HTML+CSS 实现顶部导航栏菜单制作
2021/06/03 HTML / CSS
Python max函数中key的用法及原理解析
2021/06/26 Python
mysq启动失败问题及场景分析
2021/07/15 MySQL