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格式的存储结构。

python 将pcm转为wav pcm转wav的软件_wav

python 将pcm转为wav pcm转wav的软件_pcm转wav_02

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:就是其他编码文件内容

文件实例:

python 将pcm转为wav pcm转wav的软件_python 将pcm转为wav_03

  • 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);
    }

}