מסגרת המולטימדיה של 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; } } }
מידע נוסף
הדפים האלה כוללים נושאים שקשורים להקלטה, לאחסון ולהפעלה של אודיו ווידאו.