音视频系列
什么是wav
wav是一种无损的音频文件格式,wav文件有两部分,第一部分是文件头,记录一些重要的参数信息,如音频的采样率,通道数,数据位宽,第二部分是数据部分,数据部分可以是PCM,也可以是其它的编码格式的数据
为什么要将音频存储wav格式
存储为该格式,音乐播放器可以通过读取wav头,识别出它是音频文件,从而进行播放。
因为后缀名是可以任意修改的,不能简单的通过后缀名来判断该文件是否是音频文件
wav与pcm的区别
pcm是一种未经压缩的编码方式
wav是一种无损的音频文件格式
wav文件结构说明
little
小端法,低位字节放在内存的低地址端
big
大端法,低位字节放在内存的高地址端
public final void writeInt(int v) throws IOException { out.write((v >>> 24) & 0xFF); out.write((v >>> 16) & 0xFF); out.write((v >>> 8) & 0xFF); out.write((v >>> 0) & 0xFF); incCount(4); }
write(int)和writeInt(int)区别
write只写入最低的8位
writeInt会按大端法写
字段详细说明
WaveHeader代码
public class WavFileHeader { public static final int WAV_FILE_HEADER_SIZE = 44; public static final int WAV_CHUNKSIZE_EXCLUDE_DATA = 36; public static final int WAV_CHUNKSIZE_OFFSET = 4; public static final int WAV_SUB_CHUNKSIZE1_OFFSET = 16; public static final int WAV_SUB_CHUNKSIZE2_OFFSET = 40; public String mChunkID="RIFF"; public int mChunkSize=0; public String mFormat="WAVE"; public String mSubChunk1ID="fmt "; public int mSubChunk1Size = 16; public short mAudioFormat = 1; public short mNumChannel = 1; public int mSampleRate = 8000; public int mByteRate = 0; public short mBlockAlign = 0; public short mBitsPerSample = 8; public String mSubChunk2ID = "data"; public int mSubChunk2Size = 0; public WavFileHeader(){ } public WavFileHeader(int sampleRateInHz, int channels, int bitsPerSample){ mSampleRate = sampleRateInHz; mNumChannel = (short) channels; mBitsPerSample = (short) bitsPerSample; mByteRate = mSampleRate * mNumChannel * mBitsPerSample / 8; mBlockAlign = (short) (mNumChannel * mBitsPerSample / 8); } }
将录音存储为wav文件
public class WavFileWriter { private static final String TAG = "WavFileWriter"; private String mFilePath; private int mDataSize = 0; private DataOutputStream dos; /** * * @param filePath * @param sampleRateInHz 采样率 44100 * @param channels 声道数 1单声道 2双声道 * @param bitsPerSample 每个样点对应的位数 16 * @return */ public boolean openFile(String filePath, int sampleRateInHz, int channels, int bitsPerSample) { if (dos != null) { closeFile(); } mFilePath = filePath; try { dos = new DataOutputStream(new FileOutputStream(mFilePath)); return writeHeader(sampleRateInHz, channels, bitsPerSample); } catch (FileNotFoundException e) { e.printStackTrace(); return false; } } public boolean closeFile() { boolean result=false; if (dos != null) { try { result=writeDataSize(); dos.close(); dos=null; } catch (IOException e) { e.printStackTrace(); } } return result; } public boolean writeData(byte[] buffer, int offset, int count) { if (dos == null) { return false; } try { dos.write(buffer, offset, count); mDataSize += count; } catch (IOException e) { e.printStackTrace(); return false; } return true; } /** * 将一些需要计算出来的字段重新赋值 * mChunkSize 位置4-8,值=36+原始音频数据大小 * mSubChunk1Size 固定值16 * mSubChunk2Size 位置40-44 值=原始音频数据大小 */ private boolean writeDataSize() { if (dos == null) { return false; } try { RandomAccessFile waveAccessFile = new RandomAccessFile(mFilePath, "rw"); waveAccessFile.seek(WavFileHeader.WAV_CHUNKSIZE_OFFSET); waveAccessFile.write(intToByteArray(WavFileHeader.WAV_CHUNKSIZE_EXCLUDE_DATA + mDataSize), 0, 4); waveAccessFile.seek(WavFileHeader.WAV_SUB_CHUNKSIZE2_OFFSET); waveAccessFile.write(intToByteArray(mDataSize), 0, 4); waveAccessFile.close(); } catch (FileNotFoundException e) { e.printStackTrace(); return false; } catch (IOException e) { e.printStackTrace(); return false; } return true; } private boolean writeHeader(int sampleRateInHz, int channels, int bitsPerSample) { if (dos == null) { return false; } WavFileHeader header = new WavFileHeader(sampleRateInHz, channels, bitsPerSample); //按照wav文件结构依次写入 try { dos.writeBytes(header.mChunkID); //这里不直接用writeInt的原因是它采用的大端法存储 dos.write(intToByteArray(header.mChunkSize), 0, 4); dos.writeBytes(header.mFormat); dos.writeBytes(header.mSubChunk1ID); dos.write(intToByteArray(header.mSubChunk1Size), 0, 4); dos.write(shortToByteArray(header.mAudioFormat), 0, 2); dos.write(shortToByteArray(header.mNumChannel), 0, 2); dos.write(intToByteArray(header.mSampleRate), 0, 4); dos.write(intToByteArray(header.mByteRate), 0, 4); dos.write(shortToByteArray(header.mBlockAlign), 0, 2); dos.write(shortToByteArray(header.mBitsPerSample), 0, 2); dos.writeBytes(header.mSubChunk2ID); dos.write(intToByteArray(header.mSubChunk2Size), 0, 4); } catch (IOException e) { e.printStackTrace(); return false; } return true; } private static byte[] intToByteArray(int data) { return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(data).array(); } private static byte[] shortToByteArray(short data) { return ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(data).array(); } }
解析wav文件并播放
public class WavFileReader { private static final String TAG="WavFileReader"; private DataInputStream dis; private WavFileHeader mWavFileHeader; public WavFileHeader getWavFileHeader(){ return mWavFileHeader; } public boolean openFile(String filePath){ if(dis!=null){ closeFile(); } try { dis=new DataInputStream(new FileInputStream(filePath)); } catch (FileNotFoundException e) { e.printStackTrace(); } return readHeader(); } public void closeFile(){ if(dis!=null){ try { dis.close(); dis=null; } catch (IOException e) { e.printStackTrace(); } } } public int readData(byte[] buffer, int offset, int count) { if (dis == null || mWavFileHeader == null) { return -1; } try { int nbytes = dis.read(buffer, offset, count); if (nbytes == -1) { return 0; } return nbytes; } catch (IOException e) { e.printStackTrace(); } return -1; } /** *read和read(byte b[]) * read每次读取一个字节,返回0-255的int字节值 * read(byte b[])读取一定数量的字节,返回实际读取的字节的数量 */ private boolean readHeader(){ if(dis==null){ return false; } WavFileHeader header=new WavFileHeader(); byte[] intValue = new byte[4]; byte[] shortValue = new byte[2]; try { header.mChunkID = "" + (char) dis.readByte() + (char) dis.readByte() + (char) dis.readByte() + (char) dis.readByte(); Log.d(TAG, "Read file chunkID:" + header.mChunkID); dis.read(intValue); header.mChunkSize=byteArrayToInt(intValue); Log.d(TAG, "Read file chunkSize:" + header.mChunkSize); header.mFormat = "" + (char) dis.readByte() + (char) dis.readByte() + (char) dis.readByte() + (char) dis.readByte(); Log.d(TAG, "Read file format:" + header.mFormat); header.mSubChunk1ID = "" + (char) dis.readByte() + (char) dis.readByte() + (char) dis.readByte() + (char) dis.readByte(); Log.d(TAG, "Read fmt chunkID:" + header.mSubChunk1ID); dis.read(intValue); header.mSubChunk1Size = byteArrayToInt(intValue); Log.d(TAG, "Read fmt chunkSize:" + header.mSubChunk1Size); dis.read(shortValue); header.mAudioFormat = byteArrayToShort(shortValue); Log.d(TAG, "Read audioFormat:" + header.mAudioFormat); dis.read(shortValue); header.mNumChannel = byteArrayToShort(shortValue); Log.d(TAG, "Read channel number:" + header.mNumChannel); dis.read(intValue); header.mSampleRate = byteArrayToInt(intValue); Log.d(TAG, "Read samplerate:" + header.mSampleRate); dis.read(intValue); header.mByteRate = byteArrayToInt(intValue); Log.d(TAG, "Read byterate:" + header.mByteRate); dis.read(shortValue); header.mBlockAlign = byteArrayToShort(shortValue); Log.d(TAG, "Read blockalign:" + header.mBlockAlign); dis.read(shortValue); header.mBitsPerSample = byteArrayToShort(shortValue); Log.d(TAG, "Read bitspersample:" + header.mBitsPerSample); header.mSubChunk2ID = "" + (char) dis.readByte() + (char) dis.readByte() + (char) dis.readByte() + (char) dis.readByte(); Log.d(TAG, "Read data chunkID:" + header.mSubChunk2ID); dis.read(intValue); header.mSubChunk2Size = byteArrayToInt(intValue); Log.d(TAG, "Read data chunkSize:" + header.mSubChunk2Size); Log.d(TAG, "Read wav file success !"); } catch (IOException e) { e.printStackTrace(); return false; } mWavFileHeader=header; return true; } private int byteArrayToInt(byte[] b){ return ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).getInt(); } private short byteArrayToShort(byte[] b){ return ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).getShort(); } }
测试
public class AudioWavActivity extends UIRootActivity { private Button btn_audio_record; private Button btn_audio_record_play; private AudioCapture audioCapture; private AudioPlayer audioPlayer; private WavFileWriter wavFileWriter; private WavFileReader wavFileReader; private boolean isReading; private String path=""; @Override protected int getLayoutId() { return R.layout.activity_media_audio; } @Override protected void initTitle() { head_title.setText("wav音频文件的存储和解析"); } @Override public void initView() { btn_audio_record=findViewById(R.id.btn_audio_record); btn_audio_record_play=findViewById(R.id.btn_audio_record_play); } @Override public void initData() { path=FileUtil.getAudioDir(this)+"/audioTest.wav"; audioCapture=new AudioCapture(); audioPlayer=new AudioPlayer(); wavFileReader=new WavFileReader(); wavFileWriter=new WavFileWriter(); String des = "录音权限被禁止,我们需要打开录音权限"; String[] permissions = new String[]{Manifest.permission.RECORD_AUDIO}; baseAt.requestPermissions(des, permissions, 100, new PermissionsResultListener() { @Override public void onPermissionGranted() { } @Override public void onPermissionDenied() { finish(); } }); } @Override public void initEvent() { btn_audio_record.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if(event.getAction()==MotionEvent.ACTION_DOWN){ Log.d("TAG","按住"); start(); }else if(event.getAction()==MotionEvent.ACTION_UP){ Log.d("TAG","松开"); stop(); } return false; } }); btn_audio_record_play.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { play(); } }); } //播放录音 private void play(){ isReading=true; wavFileReader.openFile(path); audioPlayer.startPlay(); new AudioTrackThread().start(); } private class AudioTrackThread extends Thread{ @Override public void run() { byte[] buffer = new byte[1024]; while (isReading && wavFileReader.readData(buffer,0,buffer.length)>0){ audioPlayer.play(buffer,0,buffer.length); } audioPlayer.stopPlay(); wavFileReader.closeFile(); } } //开始录音 private void start(){ wavFileWriter.openFile(path,44100,2,16); btn_audio_record.setText("松开 结束"); audioCapture.startRecord(); audioCapture.setOnAudioFrameCaptureListener(new AudioCapture.onAudioFrameCaptureListener() { @Override public void onAudioFrameCapture(byte[] audioData) { wavFileWriter.writeData(audioData,0,audioData.length); } }); } //结束录音 private void stop(){ btn_audio_record.setText("按住 录音"); audioCapture.stopRecord(); wavFileWriter.closeFile(); } }