• android音频学习笔记之wav文件


    如何存储和解析wav文件
    定义:wav格式,就是微软开发的一种文件格式规范,文件分为两部分
    (1)第一部分:文件头,记录重要的参数信息,对于音频而言,包括:采样率,通道数,位宽等等
    (2)第二部分:数据块,也就是一帧一帧的二进制数据,对于音频而言,就是原始的PCM数据

    (一)将采集的PCM音频数据保存到wav文件

    文件头
    在这里插入图片描述
    wav格式头,主要分为三个部分
    (1)属于最顶层的信息块
    ChunkID 来表示这是一个RIFF格式文件
    Format 填入WAVE 来标识这是一个wav文件
    ChunkSize 记录了整个wav文件的字节数

    (2)第二部分 属于fmt信息块
    主要记录了本wav音频文件的详细音频参数信息,例如通道数,采样率,位宽

    (3)第三部分,数据data信息块,由Subchunk2Size 这个字段来记录后面存储的二进制原始音频数据的长度

    其实,格式就是一种规范,告诉你二进制数据是怎么存储的,你应该按照什么样的方式解析
    下图就是数据怎么进行二进制存储的
    在这里插入图片描述

    所以可以写一个java类来抽象和描述wav文件头

    public class WavFileHeader {
    
        public static final int WAV_FILE_HEADER_SIZE = 44;
        public static final int WAV_CHUNKSIZE_EXCLUDE_DATA = 36;//第一部分"顶层"和第二部分"fmt"信息块共占36位
    
        public static final int WAV_CHUNKSIZE_OFFSET = 4; //ChunkSize是第4位开始,占4个byte
        public static final int WAV_SUB_CHUNKSIZE1_OFFSET = 16;//SubChunk1Size是第16位开始,占4个byte
        public static final int WAV_SUB_CHUNKSIZE2_OFFSET = 40;//SubChunk2Size是第40位开始,占4个byte
    
        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;
            mBitsPerSample = (short) bitsPerSample;
            mNumChannel = (short) channels;
            mByteRate = mSampleRate * mNumChannel * mBitsPerSample / 8;
            mBlockAlign = (short) (mNumChannel * mBitsPerSample / 8);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    也就是将上图描述的用java代码进行抽象化

    读写wav文件
    wav文件就是一段“文件头+音频二进制数据”,因此:
    (1)写wav文件,其实就是先写入一个wav文件头,然后再继续写入音频二进制数据
    (2)读wav文件,其实也就是先读一个wav文件,然后再继续读出音频二进制数据即可

    值得注意的是:
    (1)wav文件头中,有哪些是变化的,哪些是不变的: 文件头开头的“RIFF”字符串就是“不变的”部分,而用来记录音频数据总长度的“Subchunk2Size”变量就是属于“变化的”部分,因为,再音频数据没有彻底全部写完之前,你是无法知道一共写入了多少字节的音频数据的,因此,这个部分,需要用一个变量记录起来,到全部写完之后,再使用 Java 的“RandomAccessFile”类,将文件指针跳转到“Subchunk2Size”字段,改写一下默认值即可。
    (2)如何将int ,short变量与byte[] 的转换

    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();
    } 
    
    private static short byteArrayToShort(byte[] b) {
        return ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).getShort();
    }
        
    private static int byteArrayToInt(byte[] b) {
        return ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).getInt();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    参考文献:Jhuster的博客

  • 相关阅读:
    从实体经济和数字经济融合展开,思考商业模式的变化
    承前启后,Java对象内存布局和对象头
    初识Spring框架及其特点
    ffmpeg安装及使用
    Java StringBuffer.setCharAt具有什么功能呢?
    calibre和cpolar搭建一个私有的网络书库
    Git详解及 github使用
    速码!!BGP最全学习笔记:IBGP和EBGP基本配置
    使用域名转发mqtt协议,避坑指南
    MySQL 主从 AUTO_INCREMENT 不一致问题分析
  • 原文地址:https://blog.csdn.net/qq_42447739/article/details/127695600