溫馨提示×

溫馨提示×

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

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

Android中怎么采集一幀音頻

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

這篇文章的內(nèi)容主要圍繞Android中怎么采集一幀音頻進(jìn)行講述,文章內(nèi)容清晰易懂,條理清晰,非常適合新手學(xué)習(xí),值得大家去閱讀。感興趣的朋友可以跟隨小編一起閱讀吧。希望大家通過這篇文章有所收獲!

Android SDK 提供了兩套音頻采集的API,分別是:MediaRecorder 和 AudioRecord,前者是一個更加上層一點(diǎn)的API,它可以直接把手機(jī)麥克風(fēng)錄入的音頻數(shù)據(jù)進(jìn)行編碼壓縮(如AMR、MP3等)并存成文件,而后者則更接近底層,能夠更加自由靈活地控制,可以得到原始的一幀幀PCM音頻數(shù)據(jù)。

如果想簡單地做一個錄音機(jī),錄制成音頻文件,則推薦使用 MediaRecorder,而如果需要對音頻做進(jìn)一步的算法處理、或者采用第三方的編碼庫進(jìn)行壓縮、以及網(wǎng)絡(luò)傳輸?shù)葢?yīng)用,則建議使用 AudioRecord,其實(shí) MediaRecorder 底層也是調(diào)用了 AudioRecord 與 Android Framework 層的 AudioFlinger 進(jìn)行交互的。

音頻的開發(fā),更廣泛地應(yīng)用不僅僅局限于本地錄音,因此,我們需要重點(diǎn)掌握如何利用更加底層的 AudioRecord API 來采集音頻數(shù)據(jù)(注意,使用它采集到的音頻數(shù)據(jù)是原始的PCM格式,想壓縮為mp3,aac等格式的話,還需要專門調(diào)用編碼器進(jìn)行編碼)。

1. AudioRecord 的工作流程

首先,我們了解一下 AudioRecord 的工作流程:

(1) 配置參數(shù),初始化內(nèi)部的音頻緩沖區(qū)

(2) 開始采集

(3) 需要一個線程,不斷地從 AudioRecord 的緩沖區(qū)將音頻數(shù)據(jù)“讀”出來,注意,這個過程一定要及時,否則就會出現(xiàn)“overrun”的錯誤,該錯誤在音頻開發(fā)中比較常見,意味著應(yīng)用層沒有及時地“取走”音頻數(shù)據(jù),導(dǎo)致內(nèi)部的音頻緩沖區(qū)溢出。

(4) 停止采集,釋放資源

2. AudioRecord 的參數(shù)配置

Android中怎么采集一幀音頻

上面是 AudioRecord 的構(gòu)造函數(shù),我們可以發(fā)現(xiàn),它主要是靠構(gòu)造函數(shù)來配置采集參數(shù)的,下面我們來一一解釋這些參數(shù)的含義(建議對照著我的上一篇文章來理解):

(1) audioSource

該參數(shù)指的是音頻采集的輸入源,可選的值以常量的形式定義在 MediaRecorder.AudioSource 類中,常用的值包括:DEFAULT(默認(rèn)),VOICE_RECOGNITION(用于語音識別,等同于DEFAULT),MIC(由手機(jī)麥克風(fēng)輸入),VOICE_COMMUNICATION(用于VoIP應(yīng)用)等等。

(2) sampleRateInHz

采樣率,注意,目前44100Hz是唯一可以保證兼容所有Android手機(jī)的采樣率。

(3) channelConfig

通道數(shù)的配置,可選的值以常量的形式定義在 AudioFormat 類中,常用的是 CHANNEL_IN_MONO(單通道),CHANNEL_IN_STEREO(雙通道)

(4) audioFormat

這個參數(shù)是用來配置“數(shù)據(jù)位寬”的,可選的值也是以常量的形式定義在 AudioFormat 類中,常用的是 ENCODING_PCM_16BIT(16bit),ENCODING_PCM_8BIT(8bit),注意,前者是可以保證兼容所有Android手機(jī)的。

(5) bufferSizeInBytes

這個是最難理解又最重要的一個參數(shù),它配置的是 AudioRecord 內(nèi)部的音頻緩沖區(qū)的大小,該緩沖區(qū)的值不能低于一幀“音頻幀”(Frame)的大小,而前一篇文章介紹過,一幀音頻幀的大小計算如下:

int size = 采樣率 x 位寬 x 采樣時間 x 通道數(shù)

采樣時間一般取 2.5ms~120ms 之間,由廠商或者具體的應(yīng)用決定,我們其實(shí)可以推斷,每一幀的采樣時間取得越短,產(chǎn)生的延時就應(yīng)該會越小,當(dāng)然,碎片化的數(shù)據(jù)也就會越多。

在Android開發(fā)中,AudioRecord 類提供了一個幫助你確定這個 bufferSizeInBytes 的函數(shù),原型如下:

int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat);

不同的廠商的底層實(shí)現(xiàn)是不一樣的,但無外乎就是根據(jù)上面的計算公式得到一幀的大小,音頻緩沖區(qū)的大小則必須是一幀大小的2~N倍,有興趣的朋友可以繼續(xù)深入源碼探究探究。

實(shí)際開發(fā)中,強(qiáng)烈建議由該函數(shù)計算出需要傳入的 bufferSizeInBytes,而不是自己手動計算。

3. 音頻的采集線程

當(dāng)創(chuàng)建好了 AudioRecord 對象之后,就可以開始進(jìn)行音頻數(shù)據(jù)的采集了,通過下面兩個函數(shù)控制采集的開始/停止:

AudioRecord.startRecording();

AudioRecord.stop();

一旦開始采集,必須通過線程循環(huán)盡快取走音頻,否則系統(tǒng)會出現(xiàn) overrun,調(diào)用的讀取數(shù)據(jù)的接口是:

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 !");
                }														
            }		
        }    
    }
}

使用前要注意,添加如下權(quán)限:

<uses-permission android:name="android.permission.RECORD_AUDIO" />

感謝你的閱讀,相信你對“Android中怎么采集一幀音頻”這一問題有一定的了解,快去動手實(shí)踐吧,如果想了解更多相關(guān)知識點(diǎn),可以關(guān)注億速云網(wǎng)站!小編會繼續(xù)為大家?guī)砀玫奈恼拢?/p>

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI