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ùngMIC
.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ọnUNPROCESSED
. 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 choAudioManager.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ụngVOICE_RECOGNITION
. không sử dụng AGC hoặc khử tiếng ồn. Bạn có thể dùngUNPROCESSED
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()
và
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.