什么是wav
wav是一种无损的音频文件格式,wav文件有两部分,第一部分是文件头,记录一些重要的参数信息,如音频的采样率,通道数,数据位宽,第二部分是数据部分,数据部分可以是PCM,也可以是其它的编码格式的数据
为什么要将音频存储wav格式
存储为该格式,音乐播放器可以通过读取wav头,识别出它是音频文件,从而进行播放。 因为后缀名是可以任意修改的,不能简单的通过后缀名来判断该文件是否是音频文件
wav与pcm的区别
pcm是一种未经压缩的编码方式
wav是一种无损的音频文件格式
wav文件结构说明
little
小端法,低位字节放在内存的低地址端
big
大端法,低位字节放在内存的高地址端
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(); } }
摘抄
历史上的今天
暂无评论...
随机推荐
刘瑜:过去的怎样让它过去
都赫提先生,31岁。听到枪声,他慌乱中趴下,向街边的建筑匍匐,但是还没来得及抵达安全的地方,一颗子弹向他飞来,打中了他的后背。麦克吉干先生,41岁。看到中弹的都赫提跑过去施救,同时挥舞着手中的一块白手帕,以示他的救援意图,但是他还没抵达伤者,后脑门上也中了一弹,立刻倒地身亡。这是1972年1月30...
[摘]Android IO流读写文件实例2
一、超类:字节流: InputStream(读入流) OutputStream(写出流)字符流: Reader(字符 读入流) Writer (字符写出流)二、文件操作流字节流: FileInputStream ,FileOutputStream字符流: FileReader, File...
Class not found when unmarshalling
前言记录一下使用Bundle.putParcelable()或Bundle.putParcelableArrayList()传输Parcelable数量时出现的问题,方便自己查阅。正文报错日志Parcel (14686): Class not found when unmarshalli...
Android View相关摘抄
前言介绍Android的View的相关知识(主要来自《Android进阶之光》),之前记录的,附在这里方便自己查询。正文View的继承关系ViewGroup作为View或者ViewGroup这些组件的容器, 派生了多种布局控件子类, 比如LinearLayout、 RelativeLayo...
Android ContentProvider使用记录
前言ContentProvider作为Android四大组件之一都比较熟悉,之前也就使用过,但没有记录,时间一久就忘记了。因此为了方便自己查阅,这里重新记录一下。个人流水账,只方便自己查阅,不一定合适你。正文 隐藏内容!付费阅读后才能查看!¥2 ¥4多个隐藏块只需支付一次付费阅读参...
戴望舒 :寻梦者
梦会开出花来的,梦会开出娇妍的花来的:去求无价的珍宝吧。 在青色的大海里,在青色的大海的底里,深藏着金色的贝一枚。 你去攀九年的冰山吧,你去航九年的瀚海吧,然后你逢到那金色的贝。 它有天上的云雨声,它有海上的风涛声,它会使你的心沉醉。&nb...