前言
本文讲述的编码指的是PCM编码,PCM 即脉冲编码调制 (Pulse Code Modulation)。在PCM 过程中,将输入的模拟信号进行采样、量化和编码,用二进制进行编码的数来代表模拟信号的幅度 ;接收端再将这些编码还原为原来的模拟信号。即数字音频的 A/D 转换包括三个过程 :采样,量化,编码。
翻译成人话就是从一段录音里按一定的采样频率取到一组二进制数据(模拟信号的幅度[-1,1]),按采样位数(8位或者16位)编码成对应位数的另一组二进制数据,如果想生成文件,可以在数据内容前加文件头标识(比如WAV),或者是将信号数据优化压缩过的文件格式(比如MP3、OGG等)。
而解码就是编码的逆操作,将这些编码的数据还原回原来的模拟信号的数据,让扬声器能够识别播放。
编码PCM
在我们得到录音数据之后,编码成可以播放的文件,PCM格式是最直接最简单的。
录音采集
录音的方式我大概说一下就好了,主要目的是拿到录音后的所有采样数据,下面是我录音的主要代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| recordStart = function (isUnClaer) { var _this = this; navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) { _this.audioInput = _this.AudioContext.createMediaStreamSource(stream); }, function (error) { Recorder.throwError(error.name + " : " + error.message); }).then(function () { _this.audioInput.connect(_this.recorder); _this.recorder.connect(_this.context.destination); }); };
var createScript = this.AudioContext.createScriptProcessor || this.context.createJavaScriptNode;
this.recorder = createScript.apply(this.context, [4096, 1, 1]);
_this.buffer = [] _this.size = 0
this.recorder.onaudioprocess = function (e) { var inputData = e.inputBuffer.getChannelData(0); var outputData = e.outputBuffer.getChannelData(0);
_this.buffer.push(new Float32Array(inputData)); _this.size += inputData.length; }
recordStop = function() { this.audioInput && this.audioInput.disconnect(); this.recorder.disconnect(); var data = new Float32Array(this.size), offset = 0; for (var i = 0; i < this.buffer.length; i++) { data.set(this.buffer[i], offset); offset += this.buffer[i].length; } return data }
|
编码
得到所有采样数据后就可以进行PCM编码了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
encodePCM = function (bytes, sampleBits) { var offset = 0, dataLength = bytes.length * (sampleBits / 8), buffer = new ArrayBuffer(dataLength), data = new DataView(buffer); if (sampleBits === 8) { for (var i = 0; i < bytes.length; i++ , offset++) { var s = Math.max(-1, Math.min(1, bytes[i])); var val = s < 0 ? s * 128 : s * 127; val = +val + 128; data.setInt8(offset, val); } } else { for (var i = 0; i < bytes.length; i++ , offset += 2) { var s = Math.max(-1, Math.min(1, bytes[i])); data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true); } } return data; };
|
生成文件
编码完成后是DataView
对象,你可以直接调用new Blob(data)
生成可直接在浏览器执行的文件格式或者触发下载:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
download(data, filename='recorder', type='pcm') { const dataBlob = new Blob([data]); try { var oA = document.createElement('a'); oA.href = window.URL.createObjectURL(blob); oA.download = filename + '.' + type; oA.click(); } catch (e) { Recorder.throwError(e); } }
|
这样就完成了从录音->采集->编码->生成文件
的完整流程,接下来要讲解码了
解码PCM
解码过程就像开头说的就是编码的逆操作,我们按获取文件二进制数据->解码
的流程走。
假设我们有一个已经生成好的PCM文件,我们可以通过axios直接获取到文件的arraybuffer
二进制数据
获取文件二进制数据
1 2 3 4 5 6
| axios.get('pcm文件地址', { responseType: 'arraybuffer', }).then((res) => { decodePCM(res.data) }
|
解码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
decodePCM = function(arraybuffer, sampleBits=16) { var dataview = new DataView(arraybuffer) var offset = 0; var data; if (sampleBits === 8) { data = new Float32Array(dataview.byteLength) for (var i = 0; i < data.length; i++ , offset++) { var s = Math.max(0, Math.min(255, bytes.getInt8(offset, true))) - 128; data[i] = s < 0 ? s / 128 : s / 127; } } else { data = new Float32Array(dataview.byteLength / 2) for (var i = 0; i < data.length; i++, offset += 2) { var s = Math.max(-32768, Math.min(32767, bytes.getInt16(offset, true))); data[i] = s < 0 ? s / 0x8000 : s / 0x7FFF } } return data }
|
解码之后
解码之后能干什么呢? 解码之后就可以做更底层的音频处理(剪辑、变音变调、合成、混音、压缩),可以参考这两篇博客《音频处理之音频文件拼接录音及裁剪》、《音频处理之变音变调》,除了音频处理,还可以做格式转换,再编码,比如PCM是最原始的数据生成的文件格式,我们可以解码再编码成MP3格式,文件大小比PCM可以小十倍,音质还不会损失很多。
由于篇幅有点长,还有WAV编码解码和MP3编码就分到下一篇讲,第二篇传送门:《音频处理之文件编码与解码(二)》