浏览器播放PCM文件

发布: 2020-03-07 15:01:59标签: 前端开发

方法: 将pcm文件转化成base64再进行播放

01async function getWebFileArrayBuffer(url) {
02 return await fetch(url).then(response => response.arrayBuffer())
03}
04
05async function getWebPcm2WavArrayBuffer(url) {
06 const bytes = await getWebFileArrayBuffer(url)
07 return addWavHeader(bytes, 16000, 16, 1) // 这里是当前业务需求,特定的参数,采样率16000,采样位数16,声道数1
08}
09
10function addWavHeader(samples, sampleRateTmp, sampleBits, channelCount) {
11 let dataLength = samples.byteLength
12 /* 新的buffer类,预留 44 bytes 的heaer 空间 */
13 let buffer = new ArrayBuffer(44 + dataLength)
14 /* 转为 Dataview, 利用 API 来填充字节 */
15 let view = new DataView(buffer)
16 /* 定义一个内部函数,以 big end 数据格式填充字符串至 DataView */
17 function writeString(view, offset, string) {
18 for (let i = 0; i < string.length; i++) {
19 view.setUint8(offset + i, string.charCodeAt(i))
20 }
21 }
22
23 let offset = 0
24 /* ChunkID, 4 bytes, 资源交换文件标识符 */
25 writeString(view, offset, 'RIFF')
26 offset += 4
27 /* ChunkSize, 4 bytes, 下个地址开始到文件尾总字节数,即文件大小-8 */
28 view.setUint32(offset, /* 32 */ 36 + dataLength, true)
29 offset += 4
30 /* Format, 4 bytes, WAV文件标志 */
31 writeString(view, offset, 'WAVE')
32 offset += 4
33 /* Subchunk1 ID, 4 bytes, 波形格式标志 */
34 writeString(view, offset, 'fmt ')
35 offset += 4
36 /* Subchunk1 Size, 4 bytes, 过滤字节,一般为 0x10 = 16 */
37 view.setUint32(offset, 16, true)
38 offset += 4
39 /* Audio Format, 2 bytes, 格式类别 (PCM形式采样数据) */
40 view.setUint16(offset, 1, true)
41 offset += 2
42 /* Num Channels, 2 bytes, 通道数 */
43 view.setUint16(offset, channelCount, true)
44 offset += 2
45 /* SampleRate, 4 bytes, 采样率,每秒样本数,表示每个通道的播放速度 */
46 view.setUint32(offset, sampleRateTmp, true)
47 offset += 4
48 /* ByteRate, 4 bytes, 波形数据传输率 (每秒平均字节数) 通道数×每秒数据位数×每样本数据位/8 */
49 view.setUint32(offset, sampleRateTmp * channelCount * (sampleBits / 8), true)
50 offset += 4
51 /* BlockAlign, 2 bytes, 快数据调整数 采样一次占用字节数 通道数×每样本的数据位数/8 */
52 view.setUint16(offset, channelCount * (sampleBits / 8), true)
53 offset += 2
54 /* BitsPerSample, 2 bytes, 每样本数据位数 */
55 view.setUint16(offset, sampleBits, true)
56 offset += 2
57 /* Subchunk2 ID, 4 bytes, 数据标识符 */
58 writeString(view, offset, 'data')
59 offset += 4
60 /* Subchunk2 Size, 4 bytes, 采样数据总数,即数据总大小-44 */
61 view.setUint32(offset, dataLength, true)
62 offset += 4
63
64 /* 数据流需要以大端的方式存储,定义不同采样比特的 API */
65 function floatTo32BitPCM(output, offset, input) {
66 input = new Int32Array(input)
67 for (let i = 0; i < input.length; i++, offset += 4) {
68 output.setInt32(offset, input[i], true)
69 }
70 }
71 function floatTo16BitPCM(output, offset, input) {
72 input = new Int16Array(input)
73 for (let i = 0; i < input.length; i++, offset += 2) {
74 output.setInt16(offset, input[i], true)
75 }
76 }
77 function floatTo8BitPCM(output, offset, input) {
78 input = new Int8Array(input)
79 for (let i = 0; i < input.length; i++, offset++) {
80 output.setInt8(offset, input[i], true)
81 }
82 }
83 if (sampleBits == 16) {
84 floatTo16BitPCM(view, 44, samples)
85 } else if (sampleBits == 8) {
86 floatTo8BitPCM(view, 44, samples)
87 } else {
88 floatTo32BitPCM(view, 44, samples)
89 }
90 return view.buffer
91}
92
93export default async function getWebPcm2WavBase64(url) {
94 let bytes = await getWebPcm2WavArrayBuffer(url)
95 return `data:audio/wav;base64,${btoa(
96 new Uint8Array(bytes).reduce((data, byte) => {
97 return data + String.fromCharCode(byte)
98 }, '')
99 )}`
100}
101
复制代码

代码来源