Android音頻開(kāi)發(fā):PCM轉(zhuǎn)WAV格式音頻

一、wav 和 pcm

一般通過(guò)麥克風(fēng)采集的錄音數(shù)據(jù)都是PCM格式的,即不包含頭部信息,播放器無(wú)法知道音頻采樣率、位寬等參數(shù),導(dǎo)致無(wú)法播放,顯然是非常不方便的。pcm轉(zhuǎn)換成wav,我們只需要在pcm的文件起始位置加上至少44個(gè)字節(jié)的WAV頭信息即可。

RIFF

  • WAVE文件是以RIFF(Resource Interchange File Format, "資源交互文件格式")格式來(lái)組織內(nèi)部結(jié)構(gòu)的

RIFF文件結(jié)構(gòu)可以看作是樹(shù)狀結(jié)構(gòu),其基本構(gòu)成是稱(chēng)為"塊"(Chunk)的單元.

  • WAVE文件是由若干個(gè)Chunk組成的。按照在文件中的出現(xiàn)位置包括:RIFF WAVE Chunk, Format Chunk, Fact Chunk(可選), Data Chunk。

Fact Chunk 在壓縮后或在非PCM編碼時(shí)存在

二、WAV頭文件

所有的WAV都有一個(gè)文件頭,這個(gè)文件頭記錄著音頻流的編碼參數(shù)。數(shù)據(jù)塊的記錄方式是little-endian字節(jié)順序。

image.png
偏移地址 命名 內(nèi)容
00-03 ChunkId "RIFF"
04-07 ChunkSize 下個(gè)地址開(kāi)始到文件尾的總字節(jié)數(shù)(此Chunk的數(shù)據(jù)大小)
08-11 fccType "WAVE"
12-15 SubChunkId1 "fmt ",最后一位空格。
16-19 SubChunkSize1 一般為16,表示fmt Chunk的數(shù)據(jù)塊大小為16字節(jié),即20-35
20-21 FormatTag 1:表示是PCM 編碼
22-23 Channels 聲道數(shù),單聲道為1,雙聲道為2
24-27 SamplesPerSec 采樣率
28-31 BytesPerSec 碼率 :采樣率 * 采樣位數(shù) * 聲道個(gè)數(shù),bytePerSecond = sampleRate * (bitsPerSample / 8) * channels
32-33 BlockAlign n 每次采樣的大小:位寬*聲道數(shù)/8
34-35 BitsPerSample 位寬
36-39 SubChunkId2 "data"
40-43 SubChunkSize2 音頻數(shù)據(jù)的長(zhǎng)度
44-... data 音頻數(shù)據(jù)

三、java 生成頭文件

public static class WavHeader {
    /**
     * RIFF數(shù)據(jù)塊
     */
    final String riffChunkId = "RIFF";
    int riffChunkSize;
    final String riffType = "WAVE";

    /**
     * FORMAT 數(shù)據(jù)塊
     */
    final String formatChunkId = "fmt ";
    final int formatChunkSize = 16;
    final short audioFormat = 1;
    short channels;
    int sampleRate;
    int byteRate;
    short blockAlign;
    short sampleBits;

    /**
     * FORMAT 數(shù)據(jù)塊
     */
    final String dataChunkId = "data";
    int dataChunkSize;

    WavHeader(int totalAudioLen, int sampleRate, short channels, short sampleBits) {
        this.riffChunkSize = totalAudioLen;
        this.channels = channels;
        this.sampleRate = sampleRate;
        this.byteRate = sampleRate * sampleBits / 8 * channels;
        this.blockAlign = (short) (channels * sampleBits / 8);
        this.sampleBits = sampleBits;
        this.dataChunkSize = totalAudioLen - 44;
    }

    public byte[] getHeader() {
        byte[] result;
        result = ByteUtils.merger(ByteUtils.toBytes(riffChunkId), ByteUtils.toBytes(riffChunkSize));
        result = ByteUtils.merger(result, ByteUtils.toBytes(riffType));
        result = ByteUtils.merger(result, ByteUtils.toBytes(formatChunkId));
        result = ByteUtils.merger(result, ByteUtils.toBytes(formatChunkSize));
        result = ByteUtils.merger(result, ByteUtils.toBytes(audioFormat));
        result = ByteUtils.merger(result, ByteUtils.toBytes(channels));
        result = ByteUtils.merger(result, ByteUtils.toBytes(sampleRate));
        result = ByteUtils.merger(result, ByteUtils.toBytes(byteRate));
        result = ByteUtils.merger(result, ByteUtils.toBytes(blockAlign));
        result = ByteUtils.merger(result, ByteUtils.toBytes(sampleBits));
        result = ByteUtils.merger(result, ByteUtils.toBytes(dataChunkId));
        result = ByteUtils.merger(result, ByteUtils.toBytes(dataChunkSize));
        return result;
    }
}

四、PCM轉(zhuǎn)Wav

public class WavUtils {
    private static final String TAG = WavUtils.class.getSimpleName();
        /**
         * 生成wav格式的Header
         * wave是RIFF文件結(jié)構(gòu),每一部分為一個(gè)chunk,其中有RIFF WAVE chunk,
         * FMT Chunk,F(xiàn)act chunk(可選),Data chunk
         *
         * @param totalAudioLen 不包括header的音頻數(shù)據(jù)總長(zhǎng)度
         * @param sampleRate    采樣率,也就是錄制時(shí)使用的頻率
         * @param channels      audioRecord的頻道數(shù)量
         * @param sampleBits    位寬
         */
        public static byte[] generateWavFileHeader(int totalAudioLen, int sampleRate, int channels, int sampleBits) {
            WavHeader wavHeader = new WavHeader(totalAudioLen, sampleRate, (short) channels, (short) sampleBits);
            return wavHeader.getHeader();
        }
    }

    /**
     * 將header寫(xiě)入到pcm文件中 不修改文件名
     *
     * @param file   寫(xiě)入的pcm文件
     * @param header wav頭數(shù)據(jù)
     */
    public static void writeHeader(File file, byte[] header) {
        if (!FileUtils.isFile(file)) {
            return;
        }

        RandomAccessFile wavRaf = null;
        try {
            wavRaf = new RandomAccessFile(file, "rw");
            wavRaf.seek(0);
            wavRaf.write(header);
            wavRaf.close();
        } catch (Exception e) {
            Logger.e(e, TAG, e.getMessage());
        } finally {
            try {
                if (wavRaf != null) {
                    wavRaf.close();
                }
            } catch (IOException e) {
                Logger.e(e, TAG, e.getMessage());
            }
        }
private void makeFile() {
    mergePcmFiles(recordFile, files);

    //這里實(shí)現(xiàn)上一篇未完成的工作
    byte[] header = WavUtils.generateWavFileHeader((int) resultFile.length(), currentConfig.getSampleRate(), currentConfig.getChannelCount(), currentConfig.getEncoding());
    WavUtils.writeHeader(resultFile, header);
    
    Logger.i(TAG, "錄音完成! path: %s ; 大?。?s", recordFile.getAbsoluteFile(), recordFile.length());
    }

參考鏈接:

  1. http://soundfile.sapp.org/doc/WaveFormat/
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容