지난 포스팅에서 'MediaRecorder 를 이용한 오디오 레코딩 예제' 를 살펴보았습니다.

이번에는 지난번에 생성한 Project에 MediaRecorder API 를 이용한 캠코딩 예제를 추가해 보도록 하겠습니다.

즉 지난번에는 음성만 녹음하였다면, 이번에는 카메라로 들어오는 화면도 함께 캠코딩하는 것입니다.

 

1. Permission

우선 캠코딩을 하기 위해서 Camera 를 사용해야 하기 때문에, AndroidManifest.xml 에 Camera 관련 Permission 을 추가해 줍니다.

총 3개의 Permission 입니다. 음성을 녹음할 수 있는 권한과, 캠코딩된 파일을 저장해야 하기 때문에 External Storage 에 쓸수 있는 권한, 그리고 카메라로 들어오는 영상을 캠코딩해야 하므로 Camera 사용 권한도 함께 줄 수 있도록 합니다.

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAMERA"/>

 

2. Layout 구성하기

Layout은 지난번 구현하였던 오디오 레코딩 예제에 버튼을 하나 추가해서 캠코딩을 시작하고 정지할 수 있게 합니다.

카메라 프리뷰 화면과 녹화된 영상을 재생할 때 Video 를 뿌려줄 SurfaceView 를 하나 구성합니다.  

<Button
android:id="@+id/bt_camcording"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start Camcording"/>



<SurfaceView
android:id="@+id/sv"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

캠코딩 시작 버튼이 눌렸을 때 캠코더를 시작하고, 다시 눌렸을 때 종료할 수 있도록 아래와 같이 OnClickListener 를 구현 해 줍니다.

mBtCamcording = (Button)findViewById(R.id.bt_camcording);
mBtCamcording.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
hasVideo = true;
initVideoRecorder();
startVideoRecorder();
}
});

 

3. Camera 와 Surface 연결 및 레코딩 시작 하기

위에서 버튼이 눌리면 가장 먼저 실행되는 initVideoRecorder() 함수에서는 Camera Device 를 open 하고, SurfaceHolder 를 초기화 하는 일을 수행 합니다.

Camera.open() 함수는 카메라 인스턴스를 리턴하고, 사용자는 이 객체를 통해 카메라 Device 에 접근할 수 있습니다.

Camera.open(int) 함수를 이용하면 Device 에 장착되어 있는 여러개의 카메라 중, 원하는 카메라에 접근하여 사용할 수 있습니다.

void initVideoRecorder() {
mCamera = Camera.open();
mCamera.setDisplayOrientation(90);
mSurfaceHolder = mSurface.getHolder();
mSurfaceHolder.addCallback(this);
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}

mSurfaceHolder 에는 SurfaceView 의 getHolder 메소드를 통해 SurfaceHolder 의 인스턴스를 연결해 주고, Surface 의 변화가 있을 때 처리를 위해 Callback 을 등록해 줍니다.

두번째로 불리는 startVideoRecorder 함수에서는 MediaRecorder 를 초기화 하고, 실제 레코딩을 수행 합니다.

void startVideoRecorder() {
if(isRecording) {
mRecorder.stop();
mRecorder.release();
mRecorder = null;

mCamera.lock();
isRecording = false;

mBtCamcording.setText("Start Camcording");
}
else {
runOnUiThread(new Runnable() {
@Override
public void run() {
mRecorder = new MediaRecorder();
mCamera.unlock();
mRecorder.setCamera(mCamera);
mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);
mRecorder.setOrientationHint(90);

mPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/record.mp4";
Log.d(TAG, "file path is " + mPath);
mRecorder.setOutputFile(mPath);

mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
try {
mRecorder.prepare();
}catch(Exception e){
e.printStackTrace();
}
mRecorder.start();
isRecording = true;

mBtCamcording.setText("Stop Camcording");
}
});
}
}

음성 녹음만 할 때는 Audio 에 관련된 설정들만 해주면 되었지만, 캠코딩인 경우에는 Video Source 에 대한 설정도 해주어야 합니다.

setCamera(..) :  비디오 레코딩 시 사용할 카메라를 설정해 줍니다.

setVideoSource(..) : 레코딩 시 비디오 소스를 설정합니다. DEFAULT, CAMERA, SURFACE 세개의 값 중 하나를 선택하면 됩니다.

setVideoEncoder(..) : 비디오 코덱을 설정합니다. Video Encoder 의 경우 DEFAULT, H263, H264, MPEG_4_SP, VP8, HEVC 등을 제공하고 있습니다.

Android 에서 제공하는 Video/Audio Encoder/Decoder 의 정보는 하기 사이트에서 더 자세하게 볼 수 있습니다.

https://developer.android.com/guide/topics/media/media-formats.html

 

각종 설정들을 마무리 하고 mRecorder.start() 를 호출하면 드디어 캠코딩이 시작됩니다.

캠코딩 시 Preview 화면은 MediaRecorder 의 setPreviewDisplay(..) 함수를 통해 설정해 준 SurfaceView 에 뿌려지게 됩니다.

 

4. 레코딩 한 파일 재생하기

재생의 경우에는 Audio 파일 재생과 완전히 동일하지만, Video 의 경우 화면을 뿌려주기 위한 Surface View 만 추가로 지정해 주면 됩니다.

기존에 만들어 두었던 Player Button 의 OnClickListener 에 아래와 같이 Video 를 가질 경우, 만들어 놓았던 SurfaceHolder 를 지정 해 주고,

if(hasVideo == true) {
mPlayer.setDisplay(mSurfaceHolder);
mPlayer.setOnCompletionListener(mListener);
}

Video 재생이 끝났을 경우 Button 의 Text 를 변경해 주기 위한 OnCompletionListener 를 등록해 줍니다.

MediaPlayer.OnCompletionListener mListener = new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
mBtPlay.setText("Start Playing");
}
};

등록된 Listener 에서는 간단하게 버튼의 Text 만 변경해 주었습니다.

 

여기까지 아직 많은 예외처리들이 필요하지만, 기본적인 오디오, 비디오 레코더를 완성 하였습니다.

MediaRecorder API와 Camera Class 에서 제공하는 API를 사용하면 이렇게 간단하게 레코딩 기능을 구현할 수 있습니다.

전체 소스는 https://github.com/bettercho/MyGithub/tree/master/MediaRecorder/app/src 를 참고해 주세요.

 

 

  1. jpark 2018.04.23 17:05

    안녕하세요, 포스팅 보고 궁금한게 있어서 적습니다!
    위에 소스대로 했을 때, 안드로이드 스튜디오에서 hardware Camera에 대해 api21부터는 사용되지 않는다고 했는데,
    세팅 환경을 낮춰야하는 걸까요? 아니면 무시하고 사용해도 상관없는 건가요?

    • bettercho 2018.05.02 22:51 신고

      Camera API 는 말씀하신데로 API level 21 부터 deprecated 되었고, 이후 level 에서는 Camera2 API 사용을 권고하고 있습니다.
      일단은 무시하고 사용해도 동작에는 이상이 없으나 향후를 생각하면 Camera2를 사용한 예제도 함께 보시는게 좋습니다.

  2. jpark 2018.04.23 17:07

    한 가지 더 궁금한 점은, 영상 녹화를 surfaceView에 따로 뿌리지 않아도 녹화가 진행될까요?

    • bettercho 2018.05.02 23:06 신고

      setPreviewDisplay 로 preview 화면을 뿌릴 Surface 를 지정해 주지 않으면 RuntimeException 발생합니다.

      05-02 22:52:24.438 788 3372 E Camera3-Device: Camera 0: createCaptureRequest: Request references unknown stream 255
      05-02 22:52:24.438 788 3372 E Camera3-Device: Camera 0: convertMetadataListToRequestListLocked: Can't create capture request
      05-02 22:52:24.438 788 3372 E Camera2-StreamingProcessor: startStream: Camera 0: Unable to set preview request to start preview: Invalid argument (-22)
      05-02 22:52:24.507 28417 28417 E AndroidRuntime: FATAL EXCEPTION: main
      05-02 22:52:24.507 28417 28417 E AndroidRuntime: Process: com.codetravel.mediarecorder, PID: 28417
      05-02 22:52:24.507 28417 28417 E AndroidRuntime: java.lang.RuntimeException: start failed.

      Developer 사이트에 보면 startPreview() 이전에 setPreviewDisplay() 함수가 호출되어야 한다고 명시되어 있네요. 참고바랍니다.
      https://developer.android.com/reference/android/hardware/Camera#setPreviewDisplay(android.view.SurfaceHolder)

Android 에서 제공하는 Recording API 중, 지난번에 보았던 AudioRecorder 말고 더 편리한 MediaRecorder 가 있습니다.

AudioRecorder 는 오디오만 레코딩 가능 하지만, MediaRecorder 의 경우 Audio 및 Video 컨텐츠의 레코딩이 가능 합니다.

다만 인코딩 된 파일을 받기 때문에 AudioRecorder 처럼 PCM Data 를 바로 받아올 수는 없는 단점이 있습니다.

본인이 구현하려는 레코더의 사용 용도에 맞게 AudioRecorder/MediaRecorder 를 선택하여 사용하시면 되겠습니다.

 

Android Developer 의 MediaRecorder API Guide 를 참고하면, MediaRecorder 는 아래와 같은 state machine 을 갖습니다.

따라서 MediaRecorder API 를 사용할 때는 아래의 State 를 잘 따라서 코딩해야 합니다. 예를들어 Initail 상태에서는 바로 Prepared 상태로 갈 수 없고, Initailized 와 DataConfigured 상태를 거쳐야지 Prepared 상태가 될 수 있습니다.

이를 어기게 되면 StateIllegalException 이 발생하고, 원하는 동작을 얻을 수 없으니 조심해야 합니다.

 

 

Android Developer 사이트에서 설명하는 일반적인 Recording Flow 는 아래 코드와 같습니다.

각 단계별로 실제 레코딩시 일어나는 동작들에 대해 살펴보도록 하겠습니다.

MediaRecorder recorder = new MediaRecorder();
 recorder
.setAudioSource(MediaRecorder.AudioSource.MIC); //----------- (1)
 recorder
.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); // -- (2)
 recorder
.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); // ------(3)
 recorder
.setOutputFile(PATH_NAME);
 recorder
.prepare(); // -----------------------------------------------(4)
 recorder
.start();   // Recording is now started ----------------------(5)
 
...
 recorder
.stop();
 recorder
.reset();   // You can reuse the object by going back to setAudioSource() step
 recorder
.release(); // Now the object cannot be reused

 

(1) AudioSource 설정

AudioSource 의 종류는 아래 표에 정리되어 있습니다.

DEFAULT 

 0

 Default audio source

MIC 

 1

 Microphone audio source

VOICE_UPLINK  2  Voice call uplink (TX) audio source

VOICE_DOWNLINK 

 3

 Voice call downlink (RX) audio source 

VOICE_CALL

 4

 Voice call uplink + downlink audio source

CAMCORDER   5

 Microphone audio source tuned for video recording. wit the same orientation as the camera if available

VOICE_RECOGNITION

 6  Microphone audio source tuned for voice recognition

VOICE_COMMUNICATION

 7

 Microphone audio source tuned for voice communications such as VoIP

REMOTE_SUBMIX 

 8

 Audio source for a submix for audio streams to be presented remotely. An application can use this audio source to capture a mix of audio streams that should be transmitted to a remote receiver such as a Wifi display

UNPROCESSED

 9

 Microphone audio source tuned for unprocessed sound if available.

RADIO_TUNER

 1998

 Audio source for capturing broadcast radio tuner output

HOTWORD

 1999

 Audio source for preemptible, low-priority software hotword detection

보통은 마이크로 들어오는 음성을 많이 녹음하기 때문에 MIC 값을 많이 사용하고, 용도에 맞게 그 외의 값들을 설정할 수 있습니다.

제일 아래 RADIO_TUNER 와 HOTWORD 는 System API 로 일반 Application 에서는 사용 불가 합니다.

setAudioSource 함수가 호출되지 않을 경우에는 output file 에 audio track 이 포함되지 않습니다. 즉, 오디오가 녹음되지 않는다는 이야기입니다.

오디오를 녹음하고 싶으면 setAudioSource 함수를 prepare 전에 꼭 호출해 주어야 합니다.

 

(2) Output Format 설정

레코딩 후 실제 파일로 저장할 때, 저장할 파일의 format 을 설정해 줄 수 있습니다. 

DEFAULT

 0

THREE_GPP

 1

MPEG_4

 2

AMR_NB

 3

AMR_WB

 4

AAC_ADIF

 5

AAC_ADTS

 6

OUTPUT_FORMAT_RTP_AVP

 7

MPEG_2_TS

 8

WEBM

 9

비디오 포맷으로 가장 많이 사용되는 THREE_GPP 혹은 MPEG_4 가 있고, 오디오만 녹음 할 경우에는 오디오 전용 파일포맷인 AMR 이나 AAC 가 많이 사용 됩니다.

setOutputFormat() 함수는 setAudioSource()/setVideoSource() 호출 이후 / prepare() 이전에 호출되어야 합니다.

 

(3) AudioEncoder 설정

Audio Recording 시 Audio Encoder 를 설정해 주는 함수 입니다. 이 함수를 호출하지 않을 경우에 녹음된 파일에 audido track 은 포함되지 않습니다.

즉, 이 함수도 setAudioSource 와 동일하게 오디오를 녹음하고 싶다면 setOutputFormat() 이후 / prepare() 이전에 꼭 호출해 주어야 합니다.

 DEFAULT

 0

 AMR_NB

 1

 AMR_WB

 2

 AAC

 3

 HE_AAC

 4

 AAC_ELD

 5

 VORBIS

 6

 

(4) prepare

prepare 단계는 앞서 설정한 설정값들로 recording 을 준비하는 단계 입니다.

때문에 이 함수는 audio/video source와 audio/video encoder 를 모두 설정하고 file format 을 확정 지은 후에 불려야 합니다.

이 함수가 불리면 위의 State Machine 에서 recorder 는 prepared 상태가 되어 start 나 reset 을 호출할 수 있습니다.

 

(5) start

start 함수를 호출하면 정해놓은 audio/video source 에서 실제 레코딩을 시작하게 되고, state 는 Recording 상태가 됩니다.

start 이후 원하는 시점에 stop 을 호출하면, 그 시점까지 레코딩이 진행 됩니다.

 

이 외에도 API Level 24 부터 제공되는 pause() / resume() 함수를 이용해 레코딩을 잠시 멈추는 것이 가능합니다.

stop() 과 다르게 pause() 함수로 레코딩을 멈추면 기존의 configuration 은 유지된 채로 MediaRecorder 가 잠시 멈추게 됩니다.

이 상태에서 resume() 함수로 레코딩을 다시 시작할 수 있습니다. 당연한 이야기지만 paused 상태에서 레코딩 되는 내용은 버려지게 됩니다.

 

developer 사이트를 참고하면 위의 간단한 API 이외에도 MediaRecorder 에서 제공하는 다양한 API 들을 볼 수 있으니 참고하시면 되겠습니다.

https://developer.android.com/reference/android/media/MediaRecorder.html

 

다음 포스팅에서는 MediaRecorder 를 이용한 실제 레코딩 예제를 살펴보도록 하겠습니다.

 

 

 

+ Recent posts