js实现pcm音频转wav与播放

2019年04月14日Web前端

前面我们已经实现了js录制pcm编码的功能了,但是很遗憾,浏览器并不能播放pcm音频,但可以支持与他比较相近的wav格式的文件,来琢磨下这两者的转化吧。

wav

Waveform Audio File Format,是微软与IBM公司所开发在个人计算机存储音频流的编码格式。

wav可以使用多种音频编码来压缩其音频流,不过我们常见的都是音频流被pcm编码处理的wav。但这不表示wav只能使用pcm编码,mp3编码同样也可以运用在wav中。简单来说,pcm是无损wav文件中音频数据的一种编码方式,但wav还可以用其它方式编码。

wav是一种无损的音频文件格式,由于此音频格式未经过压缩,所以在音质方面不会出现失真的情况,但文件的体积因而在众多音频格式中较为大。

一般情况下,wav数据实际上就是裸数据pcm外面包了一层文件头。在前面的文章中,我们已经拿到了pcm数据了,只要在其前部增加44个字节的wav头就行了。

wav头

先上这张图吧,

ChunkID

偏移量0,占用了4字节,大端字节序,表示资源交换文件标识符,一般固定是"RIFF"。

ChunkSize

偏移量4,占用4字节,小端字节序,下个地址开始到文件尾总字节数,即文件大小-8。

Format

偏移量8,占用4字节,大端字节序,表示wav文件标志,一般固定为"WAVE"。

Subchunk1 ID

偏移量12,占用4字节,大端字节序,表示波形格式标志,一般固定为"fmt ",注意最后有空格。

Subchunk1 Size

偏移量16,占用4字节,小端字节序,表示过滤字节,一般为 0x10 = 16。

AudioFormat

偏移量20,占用2字节,小端字节序,表示格式类别,1是PCM形式采样数据,故此处填1。

Num Channels

偏移量22,占用2字节,小端字节序,表示声道数。

SampleRate

偏移量24,占用4字节,小端字节序,表示采样率。

ByteRate

偏移量28,占用4字节,小端字节序,表示波特率,即声道数 × 采样频率 × 采样位数 / 8。

BlockAlign

偏移量32,占用2字节,小端字节序,声道数 × 采样位数 / 8。

Bits Per Sample

偏移量34,占用2字节,小端字节序,采样位数.

Subchunk2 Id

偏移量36,占用4字节,大端字节序,数据标识符,一般固定为"data"。

Subchunk2 Size

偏移量40,占用4字节,小端字节序,表示采样数据总数,即数据总大小-44。

data

偏移量44,小端字节序,pcm数据。

js拼装

给pcm的数据加上wav头就可以播放了,简单的处理如下:

// 资源交换文件标识符
writeString(data, offset, 'RIFF'); offset += 4;
// 下个地址开始到文件尾总字节数,即文件大小-8
data.setUint32(offset, 36 + bytes.byteLength, true); offset += 4;
// WAV文件标志
writeString(data, offset, 'WAVE'); offset += 4;
// 波形格式标志
writeString(data, offset, 'fmt '); offset += 4;
// 过滤字节,一般为 0x10 = 16
data.setUint32(offset, 16, true); offset += 4;
// 格式类别 (PCM形式采样数据)
data.setUint16(offset, 1, true); offset += 2;
// 声道数
data.setUint16(offset, channelCount, true); offset += 2;
// 采样率,每秒样本数,表示每个通道的播放速度
data.setUint32(offset, sampleRate, true); offset += 4;
// 波形数据传输率 (每秒平均字节数) 声道数 × 采样频率 × 采样位数 / 8
data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true); offset += 4;
// 快数据调整数 采样一次占用字节数 声道数 × 采样位数 / 8
data.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2;
// 采样位数
data.setUint16(offset, sampleBits, true); offset += 2;
// 数据标识符
writeString(data, offset, 'data'); offset += 4;
// 采样数据总数,即数据总大小-44
data.setUint32(offset, bytes.byteLength, true); offset += 4;

// 给wav头增加pcm体
for (let i = 0; i < bytes.byteLength; ++i) {
    data.setUint8(offset, bytes.getUint8(i, true), true);
    offset++;
}

给pcm嵌入上wav头,那么浏览器就可以播放了。

播放

createBufferSource方式

该方法在web Audio学习与音频播放中就有了,将二进制传入就会被转成audioBuffer,这儿不多说了。

audio方式

可以创建audio标签,外加window.createObjectURL方法返回资源路径给audio标签,再通过audio的播放就ok了。

总结

到这,录音功能也算差不多完成了,当然还有很多细节待完善的,以及一些小功能可以开发。

本篇对应的demo请查看:js转化pcm到wav格式与播放。完整的录音插件请查看:recorder