MediaRecorder 概览

Android 多媒体框架支持捕获和编码各种常见的音频和视频格式。如果设备硬件支持,您可以使用 MediaRecorder API。

本文档向您介绍如何使用 MediaRecorder 编写能够从设备麦克风捕获音频、保存音频并(使用 MediaPlayer)进行播放的应用。要录制视频,您需要使用设备的摄像头以及 。具体说明请参阅相机指南。

注意:Android 模拟器无法录制音频。请务必在能够录制音频的真实设备上测试您的代码。

请求录制音频的权限

为了能够录制音频,您的应用必须告知用户它将访问设备的音频输入。您必须在应用的清单文件中添加以下权限标记:

    <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,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 进行视频录制。

以下 Activity 示例展示了如何使用 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;
            }
        }
    }
    

了解详情

以下页面介绍了有关录制、存储以及播放音频和视频的主题。