1 音频格式简介
AudioRecord录制的音频文件格式为PCM,MediaPlayer无法播放PCM格式文件,AudioTrack可以播放PCM格式文件。
PCM(Puls Code Modulation)全称脉码调制录音,PCM录音就是将声音的模拟信号表示成0,1标识的数字信号,未经任何编码和压缩处理,所以可以认为PCM是未经压缩的音频原始格式。PCM格式文件中不包含头部信息,播放器无法知道采样率,声道数,采样位数,音频数据大小等信息,导致无法播放。
PCM格式缺少头部信息,支持的播放器有限,所以一般需要把PCM格式转换成其他格式文件。
WAV是一种符合 RIFF Resource Interchange File Format规范的微软开发的音频格式,是一种无损格式。WAV对音频流的编码没有硬性规定,除了PCM之外,还有几乎所有支持ACM规范的编码都可以为WAV的音频流进行编码(类似mp3),只需要在被编码音频的前面添加带有音频流的编码参数的WAV的文件头。WAV格式支持许多压缩算法,支持多种音频位数、采样频率和声道,采用44.1kHz的采样频率,16位量化位数,因此WAV的音质与CD相差无几,但WAV格式对存储空间需求太大不便于交流和传播。
MP3利用MPEG Audio Layer3 压缩方式进行压缩,所以简称为MP3,是一种有损压缩格式。 MPEG Audio Layer 3 压缩技术可以将音乐以1:10 甚至 1:12 的压缩率,能够在音质丢失很小的情况下把文件压缩到更小的程度。由于MP3体积小,音质高互联网上音乐几乎都是这种格式。但Mp3最高比特率320K,高频部分一刀切是他的缺点,对音质要求高的话还是建议wav格式。
ARM格式全称Adaptive Multi-Rate 和 Adaptive Multi-Rate Wideband,主要用于移动设备的音频,压缩比比较大,但相对其他的压缩格式质量比较差,多用于人声,通话,是一种有损压缩格式。
Ogg全称应该是OGG Vobis(ogg Vorbis) 是一种新的音频压缩格式,类似于MP3等现有的音乐格式。相对于MP3压缩技术它是完全免费、开放和没有专利限制的,是一种有损压缩格式。
AAC(Advanced Audio Coding),中文称为“高级音频编码”,出现于1997年,基于 MPEG-2的音频编码技术,是一种有损压缩技术。
LAC即是Free Lossless Audio Codec的缩写,为无损音频压缩编码,由于不会丢失任何音频信息可以利用算法恢复原始编码,前景广阔。
2 WAV文件头信息
WAV格式全称为WAVE,前面提到只需要在PCM文件的前面添加WAV文件头,就可以生成WAV格式文件,下面说一说WAV文件头格式。
WAV符合 RIFF Resource Interchange File Format规范,RIFF文件结构可以看作是树状结构,其基本构成是称为"块"(Chunk)的单元,WAVE文件是由若干个Chunk组成的。WAV文件本身由三个“块”信息组成:将文件标识为WAV文件的RIFF块,识别采样率等参数的FORMAT块和包含实际数据(样本)的DATA块。
所有的WAV都有一个文件头,这个文件头记录着音频流的编码参数。数据块的记录方式是little-endian字节顺序。
C语言中对应的WAV的文件头结构如下:
Typedef struct
{
WAVEFORMAT wf;//波形格式;
WORD wBitsPerSample;//WAVE文件的采样大小;
}PCMWAVEFORMAT;
WAVEFORMAT结构定义如下:
typedef struct
{
WORD wFormatag;//编码格式,包括WAVE_FORMAT_PCM,WAVEFORMAT_ADPCM等
WORD nChannls;//声道数,单声道为1,双声道为2;
DWORD nSamplesPerSec;//采样频率;
DWORD nAvgBytesperSec;//每秒的数据量;
WORD nBlockAlign;//块对齐;
}WAVEFORMAT;
C语言里面的字WORD(32位),16进制文件对应2个字节(Byte),而DWORD(64位),对应4个字节。16进制文件的存储规律,对于WORD,先存储低位字节,然后存储高位字节,而DWORD,则先存储低两位的低位,然后是低两位的高位,然后是高两位的低位,然后是高两位的高位。所以看下图时要对照WORD和DWORD格式的存储结构。
WAV文件头信息由44个字节组成,所以只需要在PCM文件头部添加44个字节的WAV文件头,就可以生成WAV格式文件。
- ChunkID:大小为4个字节数据,内容为“RIFF”,表示资源交换文件标识
- ChunkSize:大小为4个字节数据,内容为一个整数,表示从下个地址开始到文件尾的总字节数
- Format:大小为4个字节数据,内容为“WAVE”,表示WAV文件标识
- Subchunkl ID:大小为4个字节数据,内容为“fmt ”,表示波形格式标识(fmt ),最后一位空格。
- Subchunkl Size:大小为4个字节数据,内容为一个整数,表示PCMWAVEFORMAT的长度。
- AudioFormat:大小为2个字节数据,内容为一个短整数,表示格式种类(值为1时,表示数据为线性PCM编码)
- NumChannels:大小为2个字节数据,内容为一个短整数,表示通道数,单声道为1,双声道为2
- SampleRate:大小为4个字节数据,内容为一个整数,表示采样率,比如44100
- ByteRate:大小为4个字节数据,内容为一个整数,表示波形数据传输速率(每秒平均字节数),大小为 采样率 * 通道数 * 采样位数
- BlockAlign:大小为2字节数据,内容为一个短整数,表示DATA数据块长度,大小为 通道数 * 采样位数
- BitsPerSample:大小为2个字节数据,内容为一个短整数,表示采样位数,即PCM位宽,通常为8位或16bit
- Subchunk2ID:大小为4个字节数据,内容为“data”,表示数据标记符
- Subchunk2 Size:大小为4个字节数据,内容为一个整数,表示接下来声音数据的总大小,需要减去头部的44个字节。
- data:就是其他编码文件内容
文件实例:
- ChunkID:“52 49 46 46 ”对应ASCII中的RIFF,固定格式。
- ChunkSize:“A4 29 01 00”:注意为DWORD格式,表示WAV文件的大小,包含了前面8个字节,所以真正的大小等于文件总字节减去8。a4 a9 01 00 对应的正序16进制为“000129a4”大小为76196,所以文件大小为76188.
- Format:“57 41 56 45 ”:表示ASCII编码的WAVE,固定写法。
- Subchunkl ID:”66 6D 74 20”表示ASCII编码的fmt,固定写法。
- Subchunkl Size:“10 00 00 00 ”DWORD格式,对应16。
- AudioFormat:“01 00”WORD格式,对应定义为编码格式“WAVE_FORMAT_PCM”。
- NumChannels:“01 00”WORD格式,对应数字1,表示声道数为1,这是个单声道Wav。
- SampleRate:”80 3e 00 00”,DWORD格式,对应16进制大小为“00003e80”采样率为1600.
- ByteRate:”00 7d 00 00”,DWORD格式,大小为传输速率为32000,
- BlockAlign:02 00,WORD格式,对应2
- BitsPerSample:10 00 ,对应WAVE文件的采样大小,数值为16,采样大小为16Bits
- Subchunk2ID:“64 61 74 61”表示为ASCII的data,开始数据区。
- Subchunk2 Size:“80 29 01 00”格式为DWORD,大小为76160减去44 得到76116.
wav文件 :https://pan.baidu.com/s/10Vzv6I1Wwbzr5VVBMnJfEg
3 PCM转WAV格式
主要采样频率和AudioFormat和声道数,如果pcm转wav过程中,这些信息和录音时的设置不同,会导致生成的wav文件都是杂音,所以抽取成了需要外部传入。
public class PCMCovWavUtil {
//录音的采样频率
private int audioRate = 16000;
//录音的声道,单声道
private int audioChannel = AudioFormat.CHANNEL_IN_MONO;
//量化的深度
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
//缓存的大小
private int bufferSize = AudioRecord.getMinBufferSize(audioRate,audioChannel,audioFormat) *2;
//PCM文件
private File pcmFile;
//WAV文件
private File wavFile;
private String basePath = Environment.getExternalStorageDirectory()
.getPath() + "/audio";
//wav文件目录
private String outFileName = basePath+"/test.wav";
//pcm文件目录
private String inFileName = basePath+"/test.pcm";
// samHz,shengdao,audioFormat,bufferSize
public PCMCovWavUtil(int audioRate,int audioChannel,int audioFormat,int bufferSize){
this.audioRate = audioRate;
this.audioChannel = audioChannel;
this.audioFormat = audioFormat;
this.bufferSize = bufferSize;
File baseFile = new File(basePath);
if(!baseFile.exists())
baseFile.mkdirs();
pcmFile = new File(inFileName);
wavFile = new File(outFileName);
try{
if (!pcmFile.exists())
pcmFile.createNewFile();
if (!wavFile.exists())
wavFile.createNewFile();
}catch(IOException e){
}
}
//转换函数
public void convertWaveFile() {
File twavFile = new File(outFileName);
if(twavFile.exists()){
twavFile.delete();
}
FileInputStream in = null;
FileOutputStream out = null;
long totalAudioLen = 0;
long totalDataLen = totalAudioLen + 36;
long longSampleRate = audioRate;
int channels = 1;
long byteRate = 16 * audioRate * channels / 8;
if (audioFormat == AudioFormat.ENCODING_PCM_16BIT){
byteRate = 16 * audioRate * channels / 8;
}else if (audioFormat == AudioFormat.ENCODING_PCM_8BIT){
byteRate = 8 * audioRate * channels / 8;
}
byte[] data = new byte[bufferSize];
try {
in = new FileInputStream(inFileName);
out = new FileOutputStream(outFileName);
totalAudioLen = in.getChannel().size();
//由于不包括前面的8个字节RIFF和WAV
totalDataLen = totalAudioLen + 36;
addWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
while (in.read(data) != -1) {
out.write(data);
}
in.close();
out.close();
System.out.println("==========转换完毕================");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//添加Wav头部信息
private void addWaveFileHeader(FileOutputStream out, long totalAudioLen, long totalDataLen, long longSampleRate,
int channels, long byteRate) throws IOException {
byte[] header = new byte[44];
// RIFF 头表示
header[0] = 'R';
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
//数据大小,数据大小,真正大小是添加了8bit
header[4] = (byte) (totalDataLen & 0xff);
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
//wave格式
header[8] = 'W';//WAVE
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
//fmt Chunk
header[12] = 'f'; // 'fmt '
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
//数据大小
header[16] = 16; // 4 bytes: size of 'fmt ' chunk
header[17] = 0;
header[18] = 0;
header[19] = 0;
//编码方式 10H为PCM编码格式
header[20] = 1; // format = 1
header[21] = 0;
//通道数
header[22] = (byte) channels;
header[23] = 0;
//采样率,每个通道的播放速度
header[24] = (byte) (longSampleRate & 0xff);
header[25] = (byte) ((longSampleRate >> 8) & 0xff);
header[26] = (byte) ((longSampleRate >> 16) & 0xff);
header[27] = (byte) ((longSampleRate >> 24) & 0xff);
//音频数据传送速率,采样率*通道数*采样深度/8
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) ((byteRate >> 8) & 0xff);
header[30] = (byte) ((byteRate >> 16) & 0xff);
header[31] = (byte) ((byteRate >> 24) & 0xff);
int tongdaowei = 1;
if ( audioChannel == AudioFormat.CHANNEL_IN_MONO){
tongdaowei =1;
}else{
tongdaowei =2;
}
// 确定系统一次要处理多少个这样字节的数据,确定缓冲区,通道数*采样位数
if (audioFormat == AudioFormat.ENCODING_PCM_16BIT){
header[32] = (byte) (tongdaowei * 16 / 8);
}else if (audioFormat == AudioFormat.ENCODING_PCM_8BIT){
header[32] = (byte) (tongdaowei * 8 / 8);
}
// header[32] = (byte) (1 * 16 / 8);
header[33] = 0;
//每个样本的数据位数
if (audioFormat == AudioFormat.ENCODING_PCM_16BIT){
header[34] = 16;
}else if (audioFormat == AudioFormat.ENCODING_PCM_8BIT){
header[34] = 8;
}
header[35] = 0;
//Data chunk
header[36] = 'd';//data
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (totalAudioLen & 0xff);
header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
out.write(header, 0, 44);
}
}