نظرة عامة على MediaRecorder

يتضمن إطار عمل الوسائط المتعددة على Android دعمًا لالتقاط مجموعة متنوعة من تنسيقات الصوت والفيديو الشائعة وترميزها. ويمكنك استخدام واجهات برمجة التطبيقات MediaRecorder API إذا كانت متوافقة مع مكوّنات الجهاز.

يوضّح لك هذا المستند كيفية استخدام MediaRecorder لكتابة تطبيق يلتقط الصوت من ميكروفون الجهاز وحفظ الصوت وإعادة تشغيله (باستخدام MediaPlayer). لتسجيل فيديو، ستحتاج إلى استخدام كاميرا الجهاز مع MediaRecorder. يتم توضيح ذلك في دليل الكاميرا.

ملاحظة: لا يمكن لمحاكي Android تسجيل الصوت. احرص على اختبار الرمز على جهاز حقيقي يمكنه التسجيل.

جارٍ طلب الإذن لتسجيل الصوت

لتتمكن من التسجيل، يجب أن يخبر تطبيقك المستخدم بأنّه سيصل إلى إدخال الصوت بالجهاز. يجب تضمين علامة الإذن هذه في ملف البيان للتطبيق:

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

يُعتبَر RECORD_AUDIO إذنًا "خطيرًا" لأنه قد يشكّل خطرًا على خصوصية المستخدم. بدءًا من الإصدار Android 6.0 (مستوى واجهة برمجة التطبيقات 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 (المستوى 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 (المستوى 28 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث، لا يمكن للتطبيقات التي تعمل في الخلفية الوصول إلى الميكروفون. وبالتالي، يجب أن يسجّل تطبيقك الصوت فقط عندما يكون في المقدّمة أو عند تضمين مثيل MediaRecorder في خدمة تعمل في المقدّمة.

استخدام MediaMuxer لتسجيل قنوات متعددة

بدءًا من الإصدار Android 8.0 (المستوى 26 من واجهة برمجة التطبيقات)، يمكنك استخدام MediaMuxer لتسجيل عدة عمليات بث صوتية وفيديو في الوقت نفسه. في الإصدارات السابقة من Android، يمكنك تسجيل مقطع صوتي واحد و/أو مسار فيديو واحد فقط في كل مرة.

استخدِم طريقة addTrack() لمزج عدة مسارات معًا.

يمكنك أيضًا إضافة مسار بيانات وصفية واحد أو أكثر مع معلومات مخصّصة لكل إطار، ولكن لحاويات MP4 فقط. يحدّد تطبيقك تنسيق البيانات الوصفية ومحتواها.

إضافة بيانات وصفية

يمكن أن تكون البيانات الوصفية مفيدة للمعالجة بلا اتصال بالإنترنت. على سبيل المثال، يمكن استخدام البيانات التي تم التقاطها من مستشعر الجيروسكوب لإجراء تثبيت الفيديو.

عند إضافة مقطع صوتي يتضمّن بيانات وصفية، يجب أن يبدأ تنسيق MIME للمقطع بالبادئة application/. البيانات الوصفية للكتابة هي نفسها كتابة بيانات الفيديو أو الصوت، لكن البيانات ليست واردة من MediaCodec. بدلاً من ذلك، يمرّر التطبيق ByteBuffer مع طابع زمني مرتبط إلى طريقة writeSampleData(). يجب أن يكون الطابع الزمني في الوقت نفسه الخاص بالمقطع الصوتي والفيديو.

يستخدم ملف MP4 الذي تم إنشاؤه TextMetaDataSampleEntry المحدّدة في الفقرة 12.3.3.2 من مواصفات ISO BMFF للإشارة إلى تنسيق MIME للبيانات الوصفية. عند استخدام MediaExtractor لاستخراج ملف يحتوي على مسارات بيانات وصفية، يظهر تنسيق MIME للبيانات الوصفية كمثيل لـ MediaFormat.

نموذج التعليمات البرمجية

يعرض نموذج MediaRecorder كيفية تسجيل فيديو باستخدام MediaRecorder وواجهة برمجة التطبيقات للكاميرا.

يوضّح النشاط الوارد في المثال أدناه كيفية استخدام ميزة "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;
        }
    }
}

مزيد من المعلومات

تتناول هذه الصفحات مواضيع تتعلق بتسجيل الصوت والفيديو وتخزينهما وتشغيلهما.