web前端-纯前端音频剪辑,vue音频编辑组件

本文包含内容概述:

  • 整理总结的音频相关资料
  • 音频相关jsapi说明及示例
  • vue编写的组件及git项目地址
在线预览:
  • http://123.57.178.145:5007/audio_edit/index.html
    在这里插入图片描述
    在这里插入图片描述

标题音频编码、格式(部分)
PCM编码

无损编码,模拟波形,占用空间大

APE格式

无损压缩

MP3编码、OGG编码

有损压缩,边读边放

MPC编码

更小的体积更好的音质

AAC编码

又称MPEG-4 AAC,即m4a,
最大能容纳48通道的音轨
高压缩比的音频压缩算法,AAC通常压缩比为18:1

WMA格式

用于网络播放。
可以限制播放时间和播放次数甚至于播放的机器等等。
支持流技术,即边读边放。

RA格式

用于网络播放。
可以根据听众的带宽来控制自己的码率,在保证流畅的前提下尽可能提高音质。
支持边读边放,也同样支持使用特殊协议来隐匿文件的真实网络地址,从而实现只在线播放而不提供下载的欣赏方式。

WAV格式

所有的WAV都有一个文件头,这个文件头包含了音频流的编码参数。
常见的都是PCM编码。
但MP3编码也可以运用在WAV中。

剪辑原理(基础方式)
  • 音频数据

采样率:每秒的采样数量。
采样大小:每次采样的采样精度。
通道数:pcm双声道数据规则为左、右声道轮流记录一个采样。

  • 最佳频率44.1kHz

人耳能够感觉到的最高频率为20kHz,
使得离散信号恢复成连续模拟信号不失真至少要让采样频率为 原始信号的2倍
因此要满足人耳的听觉要求,则需要每秒进行40k次采样

  • 加减速

对音频数据以更高的采样率播放,即可实现加速。减速同理。

  • 裁切

根据采样率计算时间对应数据,截取音频数据

  • 拼接

多个音频,转为相同采样率,拼接音频数据

  • 合并

让两个音频文件的音频数据相加即可

JS相关API代码示例
  • 获取audioContext

非js原生,可在浏览器、node.js使用,小程序不可用

let audioContext = new(window.AudioContext || window.webkitAudioContext)();
  • 获取音频arraybuffer
//方法1.get请求
let res = await this.$ajax.request('GET', url, {}, 'arraybuffer')
//ajax发送GET请求responseType:'arraybuffer'

//方法2.input选择文件,FileReader读取文件
inputchange(val){
  var reader = new FileReader();
  reader.onload = (e) => {
    let audiodata={
      name:val.file.name,
      arraybuffer:e.target.result
    }
  };
  reader.readAsArrayBuffer(val.file)
}

//属性
AudioBuffer.sampleRate 只读
//存储在缓存区的PCM数据的采样率:浮点数,单位为 sample/s。
AudioBuffer.length 只读
//返回存储在缓存区的PCM数据的采样帧率:整形。
AudioBuffer.duration 只读
//返回存储在缓存区的PCM数据的时长:双精度型(单位为秒),。
AudioBuffer.numberOfChannels 只读
//返回存储在缓存区的PCM数据的通道数:整形。

//方法
AudioBuffer.getChannelData()
//返回一个 Float32Array,包含了带有频道的PCM数据,由频道参数定义(有0代表第一个频道)
AudioBuffer.copyFromChannel()
//从AudioBuffer的指定频道复制到数组终端。
AudioBuffer.copyToChannel()
//复制样品到原数组的AudioBuffer的指定频道
  • arraybuffer(mp3、wav、mp4、ogg等浏览器可识别格式) 转码成 audiobuffer(PCM编码),以便操作数据
//解码后采样率为计算机所设置的采样率
audioContext.decodeAudioData(arraybuffer, AudioBuffer => {
    console.log(AudioBuffer)//异步解码音频文件 转为PCM编码的AudioBuffer
});
  • 创建新的音频createBuffer
audioContext.createBuffer(channels, length, sampleRate);//channels:通道数, length:采样数量, sampleRate:采样率
//采样率=采样数量/时长秒数
  • 合并音频(同时播放两个音频),示例
let bufferarr = []; //要合并的音频数组
bufferarr.push(
  await this.getaudio('http://localhost:8081/static/asd.mp3')
)//加载音频audiobuffer
bufferarr.push(
  await this.getaudio('http://localhost:8081/static/ass.mp3')
)//加载音频audiobuffer
bufferarr.sort(function (a, b) { //按长度排序
  return b.duration - a.duration;
});
let channels = 2
sourceNodebuffer = audioContext.createBuffer(channels, bufferarr[0].length, bufferarr[0].sampleRate);//创建音频
for (var channel = 0; channel < channels; channel++) {//循环声道
  var newbuffer = sourceNodebuffer.getChannelData(channel);
  for(var key = 0; key < bufferarr.length; key++){//循环音频
    let buffer = bufferarr[key].getChannelData(channel);
    for (var i = 0; i < buffer.length; i++) {
		newbuffer[i] += buffer[i];
	}
  }
}
  • 创建wav音频文件,并填入数据(audiobuffer => wav)
getFullWavData(audiobuffer) {//创建wav文件 
  var sampleRate = audiobuffer.sampleRate;
  var sampleBits = 16;
  var bytes = audiobuffer.getChannelData(0);
  var bytes2 = audiobuffer.getChannelData(1);
  var dataLength = (bytes.length+bytes2.length) * (sampleBits / 8);


  var buffer = new ArrayBuffer(44 + dataLength);
  var data = new DataView(buffer);
  var offset = 0;
  var writeString = function(str) {
    for (var i = 0; i < str.length; i++) {
      data.setUint8(offset + i, str.charCodeAt(i));
    }
  };
  // 资源交换文件标识符   
  writeString('RIFF');
  offset += 4;
  // 下个地址开始到文件尾总字节数,即文件大小-8   
  data.setUint32(offset, 36 + dataLength, true);
  offset += 4;
  // WAV文件标志  
  writeString('WAVE');
  offset += 4;
  // 波形格式标志   
  writeString('fmt ');
  offset += 4;
  // 过滤字节,一般为 0x10 = 16   
  data.setUint32(offset, 16, true);
  offset += 4;
  // 格式类别 (PCM形式采样数据)   
  data.setUint16(offset, 1, true);
  offset += 2;
  // 通道数   
  data.setUint16(offset, audiobuffer.numberOfChannels, true);
  offset += 2;
  // 采样率,每秒样本数,表示每个通道的播放速度   
  data.setUint32(offset, sampleRate, true);
  offset += 4;
  // 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8   
  data.setUint32(offset, audiobuffer.numberOfChannels * sampleRate * (sampleBits / 8), true);
  offset += 4;
  // 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8   
  data.setUint16(offset, audiobuffer.numberOfChannels * (sampleBits / 8), true);
  offset += 2;
  // 每样本数据位数   
  data.setUint16(offset, sampleBits, true);
  offset += 2;
  // 数据标识符   
  writeString('data');
  offset += 4;
  // 采样数据总数,即数据总大小-44   
  data.setUint32(offset, dataLength, true);
  offset += 4;
  // 写入采样数据   
  data = this.reshapeWavData(sampleBits, offset, bytes,bytes2, data);
  let file = new Blob([data], {
    type: 'audio/wav'
  });

  //下载音频
  var href = URL.createObjectURL(file);
  const a = document.createElement("a");
  a.download = "output.mp3";
  a.href = href;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
}

reshapeWavData(sampleBits, offset, iBytes,iBytes2, oData) { // 写入采样数据,wav左右声道数据交替填入
  if (sampleBits === 8) {
    for (var i = 0; i < iBytes.length; i++, offset++) {
      var s = Math.max(-1, Math.min(1, iBytes[i]));
      var val = s < 0 ? s * 0x8000 : s * 0x7FFF;
      val = parseInt(255 / (65535 / (val + 32768)));
      oData.setInt8(offset, val, true);
    }
  } else {
    for (var i = 0; i < iBytes.length; i++, offset += 4) {
      var s = Math.max(-1, Math.min(1, iBytes[i]));
      oData.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);//左声道

      var s = Math.max(-1, Math.min(1, iBytes2[i]));
      oData.setInt16(offset+2, s < 0 ? s * 0x8000 : s * 0x7FFF, true);//右声道
    }
  }
  return oData;
}
  • audio加载事件
let audio = new Audio("url");
//音频(视频相同)在加载过程中,触发的顺序如下:
audio.onloadstart //开始加载
audio.ondurationchange //时长发生变化时触发,加载后时长从 "NaN" =>当前时长
audio.onloadedmetadata //时长,尺寸大小(视频),文本轨道,元数据加载后触发
audio.onloadeddata //当前帧的数据加载完成,且还没有足够的数据播放下一帧时触发
audio.onprogress //在浏览器下载指定的音频时触发
audio.oncanplay //在用户可以开始播放音频时触发
audio.oncanplaythrough //音频可以正常播放且无需停顿和缓冲时触发
  • audio属性及方法
//属性
autoplay	设置或返回是否在加载完成后随即播放音频
buffered	返回表示音频已缓冲部分的 TimeRanges 对象
controller	返回表示音频当前媒体控制器的 MediaController 对象
controls	设置或返回音频是否显示控件(比如播放/暂停等)
crossOrigin	设置或返回音频的 CORS 设置
currentSrc	回当前音频的 URL
currentTime	设置或返回音频中的当前播放位置(以秒计)
defaultMuted	设置或返回音频默认是否静音
defaultPlaybackRate	设置或返回音频的默认播放速度
duration	返回当前音频的长度(以秒计)
ended	返回音频的播放是否已结束
error	返回表示音频错误状态的 MediaError 对象
loop	设置或返回音频是否应在结束时重新播放
mediaGroup	设置或返回音频所属的组合(用于连接多个音频元素)
muted	设置或返回音频是否静音
networkState	返回音频的当前网络状态
paused	设置或返回音频是否暂停
playbackRate	设置或返回音频播放的速度
played	返回表示音频已播放部分的 TimeRanges 对象
preload	设置或返回音频是否应该在页面加载后进行加载
readyState	返回音频当前的就绪状态
seekable	返回表示音频可寻址部分的 TimeRanges 对象
seeking	返回用户是否正在音频中进行查找
src	设置或返回音频元素的当前来源
textTracks	返回表示可用文本轨道的 TextTrackList 对象
volume	设置或返回音频的音量

//方法
addTextTrack()	在音频中添加一个新的文本轨道
canPlayType()	检查浏览器是否可以播放指定的音频类型
fastSeek()	在音频播放器中指定播放时间。
getStartDate()	返回一个新的Date对象,表示当前时间轴偏移量
load()	重新加载音频元素
play()	开始播放音频
pause()	暂停当前播放的音频
  • input添加音频
addfile(val){
  var reader = new FileReader();
  reader.onload = async (e) => {
    // console.log(e,val)
    let audio = await this.toaudio(val.file);
    this.fillarr.push({
      name:val.file.name,
      duration:audio.duration,//时长
      // data : new Uint8Array(e.target.result)
      buffer:e.target.result
    });
  };
  reader.readAsArrayBuffer(val.file);
},
  • 生成audio,并读取音频数据
toaudio(file){
  return new Promise((resolve, reject) => {
    let audio = new Audio(URL.createObjectURL(file));
    audio.onloadedmetadata = () => {
      //加载完成后返回audio
      resolve(audio)
    }
  })
},
使用到的编码库
  • mp3 => libmp3lame =>.mp3
  • ogg => libvorbis =>.ogg
  • aac => libfdk_aac =>.mp4
  • wma => wmav1 =>.asf
思路
  • 使用worker异步处理音频
  • 使用ffmpeg的asm版,实现音频转码、裁切
  • 操作PCM编码的audiobuffer,实现拼接合成
  • 流程
  1. 输入.mp3|.ogg|.mp4|.asf|.wav
  2. ffmpeg转码裁切 => mp3 => decodeAudioData => pcm(audiobuffer) => 拼接合成 => wav
  3. 编码成指定格式输出文件mp3、ogg、mp4、asf、wav
组件git地址

vue示例git地址:https://gitee.com/xuedongwei/audio_edit.git

//使用简介
import audioedit from '../../static/audio_edit.js';
let audio_edit = new audioedit();
//udio_edit.state: 1,//状态 1等待任务,2正在编译,3正在停止
//audio_edit.progress:0,//进度0-100
//-------------------------------------------------------------------
//音频编辑 
   audio_edit.edit()
//参数
   outformat:"mp3",   //选填,输出格式 mp3, ogg, aac, wma, wav,默认mp3
   outchannel:2,      //选填,输出音频声道数1,2,默认2
   files:[{
     name:"asd.mp3",  //必填,音频文件名,原名
     buffer:buffer,   //必填,音频buffer
     stoptime:[30,50],//选填,截取-时间s,默认全部
     inserttime:0,    //选填,插入到-时间s,默认顺延拼接
     insertchannel:['0,0','1,1'] //选填,通道操作 '0,0','1,1','0,1','1,0' 多选 ex:'0,1'左=>右
   }]
//返回示例
    resolve({
      'code': 200,//成功,输出文件
      'name': "out.wav",
      'buffer': arraybuffer
    });
    resolve({
      'code': 400,//失败
      'message': '失败原因'
    })
    
//-------------------------------------------------------------------
//格式转换 
   audio_edit.format()
//参数
   name:"asd.mp3",    //必填,音频文件名,原名
   buffer:buffer,     //必填,音频buffer
   outformat:"mp3",   //选填,输出格式 mp3, ogg, aac, wma, wav,默认mp3
   outchannel:2,      //选填,输出音频声道数1,2
   outsample:44100,   //选填,导出采样率 采样率 11025电话音质 22050广播音质 44100音频CD 48000 96000高清晰度DVD
   outbitrate:"128k", //选填,导出比特率 96k、128k、192k、320k
//返回同音频编辑

//-------------------------------------------------------------------
//停止工作 
  audio_edit.stop()//无返回
声明

由于本人水平有限,参考了很多别人的文章、组件,由于时间过长找不到源头,这里就不列出了,还请谅解

  • 13
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜雨风亭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值