نمای کلی MediaRecorder

چارچوب چند رسانه ای اندروید شامل پشتیبانی از ضبط و کدگذاری انواع فرمت های رایج صوتی و تصویری است. اگر توسط سخت افزار دستگاه پشتیبانی می شود، می توانید از API های MediaRecorder استفاده کنید.

این سند به شما نشان می دهد که چگونه از MediaRecorder برای نوشتن برنامه ای استفاده کنید که صدا را از میکروفون دستگاه ضبط می کند، صدا را ذخیره می کند و آن را پخش می کند (با MediaPlayer ). برای ضبط ویدیو باید از دوربین دستگاه به همراه MediaRecorder استفاده کنید. این در راهنمای دوربین توضیح داده شده است.

توجه: شبیه ساز اندروید نمی تواند صدا را ضبط کند. حتما کد خود را روی یک دستگاه واقعی که قابلیت ضبط را دارد تست کنید.

درخواست مجوز برای ضبط صدا

برای اینکه بتوانید ضبط کنید، برنامه شما باید به کاربر بگوید که به ورودی صوتی دستگاه دسترسی خواهد داشت. باید این تگ مجوز را در فایل مانیفست برنامه قرار دهید:

<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() تنظیم کنید. توجه داشته باشید که MediaRecorder با Android 8.0 (سطح API 26) از فرمت 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 برای ضبط چندین جریان صوتی و تصویری به طور همزمان استفاده کنید. در نسخه‌های قبلی اندروید می‌توانید هر بار فقط یک آهنگ صوتی و/یا یک آهنگ ویدیویی ضبط کنید.

از متد 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 برای پخش صدا استفاده می کند.

کاتلین

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

بیشتر بدانید

این صفحات موضوعات مربوط به ضبط، ذخیره و پخش صدا و تصویر را پوشش می دهند.