您好,登錄后才能下訂單哦!
這篇文章的內容主要圍繞Android中怎么采集一幀音頻進行講述,文章內容清晰易懂,條理清晰,非常適合新手學習,值得大家去閱讀。感興趣的朋友可以跟隨小編一起閱讀吧。希望大家通過這篇文章有所收獲!
Android SDK 提供了兩套音頻采集的API,分別是:MediaRecorder 和 AudioRecord,前者是一個更加上層一點的API,它可以直接把手機麥克風錄入的音頻數據進行編碼壓縮(如AMR、MP3等)并存成文件,而后者則更接近底層,能夠更加自由靈活地控制,可以得到原始的一幀幀PCM音頻數據。
如果想簡單地做一個錄音機,錄制成音頻文件,則推薦使用 MediaRecorder,而如果需要對音頻做進一步的算法處理、或者采用第三方的編碼庫進行壓縮、以及網絡傳輸等應用,則建議使用 AudioRecord,其實 MediaRecorder 底層也是調用了 AudioRecord 與 Android Framework 層的 AudioFlinger 進行交互的。
音頻的開發,更廣泛地應用不僅僅局限于本地錄音,因此,我們需要重點掌握如何利用更加底層的 AudioRecord API 來采集音頻數據(注意,使用它采集到的音頻數據是原始的PCM格式,想壓縮為mp3,aac等格式的話,還需要專門調用編碼器進行編碼)。
1. AudioRecord 的工作流程
首先,我們了解一下 AudioRecord 的工作流程:
(1) 配置參數,初始化內部的音頻緩沖區
(2) 開始采集
(3) 需要一個線程,不斷地從 AudioRecord 的緩沖區將音頻數據“讀”出來,注意,這個過程一定要及時,否則就會出現“overrun”的錯誤,該錯誤在音頻開發中比較常見,意味著應用層沒有及時地“取走”音頻數據,導致內部的音頻緩沖區溢出。
(4) 停止采集,釋放資源
2. AudioRecord 的參數配置
上面是 AudioRecord 的構造函數,我們可以發現,它主要是靠構造函數來配置采集參數的,下面我們來一一解釋這些參數的含義(建議對照著我的上一篇文章來理解):
(1) audioSource
該參數指的是音頻采集的輸入源,可選的值以常量的形式定義在 MediaRecorder.AudioSource 類中,常用的值包括:DEFAULT(默認),VOICE_RECOGNITION(用于語音識別,等同于DEFAULT),MIC(由手機麥克風輸入),VOICE_COMMUNICATION(用于VoIP應用)等等。
(2) sampleRateInHz
采樣率,注意,目前44100Hz是唯一可以保證兼容所有Android手機的采樣率。
(3) channelConfig
通道數的配置,可選的值以常量的形式定義在 AudioFormat 類中,常用的是 CHANNEL_IN_MONO(單通道),CHANNEL_IN_STEREO(雙通道)
(4) audioFormat
這個參數是用來配置“數據位寬”的,可選的值也是以常量的形式定義在 AudioFormat 類中,常用的是 ENCODING_PCM_16BIT(16bit),ENCODING_PCM_8BIT(8bit),注意,前者是可以保證兼容所有Android手機的。
(5) bufferSizeInBytes
這個是最難理解又最重要的一個參數,它配置的是 AudioRecord 內部的音頻緩沖區的大小,該緩沖區的值不能低于一幀“音頻幀”(Frame)的大小,而前一篇文章介紹過,一幀音頻幀的大小計算如下:
int size = 采樣率 x 位寬 x 采樣時間 x 通道數
采樣時間一般取 2.5ms~120ms 之間,由廠商或者具體的應用決定,我們其實可以推斷,每一幀的采樣時間取得越短,產生的延時就應該會越小,當然,碎片化的數據也就會越多。
在Android開發中,AudioRecord 類提供了一個幫助你確定這個 bufferSizeInBytes 的函數,原型如下:
int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat);
不同的廠商的底層實現是不一樣的,但無外乎就是根據上面的計算公式得到一幀的大小,音頻緩沖區的大小則必須是一幀大小的2~N倍,有興趣的朋友可以繼續深入源碼探究探究。
實際開發中,強烈建議由該函數計算出需要傳入的 bufferSizeInBytes,而不是自己手動計算。
3. 音頻的采集線程
當創建好了 AudioRecord 對象之后,就可以開始進行音頻數據的采集了,通過下面兩個函數控制采集的開始/停止:
AudioRecord.startRecording();
AudioRecord.stop();
一旦開始采集,必須通過線程循環盡快取走音頻,否則系統會出現 overrun,調用的讀取數據的接口是:
AudioRecord.read(byte[] audioData, int offsetInBytes, int sizeInBytes);
4. 示例代碼
我將 AudioRecord 類的接口簡單封裝了一下,提供了一個 AudioCapturer 類,可以到我的Github下載:https://github.com/Jhuster/Android/blob/master/Audio/AudioCapturer.java
這里也貼出來一份:
/* * COPYRIGHT NOTICE * Copyright (C) 2016, Jhuster <lujun.hust@gmail.com> * https://github.com/Jhuster/Android * * @license under the Apache License, Version 2.0 * * @file AudioCapturer.java * * @version 1.0 * @author Jhuster * @date 2016/03/10 */ import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder; import android.util.Log; public class AudioCapturer { private static final String TAG = "AudioCapturer"; private static final int DEFAULT_SOURCE = MediaRecorder.AudioSource.MIC; private static final int DEFAULT_SAMPLE_RATE = 44100; private static final int DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO; private static final int DEFAULT_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; private AudioRecord mAudioRecord; private int mMinBufferSize = 0; private Thread mCaptureThread; private boolean mIsCaptureStarted = false; private volatile boolean mIsLoopExit = false; private OnAudioFrameCapturedListener mAudioFrameCapturedListener; public interface OnAudioFrameCapturedListener { public void onAudioFrameCaptured(byte[] audioData); } public boolean isCaptureStarted() { return mIsCaptureStarted; } public void setOnAudioFrameCapturedListener(OnAudioFrameCapturedListener listener) { mAudioFrameCapturedListener = listener; } public boolean startCapture() { return startCapture(DEFAULT_SOURCE, DEFAULT_SAMPLE_RATE, DEFAULT_CHANNEL_CONFIG, DEFAULT_AUDIO_FORMAT); } public boolean startCapture(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat) { if (mIsCaptureStarted) { Log.e(TAG, "Capture already started !"); return false; } mMinBufferSize = AudioRecord.getMinBufferSize(sampleRateInHz,channelConfig,audioFormat); if (mMinBufferSize == AudioRecord.ERROR_BAD_VALUE) { Log.e(TAG, "Invalid parameter !"); return false; } Log.d(TAG , "getMinBufferSize = "+mMinBufferSize+" bytes !"); mAudioRecord = new AudioRecord(audioSource,sampleRateInHz,channelConfig,audioFormat,mMinBufferSize); if (mAudioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) { Log.e(TAG, "AudioRecord initialize fail !"); return false; } mAudioRecord.startRecording(); mIsLoopExit = false; mCaptureThread = new Thread(new AudioCaptureRunnable()); mCaptureThread.start(); mIsCaptureStarted = true; Log.d(TAG, "Start audio capture success !"); return true; } public void stopCapture() { if (!mIsCaptureStarted) { return; } mIsLoopExit = true; try { mCaptureThread.interrupt(); mCaptureThread.join(1000); } catch (InterruptedException e) { e.printStackTrace(); } if (mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) { mAudioRecord.stop(); } mAudioRecord.release(); mIsCaptureStarted = false; mAudioFrameCapturedListener = null; Log.d(TAG, "Stop audio capture success !"); } private class AudioCaptureRunnable implements Runnable { @Override public void run() { while (!mIsLoopExit) { byte[] buffer = new byte[mMinBufferSize]; int ret = mAudioRecord.read(buffer, 0, mMinBufferSize); if (ret == AudioRecord.ERROR_INVALID_OPERATION) { Log.e(TAG , "Error ERROR_INVALID_OPERATION"); } else if (ret == AudioRecord.ERROR_BAD_VALUE) { Log.e(TAG , "Error ERROR_BAD_VALUE"); } else { if (mAudioFrameCapturedListener != null) { mAudioFrameCapturedListener.onAudioFrameCaptured(buffer); } Log.d(TAG , "OK, Captured "+ret+" bytes !"); } } } } }
使用前要注意,添加如下權限:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
感謝你的閱讀,相信你對“Android中怎么采集一幀音頻”這一問題有一定的了解,快去動手實踐吧,如果想了解更多相關知識點,可以關注億速云網站!小編會繼續為大家帶來更好的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。