Tổng quan về MediaRecorder

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

Tài liệu này cho bạn biết cách sử dụng MediaRecorder để ghi một ứng dụng thu âm thanh từ micrô của thiết bị, 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. Tính năng này được mô tả trong hướng dẫn về Máy ảnh.

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

Yêu cầu cấp quyền ghi âm

Để có thể ghi, ứ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 quyền "nguy hiểm" vì có thể gây rủi ro cho quyền riêng tư của người dùng. Kể từ Android 6.0 (API cấp 23), một ứng dụng dùng 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ấp quyền, ứng dụng sẽ ghi nhớ và không hỏi lại. Mã mẫu bên dưới cho biết cách triển khai hành vi này bằng ActivityCompat.requestPermissions().

Tạo và chạy MediaRecorder

Khởi động 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(). Có thể bạn sẽ dùng MIC.

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

  • Đặt định dạng tệp đầu ra bằng setOutputFormat(). Lưu ý rằng kể từ Android 8.0 (API cấp 26) MediaRecorder sẽ hỗ trợ định dạng MPEG2_TS. Định dạng này rất hữu ích trong việc truyền trực tuyến:

    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 một 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 quá trình khởi chạy bằng cách gọi prepare().

Khởi động và dừng trình ghi bằng cách gọi lần lượt start()stop().

Khi bạn hoàn tất 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) trở lên, các ứng dụng chạy ở chế độ nền sẽ không thể truy cập vào micrô. Do đó, ứng dụng của bạn chỉ nên ghi âm khi ở nền trước hoặc khi bạn đưa một thực thể của MediaRecorder vào dịch vụ trên nền trước.

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

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

Sử dụng phương thức 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 từng khung hình, nhưng chỉ cho vùng chứa MP4. Ứng dụng của bạn 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ể được dùng để ổn định video.

Khi bạn thêm một bản nhạc siêu dữ liệu, đị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ư việc ghi dữ liệu âm thanh hoặc video, ngoại trừ việc dữ liệu không đến từ MediaCodec. Thay vào đó, ứng dụng sẽ chuyển một ByteBuffer có dấu thời gian liên kết đến phương thức writeSampleData(). Dấu thời gian phải trùng với thời điểm của bản âm thanh và video.

Tệp MP4 được tạo sẽ sử dụng TextMetaDataSampleEntry được xác định trong mục 12.3.3.2 của thông số kỹ thuật 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, định dạng mime của siêu dữ liệu sẽ xuất hiện dưới dạng một thực thể của MediaFormat.

Mã mẫu

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

Hoạt động mẫu dưới đây cho biết cách sử dụng MediaRecorder để ghi tệp âm thanh. Tệp này 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

Những trang này đề cập đến những chủ đề liên quan đến việc ghi lại, lưu trữ và phát âm thanh cũng như video.