zoukankan      html  css  js  c++  java
  • 音视频学习系列第(三)篇---wav文件的存储和解析

    音视频系列

    什么是wav

    wav是一种无损的音频文件格式,wav文件有两部分,第一部分是文件头,记录一些重要的参数信息,如音频的采样率,通道数,数据位宽,第二部分是数据部分,数据部分可以是PCM,也可以是其它的编码格式的数据

    为什么要将音频存储wav格式

    存储为该格式,音乐播放器可以通过读取wav头,识别出它是音频文件,从而进行播放。
    因为后缀名是可以任意修改的,不能简单的通过后缀名来判断该文件是否是音频文件

    wav与pcm的区别

    pcm是一种未经压缩的编码方式
    wav是一种无损的音频文件格式

    wav文件结构说明

    wav文件结构图.png

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


  • 相关阅读:
    匈牙利游戏
    钓鱼
    路由选择
    借教室
    有趣的数
    广告印刷
    海战
    暑假周进度报告(一)
    在Oracle创建一个自己用的用户及角色
    下载,安装oracle数据库以及navicat连接数据库
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/10489375.html
Copyright © 2011-2022 走看看