文档章节

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

乐搏学院
 乐搏学院
发布于 2016/10/20 14:04
字数 1504
阅读 4
收藏 0

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

 

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

 

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

 

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

 

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

 

1. 文件头

 

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

 

wKioL1btPw_xefJSAABiQscQxNA841.png

 

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

 

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

 

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

 

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

 

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

 

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

 

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

38

39

40

41

42

43

44

45

/*

 *  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 文件。

 

2. 读写 wav 文件

 

文章开头已经说过,其实说白了,wav 文件就是一段“文件头”+“音频二进制数据”,因此:

 

(1)写 wav 文件,其实就是先写入一个 wav 文件头,然后再继续写入音频二进制数据即可

(2)读 wav 文件,其实也就是先读一个 wav 文件头,然后再继续读出音频二进制数据即可

 

那么,在动手写代码之前,有两点你需要搞清楚:

 

(1) wav 文件头中,有哪些是“变化的”,哪些是“不变的”?

 

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

 

(2) 如何把 int、short 变量与 byte[] 的转换

 

因为 wav 文件都是二进制的方式读写,因此,“WavFileHeader”类中定义的变量都需要转换为byte字节流,具体转换方法如下:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

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. 小结

 

免费学习更多精品课程,登录乐搏学院官网http://h.learnbo.cn/

或关注我们的官方微博微信,还有更多惊喜哦~

本文出自 “Jhuster的专栏” 博客,请务必保留此出处http://ticktick.blog.51cto.com/823160/1750593

本文转载自:

乐搏学院
粉丝 9
博文 526
码字总数 707467
作品 0
丰台
程序员
私信 提问
直播,音视频编码器和解码器(EasyDarwin)-Android

使用摄像头采集视频数据,并通过MediaCodec进行H264编码,之后打包成RTSP格式并上传的。 TextuewView也提供了一个setTransform方法,该方法接收一个matrix参数,使用该参数对当前的渲染内容进...

shareus
2018/05/18
0
0
Android AudioTrack播放(解码)音频

-- MediaPlayer,AudioTrack 1.MediaPlayer能够播放多种格式的声音文件,比如MP3,AAC,WAV,OGG,MIDI等。MediaPlayer包括了AudioTrack。 2.AudioTrack仅仅能播放已经解码的PCM流,假设是文...

desaco
2018/12/26
0
0
Android音频开发之音频基本概念

Android音频开发之音频基本概念 采集一般使用 或者 音频采集的来源是什么? 一般是麦克风 采样率(单位:赫兹) 每秒钟音频采样点个数(8000/44100Hz),模拟信号数字化的过程,用0101来表示的数...

六号表哥
2018/03/31
0
0
Android获取WAV音频时长

1.简介 wav(Waveform Audio File Format)是微软公司开发的一种声音文件格式,它符合RIFF(Resource Interchange File Format)文件规范。WAV是最接近无损的音乐格式,所以文件大小相对也比较大...

tugele
2018/03/06
0
0
Android音视频开发之使用AudioRecord采集音频

AudioRecord 是 Android 系统提供的用于实现录音功能的 API,官方文档是这么解释的: AndioRecord类的主要功能是让各种Java应用能够管理音频资源,以便它们通过此类能够录制声音相关的硬件所...

落英坠露
2018/10/30
0
0

没有更多内容

加载失败,请刷新页面

加载更多

饿了么交付中心语言栈转型总结

前言: 本文介绍了饿了么交付中心由python语言栈转换到java语言栈大致过程,一来是对前段时间的工作做下总结,另外也是想通过此次总结为其他应用服务转型提供些借鉴。写的不好,欢迎板砖。 背...

一肥仔
19分钟前
3
0
移植linux4.14内核到4412开发板

最近法师收到了很多留言,其中有一部分问法师什么时候更新,还有一大部分问法师我是买迅为的IMX6UL精英版好呢还是买4412精英版好呢,因为我们这俩个都不贵。法师的建议的是入手4412!为什么呢...

书白
23分钟前
6
0
提高GMAT语法能力方法解析,掌握技巧高分不是梦

GMAT考试对考生语法能力的要求涉及各部分的题目,熟练掌握语法知识对于考生获得高分有巨大的帮助。因此,学好GMAT语法,显得非常重要。下面小编就介绍一些提高GMAT语法能力的方法技巧。 做题...

bole6
27分钟前
5
0
100天搞定机器学习|day54 聚类系列:层次聚类原理及案例

几张GIF理解K-均值聚类原理 k均值聚类数学推导与python实现 前文说了k均值聚类,他是基于中心的聚类方法,通过迭代将样本分到k个类中,使每个样本与其所属类的中心或均值最近。 今天我们看一...

机器学习算法与Python实战
29分钟前
4
0
创龙TI KeyStone C66x多核定点/浮点DSP TMS320C665x底板B2B连接器、电源接口和拔码开关

TL665x-EasyEVM是广州创龙基于SOM-TL665x核心板研发的一款TI C66x多核定点/浮点高性能DSP开发板,采用核心板+底板方式,底板尺寸为200mm*106.65mm,采用4*50pin和1*80pin B2B工业级连接器,稳...

Tronlong创龙
31分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部