MediaRecorder 總覽

Android 多媒體架構支援擷取及編碼多種常見的音訊和影片格式。如果裝置硬體支援 MediaRecorder API,您就可以使用 API。

本文件說明如何使用 MediaRecorder 編寫應用程式,以便從裝置麥克風擷取音訊、儲存音訊並播放 (使用 MediaPlayer)。如要錄製影片,您需要搭配使用裝置的相機和 MediaRecorder。詳情請參閱相機指南。

注意:Android Emulator 無法錄製音訊。請務必使用可錄製的實體裝置來測試您的程式碼。

正在要求錄音權限

為能錄製影片,應用程式必須告知使用者其會存取裝置的音訊輸入。您必須在應用程式的資訊清單檔案中加入這個權限標記:

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

系統會將 RECORD_AUDIO 視為「危險」權限,因為這麼做可能會對使用者隱私造成風險。自 Android 6.0 (API 級別 23) 起,凡是使用危險權限的應用程式,都必須在執行階段要求使用者核准。使用者授予權限後,應用程式應記住,且不要再詢問。以下程式碼範例說明如何使用 ActivityCompat.requestPermissions() 實作這個行為。

建立和執行 MediaRecorder

透過下列呼叫初始化新的 MediaRecorder 執行個體:

  • 使用 setAudioSource() 設定音訊來源。您可能會使用 MIC

    注意:大多數音訊來源 (包括 DEFAULT) 都會套用至音訊訊號。如要錄製原始音訊,請選取 UNPROCESSED。部分裝置不支援未處理的輸入。請先呼叫 AudioManager.getProperty(AudioManager.PROPERTY_SUPPORT_AUDIO_SOURCE_UNPROCESSED),確認可以使用這項功能。 如果不是,請改用 VOICE_RECOGNITION,這樣就不會使用 AGC 或雜訊抑制功能。即使系統不支援屬性,您還是可以使用 UNPROCESSED 做為音訊來源,但無法保證在此情況下不會處理訊號。

  • 使用 setOutputFormat() 設定輸出檔案格式。請注意,從 Android 8.0 (API 級別 26) 開始,MediaRecorder 支援 MPEG2_TS 格式,非常適合串流作業:

    Kotlin

    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_2_TS)
    

    Java

    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_2_TS);
    
  • 使用 setOutputFile() 設定輸出檔案名稱。您必須指定代表實際檔案的檔案描述元。
  • 使用 setAudioEncoder() 設定音訊編碼器。
  • 呼叫 prepare() 完成初始化。

分別呼叫 start()stop(),藉此啟動及停止錄製工具。

使用完 MediaRecorder 執行個體後,請呼叫 release(),盡快釋放資源。

注意:在搭載 Android 9 (API 級別 28) 以上版本的裝置中,在背景執行的應用程式無法存取麥克風。因此,只有在應用程式於前景執行,或在前景服務中加入 MediaRecorder 例項時,應用程式才應錄製音訊。

使用 MediaMuxer 錄製多個頻道

從 Android 8.0 (API 級別 26) 開始,您可以使用 MediaMuxer 同時錄製多個音訊和視訊串流。在舊版 Android 中,您一次只能錄製一個音軌和/或一個視訊軌。

使用 addTrack() 方法將多個音軌混合。

您也可以新增一或多個包含每個影格自訂資訊的中繼資料音軌,但僅限 MP4 容器。應用程式會定義中繼資料的格式和內容。

新增中繼資料

中繼資料有助於離線處理。例如,從陀螺儀感應器擷取的資料可用於執行影片防震功能。

新增中繼資料音軌時,曲目的 MIME 格式必須以前置字串 application/ 開頭。寫入中繼資料與寫入影片或音訊資料相同,但資料並非來自 MediaCodec。反之,應用程式會將含有相關聯時間戳記的 ByteBuffer 傳遞至 writeSampleData() 方法。時間戳記必須與影片和音軌相同。

產生的 MP4 檔案會使用 ISO BMFF 規格第 12.3.3.2 節定義的 TextMetaDataSampleEntry,表示中繼資料的 MIME 格式。使用 MediaExtractor 擷取包含中繼資料音軌的檔案時,中繼資料的 MIME 格式會顯示為 MediaFormat 執行個體。

程式碼範例

MediaRecorder 範例示範如何使用 MediaRecorder 和 Camera API 來錄影。

下方的活動範例說明如何使用 MediaRecorder 錄製音訊檔案。也會使用 MediaPlayer 播放音訊。

Kotlin

package com.android.audiorecordtest

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.media.MediaPlayer
import android.media.MediaRecorder
import android.os.Bundle
import android.support.v4.app.ActivityCompat
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.view.View.OnClickListener
import android.view.ViewGroup
import android.widget.Button
import android.widget.LinearLayout
import java.io.IOException

private const val LOG_TAG = "AudioRecordTest"
private const val REQUEST_RECORD_AUDIO_PERMISSION = 200

class AudioRecordTest : AppCompatActivity() {

    private var fileName: String = ""

    private var recordButton: RecordButton? = null
    private var recorder: MediaRecorder? = null

    private var playButton: PlayButton? = null
    private var player: MediaPlayer? = null

    // Requesting permission to RECORD_AUDIO
    private var permissionToRecordAccepted = false
    private var permissions: Array<String> = arrayOf(Manifest.permission.RECORD_AUDIO)

    override fun onRequestPermissionsResult(
            requestCode: Int,
            permissions: Array<String>,
            grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        permissionToRecordAccepted = if (requestCode == REQUEST_RECORD_AUDIO_PERMISSION) {
            grantResults[0] == PackageManager.PERMISSION_GRANTED
        } else {
            false
        }
        if (!permissionToRecordAccepted) finish()
    }

    private fun onRecord(start: Boolean) = if (start) {
        startRecording()
    } else {
        stopRecording()
    }

    private fun onPlay(start: Boolean) = if (start) {
        startPlaying()
    } else {
        stopPlaying()
    }

    private fun startPlaying() {
        player = MediaPlayer().apply {
            try {
                setDataSource(fileName)
                prepare()
                start()
            } catch (e: IOException) {
                Log.e(LOG_TAG, "prepare() failed")
            }
        }
    }

    private fun stopPlaying() {
        player?.release()
        player = null
    }

    private fun startRecording() {
        recorder = MediaRecorder().apply {
            setAudioSource(MediaRecorder.AudioSource.MIC)
            setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
            setOutputFile(fileName)
            setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)

            try {
                prepare()
            } catch (e: IOException) {
                Log.e(LOG_TAG, "prepare() failed")
            }

            start()
        }
    }

    private fun stopRecording() {
        recorder?.apply {
            stop()
            release()
        }
        recorder = null
    }

    internal inner class RecordButton(ctx: Context) : Button(ctx) {

        var mStartRecording = true

        var clicker: OnClickListener = OnClickListener {
            onRecord(mStartRecording)
            text = when (mStartRecording) {
                true -> "Stop recording"
                false -> "Start recording"
            }
            mStartRecording = !mStartRecording
        }

        init {
            text = "Start recording"
            setOnClickListener(clicker)
        }
    }

    internal inner class PlayButton(ctx: Context) : Button(ctx) {
        var mStartPlaying = true
        var clicker: OnClickListener = OnClickListener {
            onPlay(mStartPlaying)
            text = when (mStartPlaying) {
                true -> "Stop playing"
                false -> "Start playing"
            }
            mStartPlaying = !mStartPlaying
        }

        init {
            text = "Start playing"
            setOnClickListener(clicker)
        }
    }

    override fun onCreate(icicle: Bundle?) {
        super.onCreate(icicle)

        // Record to the external cache directory for visibility
        fileName = "${externalCacheDir.absolutePath}/audiorecordtest.3gp"

        ActivityCompat.requestPermissions(this, permissions, REQUEST_RECORD_AUDIO_PERMISSION)

        recordButton = RecordButton(this)
        playButton = PlayButton(this)
        val ll = LinearLayout(this).apply {
            addView(recordButton,
                    LinearLayout.LayoutParams(
                            ViewGroup.LayoutParams.WRAP_CONTENT,
                            ViewGroup.LayoutParams.WRAP_CONTENT,
                            0f))
            addView(playButton,
                    LinearLayout.LayoutParams(
                            ViewGroup.LayoutParams.WRAP_CONTENT,
                            ViewGroup.LayoutParams.WRAP_CONTENT,
                            0f))
        }
        setContentView(ll)
    }

    override fun onStop() {
        super.onStop()
        recorder?.release()
        recorder = null
        player?.release()
        player = null
    }
}

Java

package com.android.audiorecordtest;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;

import java.io.IOException;

public class AudioRecordTest extends AppCompatActivity {

    private static final String LOG_TAG = "AudioRecordTest";
    private static final int REQUEST_RECORD_AUDIO_PERMISSION = 200;
    private static String fileName = null;

    private RecordButton recordButton = null;
    private MediaRecorder recorder = null;

    private PlayButton   playButton = null;
    private MediaPlayer   player = null;

    // Requesting permission to RECORD_AUDIO
    private boolean permissionToRecordAccepted = false;
    private String [] permissions = {Manifest.permission.RECORD_AUDIO};

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode){
            case REQUEST_RECORD_AUDIO_PERMISSION:
                permissionToRecordAccepted  = grantResults[0] == PackageManager.PERMISSION_GRANTED;
                break;
        }
        if (!permissionToRecordAccepted ) finish();

    }

    private void onRecord(boolean start) {
        if (start) {
            startRecording();
        } else {
            stopRecording();
        }
    }

    private void onPlay(boolean start) {
        if (start) {
            startPlaying();
        } else {
            stopPlaying();
        }
    }

    private void startPlaying() {
        player = new MediaPlayer();
        try {
            player.setDataSource(fileName);
            player.prepare();
            player.start();
        } catch (IOException e) {
            Log.e(LOG_TAG, "prepare() failed");
        }
    }

    private void stopPlaying() {
        player.release();
        player = null;
    }

    private void startRecording() {
        recorder = new MediaRecorder();
        recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        recorder.setOutputFile(fileName);
        recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);

        try {
            recorder.prepare();
        } catch (IOException e) {
            Log.e(LOG_TAG, "prepare() failed");
        }

        recorder.start();
    }

    private void stopRecording() {
        recorder.stop();
        recorder.release();
        recorder = null;
    }

    class RecordButton extends Button {
        boolean mStartRecording = true;

        OnClickListener clicker = new OnClickListener() {
            public void onClick(View v) {
                onRecord(mStartRecording);
                if (mStartRecording) {
                    setText("Stop recording");
                } else {
                    setText("Start recording");
                }
                mStartRecording = !mStartRecording;
            }
        };

        public RecordButton(Context ctx) {
            super(ctx);
            setText("Start recording");
            setOnClickListener(clicker);
        }
    }

    class PlayButton extends Button {
        boolean mStartPlaying = true;

        OnClickListener clicker = new OnClickListener() {
            public void onClick(View v) {
                onPlay(mStartPlaying);
                if (mStartPlaying) {
                    setText("Stop playing");
                } else {
                    setText("Start playing");
                }
                mStartPlaying = !mStartPlaying;
            }
        };

        public PlayButton(Context ctx) {
            super(ctx);
            setText("Start playing");
            setOnClickListener(clicker);
        }
    }

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        // Record to the external cache directory for visibility
        fileName = getExternalCacheDir().getAbsolutePath();
        fileName += "/audiorecordtest.3gp";

        ActivityCompat.requestPermissions(this, permissions, REQUEST_RECORD_AUDIO_PERMISSION);

        LinearLayout ll = new LinearLayout(this);
        recordButton = new RecordButton(this);
        ll.addView(recordButton,
                new LinearLayout.LayoutParams(
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        0));
        playButton = new PlayButton(this);
        ll.addView(playButton,
                new LinearLayout.LayoutParams(
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        0));
        setContentView(ll);
    }

    @Override
    public void onStop() {
        super.onStop();
        if (recorder != null) {
            recorder.release();
            recorder = null;
        }

        if (player != null) {
            player.release();
            player = null;
        }
    }
}

瞭解詳情

這些頁面涵蓋與錄製、儲存及播放音訊和視訊相關的主題。