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

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

يوضح هذا المستند كيفية استخدام 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;
        }
    }
}

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

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