Мультимедийная платформа Android включает поддержку захвата и кодирования различных распространенных аудио- и видеоформатов. Вы можете использовать API MediaRecorder
, если они поддерживаются аппаратным обеспечением устройства.
В этом документе показано, как использовать MediaRecorder
для написания приложения, которое захватывает звук с микрофона устройства, сохраняет звук и воспроизводит его (с помощью MediaPlayer
). Для записи видео вам потребуется использовать камеру устройства вместе с MediaRecorder
. Это описано в руководстве по камере .
Примечание. Эмулятор 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
, который не использует АРУ или подавление шума. Вы можете использоватьUNPROCESSED
в качестве источника звука, даже если это свойство не поддерживается, но нет гарантии, будет ли сигнал в этом случае необработанным или нет. - Установите формат выходного файла с помощью
setOutputFormat()
. Обратите внимание, что начиная с Android 8.0 (уровень API 26)MediaRecorder
поддерживает формат MPEG2_TS, который полезен для потоковой передачи:Котлин
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_2_TS)
Ява
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 использует TextMetaDataSampleEntry
определенный в разделе 12.3.3.2 спецификации ISO BMFF, для обозначения формата mime метаданных. Когда вы используете MediaExtractor
для извлечения файла, содержащего дорожки метаданных, формат mime метаданных отображается как экземпляр MediaFormat
.
Пример кода
Пример MediaRecorder демонстрирует, как сделать видеозапись с помощью MediaRecorder и API камеры.
В примере действия ниже показано, как использовать MediaRecorder
для записи аудиофайла. Он также использует MediaPlayer
для воспроизведения звука.
Котлин
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 } }
Ява
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; } } }
Узнать больше
На этих страницах рассматриваются темы, касающиеся записи, хранения и воспроизведения аудио и видео.