סקירה כללית של MediaRecorder

מסגרת המולטימדיה של 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 במקום זאת. שלא כוללים AGC או ביטול רעשים. אפשר להשתמש ב-UNPROCESSED כמקור אודיו, גם כשהנכס לא נתמך, אבל לא ניתן להבטיח במקרה כזה, האות לא יהיה מעובד או לא.

  • הגדרת הפורמט של קובץ הפלט באמצעות setOutputFormat() הערה: החל מ-Android 8.0 (רמת API 26), MediaRecorder תומך ב-MPEG2_TS שהוא שימושי לסטרימינג:

    Kotlin

    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_2_TS)
    

    Java

    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 ו- Camera API.

בפעילות לדוגמה שלמטה אפשר לראות איך להשתמש ב-MediaRecorder כדי להקליט קובץ אודיו. הוא נעשה שימוש גם ב-MediaPlayer כדי להשמיע את האודיו.

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

מידע נוסף

הדפים האלה כוללים נושאים שקשורים להקלטה, לאחסון ולהפעלה של אודיו ווידאו.