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 相关文章推荐
Javascript 跨域访问解决方案
Feb 14 Javascript
document.getElementById为空或不是对象的解决方法
Jan 24 Javascript
jQuery学习笔记 操作jQuery对象 CSS处理
Sep 19 Javascript
JavaScript对表格或元素按文本,数字或日期排序的方法
May 26 Javascript
关于List.ToArray()方法的效率测试
Sep 30 Javascript
react native实现往服务器上传网络图片的实例
Aug 07 Javascript
JS实现统计字符串中字符出现个数及最大个数功能示例
Jun 04 Javascript
js jquery 获取某一元素到浏览器顶端的距离实现方法
Sep 05 jQuery
微信小程序自定义轮播图
Nov 04 Javascript
vue实现绑定事件的方法实例代码详解
Jun 20 Javascript
Vue的全局过滤器和私有过滤器的实现
Apr 20 Javascript
原生js实现购物车
Sep 23 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
PHPnow安装服务[apache_pn]失败的问题的解决方法
2010/09/10 PHP
PHP调试函数和日志记录函数分享
2015/01/31 PHP
javascript之querySelector和querySelectorAll使用说明
2011/10/09 Javascript
Jquery增加鼠标中间功能mousewheel的实例代码
2013/09/05 Javascript
jquery限定文本框只能输入数字即整数和小数
2013/11/29 Javascript
yepnope.js使用详解及示例分享
2014/06/23 Javascript
jquery实现简洁文件上传表单样式
2015/11/02 Javascript
JavaScript设计模式初探
2016/01/07 Javascript
javascript实现移动端上的触屏拖拽功能
2016/03/04 Javascript
JavaScript中利用jQuery绑定事件的几种方式小结
2016/03/06 Javascript
很实用的js选项卡切换效果
2016/08/12 Javascript
AngularJS教程之环境设置
2016/08/16 Javascript
NodeJs下的测试框架Mocha的简单介绍
2017/02/22 NodeJs
Bootstrap DateTime Picker日历控件简单应用
2017/03/25 Javascript
AngularJS实用基础知识_入门必备篇(推荐)
2017/07/10 Javascript
webpack构建的详细流程探底
2018/01/08 Javascript
vue+php实现的微博留言功能示例
2019/03/16 Javascript
JS中间件设计模式的深入探讨与实例分析
2020/04/11 Javascript
react-native 实现购物车滑动删除效果的示例代码
2021/01/15 Javascript
[03:52]显微镜下的DOTA2第三期——英雄在无聊的时候干什么
2014/06/20 DOTA
[00:35]TI7不朽珍藏III——寒冰飞龙不朽展示
2017/07/15 DOTA
python技能之数据导出excel的实例代码
2017/08/11 Python
基于numpy中数组元素的切片复制方法
2018/11/15 Python
pytorch 使用单个GPU与多个GPU进行训练与测试的方法
2019/08/19 Python
使用pandas 将DataFrame转化成dict
2019/12/10 Python
pytorch 实现tensor与numpy数组转换
2019/12/27 Python
Python爬虫爬取糗事百科段子实例分享
2020/07/31 Python
俄罗斯电动工具和设备购物网站:Vseinstrumenti.ru
2020/11/12 全球购物
毕业实习评语
2014/02/10 职场文书
《台湾的蝴蝶谷》教学反思
2014/02/20 职场文书
《愚公移山》教学反思
2014/02/20 职场文书
简单的离婚协议书范本
2014/11/16 职场文书
2014年体育教学工作总结
2014/12/09 职场文书
企业安全生产规章制度
2015/08/06 职场文书
python基于tkinter制作无损音乐下载工具
2021/03/29 Python
Nginx隐藏式跳转(浏览器URL跳转后保持不变)
2022/04/07 Servers