Android音频开发(4):如何存储和解析wav文件

无论是文字、图像还是声音,都必须以一定的格式来组织和存储起来,这样播放器才知道以怎样的方式去解析这一段数据,例如,对于原始的图像数据,我们常见的格式有 YUV、Bitmap,而对于音频来说,最简单常见的格式就是 wav 格式了。

wav 格式,与 bitmap 一样,都是微软开发的一种文件格式规范,它们都有一个相似之处,就是整个文件分为两部分,第一部分是“文件头”,记录重要的参数信息,对于音频而言,就包括:采样率、通道数、位宽等等,对于图像而言,就包括:图像的宽高、色彩位数等等;第二部分是“数据块”,即一帧一帧的二进制数据,对于音频而言,就是原始的 PCM 数据;对于图像而言,就是 RGB 数据。

前面几篇文章讲了如何利用 Android 平台的 API 完成原始音频信号的采集和播放,而本文则重点关注如何在 Android 平台上,将采集到的 PCM 音频数据保存到 wav 文件,同时,也介绍如何读取和解析 wav 文件。

而文章最后,我还会给出一段 AudioDemo 程序,该程序将最近的几篇文章涉及到的代码综合起来了,演示了一个完整的 Android 音频从采集到播放的全过程。

下面言归正传,讲讲如何读写 wav 文件格式。

1. 文件头

首先,我们了解一下 wav 格式的“文件头”,可以参考这篇文章:《WAVE PCM soundfile format》

我们可以简单地分析一下这个 wav 格式头,它主要分为三个部分:

第一部分,属于最“顶层”的信息块,通过“ChunkID”来表示这是一个 “RIFF”格式的文件,通过“Format”填入“WAVE”来标识这是一个 wav 文件。而“ChunkSize”则记录了整个 wav 文件的字节数。

第二部分,属于“fmt”信息块,主要记录了本 wav 音频文件的详细音频参数信息,例如:通道数、采样率、位宽等等(含义请参考我的第一篇文章《Android音频开发(1):基础知识》

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

分析到这里,我想大家应该就明白了,其实,做一种多媒体格式的解析,也不是一件特别复杂的事,说白了,格式就是一种规范,告诉你,我的二进制数据是怎么存储的,你应该按照什么样的方式来解析。

具体而言,我们可以定义一个如下的 Java 类来抽象和描述 wav 文件头:

/*
 *  COPYRIGHT NOTICE  
 *  Copyright (C) 2016, Jhuster <[email protected]>
 *  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 文件。

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 项目中。

3. 小结

关于如何在 Android 平台读写 wav 格式的文件就介绍到这儿了,文章中有不清楚的地方欢迎留言或者来信 [email protected] 交流,或者关注我的新浪微博 @卢_俊 或者 微信公众号 @Jhuster 获取最新的文章和资讯。

时间: 2024-08-04 10:27:46

Android音频开发(4):如何存储和解析wav文件的相关文章

Android应用开发之存储和解析xml文件

package com.m520it.saveandparseXML; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.TextView; import com.m520it.temp1.R; public class MainActivity extends Activity { @Override protected void onCr

Android音频开发(5):音频数据的编解码

前面四篇文章分别介绍了音频开发必备的基础知识.如何采集一帧音频.如何播放一帧音频.如何存储和解析wav格式的文件,建议有兴趣的小伙伴们先读一读,本文则重点关注如何对一帧音频数据进行编码和解码. 1. Android 官方的 MediaCodec API 首先,我们了解一下 Android 官方提供的音频编解码的 API,即 MediaCodec 类,该 API 是在 Andorid 4.1 (API 16) 版本引入的,因此只能工作于 Android 4.1 以上的手机上. 1.1 MediaC

Android音频开发之——如何播放一帧音频

本文重点关注如何在Android平台上播放一帧音频数据.阅读本文之前,建议先读一下<Android音频开发(1):基础知识>,因为音频开发过程中,经常要涉及到这些基础知识,掌握了这些重要的概念后,开发过程中的很多参数和流程就会更加容易理解. Android SDK 提供了3套音频播放的API,分别是:MediaPlayer,SoundPool,AudioTrack,关于它们的区别可以看这篇文章:<Intro to the three Android Audio APIs>,简单来说

Android音频开发(2):如何采集一帧音频

本文重点关注如何在Android平台上采集一帧音频数据.阅读本文之前,建议先读一下我的上一篇文章<Android音频开发(1):基础知识>,因为音频开发过程中,经常要涉及到这些基础知识,掌握了这些重要的概念后,开发过程中的很多参数和流程就会更加容易理解. Android SDK 提供了两套音频采集的API,分别是:MediaRecorder 和 AudioRecord,前者是一个更加上层一点的API,它可以直接把手机麦克风录入的音频数据进行编码压缩(如AMR.MP3等)并存成文件,而后者则更接

Android音频开发(3):如何播放一帧音频

本文重点关注如何在Android平台上播放一帧音频数据.阅读本文之前,建议先读一下<Android音频开发(1):基础知识>,因为音频开发过程中,经常要涉及到这些基础知识,掌握了这些重要的概念后,开发过程中的很多参数和流程就会更加容易理解. Android SDK 提供了3套音频播放的API,分别是:MediaPlayer,SoundPool,AudioTrack,关于它们的区别可以看这篇文章:<Intro to the three Android Audio APIs>,简单来说

Android音频开发(7):使用 OpenSL ES API(下)

本文是我的<Android音频开发>系列的第七篇文章,上一篇文章总整体上介绍了 Android OpenSL ES API 的基本概况,告诉了大家这个框架有什么特性,可以做什么,不能做什么.本文则重点介绍 OpenSL ES 框架及其API接口的一些关键的设计和概念,只有理解了它们,你才能更好地读懂 OpenSL ES 的相关代码.示例代码则放到了文章的最后,相信大家理解了这些基本的概念后,就能很容易地读懂这些代码的细节了. 1. 面向对象的 C 语言接口 OpenSL ES 虽然是 C 语言

Python解析Wav文件并绘制波形的方法

资源下载 #本文PDF版下载 Python解析Wav文件并绘制波形的方法 #本文代码下载 Wav波形绘图代码 #本文实例音频文件night.wav下载 音频文件下载 (石进-夜的钢琴曲) 前言 在现在繁忙的生活中,我们经常会听些歌来放松一下自己,我们经常会从各种播放软件中听自己喜欢的歌,并且往往我们会下载一部分歌曲,而现在音频的种类也相当繁多,像是Wav,Mp3,FLAC,AAC等等很多格式,最近由于需要做一个能够分析Wav格式音频的波形来取得一些数据比如获取人录音时是否说完等等用途.本周先对解

Android音频开发(1):基础知识

先来点闲言碎语,前段时间我有一段感悟:Android开发,本身并不是一个可以走得多远的方向,它只是一个平台,提供了许多封装好的API,让大家能够快速开发出针对特定业务的应用. 真正有价值的地方就在于Android与具体的业务方向结合,比如:Android与音视频技术,Android与智能硬件交互,Android与前端技术的融合与探索,Android信息安全,Android源码深度定制等等. 我一直比较看好音视频/多媒体方向,希望在此能够深入积累和探索,前段时间我发布了一款Android VoIP

Android音频开发(6):使用 OpenSL ES API(上)

前面几篇文章介绍了如何在 Java 层,利用 Android 提供的 AudioRecord 采集音频,利用 AudioTrack 播放音频,利用 MediaCodec 来编解码,这些 API 均是 Android 提供的 Java 层 API,无论是采集.播放还是编解码,这些 API 接口都需要将音频数据从 Java 拷贝到 native 层,或者从 native 层拷贝到 Java,如果希望减少拷贝,开发更加高效的 Android 音频应用,则建议使用 Android NDK 提供的 Ope