지난 포스팅에서 '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 를 참고해 주세요.

 

 

Android 에서 음성을 녹음할 때 사용할 수 있는 API 는 여러가지가 있지만, 지난 포스팅에서 가장 대표적으로 사용되는 AudioRecord API 를 이용한 예제를 다루어 보았습니다.

이번 포스팅에서는 MediaRecorder API 를 이용한 음성 녹음 예제를 다루어 보겠습니다.

AudioRecord 를 이용한 예제처럼 간단하게 완성할 수 있습니다.

 

1. main layout 구성하기

layout 은 간단히 버튼을 두개 놓고, 하나는 녹음 시작/정지, 다른 하나는 녹음된 컨텐츠를 재생/정지 하도록 구현해 보겠습니다.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.codetravel.mediarecorder.MainActivity">

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

<Button
android:id="@+id/bt_play"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start Playing"/>
</LinearLayout>

이를 위해 bt_record 와 bt_play 를 각각 layout 에 추가 하고, MainActivity 에서 각각의 버튼에 대한 OnClickListener() 를 구현 해 줍니다.

OnClickListener() 에서는 현재 레코딩 상태에 따라서 recorder 를 Start 하거나 Stop 하는 부분이 구현될 것입니다.

 

2. MediaRecorder 생성 후 레코딩 준비하기

recorder 를 start 하기에 앞서, MediaRecorder 객체를 하나 생성 하고 레코딩 할 파일의 포맷, 인코더 등등을 설정 해 줍니다.

    @Override                                                     
    protected void onCreate(Bundle savedInstanceState) {          
        super.onCreate(savedInstanceState);                       
        setContentView(R.layout.activity_main);    

        mRecorder = new MediaRecorder();                          
    }    

    // 레코더 기본 설정 
    void initAudioRecorder() {                                                                
        mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);                              
        mRecorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS);                       
        mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);                            
                                                                                              
        mPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/record.aac";  
        Log.d(TAG, "file path is " + mPath);                                                  
        mRecorder.setOutputFile(mPath);                                                       
        try {                                                                                 
            mRecorder.prepare();                                                              
        } catch (Exception e) {                                                               
            e.printStackTrace();                                                              
        }                                                                                     
    }                                                                                                                                                 

initAudioRecorder() 함수 안에다가 MediaRecorder prepare 단계 까지 모든 설정들을 구현 합니다.

MediaRecorder 의 State Diagram 을 참고하면, 한번 MediaRecorder 를 Stop 할 시에, 각종 설정들을 다시 해 주어야 하기 때문에 함수로 분리하여 구현하는 것이 추후 더 편리합니다.

 

3. 녹음 시작/ 정지

isRecording 이라는 flag 를 두어 현재 녹음 중인지 아닌지를 구분 할 수 있습니다.

현재 녹음중이 아니면 initAudioRecorder(), start() 를 수행 하여 녹음을 시작하고, 녹음중 일 경우에는 stop() 을 호출하여 녹음을 정지합니다.

isRecording flag 와 button 의 text 는 현재 상태에 맞게 update 합니다.

    boolean isRecording = false;                                                                      
    Button mBtRecord = null;                                       
                                                                  
    @Override                                                     
    protected void onCreate(Bundle savedInstanceState) {          
        ..
        mBtRecord = (Button) findViewById(R.id.bt_record);        
        mBtRecord.setOnClickListener(new View.OnClickListener() { 
            @Override                                             
            public void onClick(View v) {                         
                if (isRecording == false) {                       
                    initAudioRecorder();                          
                    mRecorder.start();                            
                                                                  
                    isRecording = true;                           
                    mBtRecord.setText("Stop Recording");          
                } else {                                          
                    mRecorder.stop();
                             
                    isRecording = false;                          
                    mBtRecord.setText("Start Recording");         
                }                                                 
            }                                                     
        });                                                       

 

4. 재생 시작하기

재생 버튼에 대한 onClickListener 를 생성하고 재생을 위한 MediaPlayer 객체를 하나 생성합니다.

버튼이 한번 클릭 되었을 때, setDataSource/prepare/start 과정을 진행 해줍니다.

'MediaPlayer' 도 MediaRecorder 와 같이 State Diagram 을 참고하여 각 State 에 맞는 함수를 호출해 주어야 합니다.

한번 Stop 을 호출 하게 되면 stopped 상태가 되어 다시 started 상태로 가기 위해서는 prepare/start 함수를 순차적으로 호출해야 합니다.

=> 만약 현재 상태에서 부를 수 없는 함수를 호출하는 경우 ( 예를들어 stopped 상태에서 prepare를 호출하지 않고 start 부터 호출하는 경우)에는 IllegalStateException 이 발생하니 주의 해야 합니다.

 
    MediaPlayer mPlayer = null;
    boolean isPlaying = false;
    Button mBtPlay = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ..
        mBtPlay = (Button) findViewById(R.id.bt_play);
        mBtPlay.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (isPlaying == false) {
                    try {
                        mPlayer.setDataSource(mPath);
                        mPlayer.prepare();
                    }catch (Exception e) {
                        e.printStackTrace();
                    }
                    mPlayer.start();

                    isPlaying = true;
                    mBtPlay.setText("Stop Playing");
                }
                else {
                    mPlayer.stop();

                    isPlaying = false;
                    mBtPlay.setText("Start Playing");
                }
            }
        });

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

MediaPlayer는 onCompletion callback method 를 제공해 주고 있어서, 파일 재생이 끝날 경우에 대한 처리를 편리하게 해 줄 수 있습니다.

파일 재생이 완료되면 isPlaying flag 를 false 로 설정 하고, 버튼의 Text 를 변경해 주는 일을 수행하도록 구현 하였습니다.

 

여기까지 진행하면 음성을 녹음하고 재생하는 코드가 완성 됩니다. 상세 코드는 'github 코드' 를 참고하세요.

현재는 음성 녹음 뿐이지만 다음 포스팅에서는 카메라로 들어오는 Video 컨텐츠도 함께 레코딩 하여 비디오 파일을 만들어 보도록 하겠습니다.

 

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