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》


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




本文转自 Jhuster 51CTO博客,原文链接:http://blog.51cto.com/ticktick/1752947,如需转载请自行联系原作者

相关文章
|
3天前
|
Shell Android开发
Android系统 adb shell push/pull 禁止特定文件
Android系统 adb shell push/pull 禁止特定文件
16 1
|
4天前
|
SQL 存储 关系型数据库
数据库开发之图形化工具以及表操作的详细解析
数据库开发之图形化工具以及表操作的详细解析
21 0
|
17天前
|
存储 缓存 NoSQL
深入解析Redis:一种快速、高效的键值存储系统
**Redis** 是一款高性能的键值存储系统,以其内存数据、高效数据结构、持久化机制和丰富的功能在现代应用中占有一席之地。支持字符串、哈希、列表、集合和有序集合等多种数据结构,适用于缓存、计数、分布式锁和消息队列等场景。安装Redis涉及下载、编译和配置`redis.conf`。基本操作包括键值对的设置与获取,以及哈希、列表、集合和有序集合的操作。高级特性涵盖发布/订阅、事务处理和Lua脚本。优化策略包括选择合适数据结构、配置缓存和使用Pipeline。注意安全、监控和备份策略,以确保系统稳定和数据安全。
220 1
|
24天前
|
XML Java Android开发
Android实现自定义进度条(源码+解析)
Android实现自定义进度条(源码+解析)
51 1
|
4天前
|
SQL 存储 关系型数据库
数据库开发之mysql前言以及详细解析
数据库开发之mysql前言以及详细解析
14 0
|
28天前
|
安全 Java 数据安全/隐私保护
【深入浅出Spring原理及实战】「EL表达式开发系列」深入解析SpringEL表达式理论详解与实际应用
【深入浅出Spring原理及实战】「EL表达式开发系列」深入解析SpringEL表达式理论详解与实际应用
65 1
|
27天前
|
存储 NoSQL 算法
【Redis技术进阶之路】「底层源码解析」揭秘高效存储模型与数据结构底层实现(字典)(二)
【Redis技术进阶之路】「底层源码解析」揭秘高效存储模型与数据结构底层实现(字典)
44 0
|
3天前
|
存储 Java API
Android系统 文件访问权限笔记
Android系统 文件访问权限笔记
34 1
|
3天前
|
移动开发 Java Unix
Android系统 自动加载自定义JAR文件
Android系统 自动加载自定义JAR文件
21 1
|
3天前
|
Android开发
Android源代码定制:添加customize.mk文件进行分项目和分客户的定制
Android源代码定制:添加customize.mk文件进行分项目和分客户的定制
2 0

推荐镜像

更多