Android音視頻開發(五)AudioRecord錄製音頻

簡介

AudioRecord是安卓多媒體框架中用於錄製音頻的工具。它支持錄製原始音頻數據,即PCM數據,PCM數據不能被播放器直接播放,需要編碼壓縮成常見音頻格式才能被播放器識別。而原生api也提供了AudioTrack播放PCM數據。
谷歌Api文檔

錄音流程

AudioRecord是通過read方式不斷讀取來自音源輸入的數據流(字節流),進而把數據流保存成PCM數據。
開始錄音的時候,AudioRecord需要創建一個緩衝區, 這個緩衝區主要是用來保存新的音頻數據,它用於標識一個AudioRecord對象還沒有被讀取(同步)聲音數據前能錄多長的音(即一次可以錄製的聲音容量)。聲音數據不斷從音頻硬件中被讀出,每次讀取的數據大小不超過初始化緩衝區的容量(錄音數據的大小)。
流程如下:

  1. 構造一個AudioRecord對象。其中最小錄音數據緩存的緩衝區大小可以通過getMinBufferSize方法得到,如果緩衝區容量過小,將導致對象構造的失敗。
  2. 初始化一個緩衝區,該緩衝區大小大於等於AudioRecord對象用於寫聲音數據的緩衝區大小,用於緩存讀取的音頻數據。
  3. startRecording開始錄音
  4. 創建一個數據流,不斷地從AudioRecord中讀取聲音數據到初始化的緩衝區,然後將緩衝區中的數據輸出。
  5. 關閉數據流
  6. 停止錄音

示例

下面使用Kotlin代碼展示AudioRecord如何錄製音頻數據:

class AudioActivity : AppCompatActivity() {
    //音頻錄製
    private var audioRecord: AudioRecord? = null
    //緩衝區大小,緩衝區用於保存音頻數據流
    private var bufferSize: Int = 0
    //記錄是否正在錄製音頻
    @Volatile private var isRecording = false
	//錄音線程
    private var recordThread: Thread? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_media)
        initRecoder()
    }

    /**
     * 初始化
     */
    private fun initRecoder() {
        /*
            getMinBufferSize用於獲取成功創建AudioRecord對象所需的最小緩衝區大小,
            此大小不能保證在負載下能順利錄製,應根據預期的頻率選擇更高的值,
            在該頻率下,將對AudioRecord實例進行輪詢以獲取新數據
            參數介紹:(具體看官網api介紹)
            sampleRateInHz:採樣率,以赫茲爲單位
            channelConfig:音頻通道的配置
            audioFormat:音頻數據的格式
         */
        bufferSize = AudioRecord.getMinBufferSize(
            44100,
            AudioFormat.CHANNEL_IN_MONO,
            AudioFormat.ENCODING_PCM_16BIT
        )

        /*
            構建AudioRecord對象。
            參數介紹:
            audioSource:音頻來源
            sampleRateInHz:採樣率,以赫茲爲單位。目前,只有44100Hz是保證在所有設備上都可以使用的速率(最適合人耳的),但是其他速率(例如22050、16000和11025)可能在某些設備上可以使用
            channelConfig:音頻通道的配置
            audioFormat:音頻數據的格式
            bufferSizeInBytes:在錄製期間寫入音頻數據的緩衝區的總大小(以字節爲單位)
        */
        audioRecord = AudioRecord(
            MediaRecorder.AudioSource.MIC,
            44100,
            AudioFormat.CHANNEL_IN_MONO,
            AudioFormat.ENCODING_PCM_16BIT,
            bufferSize * 2
        )
    }

    /**
     * 開始錄製
     */
    fun startRecord(view: View) {
        if (isRecording) {
            return
        }
        isRecording = true
        if (recordThread == null) {
            recordThread = Thread(recordRunnable)
        }
        recordThread!!.start()
    }

    /**
     * 停止錄製
     */
    fun stopRecord(view: View) {
    	//置爲false,表示線程循環就結束了,線程也執行完畢了
    	//也可以直接中斷線程
        isRecording = false
        audioRecord = null
        recordThread = null
    }

    /**
     * 錄音線程
     *
     * 由於需要不斷讀取音頻數據,所以放在子線程操作
     */
    private val recordRunnable = Runnable {
        //設置線程優先級
        android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO)
        //創建文件
        val tmpFile: File? = FileUtil.createFile("${System.currentTimeMillis()}.pcm")
        //文件輸出流
        var fos: FileOutputStream = FileOutputStream(tmpFile?.getAbsoluteFile())
        try {

            if (audioRecord?.getState() !== AudioRecord.STATE_INITIALIZED) {
                //沒有初始化成功
                return@Runnable
            }
            //開始錄製
            audioRecord?.startRecording()

            var buffer = 0
            val bytes = ByteArray(bufferSize)
            //輪詢讀取數據
            while (isRecording) {
                if (audioRecord != null) {
                    buffer = audioRecord!!.read(bytes, 0, bufferSize)
                    if (buffer == AudioRecord.ERROR_INVALID_OPERATION || buffer == AudioRecord.ERROR_BAD_VALUE) {
                        continue
                    }
                    if (buffer == 0 || buffer == -1) {
                        break
                    }
                    //在此可以對錄製音頻的數據進行二次處理 如變聲,壓縮,降噪等操作
                    //也可以直接發送至服務器(實時語音傳輸) 對方可採用AudioTrack進行播放
                    //這裏直接將pcm音頻數據寫入文件
                    fos.write(bytes)
                }
            }
        } catch (e: Exception) {
            Log.e("Test", "出錯了", e)
        } finally {
            try {
                fos?.close()
            } catch (ex: IOException) {
            }
        	audioRecord?.stop()
        	audioRecord?.release()
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章