Omówienie MediaRecorder

Platforma multimediów Android obsługuje przechwytywanie i kodowanie różnych popularnych formatów audio i wideo. Możesz używać interfejsów API MediaRecorder, jeśli jest to obsługiwane przez sprzęt urządzenia.

W tym dokumencie opisujemy, jak za pomocą MediaRecorder napisać aplikację, która przechwytuje dźwięk z mikrofonu urządzenia, zapisuje dźwięk i odtwarza go (przy użyciu MediaPlayer). Do nagrywania filmów konieczne będzie użycie aparatu urządzenia razem z usługą MediaRecorder. Zostało to opisane w przewodniku Aparat.

Uwaga: emulator Androida nie może nagrywać dźwięku. Pamiętaj, aby przetestować kod na prawdziwym urządzeniu, które może nagrywać.

Wysyłam prośbę o zgodę na nagrywanie dźwięku

Aby umożliwić nagrywanie, aplikacja musi poinformować użytkownika, że będzie mieć dostęp do wejścia audio urządzenia. W pliku manifestu aplikacji musisz umieścić ten tag uprawnień:

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

Uprawnienia RECORD_AUDIO są uznawane za „niebezpieczne” uprawnienie, ponieważ może zagrażać prywatności użytkownika. Od Androida 6.0 (poziom interfejsu API 23) aplikacja, która korzysta z niebezpiecznego uprawnienia, musi prosić użytkownika o zgodę w czasie działania. Gdy użytkownik przyzna odpowiednie uprawnienia, aplikacja powinna ją zapamiętać i nie pytać ponownie. Przykładowy kod poniżej pokazuje, jak wdrożyć to zachowanie za pomocą właściwości ActivityCompat.requestPermissions().

Tworzenie i uruchamianie programu MediaRecorder

Zainicjuj nową instancję instancji MediaRecorder z tymi wywołaniami:

  • Ustaw źródło dźwięku za pomocą funkcji setAudioSource(). Prawdopodobnie użyjesz MIC.

    Uwaga: większość źródeł audio (w tym DEFAULT) wykorzystuje przetwarzanie sygnału. Aby nagrać nieprzetworzony dźwięk, wybierz UNPROCESSED. Niektóre urządzenia nie obsługują nieprzetworzonych danych wejściowych. Zadzwoń pod numer AudioManager.getProperty(AudioManager.PROPERTY_SUPPORT_AUDIO_SOURCE_UNPROCESSED), aby sprawdzić, czy jest dostępny. Jeśli nie, spróbuj zamiast tego użyć elementu VOICE_RECOGNITION, który nie stosuje AGC ani eliminowania szumu. Możesz używać UNPROCESSED jako źródła dźwięku nawet wtedy, gdy ta usługa nie jest obsługiwana. Nie możemy jednak zagwarantować, że w tym przypadku sygnał będzie nieprzetworzony.

  • Ustaw format pliku wyjściowego za pomocą właściwości setOutputFormat(). Pamiętaj, że od Androida 8.0 (poziom interfejsu API 26) MediaRecorder obsługuje format MPEG2_TS, który jest przydatny w przypadku strumieniowania:

    Kotlin

    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_2_TS)
    

    Java

    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_2_TS);
    
  • Ustaw nazwę pliku wyjściowego za pomocą właściwości setOutputFile(). Musisz określić deskryptor pliku, który reprezentuje faktyczny plik.
  • Ustaw koder dźwięku za pomocą setAudioEncoder().
  • Dokończ inicjowanie, wywołując prepare().

Uruchom i zatrzymaj dyktafon, wywołując odpowiednio funkcje start() i stop().

Gdy skończysz korzystać z instancji MediaRecorder, najszybciej jak to możliwe zwolnij jej zasoby, wywołując metodę release().

Uwaga: na urządzeniach z Androidem 9 (poziom interfejsu API 28) lub nowszym aplikacje działające w tle nie mają dostępu do mikrofonu. Dlatego aplikacja powinna nagrywać dźwięk tylko wtedy, gdy działa na pierwszym planie lub gdy dodasz wystąpienie MediaRecorder w usłudze na pierwszym planie.

Nagrywanie kilku kanałów za pomocą MediaMuxer

Od Androida 8.0 (poziom interfejsu API 26) możesz używać interfejsu MediaMuxer do nagrywania wielu jednoczesnych strumieni audio i wideo. We wcześniejszych wersjach Androida można w danym momencie nagrywać tylko 1 ścieżkę audio lub wideo.

Aby połączyć ze sobą kilka ścieżek, użyj metody addTrack().

Możesz też dodać jedną lub więcej ścieżek metadanych z własnymi informacjami dla każdej klatki, ale tylko do kontenerów MP4. Aplikacja określa format i zawartość metadanych.

Dodawanie metadanych

Metadane mogą być przydatne przy przetwarzaniu offline. Na przykład dane zebrane przez żyroskop można wykorzystać do stabilizacji wideo.

Gdy dodajesz ścieżkę metadanych, format MIME ścieżki musi zaczynać się od prefiksu application/. Zapisywanie metadanych jest takie samo jak zapisywanie danych wideo lub audio, z tą różnicą, że nie pochodzą one z MediaCodec. Zamiast tego przekazuje do metody writeSampleData() parametr ByteBuffer z powiązaną sygnaturą czasową. Sygnatura czasowa musi przypadać w tym samym czasie co ścieżki wideo i audio.

Wygenerowany plik MP4 korzysta z TextMetaDataSampleEntry zdefiniowanego w sekcji 12.3.3.2 w specyfikacji ISO BMFF do sygnalizowania formatu MIME metadanych. Gdy za pomocą polecenia MediaExtractor wyodrębnisz plik zawierający ścieżki metadanych, format MIME metadanych będzie miał postać wystąpienia MediaFormat.

Kod demonstracyjny

Przykład MediaRecorder pokazuje, jak nagrać film za pomocą MediaRecorder i Camera API.

W przykładowej aktywności poniżej pokazujemy, jak za pomocą MediaRecorder nagrać plik audio. Używa też MediaPlayer do odtwarzania dźwięku.

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

Więcej informacji

Te strony zawierają tematy związane z nagrywaniem, przechowywaniem i odtwarzaniem plików audio i wideo.