Обзор МедиаРекордера

Мультимедийная платформа 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;
        }
    }
}

Узнать больше

На этих страницах рассматриваются темы, касающиеся записи, хранения и воспроизведения аудио и видео.