修改wav格式音频比特率的标准方法

前 言

记得之前写过一篇文章,介绍怎么将amr音频转为wav格式,这个过程是没有问题的,转码产生的音频文件是可以正常播放的。但是,由于项目中的服务器智能播放比特率为64kbps的wav音频,而转码产生的wav音频比特率为128kbps,导致不可用。

wav音频

WAVE文件作为多媒体中使用的声波文件格式之一,它是以RIFF格式为标准的。RIFF是英文Resource Interchange File Format的缩写,每个WAVE文件的头四个字节便是“RIFF”。

技术选型

由于转码产生的音频比特率不符合要求,那么就需要修改wav文件的比特率为64kbps了。该音频的采样率为16k,改为8k就可以。

构建一个wav文件头

wav文件头是有一定结构的,占用44个字节记录文件的信息。

public class PcmWavHead {

   /**
    * 0-3个字节,WAV固定为RIFF,RIFF是windows下的一种常用多媒体格式存储标准
    */
   public String ChunkID="RIFF";

   /**
    * 4-7个字节,文件长度
    */
   public int ChunkSize=0;

   /**
    * 8-11个字节,WAV文件格式标志
    */
   public String Format="WAVE";


   /**
    * 12-15个字节,"fmt "标志
    */
   public String SubChunk1ID="fmt ";


   /**
    * 16-19个字节 块大小,初始化为一个数,但是实际上的大小决定,计算的公式为   (HeadSize + DataSize - 8) = (44-8+DataSize) 字节
    */
   public int SubChunk1Size=0x10;       //


   /**
    * 20-21个字节,格式类别(0x01H为PCM形式的声音数据)
    */
   public int AudioFormat=0x1;         //


   /**
    * 22-23个字节, 通道数,单声道为1,双声道为2
    */
   public int NumChannels=1;         //


   /**
    * 24-27个字节 采样率(每秒样本数),表示每个通道的播放速度,8000 | 6000 | 11025 | 16000
    */
   public int SampleRate=16000;          //


   /**
    * 28-31个字节 波形音频数据传送速率,其值Channels×SamplesPerSec×BitsPerSample/8   每秒字节数
    */
   public int ByteRate=32000;            //32Kbps


   /**
    * 32-33个字节,数据块的调整数(按字节算的),其值为Channels×BitsPerSample/8
    */
   public int BlockAlign=2;          //


   /**
    * 34-35个字节 每样本的数据位数,表示每个声道中各个样本的数据位数。如果有多个声道,对每个声道而言,样本大小都一样。量化比特数: 8 | 16
    */
   public int BitsPerSample=16;       //


   /**
    * 36-39个字节,数据标记符"data",前11个是用于鉴别文件的头部信息
    */
   public String DataTag="data";              //


   /**
    * 40-43个字节,语音数据的长度(文长-44)
    */
   public int DataSize=0;          //


   /**
    * 固定头信息文长44个字节
    */
   public static int HeadSize=44;

}

读取wav的头结构

/**
* 从data流中读取文件头
*
* @param dis 输入数据流
* @return void
*/
public void readHead(DataInputStream dis) {
   byte[] byteHeads= new byte[HeadSize];
   try {
      dis.readFully(byteHeads, 0, HeadSize);//读取pcm文件的前44个字节,保存到byteHeads当中
   } catch (IOException e) {
      logger.error(ExceptionTool.getExceptionStacksMessage(e));
   }
   readHead(byteHeads);
}


/**
* 从文件流中读取文件头
*
* @param fis 输入数据流
* @return void
*/
public void readHead(FileInputStream fis) {
   byte[] byteHeads= new byte[HeadSize];
   try {
      fis.read(byteHeads, 0, HeadSize);
   } catch (IOException e) {
      e.printStackTrace();
   }
   readHead(byteHeads);
}


/**
* 将保存了头信息的44个字节,装载到类当中
*
* @param pcmHead 保存了pcm文件的前44个字节的数组
* @return void
*/
public void readHead(byte[] pcmHead)
{
   //11个关键文件头字段
   ChunkID = SpeexUtil.readString(pcmHead, 0, 4);
   ChunkSize = SpeexUtil.readInt(pcmHead, 4);
   Format = SpeexUtil.readString(pcmHead, 8, 4);
   SubChunk1ID = SpeexUtil.readString(pcmHead, 12, 4);
   SubChunk1Size = SpeexUtil.readInt(pcmHead, 16);
   AudioFormat = SpeexUtil.readShort(pcmHead, 20);//
   NumChannels = SpeexUtil.readShort(pcmHead, 22);//
   SampleRate = SpeexUtil.readInt(pcmHead, 24);
   ByteRate = SpeexUtil.readInt(pcmHead, 28);
   BlockAlign = SpeexUtil.readShort(pcmHead, 32);//
   BitsPerSample = SpeexUtil.readShort(pcmHead, 34);//
   DataTag = SpeexUtil.readString(pcmHead, 36, 4);
   DataSize = SpeexUtil.readInt(pcmHead, 40);
}

SpeexUtil中的方法如下:

/**
* 使用Int的方式读取数据流中数据,保存在byte数组当中
*
* @param data 用于保存读取信息的byte数组
* @param offset 偏置第几个字节
* @return int
*/
public static int readInt(final byte[] data, final int offset) {
   /*
    * no 0xff on the last one to keep the sign
    */
   return (data[offset] & 0xff) | ((data[offset + 1] & 0xff) << 8)
         | ((data[offset + 2] & 0xff) << 16) | (data[offset + 3] << 24);
}

/**
* 使用short的方式读取数据流中数据,保存在byte数组当中
*
* @param data 用于保存读取信息的byte数组
* @param offset 偏置第几个字节
* @return short
*/
public static int readShort(final byte[] data, final int offset) {
   /*
    * no 0xff on the last one to keep the sign
    */
   return (data[offset] & 0xff) | (data[offset + 1] << 8);
}

/**
* 使用String的方式读取数据流中数据,保存在byte数组当中
*
* @param data 用于保存读取信息的byte数组
* @param offset 偏置第几个字节
* @return String
*/
public static String readString(final byte[] data, final int offset, final int len) {
   return new String(data, offset, len);
}

读取文件字节数组

int totalpcmsize=pcmWavHead.DataSize;


// 跳过44个字节
int wavLen= (int) (totalpcmsize)/2;   // 输入数据长度
int rawLen= wavLen/2;      // 输出数据长度


short[] wavData=new short[wavLen]; // 输入数据数组
short[] rawData = new short[ rawLen] ; // 输出数据数组


byte[] buffer1=new byte[totalpcmsize];
dis.read(buffer1, 0, totalpcmsize);


//将字节拼成short
wavData=byteArray2ShortArray(buffer1, wavLen);

将字节数组转为short数组

/**
* 将byte型的数组,转换为short型的数组
*
* @param data
* @param items
* @return short[]
*/
public static short[] byteArray2ShortArray(byte[] data, int items) {
   short[] retVal =new short[items];
   for (int i =0; i < retVal.length; i++)
      retVal[i] = (short) ((data[i *2]&0xff) | (data[i *2+1]&0xff) <<8);
   return retVal;
}

修改文件采样率为8k

采样率变了之后,数据大小也会跟着变

计算 Pcm的时长:时长=数据量byte/(采样率*(采样位数/8)*声道数)

pcmWavHead.SampleRate=8000;
pcmWavHead.DataSize=wavLen;//   (int) m_duration * (pcmWavHead.SampleRate*(16/8)*1);
byte[] headBytes=pcmWavHead.buildHeader();

//将头文件写入新wav文件
outputStream.write(headBytes, 0, PcmWavHead.HeadSize);

/**
* 根据当前类的实例,构建的一个pcm头,并以byte数组的形式返回
*
* @param
* @return byte[] byte数组的形式返回的头信息
*/
public byte[] buildHeader() {
   byte[] header = new byte[HeadSize];
   SpeexUtil.writeString(header,  0, "RIFF");
   SpeexUtil.writeInt   (header,  4, DataSize+HeadSize-8);//总体长度减去8
   SpeexUtil.writeString(header,  8, "WAVE");
   SpeexUtil.writeString(header, 12, "fmt ");
   SpeexUtil.writeInt   (header, 16, 0x10);                    // Size of format chunk
   SpeexUtil.writeShort (header, 20, (short) 0x01);          // Format tag: PCM
   SpeexUtil.writeShort (header, 22, (short) NumChannels);      // Number of channels
   SpeexUtil.writeInt   (header, 24, SampleRate);            // Sampling frequency
   SpeexUtil.writeInt   (header, 28, SampleRate*NumChannels * (BitsPerSample/8)); // Average bytes per second
   SpeexUtil.writeShort (header, 32, (short) NumChannels * (BitsPerSample/8));    // Blocksize of data
   SpeexUtil.writeShort (header, 34, (short) BitsPerSample);            // Bits per sample
   SpeexUtil.writeString(header, 36, "data");
   SpeexUtil.writeInt   (header, 40, DataSize); // Data Size
   return header;
}

抽取数据转为8k

//抽取数据 将16k转为 8K
for(int i=0;i<rawLen-1;i++){
   rawData[i]=(short) ((wavData[i*2] + wavData[(i+1)*2])/2);
}


for (int i = 0; i < rawLen-1; i++) {
   //写数据,不减1 数组会越界why?
   writeShort(outputStream, rawData[i]);
}

总 结

转化wav音频的转化率还是比较复杂的,涉及到底层数组的的操作。原理就是将文件头保存的采样率16k改为8k,再计算文件的大小,并将文件数据转化为8k。这个过程还是需要对wav文件的结构熟悉。总之,这个部分还是很复杂的,我现在还只是知道个大概。今天将这个知识点写出来,就是想将自己的思考整理一下吧。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值