يتضمن إطار عمل الوسائط المتعددة على 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; } } }
مزيد من المعلومات
تتناول هذه الصفحات مواضيع تتعلق بتسجيل الصوت والفيديو وتخزينهما وتشغيلهما.