마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

비디오녹화 surfaceholder 질문합니다

0 추천
package com.example.administrator.ttttest;

import android.content.Context;
import android.content.pm.ActivityInfo;
import android.hardware.Camera;
import android.media.MediaRecorder;
import android.os.CountDownTimer;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.Toast;

import java.io.File;
import java.io.IOException;

public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback{
    static Context ctx;
    String Path= Environment.getExternalStorageDirectory()+"/videotest.mp4";
    private MediaRecorder mRecoder = null;
    private SurfaceView mPreview;
    private SurfaceHolder mHolder;
    private Camera mCamera;
    //private static final int RECORD_TIME = 5000; //5min 300000
    String TAG;

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

        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); //화면 세로고정

        mPreview = (SurfaceView)findViewById(R.id.surfaceView);
        mHolder = mPreview.getHolder(); //videoView로부터 인스턴스 얻어옴.
        mHolder.addCallback(this); //표면 변화를 통지받을 콜백 객체 등록
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); //버퍼 없이 화면 표시
        Toast.makeText(getApplicationContext(), "call Video record", Toast.LENGTH_SHORT).show();
        beginRecording();

    }


    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        try{
            mCamera = Camera.open(); //객체생성
            Camera.Parameters parameters = mCamera.getParameters(); //객체 파라미터 얻음
            mCamera.setDisplayOrientation(90);
            mCamera.setPreviewDisplay(mHolder); //프리뷰 디스플레이 담당한 서피스 홀더 설정
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        if(mCamera!=null){
            Camera.Parameters parameters = mCamera.getParameters();
            //프리뷰 사이즈 값 재조정
            parameters.setPreviewSize(1280,720);
            mCamera.setParameters(parameters);
            //프리뷰 재시작
            mCamera.startPreview();
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if(mCamera!=null){
            mCamera.stopPreview(); //프리뷰 멈춤
            mCamera.setPreviewCallbackWithBuffer(null);
            mCamera.release();
            mCamera = null; //카메라 객체 초기화
        }
    }


    private void beginRecording(){
        Toast.makeText(getApplicationContext(), "진짜 시작", Toast.LENGTH_SHORT).show();
        //파일 생성, 초기화
        File videofile = new File(Path);
        if(videofile.exists()){
            videofile.delete();
        }

        Toast.makeText(getApplicationContext(), "파일생성초기화", Toast.LENGTH_SHORT).show();


        /*
        //레코더 객체 초기화
        if(mRecoder!=null){
            mRecoder.stop();
            mRecoder.release();
            mRecoder = null;
        }

        if(mCamera!=null){
            mCamera.stopPreview(); //프리뷰 멈춤
            mCamera.release();
            mCamera = null; //카메라 객체 초기화
        }
        */

        try{
            Toast.makeText(getApplicationContext(), "요기까진오는구나", Toast.LENGTH_SHORT).show();
            mRecoder = new MediaRecorder();
            //video, audio 소스 결정
            mRecoder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
            mRecoder.setAudioSource(MediaRecorder.AudioSource.MIC);
            mRecoder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);

            mRecoder.setVideoFrameRate(MediaRecorder.VideoEncoder.MPEG_4_SP);
            mRecoder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);

            //녹화할 대상 파일 설정
            mRecoder.setOutputFile(Path);


            //mRecoder.setMaxDuration(RECORD_TIME);  //녹화시간

            Toast.makeText(getApplicationContext(), "프리뷰설정", Toast.LENGTH_SHORT).show();
            //프리뷰 보여 줄 surface 설정
            mRecoder.setPreviewDisplay(mHolder.getSurface()); /////요기가문제다!!!!!!!!!!

            //녹화 준비, 시작
            mRecoder.prepare();
            Toast.makeText(getApplicationContext(), "녹화준비", Toast.LENGTH_SHORT).show();
            mRecoder.start();
            Toast.makeText(getApplicationContext(), "녹화시작", Toast.LENGTH_SHORT).show();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

videoRecording 함수에 try끝나기 전에

mRecoder.setPreviewDisplay(mHolder.getSurface()); /////요기가문제다!!!!!!!!!!

이 부분에서 더이상 안넘어가고 에러가 뜨는거 같은데......

왜 안되는건지 도저히 모르겠네요 ㅠㅠㅠㅠ

그리고 제가 버튼없이 5분동안만 녹화를 할 거라서 MaxDuration사용해서 설정해 줄 건데, 이게 5분만 녹화하고 미디어레코드를 반환시켜주나요..?

mRecoder.stop();
mRecoder.release();
mRecoder = null;

따로 해줘야 되나요 이걸..? 그리고 또 궁금한게, 저렇게 해제하지 않으면 파일이 저장이 안되는건가요?

파일저장까지는 되는데 항상 용량이 0이네요... 아무래도 영상데이터 자체를 파일에 못넣는거 같아요...

혹시 도움될만한 사이트나 조언 부탁드립니다 ㅠ

sksk (320 포인트) 님이 2016년 8월 4일 질문

1개의 답변

+1 추천
 
채택된 답변

1. mRecoder.setVideoFrameRate(MediaRecorder.VideoEncoder.MPEG_4_SP); 값이 이상합니다.

FPS를 지정하는 API인데 컨테이너 타입을 입력하셨네요.

2. mRecoder.setPreviewDisplay(mHolder.getSurface()); 에서 mHolder 값이 제대로 설정 안 된 듯 합니다.

public void surfaceCreated(SurfaceHolder holder) {

  mHolder = holder;

}

와 같이 surfaceCreated 가 호출 될 때 mHolder 값을 설정 하고,

 

이 후에 beginRecording 가 호출 되게 변경하세요..

 

3. MaxDuration을 설정할 경우 OnInfoListener 로 

정보가 반환되니 setOnInfoListener 로 등록을 시켰다가,

이 이벤트가 올 때 

mRecoder.stop();
mRecoder.release();
mRecoder = null;  를 따로 해 주셔야 합니다.

사악미소 (65,330 포인트) 님이 2016년 8월 4일 답변
친절한 답변 감사합니다ㅠ 참고해서 수정해 보겠습니다ㅎㅎ
ㅎㅎ...
말씀하신 대로 수정했는데 똑같이 안되네요 ㅠㅠ 똑같은 단계에서 안 넘어가는데 뭐가 잘못된 걸까유 ㅠㅠ

package com.example.administrator.ttttest;

import android.content.Context;
import android.content.pm.ActivityInfo;
import android.hardware.Camera;
import android.media.MediaRecorder;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.Toast;

import java.io.File;
import java.io.IOException;

public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback{
    static Context ctx;
    String Path= Environment.getExternalStorageDirectory()+"/videotest.mp4";
    private MediaRecorder mRecoder = null;
    private SurfaceView mPreview;
    private SurfaceHolder mHolder;
    private Camera mCamera;
    //private static final int RECORD_TIME = 5000; //5min 300000
    String TAG;

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

        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); //화면 세로고정

        mPreview = (SurfaceView)findViewById(R.id.surfaceView);
        mHolder = mPreview.getHolder(); //videoView로부터 인스턴스 얻어옴.
        mHolder.addCallback(this); //표면 변화를 통지받을 콜백 객체 등록
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); //버퍼 없이 화면 표시
        Toast.makeText(getApplicationContext(), "call Video record", Toast.LENGTH_SHORT).show();
        beginRecording();

    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mHolder = holder;
        try{
            mCamera = Camera.open(); //객체생성
            Camera.Parameters parameters = mCamera.getParameters(); //객체 파라미터 얻음
            mCamera.setDisplayOrientation(90);
            mCamera.setPreviewDisplay(mHolder); //프리뷰 디스플레이 담당한 서피스 홀더 설정
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        if(mCamera!=null){
            Camera.Parameters parameters = mCamera.getParameters();
            //프리뷰 사이즈 값 재조정
            parameters.setPreviewSize(1280,720);
            mCamera.setParameters(parameters);
            //프리뷰 재시작
            mCamera.startPreview();
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if(mCamera!=null){
            mCamera.stopPreview(); //프리뷰 멈춤
            mCamera.setPreviewCallbackWithBuffer(null);
            mCamera.release();
            mCamera = null; //카메라 객체 초기화
        }
    }


    private void beginRecording(){
        Toast.makeText(getApplicationContext(), "진짜 시작", Toast.LENGTH_SHORT).show();
        //파일 생성, 초기화
        File videofile = new File(Path);
        if(videofile.exists()){
            videofile.delete();
        }

        Toast.makeText(getApplicationContext(), "파일생성초기화", Toast.LENGTH_SHORT).show();


        /*
        //레코더 객체 초기화
        if(mRecoder!=null){
            mRecoder.stop();
            mRecoder.release();
            mRecoder = null;
        }

        if(mCamera!=null){
            mCamera.stopPreview(); //프리뷰 멈춤
            mCamera.release();
            mCamera = null; //카메라 객체 초기화
        }
        */

        try{
            Toast.makeText(getApplicationContext(), "요기까진오는구나", Toast.LENGTH_SHORT).show();
            mRecoder = new MediaRecorder();
            //video, audio 소스 결정
            mRecoder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
            mRecoder.setAudioSource(MediaRecorder.AudioSource.MIC);
            mRecoder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);

            mRecoder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);
            mRecoder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);

            //mRecoder.setVideoFrameRate(15);
            //녹화할 대상 파일 설정
            mRecoder.setOutputFile(Path);


            //mRecoder.setMaxDuration(RECORD_TIME);  //녹화시간

            Toast.makeText(getApplicationContext(), "프리뷰설정", Toast.LENGTH_SHORT).show();
            //프리뷰 보여 줄 surface 설정
            mRecoder.setPreviewDisplay(mHolder.getSurface()); /////요기가문제다!!!!!!!!!!

            //녹화 준비, 시작
            mRecoder.prepare();
            Toast.makeText(getApplicationContext(), "녹화준비", Toast.LENGTH_SHORT).show();
            mRecoder.start();
            Toast.makeText(getApplicationContext(), "녹화시작", Toast.LENGTH_SHORT).show();

            //녹화끝날 시
            mRecoder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
                @Override
                public void onInfo(MediaRecorder mr, int what, int extra) {
                    if(what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED){
                        mRecoder.stop();
                        mRecoder.release();
                        mRecoder = null;
                    }
                    //MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED 정해놓은 사이즈 넘을 시
                    //MEDIA_RECORDER_INFO_MAX_DURATION_REACHED 정해놓은 시간 넘을 시
                }
            });

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
beginRecording();를 surfaceCreated 이후 호출 되게 하라고 가이드 드렸는데,


안 바뀐듯 한데요..

onCreate에서 호출하지 마시고 surfaceCreated 맨 아래에서 호출하게 바꿔보세요

그리고
mCamera.unlock();
mRecoder.setCamera(camera); 도  prepare호출 이전에 호출하게 넣어주세요
아 이미 해봤는데 beginRecording 위치를 바꾸니깐 실행 오류가 뜨더라고요 ㅠ
밑에 코드도 추가하면 실행오류가 뜨네요......

FATAL EXCEPTION: main
                                                                                 Process: com.example.administrator.ttttest, PID: 7544
                                                                                 java.lang.RuntimeException: start failed.
                                                                                     at android.media.MediaRecorder.native_start(Native Method)
                                                                                     at android.media.MediaRecorder.start(MediaRecorder.java:792)
                                                                                     at com.example.administrator.ttttest.MainActivity.beginRecording(MainActivity.java:133)
                                                                                     at com.example.administrator.ttttest.MainActivity.surfaceCreated(MainActivity.java:55)
                                                                                     at android.view.SurfaceView.updateWindow(SurfaceView.java:675)
                                                                                     at android.view.SurfaceView$3.onPreDraw(SurfaceView.java:185)
                                                                                     at android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.java:944)
                                                                                     at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2050)
                                                                                     at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1117)
                                                                                     at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6175)
                                                                                     at android.view.Choreographer$CallbackRecord.run(Choreographer.java:773)
                                                                                     at android.view.Choreographer.doCallbacks(Choreographer.java:586)
                                                                                     at android.view.Choreographer.doFrame(Choreographer.java:556)
                                                                                     at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:759)
                                                                                     at android.os.Handler.handleCallback(Handler.java:739)
                                                                                     at android.os.Handler.dispatchMessage(Handler.java:95)
                                                                                     at android.os.Looper.loop(Looper.java:135)
                                                                                     at android.app.ActivityThread.main(ActivityThread.java:5421)
                                                                                     at java.lang.reflect.Method.invoke(Native Method)
                                                                                     at java.lang.reflect.Method.invoke(Method.java:372)
                                                                                     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:914)
                                                                                     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:707)


그리고 테스트 해보니깐 beginRecording에서 setPreviewDisplay 까지 실행이 안되고 SurfaceCreate로 가는 것 같더라고요.. 저는 SurfaceCreate가 먼저 될 줄 알았는데 이게 문제인거 같네요 ㅠ 근데 beginRecording을 surface함수에 넣으니 실행 오류가 뜨네요...
mCamera.unlock();
mRecoder.setCamera(camera); 도  prepare호출 이전에 호출하게 넣어주신거 맞나요?

로그상은
mRecoder.start() 에서 RuntimeException이 발생했는데,
정확히 어떤 부분에서 오류가 났는지 로그만으로는 알 수 없습니다만,
카메라쪽 permission이 없어 발생했을 수도 있으니 확인 해 보세요..

또한 가급적 롤리팝 이하 단말에서 테스트 하세요. 롤리팝 이상의 경우 Camera API가 deprecated 되서 단말에 따라 지원 안될 수 있습니다.
...