Tổng quan về MediaRecorder

Khung đa phương tiện của Android bao gồm hỗ trợ chụp và mã hoá nhiều âm thanh và video. Bạn có thể sử dụng các API MediaRecorder nếu được hỗ trợ theo phần cứng thiết bị.

Tài liệu này hướng dẫn bạn cách dùng MediaRecorder để viết một ứng dụng ghi âm từ một thiết bị micrô, lưu âm thanh và phát lại (bằng MediaPlayer). Để quay video, bạn cần sử dụng máy ảnh của thiết bị cùng với MediaRecorder. Vấn đề này được mô tả trong hướng dẫn dành cho Camera.

Lưu ý: Trình mô phỏng Android không thể ghi lại âm thanh. Hãy nhớ kiểm tra mã của bạn trên thiết bị thực có thể ghi âm.

Đang yêu cầu quyền ghi âm

Để có thể ghi âm, ứng dụng của bạn phải cho người dùng biết rằng ứng dụng sẽ truy cập vào đầu vào âm thanh của thiết bị. Bạn phải đưa thẻ quyền này vào tệp kê khai của ứng dụng:

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

RECORD_AUDIO được coi là một "nguy hiểm" quyền vì việc này có thể gây rủi ro cho quyền riêng tư của người dùng. Bắt đầu với Android 6.0 (API cấp 23) của một ứng dụng ứng dụng sử dụng một quyền nguy hiểm phải yêu cầu người dùng phê duyệt trong thời gian chạy. Sau khi người dùng đã được cấp quyền, ứng dụng sẽ ghi nhớ và không hỏi lại. Mã mẫu dưới đây cho biết cách và triển khai hành vi này bằng cách sử dụng ActivityCompat.requestPermissions().

Tạo và chạy MediaRecorder

Khởi chạy một thực thể mới của MediaRecorder bằng các lệnh gọi sau:

  • Đặt nguồn âm thanh bằng setAudioSource(). Bạn sẽ có thể dùng MIC.

    Lưu ý: Hầu hết các nguồn âm thanh (bao gồm cả DEFAULT) đều áp dụng quy trình xử lý cho tín hiệu âm thanh. Để ghi âm thanh thô, hãy chọn UNPROCESSED. Một số thiết bị không hỗ trợ xử lý dữ liệu chưa được xử lý đầu vào. Trước tiên, hãy gọi cho AudioManager.getProperty(AudioManager.PROPERTY_SUPPORT_AUDIO_SOURCE_UNPROCESSED) để xác minh rằng dịch vụ này có sẵn. Nếu không, hãy thử sử dụng VOICE_RECOGNITION. không sử dụng AGC hoặc khử tiếng ồn. Bạn có thể dùng UNPROCESSED dưới dạng nguồn âm thanh ngay cả khi thuộc tính không được hỗ trợ, nhưng không có gì đảm bảo rằng tín hiệu sẽ không được xử lý hoặc không trong trường hợp đó.

  • Đặt định dạng tệp đầu ra bằng cách sử dụng setOutputFormat(). Lưu ý rằng kể từ Android 8.0 (API cấp 26) MediaRecorder sẽ hỗ trợ tệp MPEG2_TS rất hữu ích cho việc phát trực tiếp:

    Kotlin

    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_2_TS)
    

    Java

    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_2_TS);
    
  • Đặt tên tệp đầu ra bằng cách sử dụng setOutputFile(). Bạn phải chỉ định chỉ số mô tả tệp đại diện cho một tệp thực tế.
  • Đặt bộ mã hoá âm thanh bằng setAudioEncoder().
  • Hoàn tất việc khởi chạy bằng cách gọi prepare().

Khởi động và dừng trình ghi âm bằng cách gọi start()stop() .

Khi bạn hoàn tất việc thực thể MediaRecorder, hãy giải phóng tài nguyên của thực thể đó càng sớm càng tốt bằng cách gọi release()

Lưu ý: Trên các thiết bị chạy Android 9 (API cấp 28) hoặc cao hơn, các ứng dụng chạy ở chế độ nền sẽ không truy cập được micrô. Do đó, ứng dụng của bạn chỉ nên ghi âm khi chạy trên nền trước hoặc khi bạn thêm một thực thể của MediaRecorder trong một dịch vụ trên nền trước.

Sử dụng MediaMuxer để ghi nhiều kênh

Kể từ Android 8.0 (API cấp 26), bạn có thể sử dụng MediaMuxer để ghi lại nhiều luồng âm thanh và video đồng thời. Trong các phiên bản Android cũ hơn, bạn chỉ có thể ghi lại một bản âm thanh và/hoặc một bản video vào một thời điểm.

Sử dụng addTrack() để kết hợp nhiều bản nhạc với nhau.

Bạn cũng có thể thêm một hoặc nhiều bản siêu dữ liệu có thông tin tuỳ chỉnh cho mỗi khung hình, nhưng chỉ với vùng chứa MP4. Ứng dụng của bạn sẽ xác định định dạng và nội dung của siêu dữ liệu.

Thêm siêu dữ liệu

Siêu dữ liệu có thể hữu ích cho việc xử lý ngoại tuyến. Ví dụ: dữ liệu được thu thập từ cảm biến con quay hồi chuyển có thể dùng để ổn định video.

Khi bạn thêm một bản nhạc siêu dữ liệu, thì định dạng MIME của bản nhạc đó phải bắt đầu bằng tiền tố application/. Việc ghi siêu dữ liệu cũng giống như ghi dữ liệu video hoặc âm thanh, ngoại trừ rằng dữ liệu không phải từ MediaCodec. Thay vào đó, ứng dụng truyền một ByteBuffer kèm theo dấu thời gian liên kết với writeSampleData(). Dấu thời gian này phải cùng cơ sở thời gian với video và bản âm thanh.

Tệp MP4 được tạo sử dụng TextMetaDataSampleEntry được xác định trong mục 12.3.3.2 về quy cách ISO BMFF để báo hiệu định dạng mime của siêu dữ liệu. Khi bạn sử dụng MediaExtractor để trích xuất tệp chứa các bản nhạc siêu dữ liệu, thì mime của siêu dữ liệu định dạng xuất hiện dưới dạng một thực thể của MediaFormat.

Mã mẫu

MediaRecorder mẫu minh hoạ cách quay video bằng MediaRecorder và Camera API.

Hoạt động mẫu dưới đây cho biết cách sử dụng MediaRecorder để ghi tệp âm thanh. Nó Cũng sử dụng MediaPlayer để phát lại âm thanh.

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;
        }
    }
}

Tìm hiểu thêm

Các trang này bao gồm các chủ đề liên quan đến việc ghi âm, lưu trữ và phát âm thanh cũng như video.