無論是文字、圖像還是聲音,都必須以一定的格式來組織和存儲起來,這樣播放器才知道以怎樣的方式去解析這一段數據,例如,對於原始的圖像數據,我們常見的格式有 YUV、Bitmap,而對於音頻來說,最簡單常見的格式就是 wav 格式了。
wav 格式,與 bitmap 一樣,都是微軟開發的一種文件格式規範,它們都有一個相似之處,就是整個文件分為兩部分,第一部分是“文件頭”,記錄重要的參數信息,對於音頻而言,就包括:採樣率、通道數、位寬等等,對於圖像而言,就包括:圖像的寬高、色彩位數等等;第二部分是“數據塊”,即一幀一幀的二進制數據,對於音頻而言,就是原始的 PCM 數據;對於圖像而言,就是 RGB 數據。
前面幾篇文章講了如何利用 Android 平台的 API 完成原始音頻信號的採集和播放,而本文則重點關注如何在 Android 平台上,將採集到的 PCM 音頻數據保存到 wav 文件,同時,也介紹如何讀取和解析 wav 文件。
而文章最後,我還會給出一段 AudioDemo 程序,該程序將最近的幾篇文章涉及到的代碼綜合起來了,演示了一個完整的 Android 音頻從採集到播放的全過程。
下面言歸正傳,講講如何讀寫 wav 文件格式。
1. 文件頭
首先,我們了解一下 wav 格式的“文件頭”

我們可以簡單地分析一下這個 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 音頻相關技術,代碼地址:
註:本系列文章的所有代碼,以後都會併入到該 demo 項目中。
原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/223110.html