在android中, 通过AudioRecord
获取到音频数据是PCM
格式的, 但是有时候我们需要的可能是更常见的WAV
格式, 此时我们就需要手动把PCM
数据转成WAV
.
对于PCM
和WAV
的其他信息我就不搬运了, 本文仅关注
从
PCM
的byte数组得到WAV
的byte数组.
PCM
和WAV
的关系
简单地说, PCM
是音频的原始数据, WAV
则是一种封装音频数据的容器, 而且它的格式还很简单, 只是
在数据开头添加一些和音频数据相关的头信息.
首先我们看一下WAV
的格式规则, 如下图
千万不要被这图吓到, 上面的链接中也有对每一个值进行说明, 仔细阅读后就会发现并不复杂. 接下来逐个分析.
橙色部分中的data
, 就是原始音频数据(raw sound data), 也就是我们从AudioRecord
中获取到的PCM
byte数组. 其余部分则是上文提到的音频数据相关的头信息.
图中每一个方块表示一个数据段, 从左侧可以看到, 头信息的总长度是44个byte, 也可以知道每一个方块的起始和结束下标, 例如Subchunk2ID
的下标是36~40, 从右侧可以看到每一个数据段的长度, 例如ChunkID
占4个byte.
下面我们用header[n]
来表示头信息的第n个byte, pcmLen
表示PCM
byte数组的长度
接下来我们从下往上分析每个数据段的意思
-
data
, 占pcmLen
个byte, 表示原始的PCM
音频数据 -
Subchunk2Size
, 占4byte, 描述音频数据的长度, 就是pcmLen
, 注意先写低位再写高位, 即
header[40] = (byte) (pcmLen & 0xff);
header[41] = (byte) ((pcmLen >> 8) & 0xff);
header[42] = (byte) ((pcmLen >> 16) & 0xff);
header[43] = (byte) ((pcmLen >> 24) & 0xff);
-
SubchunkID
, 占4byte, 固定值"data", 即
header[36] = 'd';
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
-
BitPerSample
, 占2byte, 对应AudioRecord
中的audioFormat
, 8 bits = 8, 16 bits = 16, 以16bits为例, 如下
header[34] = 16;
header[35] = 0;
-
BlockAlign
, 占2byte, 值为NumChannels * BitsPerSample / 8
-
ByteRate
, 占4byte, 值为SampleRate * BlockAlign
-
SampleRate
, 占4byte, 对应AudioRecord
中的sampleRateInHz
, 即采样频率, 例如8000, 16000, 44100 -
NumChannels
, 占2byte, 对应AudioRecord
中的channelConfig
, 单声道Mono = 1, 立体声Stereo = 2 -
AudioFormat
, 占2byte, 数据为PCM
时, 值为1, 其他值表示数据进行过某种压缩, 本文不探讨 -
Subchunk1Size
, 占4byte, 数据为PCM
时, 值为16 -
Subchunk1ID
, 占4byte, 固定值"ftm "(注意空格补全4位) -
Format
, 占4byte, 固定值"WAVE" -
ChunkSize
, 占4byte, 值为4 + (8 + SubChunk1Size) + (8 + SubChunk2Size)
, 其中如果原始数据是PCM
, 简化为36 + SubChunk2Size
-
ChunkID
, 占4byte, 固定值"RIFF"
到这就分析完毕了, 所以如果我们有PCM
的原始数据, 也知道这些数据的采集参数, 例如单声道/立体声, 采样频率, 数据格式等, 就能够按上述规则生成WAV
的头部信息, 拼接PCM
byte数组后即得到WAV
的byte数组.
代码戳PcmToWavUtil