Android音頻開發(fā):PCM轉WAV格式音頻

一娩井、wav 和 pcm

一般通過麥克風采集的錄音數(shù)據(jù)都是PCM格式的,即不包含頭部信息锦茁,播放器無法知道音頻采樣率攘轩、位寬等參數(shù),導致無法播放码俩,顯然是非常不方便的度帮。pcm轉換成wav,我們只需要在pcm的文件起始位置加上至少44個字節(jié)的WAV頭信息即可稿存。

RIFF

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

RIFF文件結構可以看作是樹狀結構,其基本構成是稱為"塊"(Chunk)的單元.

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

Fact Chunk 在壓縮后或在非PCM編碼時存在

二瓣履、WAV頭文件

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

image.png
偏移地址 命名 內(nèi)容
00-03 ChunkId "RIFF"
04-07 ChunkSize 下個地址開始到文件尾的總字節(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ù) * 聲道個數(shù)脯宿,bytePerSecond = sampleRate * (bitsPerSample / 8) * channels
32-33 BlockAlign n 每次采樣的大心罹薄:位寬*聲道數(shù)/8
34-35 BitsPerSample 位寬
36-39 SubChunkId2 "data"
40-43 SubChunkSize2 音頻數(shù)據(jù)的長度
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轉Wav

public class WavUtils {
    private static final String TAG = WavUtils.class.getSimpleName();
        /**
         * 生成wav格式的Header
         * wave是RIFF文件結構榴芳,每一部分為一個chunk嗡靡,其中有RIFF WAVE chunk,
         * FMT Chunk窟感,F(xiàn)act chunk(可選),Data chunk
         *
         * @param totalAudioLen 不包括header的音頻數(shù)據(jù)總長度
         * @param sampleRate    采樣率,也就是錄制時使用的頻率
         * @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寫入到pcm文件中 不修改文件名
     *
     * @param file   寫入的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);

    //這里實現(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/
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末躏精,一起剝皮案震驚了整個濱河市悲幅,隨后出現(xiàn)的幾起案子幸海,更是在濱河造成了極大的恐慌诫肠,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卢佣,死亡現(xiàn)場離奇詭異重荠,居然都是意外死亡,警方通過查閱死者的電腦和手機虚茶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門戈鲁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人媳危,你說我怎么就攤上這事荞彼「粤玻” “怎么了待笑?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長抓谴。 經(jīng)常有香客問我暮蹂,道長,這世上最難降的妖魔是什么癌压? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任仰泻,我火速辦了婚禮,結果婚禮上滩届,老公的妹妹穿的比我還像新娘集侯。我一直安慰自己,他們只是感情好帜消,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布棠枉。 她就那樣靜靜地躺著,像睡著了一般泡挺。 火紅的嫁衣襯著肌膚如雪辈讶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天娄猫,我揣著相機與錄音贱除,去河邊找鬼生闲。 笑死,一個胖子當著我的面吹牛月幌,可吹牛的內(nèi)容都是我干的碍讯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼扯躺,長吁一口氣:“原來是場噩夢啊……” “哼冲茸!你這毒婦竟也來了?” 一聲冷哼從身側響起缅帘,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤轴术,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后钦无,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逗栽,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年失暂,在試婚紗的時候發(fā)現(xiàn)自己被綠了彼宠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡弟塞,死狀恐怖凭峡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情决记,我是刑警寧澤摧冀,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站系宫,受9級特大地震影響索昂,放射性物質發(fā)生泄漏。R本人自食惡果不足惜扩借,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一椒惨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧潮罪,春花似錦康谆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至屯碴,卻和暖如春描睦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背导而。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工忱叭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留隔崎,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓韵丑,卻偏偏與公主長得像爵卒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子撵彻,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容