Android 멀티미디어 프레임워크에는
오디오 및 동영상 형식입니다. 지원되는 경우 MediaRecorder
API를 사용할 수 있습니다.
영향을 받습니다.
이 문서에서는 MediaRecorder
를 사용하여 기기에서 오디오를 캡처하는 애플리케이션을 작성하는 방법을 보여줍니다.
마이크를 설정하고 오디오를 저장 및 재생합니다 (MediaPlayer
사용). 동영상을 녹화하려면 다음 작업을 완료해야 합니다.
MediaRecorder
과(와) 함께 기기의 카메라를 사용합니다. 이에 대해서는 카메라 가이드에 설명되어 있습니다.
참고: Android Emulator는 오디오입니다. 녹음할 수 있는 실제 기기에서 코드를 테스트해야 합니다.
오디오 녹음 권한 요청
녹화하려면 앱에서 오디오 입력을 받습니다. 앱의 매니페스트 파일에 다음 권한 태그를 포함해야 합니다.
<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)
자바
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_2_TS);
- 다음을 사용하여 출력 파일 이름을 설정합니다.
setOutputFile()
실제 파일을 나타내는 파일 설명자를 지정해야 합니다. - 다음을 사용하여 오디오 인코더 설정:
setAudioEncoder()
- 다음을 호출하여 초기화를 완료합니다.
prepare()
다음을 호출하여 녹음기 시작 및 중지
start()
및
stop()
각각 1개의 값으로 사용합니다.
MediaRecorder
인스턴스를 완료하면 해당 리소스를 해제합니다.
가능한 한 빨리
release()
입니다.
참고: Android 9 (API 수준 28) 또는
백그라운드에서 실행되는 앱은 마이크에 액세스할 수 없습니다. 따라서
앱은 포그라운드에 있을 때나 사용자가 오디오를 재생할 때만 오디오를 녹음해야 합니다.
MediaRecorder
의 인스턴스를 포함하는 경우
포그라운드 서비스.
MediaMuxer를 사용하여 여러 채널 녹음하기
Android 8.0 (API 수준 26)부터 MediaMuxer
를 사용할 수 있습니다.
동시에 여러 개의 오디오 및 동영상 스트림을 녹화할 수 있습니다. 이전 버전의 Android에서는
한 번에 하나의 오디오 트랙 및/또는 하나의 동영상 트랙을 녹음합니다.
addTrack()
사용
메서드를 사용하여 여러 트랙을 혼합할 수 있습니다.
각 프레임에 대한 사용자 지정 정보가 있는 하나 이상의 메타데이터 트랙을 추가할 수도 있지만 MP4 컨테이너에만 적용됩니다 앱은 메타데이터의 포맷과 콘텐츠를 정의합니다.
메타데이터 추가
메타데이터는 오프라인 처리에 유용할 수 있습니다. 예를 들어 자이로 센서는 동영상 흔들림 보정을 수행하는 데 사용될 수 있습니다.
메타데이터 트랙을 추가할 때 트랙의 MIME 형식은 접두사로 시작해야 합니다.
application/
메타데이터 쓰기는 다음을 제외하면 동영상 또는 오디오 데이터 쓰기와 동일합니다.
MediaCodec
에서 가져온 데이터가 아님을 확인합니다. 대신 앱은
ByteBuffer
및
writeSampleData()
메서드를 사용하여 지도 가장자리에
패딩을 추가할 수 있습니다.
타임스탬프는 동영상 및 오디오 트랙과 동일한 시간 베이스에 있어야 합니다.
생성된 MP4 파일은 섹션 12.3.3.2에 정의된 TextMetaDataSampleEntry
를 사용합니다.
ISO BMFF 사양 준수
를 사용합니다. 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 } }
자바
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; } } }
자세히 알아보기
이 페이지에서는 오디오와 동영상 녹음/녹화, 저장 및 재생과 관련된 주제를 다룹니다.