本文包含内容概述:
- 整理总结的音频相关资料
- 音频相关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双声道数据规则为左、右声道轮流记录一个采样。
人耳能够感觉到的最高频率为20kHz,
使得离散信号恢复成连续模拟信号不失真至少要让采样频率为 原始信号的2倍
因此要满足人耳的听觉要求,则需要每秒进行40k次采样
对音频数据以更高的采样率播放,即可实现加速。减速同理。
根据采样率计算时间对应数据,截取音频数据
多个音频,转为相同采样率,拼接音频数据
让两个音频文件的音频数据相加即可
JS相关API代码示例
非js原生,可在浏览器、node.js使用,小程序不可用
let audioContext = new(window.AudioContext || window.webkitAudioContext)();
//方法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的指定频道
//解码后采样率为计算机所设置的采样率
audioContext.decodeAudioData(arraybuffer, AudioBuffer => {
console.log(AudioBuffer)//异步解码音频文件 转为PCM编码的AudioBuffer
});
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];
}
}
}
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;
}
let audio = new Audio("url");
//音频(视频相同)在加载过程中,触发的顺序如下:
audio.onloadstart //开始加载
audio.ondurationchange //时长发生变化时触发,加载后时长从 "NaN" =>当前时长
audio.onloadedmetadata //时长,尺寸大小(视频),文本轨道,元数据加载后触发
audio.onloadeddata //当前帧的数据加载完成,且还没有足够的数据播放下一帧时触发
audio.onprogress //在浏览器下载指定的音频时触发
audio.oncanplay //在用户可以开始播放音频时触发
audio.oncanplaythrough //音频可以正常播放且无需停顿和缓冲时触发
//属性
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() 暂停当前播放的音频
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);
},
toaudio(file){
return new Promise((resolve, reject) => {
let audio = new Audio(URL.createObjectURL(file));
audio.onloadedmetadata = () => {
//加载完成后返回audio
resolve(audio)
}
})
},
使用到的编码库
思路
- 使用worker异步处理音频
- 使用ffmpeg的asm版,实现音频转码、裁切
- 操作PCM编码的audiobuffer,实现拼接合成
- 流程
- 输入.mp3|.ogg|.mp4|.asf|.wav
- ffmpeg转码裁切 => mp3 => decodeAudioData => pcm(audiobuffer) => 拼接合成 => wav
- 编码成指定格式输出文件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()//无返回
声明
由于本人水平有限,参考了很多别人的文章、组件,由于时间过长找不到源头,这里就不列出了,还请谅解