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 相关文章推荐
Extjs学习笔记之八 继承和事件基础
Jan 08 Javascript
各情景下元素宽高的获取实现代码
Sep 13 Javascript
jQuery EasyUI API 中文文档 - Menu菜单
Oct 03 Javascript
原始的js代码和jquery对比体会
Sep 10 Javascript
jQuery实现的导航条切换可显示隐藏
Oct 22 Javascript
JavaScript快速切换繁体中文和简体中文的方法及网站支持简繁体切换的绝招
Mar 07 Javascript
js实现百度登录框鼠标拖拽效果
Mar 07 Javascript
AngularJS 教程及实例代码
Oct 23 Javascript
通过jquery获取上传文件名称、类型和大小的实现代码
Apr 19 jQuery
详解JavaScript中的函数、对象
Apr 01 Javascript
使用VScode 插件debugger for chrome 调试react源码的方法
Sep 13 Javascript
vue项目中使用bpmn为节点添加颜色的方法
Apr 30 Javascript
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
PHP3 safe_mode 失效漏洞
2006/10/09 PHP
用Php编写注册后Email激活验证的实例代码
2013/03/11 PHP
THINKPHP截取中文字符串函数实例代码
2017/03/20 PHP
PHP与SQL语句写一句话木马总结
2019/10/11 PHP
jQuery 1.2.x 升? 1.3.x 注意事项
2009/05/06 Javascript
用javascript关闭本窗口技巧小结
2014/09/05 Javascript
DOM基础教程之使用DOM + Css
2015/01/20 Javascript
JAVASCRIPT代码编写俄罗斯方块网页版
2015/11/26 Javascript
jQuery 的 ready()的纯js替代方法
2016/11/20 Javascript
移动开发之自适应手机屏幕宽度
2016/11/23 Javascript
开源免费天气预报接口API及全国所有地区代码(国家气象局提供)
2016/12/26 Javascript
javascript中BOM基础知识总结
2017/02/14 Javascript
vue-cli webpack2项目打包优化分享
2018/02/07 Javascript
angularjs中$http异步上传Excel文件方法
2018/02/23 Javascript
vue在index.html中引入静态文件不生效问题及解决方法
2019/04/29 Javascript
layui按条件隐藏表格列的实例
2019/09/19 Javascript
解决Vue项目打包后打开index.html页面显示空白以及图片路径错误的问题
2019/10/25 Javascript
解决node终端下运行js文件不支持ES6语法
2020/04/04 Javascript
JavaScript中展开运算符及应用的实例代码
2021/01/14 Javascript
[01:18:35]DOTA2-DPC中国联赛 正赛 Elephant vs LBZS BO3 第一场 1月29日
2021/03/11 DOTA
CentOS下使用yum安装python-pip失败的完美解决方法
2017/08/16 Python
Python设计模式之工厂模式简单示例
2018/01/09 Python
python 获取当天每个准点时间戳的实例
2018/05/22 Python
python读取excel指定列数据并写入到新的excel方法
2018/07/10 Python
python删除字符串中指定字符的方法
2018/08/13 Python
使用Python开发SQLite代理服务器的方法
2018/12/07 Python
讲解Python3中NumPy数组寻找特定元素下标的两种方法
2019/08/04 Python
NEW LOOK官网:英国时装零售巨头之一,快时尚品牌
2017/01/11 全球购物
暑期实践思想汇报
2014/01/06 职场文书
公司的门卫岗位职责
2014/09/09 职场文书
中国文明网向国旗敬礼寄语大全
2014/09/27 职场文书
优秀党员个人总结
2015/02/14 职场文书
优秀团员个人总结
2015/02/26 职场文书
2015年大学迎新晚会总结
2015/07/16 职场文书
详细聊聊关于Mysql联合查询的那些事儿
2021/10/24 MySQL
游戏《东方异文石:爱亚利亚黎明》正式版发布
2022/04/03 其他游戏