中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android中怎么采集一幀音頻

發布時間:2022-01-12 11:58:50 來源:億速云 閱讀:147 作者:柒染 欄目:移動開發

這篇文章的內容主要圍繞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 的參數配置

Android中怎么采集一幀音頻

上面是 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中怎么采集一幀音頻”這一問題有一定的了解,快去動手實踐吧,如果想了解更多相關知識點,可以關注億速云網站!小編會繼續為大家帶來更好的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

马山县| 阿克| 林周县| 余干县| 广丰县| 佛冈县| 重庆市| 同德县| 台中县| 遂宁市| 北安市| 于都县| 东乌珠穆沁旗| 巴林右旗| 古浪县| 灵宝市| 彰武县| 泾川县| 土默特左旗| 吉木萨尔县| 博爱县| 曲麻莱县| 通化市| 霍山县| 菏泽市| 平凉市| 古交市| 保靖县| 蓬溪县| 荔浦县| 达孜县| 富平县| 昭通市| 潼南县| 招远市| 乌拉特前旗| 海安县| 大理市| 南雄市| 苏尼特左旗| 库尔勒市|