什么是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(); } }
摘抄
历史上的今天
暂无评论...
随机推荐
林徽因:一片阳光
放了假,春初的日子松弛下来。将午未午时候的阳光,澄黄的一片,由窗棂横浸到室内,晶莹地四处射。我有点发怔,习惯地在沉寂中惊讶我的周围。我望着太阳那湛明的体质,像要辨别它那交织绚烂的色泽,追逐它那不着痕迹的流动。看它洁净地映到书桌上时,我感到桌面上平铺着一种恬静,一种精神上的豪兴,情趣上的闲逸;即或所谓...
Android中走马灯相关问题总结
前言Android开发中,应该都或多或少使用过TextView的走马灯(或跑马灯)。对于走马灯存在的问题,网上很多,我也在这里整(抄)理(袭)一下,方便自己查阅。跑马灯耗CPU可以BiuTextView替换,请访问------>《BiuTextView完美替代TextView进行跑马...
system.exit()的作用
前言记录一下,system.exit(1) 和 system.exit(0)的用法与作用。方便自己查阅和回顾。好记性不如烂笔头正文System.exit(status)不管status为何值都会退出程序,也就是后面的代码不会再执行。public static void exit(in...
冯友兰:我的读书经验
我今年八十七岁了,从七岁上学起就读书,一直读了八十年,其间基本上没有间断,不能说对于读书没有一点经验。我所读的书,大概都是文、史、哲方面的,特别是哲。我的经验总结起来有四点:(1)精其选,(2)解其言,(3)知其意,(4)明其理。先说第一点。古今中外,积累起来的书真是多极了,真是浩如烟海。但是,书...
毕淑敏:世上千寒,心中永暖
记得当年做医学生实习时,轮到去产科学接生。见那刚生下来的宝宝,一出母体,便放声大哭。倘在别处,听到有人痛嚎,众人必是关切不安,以示慰问。在产科接生室内,哭声便是捷报。若是不闻哭声,助产士便要心焦了。民间流传说,老式的接生婆如果听不到新生儿哭,会立马把孩子头朝下倒拎着,在屁股上猛砸几巴掌,娃儿惊哭出来...
Git tag 简单使用
前言打标签像其他版本控制系统(VCS)一样,Git可以给仓库历史中的某一个提交打上标签,以示重要。比较有代表性的是人们会使用这个功能来标记发布结点( v1.0 、 v2.0 等等)。在项目中,为了区分SOP的版本代码,常要求打标签。正文下面简单的介绍一下TAG的使用.列出标签git...