MediaRecorder

https://developer.android.com/guide/topics/media/audio-capture.html 의 내용입니다.

Audio Capture

Android multimedia framework은 다양한 오디오 형식의 획득(capture) 및 인코딩을 지원하여 application 작성 시 쉽게 사용할 수 있다. 이 때 MediaRecorder API를 사용하면 하드웨어 자원을 사용할 수 있다.

Performing Audio Capture

Audio capture 기능을 사용하기 위해서는 다음과 같은 과정들이 필요하다.

  1. android.media.MediaRecorder의 새 개체(instance)를 생성.
  2. MediaRecorder.setAudioSource() 로 사용할 audio source를 설정. 보통 MediaRecorder.AudioSource.MIC가 된다.
  3. MediaRecorder.setOutputFormat() 으로 출력 파일의 형식을 설정.
  4. MediaRecorder.setOutputFile() 로 출력 파일의 이름을 설정.
  5. MediaRecorder.setAudioEncoder() 로 audio encoder를 설정.
  6. MediaRecorder.prepare() 를 호출.
  7. MediaRecorder.start() 로 녹음 시작.
  8. MediaRecorder.stop() 으로 녹음 종료.
  9. 사용이 끝나면 MediaRecorder.release()를 호출.

Statement diagram

MediaRecorder는 아래와 같은 state machine으로 동작한다.

위의 1~9 번 과정이 이루어져야 하는 이유이다.

https://developer.android.com/images/mediarecorder_state_diagram.gif

예제 코드

https://developer.android.com/guide/topics/media/audio-capture.html 제공

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/*
* The application needs to have the permission to write to external storage
* if the output file is written to the external storage, and also the
* permission to record audio. These permissions must be set in the
* application's AndroidManifest.xml file, with something like:
*
* <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
* <uses-permission android:name="android.permission.RECORD_AUDIO" />
*
*/
package com.android.audiorecordtest;
import android.app.Activity;
import android.widget.LinearLayout;
import android.os.Bundle;
import android.os.Environment;
import android.view.ViewGroup;
import android.widget.Button;
import android.view.View;
import android.view.View.OnClickListener;
import android.content.Context;
import android.util.Log;
import android.media.MediaRecorder;
import android.media.MediaPlayer;
import java.io.IOException;
public class AudioRecordTest extends Activity
{
private static final String LOG_TAG = "AudioRecordTest";
private static String mFileName = null;
private RecordButton mRecordButton = null;
private MediaRecorder mRecorder = null;
private PlayButton mPlayButton = null;
private MediaPlayer mPlayer = null;
private void onRecord(boolean start) {
if (start) {
startRecording();
} else {
stopRecording();
}
}
private void onPlay(boolean start) {
if (start) {
startPlaying();
} else {
stopPlaying();
}
}
private void startPlaying() {
mPlayer = new MediaPlayer();
try {
mPlayer.setDataSource(mFileName);
mPlayer.prepare();
mPlayer.start();
} catch (IOException e) {
Log.e(LOG_TAG, "prepare() failed");
}
}
private void stopPlaying() {
mPlayer.release();
mPlayer = null;
}
private void startRecording() {
mRecorder = new MediaRecorder();
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mRecorder.setOutputFile(mFileName);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
try {
mRecorder.prepare();
} catch (IOException e) {
Log.e(LOG_TAG, "prepare() failed");
}
mRecorder.start();
}
private void stopRecording() {
mRecorder.stop();
mRecorder.release();
mRecorder = 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);
}
}
public AudioRecordTest() {
mFileName = Environment.getExternalStorageDirectory().getAbsolutePath();
mFileName += "/audiorecordtest.3gp";
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
LinearLayout ll = new LinearLayout(this);
mRecordButton = new RecordButton(this);
ll.addView(mRecordButton,
new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
0));
mPlayButton = new PlayButton(this);
ll.addView(mPlayButton,
new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
0));
setContentView(ll);
}
@Override
public void onPause() {
super.onPause();
if (mRecorder != null) {
mRecorder.release();
mRecorder = null;
}
if (mPlayer != null) {
mPlayer.release();
mPlayer = null;
}
}
}

Capturing videos

Camera 객체는 API 21에서 변경이 있었다.

아무튼, MediaRecorder와 함께 Camera를 제어할 수 있다. Camera 녹화를 시작할 때에는 반드시 Camera.lock(), Camera.unlock()을 통해 MediaRecorder가 camera 하드웨어에 접근할 수 있게 하고 Camera.open(), Camera.release()를 호출할 수 있다.

Android 4.0 (API 14)부터 Camera.lock()과 Camera.unlock()은 자동으로 제어된다.

사진을 찍는 것과는 달리 Video 녹화는 호출 순서가 세분화되어있다. 아래의 순서를 반드시 따라야 한다.

  1. Camera.open() 을 통해 Camera 객체를 가져온다.
  2. Camera.setPreviewDisplay() 를 통해 SurfaceView에 접근하고 liver camera image를 준비한다.
  3. Camera.startPreview() 를 호출하여 liver camera image를 보여준다.
  4. Camera.unlock() 한다.
  5. MediaRecorder.setCamera() 로 사용할 camera를 선택한다.
  6. MediaRecorder.setAudioSource() 로 audio source를 선택한다. MediaRecorder.AudioSource.CAMCORDER 를 사용한다.
  7. MediaRecorder.setVideoSource() 로 video source를 선택한다. MediaRecorder.VideoSource.CAMERA 를 사용한다.
  8. 출력 파일 및 인코딩을 설정한다.
    8.1. Android 2.2까지
    • setOutputFormat(): Default는 MediaRecorder.OutputFormat.MPEG_4
    • setAudioEncoder(): Default는 MediaRecorder.AudioEncoder.AMR_NB
    • setVideoEncoder(): Default는 MediaRecorder.VideoEncoder.MPEG_4_SP
      8.2. Android 2.2 (API 8) 이상
    • MediaRecorder.setProfile 메소드를 사용하며, CamcorderProfile.get() 으로 프로필 개체를 가져온다.
  9. setOutputFile() 로 출력 파일을 설정한다. getOutputMediaFile(MEDIA_TYPE_VIDEO).toString() 을 사용하며, Saving Media Files에서 예제를 확인할 수 있다.
  10. setPreviewDisplay() 를 통해 preview를 확인할 수 있다.
  11. MediaRecorder.prepare()를 호출하여 설정사항을 준비한다.
  12. MediaRecorder.start() 로 시작한다.
  13. MediaRecorder.stop() 으로 중지한다.
  14. MediaRecorder.reset() 으로 설정사항을 제거한다. 이 과정은 선택 사항이다.
  15. MediaRecorder.release() 로 MediaRecorder를 release한다.
  16. Camera.lock() 으로 잠근다. Android 4.0(API 14)부터는 MediaRecorder.prepare()의 호출이 실패하지 않는다면 이 작업은 필요없다.
  17. Camera.stopPreview() 로 preview를 중지한다.
  18. Camera.release() 로 Camera를 release한다.

MediaRecorder를 이용하여 Camera preview를 생성하지 않고 몇 개의 단계를 건너뛸 수 있지만 일반적으로는 그렇게 하지 않기 때문에 여기서 다루지는 않는다.

Application에서 Video 녹화를 빈번하게 사용한다면 setRecordingHint(boolean)을 사용하여 시간을 아낄 수 있다.

Configure MediaRecorder

MediaRecorder를 사용하여 Video 녹화를 할 때, 설정사항을 체크하고 구현하기 위해 MediaRecorder.prepare()를 호출해야만 한다. 아래의 예제는 그 과정을 나타낸 좋은 예제이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private boolean prepareVideoRecorder(){
mCamera = getCameraInstance();
mMediaRecorder = new MediaRecorder();
// Step 1: Unlock and set camera to MediaRecorder
mCamera.unlock();
mMediaRecorder.setCamera(mCamera);
// Step 2: Set sources
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
// Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));
// Step 4: Set output file
mMediaRecorder.setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());
// Step 5: Set the preview output
mMediaRecorder.setPreviewDisplay(mPreview.getHolder().getSurface());
// Step 6: Prepare configured MediaRecorder
try {
mMediaRecorder.prepare();
} catch (IllegalStateException e) {
Log.d(TAG, "IllegalStateException preparing MediaRecorder: " + e.getMessage());
releaseMediaRecorder();
return false;
} catch (IOException e) {
Log.d(TAG, "IOException preparing MediaRecorder: " + e.getMessage());
releaseMediaRecorder();
return false;
}
return true;
}

Android 2.2(API 8)까지는 출력 형식 및 인코딩 형식에 대해 CamcorderProfile를 사용하지 않고 아래 코드처럼 직접 설정했어야 했다.

1
2
3
4
// Step 3: Set output format and encoding (for versions prior to API Level 8)
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);

아래의 설정값들은 MediaRecorder에 의해 기본으로 설정되지만 원하는대로 변경할 수 있다.

  • setViedeoEncodingBitRate()
  • setVideoSize()
  • setVideoFrameRate()
  • setAudioEncodingBitRate()
  • setAudioChannels()
  • setAudioSamplingRate()

Starting and stopping MediaRecorder

MediaRecorder를 사용하여 video 녹화를 시작/중지할 때에는 반드시 아래의 순서를 따라야 한다.

  1. Camera.unlock()
  2. 아래 예시 코드처럼 MediaRecorder를 설정한다.
  3. MediaRecorder.start() 로 녹화 시작
  4. 녹화
  5. MediaRecorder.stop() 으로 녹화 중지
  6. MediaRecorder.release()
  7. Camera.lock()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private boolean isRecording = false;
// Add a listener to the Capture button
Button captureButton = (Button) findViewById(id.button_capture);
captureButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isRecording) {
// stop recording and release camera
mMediaRecorder.stop(); // stop the recording
releaseMediaRecorder(); // release the MediaRecorder object
mCamera.lock(); // take camera access back from MediaRecorder
// inform the user that recording has stopped
setCaptureButtonText("Capture");
isRecording = false;
} else {
// initialize video camera
if (prepareVideoRecorder()) {
// Camera is available and unlocked, MediaRecorder is prepared,
// now you can start recording
mMediaRecorder.start();
// inform the user that recording has started
setCaptureButtonText("Stop");
isRecording = true;
} else {
// prepare didn't work, release the camera
releaseMediaRecorder();
// inform user
}
}
}
}
);

Releasing the camera

Camera는 shared resource이기 때문에 개체를 얻어와서 사용하고 사용이 끝나면 release해줘야 한다. Activity.onPause() 상태가 되어도 release해줘야 한다. Application에서 camera를 적절히 release해주지 않는다면 application이 종료될 때까지 다른 곳에서 접근할 수 없다.

Camera 개체를 release하기 위해 Camera.release()를 사용하면 된다. 아래 예시를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class CameraActivity extends Activity {
private Camera mCamera;
private SurfaceView mPreview;
private MediaRecorder mMediaRecorder;
...
@Override
protected void onPause() {
super.onPause();
releaseMediaRecorder(); // if you are using MediaRecorder, release it first
releaseCamera(); // release the camera immediately on pause event
}
private void releaseMediaRecorder(){
if (mMediaRecorder != null) {
mMediaRecorder.reset(); // clear recorder configuration
mMediaRecorder.release(); // release the recorder object
mMediaRecorder = null;
mCamera.lock(); // lock camera for later use
}
}
private void releaseCamera(){
if (mCamera != null){
mCamera.release(); // release the camera for other applications
mCamera = null;
}
}
}

Saving Media Files

사진, 비디오 등의 Media 파일들은 장치의 외부 저장소 (SD Card)에 저장되어야 한다. 그래야 사용자가 접근할 수 있고 System 공간을 보존할 수 있다. 아무 디렉토리에나 저장이 가능하긴 하지만 다음과 같은 두 개의 표준 저장소를 제공한다.

  • Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
    • 사진 및 비디오를 저장하기 위해 추천되는 저장소이다. 이 공간은 public이기 때문에 쉽게 검색, 읽기, 쓰기, 삭제를 할 수 있다. Application을 삭제하더라도 여기에 남아있는 자료는 삭제되지 않는다. 사용자가 원래 사용하던 파일들과의 간섭을 피하기 위해 하위 디렉토리를 생성해서 사용하자(아래 예시에 나와있다). 이 메소드는 Android 2.2 (API 8)부터 사용 가능하다. 이전 버전에서는 Saving Shard Files를 확인하자.
  • Context.getExternalFilesDir(Environment.DIRECTORY_PICTURE)
    • 이 메소드는 해당 application에 속한 디렉토리를 반환한다. Application이 삭제될 때 여기에 있는 파일들이 같이 삭제된다. 이 파일들에대해 보안규칙이 적용되지 않기 떄문에 다름 application들도 이 파일들을 읽기/수정/삭제 할 수 있다.

아래 예제는 Media 파일을 위한 File 또는 Uri 저장소를 어떻게 생성하고 Camera application이 Intent를 통해 사용하는 방법을 나타낸다.

더 자세한 정보는 Data Storage 에서 확인할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public static final int MEDIA_TYPE_IMAGE = 1;
public static final int MEDIA_TYPE_VIDEO = 2;
/** Create a file Uri for saving an image or video */
private static Uri getOutputMediaFileUri(int type){
return Uri.fromFile(getOutputMediaFile(type));
}
/** Create a File for saving an image or video */
private static File getOutputMediaFile(int type){
// To be safe, you should check that the SDCard is mounted
// using Environment.getExternalStorageState() before doing this.
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), "MyCameraApp");
// This location works best if you want the created images to be shared
// between applications and persist after your app has been uninstalled.
// Create the storage directory if it does not exist
if (! mediaStorageDir.exists()){
if (! mediaStorageDir.mkdirs()){
Log.d("MyCameraApp", "failed to create directory");
return null;
}
}
// Create a media file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
File mediaFile;
if (type == MEDIA_TYPE_IMAGE){
mediaFile = new File(mediaStorageDir.getPath() + File.separator +
"IMG_"+ timeStamp + ".jpg");
} else if(type == MEDIA_TYPE_VIDEO) {
mediaFile = new File(mediaStorageDir.getPath() + File.separator +
"VID_"+ timeStamp + ".mp4");
} else {
return null;
}
return mediaFile;
}

참조

Share