我们可以简单地分析一下这个 wav 格式头,它主要分为三个部分:
第一部分,属于最“顶层”的信息块,通过“ChunkID”来表示这是一个 “RIFF”格式的文件,通过“Format”填入“WAVE”来标识这是一个 wav 文件。而“ChunkSize”则记录了整个 wav 文件的字节数
第二部分,属于“fmt”信息块,主要记录了本 wav 音频文件的详细音频参数信息,例如:通道数、采样率、位宽等等
第三部分,属于“data”信息块,由“Subchunk2Size”这个字段来记录后面存储的二进制原始音频数据的长度。
分析到这里,我想大家应该就明白了,其实,做一种多媒体格式的解析,也不是一件特别复杂的事,说白了,格式就是一种规范,告诉你,我的二进制数据是怎么存储的,你应该按照什么样的方式来解析。
具体而言,我们可以定义一个如下的 Java 类来抽象和描述 wav 文件头:
/* * COPYRIGHT NOTICE * Copyright (C) 2016, Jhuster <lujun.hust@gmail.com> * https://github.com/Jhuster/AudioDemo * * @license under the Apache License, Version 2.0 * * @file WavFileHeader.java * * @version 1.0 * @author Jhuster * @date 2016/03/19 */package com.jhuster.audiodemo.api;public class WavFileHeader { 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 bitsPerSample, int channels) { mSampleRate = sampleRateInHz; mBitsPerSample = (short)bitsPerSample; mNumChannel = (short)channels; mByteRate = mSampleRate*mNumChannel*mBitsPerSample/8; mBlockAlign = (short)(mNumChannel*mBitsPerSample/8); }}
具体每一个字段的含义,可以参考我上面给出的链接,下面我们再看看如何读写 wav 文件。
音视频开发学习地址:【免费】
FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发-学习视频教程-腾讯课堂
【文章福利】:小编整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!~点击832218493加入(需要自取)
2. 读写 wav 文件
文章开头已经说过,其实说白了,wav 文件就是一段“文件头”+“音频二进制数据”,因此:
(1)写 wav 文件,其实就是先写入一个 wav 文件头,然后再继续写入音频二进制数据即可
(2)读 wav 文件,其实也就是先读一个 wav 文件头,然后再继续读出音频二进制数据即可
那么,在动手写代码之前,有两点你需要搞清楚:
(1) wav 文件头中,有哪些是“变化的”,哪些是“不变的”?
比如:文件头开头的“RIFF”字符串就是“不变的”部分,而用来记录音频数据总长度的“Subchunk2Size”变量就是属于“变化的”部分,因为,再音频数据没有彻底全部写完之前,你是无法知道一共写入了多少字节的音频数据的,因此,这个部分,需要用一个变量记录起来,到全部写完之后,再使用 Java 的“RandomAccessFile”类,将文件指针跳转到“Subchunk2Size”字段,改写一下默认值即可。
(2) 如何把 int、short 变量与 byte[] 的转换
因为 wav 文件都是二进制的方式读写,因此,“WavFileHeader”类中定义的变量都需要转换为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();}
关于 wav 文件读写的类我已经帮大家“封装”好了,并且结合着前面几篇文章给出的音频采集和播放的代码,完成了一个 AudioDemo 程序,放在我的 Github 上了,欢迎大家下载运行测试,然后结合着代码具体学习 Android 音频相关技术,代码地址:
https://github.com/Jhuster/AudioDemo
注:本系列文章的所有代码,以后都会并入到该 demo 项目中。