音视频系列

什么是wav

wav是一种无损的音频文件格式,wav文件有两部分,第一部分是文件头,记录一些重要的参数信息,如音频的采样率,通道数,数据位宽,第二部分是数据部分,数据部分可以是PCM,也可以是其它的编码格式的数据

为什么要将音频存储wav格式

存储为该格式,音乐播放器可以通过读取wav头,识别出它是音频文件,从而进行播放。
因为后缀名是可以任意修改的,不能简单的通过后缀名来判断该文件是否是音频文件

wav与pcm的区别

pcm是一种未经压缩的编码方式
wav是一种无损的音频文件格式

wav文件结构说明

wav文件结构图.png

little
小端法,低位字节放在内存的低地址端
big
大端法,低位字节放在内存的高地址端

public final void writeInt(int v) throws IOException {
out.write((v >>> 24) & 0xFF);
out.write((v >>> 16) & 0xFF);
out.write((v >>> 8) & 0xFF);
out.write((v >>> 0) & 0xFF);
incCount(4);
}

write(int)和writeInt(int)区别
write只写入最低的8位
writeInt会按大端法写

字段详细说明

WaveHeader代码

public class WavFileHeader {

  public static final int WAV_FILE_HEADER_SIZE = 44;
public static final int WAV_CHUNKSIZE_EXCLUDE_DATA = 36; public static final int WAV_CHUNKSIZE_OFFSET = 4;
public static final int WAV_SUB_CHUNKSIZE1_OFFSET = 16;
public static final int WAV_SUB_CHUNKSIZE2_OFFSET = 40; 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;
mNumChannel = (short) channels;
mBitsPerSample = (short) bitsPerSample;
mByteRate = mSampleRate * mNumChannel * mBitsPerSample / 8;
mBlockAlign = (short) (mNumChannel * mBitsPerSample / 8);
}
}

将录音存储为wav文件

public class WavFileWriter {

private static final String TAG = "WavFileWriter";

private String mFilePath;
private int mDataSize = 0;
private DataOutputStream dos; /**
*
* @param filePath
* @param sampleRateInHz 采样率 44100
* @param channels 声道数 1单声道 2双声道
* @param bitsPerSample 每个样点对应的位数 16
* @return
*/
public boolean openFile(String filePath, int sampleRateInHz, int channels, int bitsPerSample) {
if (dos != null) {
closeFile();
} mFilePath = filePath;
try {
dos = new DataOutputStream(new FileOutputStream(mFilePath));
return writeHeader(sampleRateInHz, channels, bitsPerSample);
} catch (FileNotFoundException e) {
e.printStackTrace();
return false;
}
} public boolean closeFile() {
boolean result=false;
if (dos != null) {
try {
result=writeDataSize();
dos.close();
dos=null;
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
} public boolean writeData(byte[] buffer, int offset, int count) {
if (dos == null) {
return false;
}
try {
dos.write(buffer, offset, count);
mDataSize += count;
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
} /**
* 将一些需要计算出来的字段重新赋值
* mChunkSize 位置4-8,值=36+原始音频数据大小
* mSubChunk1Size 固定值16
* mSubChunk2Size 位置40-44 值=原始音频数据大小
*/
private boolean writeDataSize() {
if (dos == null) {
return false;
}
try {
RandomAccessFile waveAccessFile = new RandomAccessFile(mFilePath, "rw");
waveAccessFile.seek(WavFileHeader.WAV_CHUNKSIZE_OFFSET);
waveAccessFile.write(intToByteArray(WavFileHeader.WAV_CHUNKSIZE_EXCLUDE_DATA + mDataSize), 0, 4);
waveAccessFile.seek(WavFileHeader.WAV_SUB_CHUNKSIZE2_OFFSET);
waveAccessFile.write(intToByteArray(mDataSize), 0, 4);
waveAccessFile.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
return false;
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
} private boolean writeHeader(int sampleRateInHz, int channels, int bitsPerSample) {
if (dos == null) {
return false;
} WavFileHeader header = new WavFileHeader(sampleRateInHz, channels, bitsPerSample); //按照wav文件结构依次写入
try {
dos.writeBytes(header.mChunkID);
//这里不直接用writeInt的原因是它采用的大端法存储
dos.write(intToByteArray(header.mChunkSize), 0, 4);
dos.writeBytes(header.mFormat);
dos.writeBytes(header.mSubChunk1ID);
dos.write(intToByteArray(header.mSubChunk1Size), 0, 4);
dos.write(shortToByteArray(header.mAudioFormat), 0, 2);
dos.write(shortToByteArray(header.mNumChannel), 0, 2);
dos.write(intToByteArray(header.mSampleRate), 0, 4);
dos.write(intToByteArray(header.mByteRate), 0, 4);
dos.write(shortToByteArray(header.mBlockAlign), 0, 2);
dos.write(shortToByteArray(header.mBitsPerSample), 0, 2);
dos.writeBytes(header.mSubChunk2ID);
dos.write(intToByteArray(header.mSubChunk2Size), 0, 4);
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
} 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();
}
}

解析wav文件并播放

public class WavFileReader {
private static final String TAG="WavFileReader"; private DataInputStream dis;
private WavFileHeader mWavFileHeader; public WavFileHeader getWavFileHeader(){
return mWavFileHeader;
} public boolean openFile(String filePath){
if(dis!=null){
closeFile();
}
try {
dis=new DataInputStream(new FileInputStream(filePath));
} catch (FileNotFoundException e) {
e.printStackTrace();
} return readHeader();
} public void closeFile(){
if(dis!=null){
try {
dis.close();
dis=null;
} catch (IOException e) {
e.printStackTrace();
} }
} public int readData(byte[] buffer, int offset, int count) {
if (dis == null || mWavFileHeader == null) {
return -1;
} try {
int nbytes = dis.read(buffer, offset, count);
if (nbytes == -1) {
return 0;
}
return nbytes;
} catch (IOException e) {
e.printStackTrace();
} return -1;
} /**
*read和read(byte b[])
* read每次读取一个字节,返回0-255的int字节值
* read(byte b[])读取一定数量的字节,返回实际读取的字节的数量
*/
private boolean readHeader(){
if(dis==null){
return false;
} WavFileHeader header=new WavFileHeader();
byte[] intValue = new byte[4];
byte[] shortValue = new byte[2]; try {
header.mChunkID = "" + (char) dis.readByte() + (char) dis.readByte() + (char) dis.readByte() + (char) dis.readByte();
Log.d(TAG, "Read file chunkID:" + header.mChunkID); dis.read(intValue);
header.mChunkSize=byteArrayToInt(intValue);
Log.d(TAG, "Read file chunkSize:" + header.mChunkSize); header.mFormat = "" + (char) dis.readByte() + (char) dis.readByte() + (char) dis.readByte() + (char) dis.readByte();
Log.d(TAG, "Read file format:" + header.mFormat); header.mSubChunk1ID = "" + (char) dis.readByte() + (char) dis.readByte() + (char) dis.readByte() + (char) dis.readByte();
Log.d(TAG, "Read fmt chunkID:" + header.mSubChunk1ID); dis.read(intValue);
header.mSubChunk1Size = byteArrayToInt(intValue);
Log.d(TAG, "Read fmt chunkSize:" + header.mSubChunk1Size); dis.read(shortValue);
header.mAudioFormat = byteArrayToShort(shortValue);
Log.d(TAG, "Read audioFormat:" + header.mAudioFormat); dis.read(shortValue);
header.mNumChannel = byteArrayToShort(shortValue);
Log.d(TAG, "Read channel number:" + header.mNumChannel); dis.read(intValue);
header.mSampleRate = byteArrayToInt(intValue);
Log.d(TAG, "Read samplerate:" + header.mSampleRate); dis.read(intValue);
header.mByteRate = byteArrayToInt(intValue);
Log.d(TAG, "Read byterate:" + header.mByteRate); dis.read(shortValue);
header.mBlockAlign = byteArrayToShort(shortValue);
Log.d(TAG, "Read blockalign:" + header.mBlockAlign); dis.read(shortValue);
header.mBitsPerSample = byteArrayToShort(shortValue);
Log.d(TAG, "Read bitspersample:" + header.mBitsPerSample); header.mSubChunk2ID = "" + (char) dis.readByte() + (char) dis.readByte() + (char) dis.readByte() + (char) dis.readByte();
Log.d(TAG, "Read data chunkID:" + header.mSubChunk2ID); dis.read(intValue);
header.mSubChunk2Size = byteArrayToInt(intValue);
Log.d(TAG, "Read data chunkSize:" + header.mSubChunk2Size); Log.d(TAG, "Read wav file success !");
} catch (IOException e) {
e.printStackTrace();
return false;
} mWavFileHeader=header;
return true;
} private int byteArrayToInt(byte[] b){
return ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).getInt();
} private short byteArrayToShort(byte[] b){
return ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).getShort();
}
}

测试

public class AudioWavActivity extends UIRootActivity {

private Button btn_audio_record;
private Button btn_audio_record_play; private AudioCapture audioCapture;
private AudioPlayer audioPlayer; private WavFileWriter wavFileWriter;
private WavFileReader wavFileReader;
private boolean isReading; private String path=""; @Override
protected int getLayoutId() {
return R.layout.activity_media_audio;
} @Override
protected void initTitle() {
head_title.setText("wav音频文件的存储和解析");
} @Override
public void initView() {
btn_audio_record=findViewById(R.id.btn_audio_record);
btn_audio_record_play=findViewById(R.id.btn_audio_record_play);
} @Override
public void initData() {
path=FileUtil.getAudioDir(this)+"/audioTest.wav";
audioCapture=new AudioCapture();
audioPlayer=new AudioPlayer();
wavFileReader=new WavFileReader();
wavFileWriter=new WavFileWriter(); String des = "录音权限被禁止,我们需要打开录音权限";
String[] permissions = new String[]{Manifest.permission.RECORD_AUDIO};
baseAt.requestPermissions(des, permissions, 100, new PermissionsResultListener() {
@Override
public void onPermissionGranted() { }
@Override
public void onPermissionDenied() {
finish(); }
}); } @Override
public void initEvent() {
btn_audio_record.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction()==MotionEvent.ACTION_DOWN){
Log.d("TAG","按住");
start();
}else if(event.getAction()==MotionEvent.ACTION_UP){
Log.d("TAG","松开");
stop();
}
return false;
}
}); btn_audio_record_play.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
play(); }
}); } //播放录音
private void play(){
isReading=true;
wavFileReader.openFile(path);
audioPlayer.startPlay();
new AudioTrackThread().start();
} private class AudioTrackThread extends Thread{
@Override
public void run() {
byte[] buffer = new byte[1024];
while (isReading && wavFileReader.readData(buffer,0,buffer.length)>0){
audioPlayer.play(buffer,0,buffer.length);
}
audioPlayer.stopPlay();
wavFileReader.closeFile();
}
} //开始录音
private void start(){
wavFileWriter.openFile(path,44100,2,16);
btn_audio_record.setText("松开 结束");
audioCapture.startRecord();
audioCapture.setOnAudioFrameCaptureListener(new AudioCapture.onAudioFrameCaptureListener() {
@Override
public void onAudioFrameCapture(byte[] audioData) {
wavFileWriter.writeData(audioData,0,audioData.length);
}
});
} //结束录音
private void stop(){
btn_audio_record.setText("按住 录音");
audioCapture.stopRecord();
wavFileWriter.closeFile();
}
}

音视频学习系列第(三)篇---wav文件的存储和解析的更多相关文章

  1. 音视频学习系列第(七)篇---MediaCodec的使用

    音视频系列 什么是MediaCodec MediaCodec是安卓官方提供的一套用于音视频编码和解码的API,该API是在安卓4.1(API 16)引入的,因此只能用于4.1以上的手机 MediaCo ...

  2. 音视频学习系列第(五)篇---MediaRecorder的使用

    音视频系列 什么是MediaRecorder MediaRecorder是安卓提供的一个用于音视频采集的类 在前几篇文章中,我们已经介绍了如何进行音频和视频的采集,即通过AudioRecord采集音频 ...

  3. Android 音视频开发(一) : 通过三种方式绘制图片

    版权声明:转载请说明出处:http://www.cnblogs.com/renhui/p/7456956.html 在 Android 音视频开发学习思路 里面,我们写到了,想要逐步入门音视频开发,就 ...

  4. javascript面向对象系列第三篇——实现继承的3种形式

    × 目录 [1]原型继承 [2]伪类继承 [3]组合继承 前面的话 学习如何创建对象是理解面向对象编程的第一步,第二步是理解继承.本文是javascript面向对象系列第三篇——实现继承的3种形式 [ ...

  5. 实时音视频互动系列(下):基于 WebRTC 技术的实战解析

    在 WebRTC 项目中,又拍云团队做到了覆盖系统全局,保证项目进程流畅.这牵涉到主要三大块技术点: 网络端.服务端的开发和传输算法 WebRTC 协议中牵扯到服务端的应用协议和信令服务 客户端iOS ...

  6. Java命令学习系列(三)——Jmap

    Java命令学习系列(三)——Jmap 2015-05-16 分类:Java 阅读(479) 评论(0) Jmap jmap是JDK自带的工具软件,主要用于打印指定Java进程(或核心文件.远程调试服 ...

  7. 【Silverlight】Bing Maps学习系列(三):如何控制地图

    [Silverlight]Bing Maps学习系列(三):如何控制地图 本篇主要介绍如何对地图的一些常用控制操作,包括地图加载模式.根据精度和纬度定位.变焦程度等. 一.动态设置地图加载模式 在本系 ...

  8. 深入理解javascript函数系列第三篇——属性和方法

    × 目录 [1]属性 [2]方法 前面的话 函数是javascript中的特殊的对象,可以拥有属性和方法,就像普通的对象拥有属性和方法一样.甚至可以用Function()构造函数来创建新的函数对象.本 ...

  9. 深入理解javascript作用域系列第三篇——声明提升(hoisting)

    × 目录 [1]变量 [2]函数 [3]优先 前面的话 一般认为,javascript代码在执行时是由上到下一行一行执行的.但实际上这并不完全正确,主要是因为声明提升的存在.本文是深入理解javasc ...

随机推荐

  1. 软件包管理:rpm命令管理-包命名与依赖性

    rpm包的管理主要有两种方法:一种是rpm命令管理另一种是yum在线管理 注意软件包没有扩展名,写上只是为了好看,便于识别而已. 注意区别包名,包全名.之所以要区分,就是因为有些命令十分挑剔,需要跟正 ...

  2. linux命令:帮助命令

    帮助命令:man 命令名称:man 命令英文原意:manual 命令所在路径:/usr/bin/man 执行权限:所有用户 语法:man [命令或配置文件] 功能描述:获得帮助信息 范例:$man l ...

  3. 开发安卓安装流程(codorva+ionic)

    开发安卓安装流程 0 安装操作系统  Win10   用户名称尽量英文字母加数字,避免编码问题 1 安装Java sdk 1.8.0_45   所需文件 jdk-8u45-windows-x64 1. ...

  4. 014-配置SSH免密钥登录

    问题:client端需要免密钥登录服务器server如何配置?1.前提:客户端已安装openssh-client;服务端已安装openssh-server;服务器端22号端口已经打开2.需要密钥登录时 ...

  5. 企业中如何批量更改mysql中表的存储引擎?

    一.首先必须熟悉Mysql中有哪些基本的数据库,在mysql中database等价于schema,默认的基本库有四个:mysql,information_schema,performance_sche ...

  6. IO—代码—基础及其用例

    字节流:文件.图片.歌曲 使用字节流的应用场景:如果是读写的数据都不需要转换成字符的时候,则使用字节流. 字节流处理单元为1个字节, 操作字节和字节数组.不能直接处理Unicode字符 字节流可用于任 ...

  7. Sizzle源码分析 (一)

    Sizzle 源码分析 (一) 2.1 稳定 版本 Sizzle 选择器引擎博大精深,下面开始阅读它的源代码,并从中做出标记 .先从入口开始,之后慢慢切入 . 入口函数 Sizzle () 源码 19 ...

  8. 神经网络 java包

    java神经网络组件Joone.Encog和Neuroph https://github.com/deeplearning4j/deeplearning4j http://muchong.com/ht ...

  9. ng-深度学习-课程笔记-0: 概述

    课程概述 这是一个专项课程(Specialization),包含5个独立的课程,学习这门课程后做了相关的笔记记录. (1) 神经网络和深度学习 (2)  改善深层神经网络:超参数调试,正则化,优化 ( ...

  10. BP神经网络的Java实现(转载)

    神经网络的计算过程 神经网络结构如下图所示,最左边的是输入层,最右边的是输出层,中间是多个隐含层,隐含层和输出层的每个神经节点,都是由上一层节点乘以其权重累加得到,标上“+1”的圆圈为截距项b,对输入 ...