前置知识
1、audio与video标签支持的文件格式
- audio标签支持的文件格式
- (1)ogg:一种新的音频压缩格式,是完全免费、开放和没有专利限制的
- (2)mp3:是一种音频压缩技术。它被设计用来大幅度地降低音频数据量
- (3)wav:为微软公司开发的一种声音文件格式,声音文件质量和CD相差无几
- video标签支持的文件格式
- (1)mp4:mpeg4文件使用H264视频编解码器和AAC音频编解码器(mpeg4=mp4)
- (2)webm:webm文件使用VP8视频编解码器和Vorbis音频编解码器
- (3)avi:avi支持256色和RLE压缩,它对视频文件采用了一种有损压缩方式
- (4)ogv:ogv是html5中的一个名为ogg theora的视频格式
2、
-
以AA..开头的代表静音数据开头,为纯pcm格式,需要先把pcm转化为wav格式
浏览器是无法直接播放 pcm 音频的,因为 pcm 是比较原始的音频格式
PCM(Puls Code Modulation)全称脉码调制录音,PCM录音就是将声音的模拟信号表示成0,1标识的数字信号,未经任何编码和压缩处理,所以可以认为PCM是未经压缩的音频原始格式。PCM格式文件中不包含头部信息,播放器无法知道采样率,声道数,采样位数,音频数据大小等信息,导致无法播放。
-
只有不是A的字符开头的,才可能有wav的头部,可以利用'data:audio/wav;base64,' + base64的audio数据;拼接形成url
实现
主要代码
import '@/utils/polyfill';
const playAudio= async (file : File) => {
//file为用户上传的含有base64格式音频数据的文件(业务上:该base64可能已经含有wav头部可能只是纯pcm格式)
const text = await readerPlainText(file.raw!);
const json = JSON.parse(text);
//audio为base64的音频数据,sampleRate为采样率
const audio =json.audio;
const sampleRate = json.sampling;
//file.audioCodec为业务上就包含了这个判断字段,也可以采用audio.startWith('A')来简要判断格式
if (file.audioCodec === 'pcm') {
const audioBuffer = base64ToArrayBuffer(audio);
const wav = pcm2wav.encodeWav(Buffer.from(audioBuffer), {
sampleRate: sampleRate || 24000,
sampleBits: 16,
numChannels: 1,
});
audioSrc.value = URL.createObjectURL(new Blob([wav]));
} else {
audioSrc.value = 'data:audio/wav;base64,' + audio;
}
}
补充
以下主要是对上面主要代码的一些补充解释
1、引入polyfill.ts
需要引入polyfill.ts这个文件,否则报错Buffer is not defined 因为Buffer是node的东西,浏览器中JS没有Buffer对象
//polyfill.ts
import fill from 'buffer';
declare global {
interface Window {
Buffer: any;
}
}
window.Buffer = fill.Buffer;
解决:将Buffer对象改用ArrayBuffer对象,ArrayBuffer对象是 ES6 才写入标准的。浏览器原生提供ArrayBuffer()构造函数,用来生成实例。它接受一个整数作为参数,表示这段二进制数据占用多少个字节。
//base64ToArrayBuffer 函数
export const base64ToArrayBuffer = (base64: string) => {
const binary_string = window.atob(base64);
const len = binary_string.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes;
};
转换前:
转换后:
2、pcm2wav代码实现
//pcm2wav.ts
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* ------PCM二进制转wav格式。将PCM数据拼上encodeWAV返回的头即可-------- */
/**
* @param rawPCM buffer || binary
* @param options.numChannels
* @param options.sampleRate
* @param options.byteRate
* @return Buffer
* @throws Exception
*/
interface Options {
numChannels: number;
sampleRate: number;
sampleBits: number;
}
function encodeWav(rawPCM: string | Buffer, options: Options): Buffer {
if (typeof rawPCM === 'string') {
rawPCM = Buffer.from(rawPCM, 'binary');
}
if (!Buffer.isBuffer(rawPCM)) {
throw new TypeError('pcm data must be Buffer or string');
}
const opt = options || {};
const sampleRate = opt.sampleRate || 16000;
const numChannels = opt.numChannels || 1;
const sampleBits = opt.sampleBits || 16;
const buf = rawPCM;
//@ts-ignore
const header = new Buffer.alloc(44);
header.write('RIFF', 0); // ChunkID。固定
header.writeUInt32LE(buf.length + 44 - 8, 4); // ChunkSize。下个地址开始到文件尾总字节数,即文件大小-8
header.write('WAVE', 8); // Format。固定
header.write('fmt ', 12); // Subchunk1 ID。固定
header.writeUInt8(16, 16); // Subchunk1 Size。一般为16
header.writeUInt8(1, 20); // AudioFormat。1表示pcm
header.writeUInt8(numChannels, 22); // Num Channels。声道数
header.writeUInt32LE(sampleRate, 24); // SampleRate。采样率
header.writeUInt32LE((sampleRate * numChannels * sampleBits) / 8, 28); // ByteRate。波特率,即声道数 × 采样频率 × 采样位数 / 8。
header.writeUInt8((numChannels * sampleBits) / 8, 32); // BlockAlign。声道数 × 采样位数 / 8
header.writeUInt8(sampleBits, 34); // Bits Per Sample。采样位数
header.write('data', 36); // Subchunk2 Id。固定
header.writeUInt32LE(buf.length, 40); // Subchunk2 Size。
return Buffer.concat([header, buf]);
}
function decodeWav(rawWav: string | Buffer): Buffer {
if (typeof rawWav === 'string') {
rawWav = Buffer.from(rawWav, 'binary');
}
if (!Buffer.isBuffer(rawWav)) {
throw new TypeError('pcm data must be Buffer or string');
}
// remove the header of pcm format
rawWav = rawWav.slice(44);
return rawWav;
}
export default {
encodeWav,
decodeWav,
};
3、ArrayBuffer与Uint8Array :
1.常见的js数组
var arr = new Array(5)
2.类型化数组TypedArray
在html5版本时中,TypedArray在WEBGL规范中被引入用于解决Javascript处理二进制数据的问题 (类型化数组也是数组,只不过其元素被设置为特定类型的值)
2.1 类型化数组ArrayBuffer
类型化数组的核心是一个名为ArrayBuffer的类型
每个ArrayBuffer对象表示的只是内存中指定的字节数; 但不会指定这些字节用于保存什么类型的数据; 通过ArrayBuffer能做的,就是为了将来使用而分配一定数量的字节.
// 创建一个8-byte的ArrayBuffer
var b = new ArrayBuffer(8);
// 创建一个b的引用,类型是Int32,起始位置在0,结束位置为缓冲区尾部
var v1 = new Int32Array(b);
// 创建一个b的引用,类型是Uint8,起始位置在2,结束位置为缓冲区尾部
var v2 = new Uint8Array(b, 2);
// 创建一个b的引用,类型是Int16,起始位置在2,总长度为2
var v3 = new Int16Array(b, 2, 2);
4、Buffer.from
5、atob
atob()函数对使用Base64编码编码的数据字符串进行解码。btoa()方法对数据进行编码和传输,否则可能会导致通信问题,然后传输并 atob()再次使用该方法对数据进行解码。例如,您可以编码、传输和解码控制字符,例如 ASCII 值 0 到 31。
const encodedData = btoa("Hello, world"); // encode a string
const decodedData = atob(encodedData); // decode the string
其他解决方法
1.在vue.config.js中配置ProvidePlugin
// vue.config.js
...
configureWebpack: {
plugins: [
...
new webpack.ProvidePlugin({
process: 'process/browser',
Buffer: ['buffer', 'Buffer']
})
]
}
stackoverflow.com/questions/7…
参考链接