安卓音頻輸出設置步驟:android播放音頻文件

無論是文字、圖像還是聲音,都必須以一定的格式來組織和存儲起來,這樣播放器才知道以怎樣的方式去解析這一段數據,例如,對於原始的圖像數據,我們常見的格式有 YUV、Bitmap,而對於音頻來說,最簡單常見的格式就是 wav 格式了。

wav 格式,與 bitmap 一樣,都是微軟開發的一種文件格式規範,它們都有一個相似之處,就是整個文件分為兩部分,第一部分是「文件頭」,記錄重要的參數信息,對於音頻而言,就包括:採樣率、通道數、位寬等等,對於圖像而言,就包括:圖像的寬高、色彩位數等等;第二部分是「數據塊」,即一幀一幀的二進位數據,對於音頻而言,就是原始的 PCM 數據;對於圖像而言,就是 RGB 數據。

前面幾篇文章講了如何利用 Android 平台的 API 完成原始音頻信號的採集和播放,而本文則重點關注如何在 Android 平台上,將採集到的 PCM 音頻數據保存到 wav 文件,同時,也介紹如何讀取和解析 wav 文件。

而文章最後,我還會給出一段 AudioDemo 程序,該程序將最近的幾篇文章涉及到的代碼綜合起來了,演示了一個完整的 Android 音頻從採集到播放的全過程。

下面言歸正傳,講講如何讀寫 wav 文件格式。

1. 文件頭

首先,我們了解一下 wav 格式的「文件頭」

Android音頻開發:如何存儲和解析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加入(需要自取)

Android音頻開發:如何存儲和解析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 項目中。

原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/223110.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
投稿專員的頭像投稿專員
上一篇 2024-12-09 14:14
下一篇 2024-12-09 14:14

相關推薦

發表回復

登錄後才能評論