Android视频录制时的预览处理

8
我正在使用Android开发(SDK 2.2),我想用mediaRecorder录制视频,同时对每个预览帧进行处理。
在一个项目中,我使用MediaRecorder记录视频,在另一个项目中,我使用PreviewCallback中的onPreviewFrame(byte[] data, Camera camera)处理预览图片。
我尝试创建一个相机,并使用setCamera函数将其与mediaRecorder一起使用,但它不起作用。
是否可能同时执行这两个操作?
实际上,我不知道如何将两件事联系起来?
我的代码:
package ch.fraise;

import java.io.IOException;
import android.app.Activity;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class CameraActivity extends Activity implements SurfaceHolder.Callback,
    Camera.AutoFocusCallback {

private SurfaceView preview;
private SurfaceHolder previewHolder;

private MediaRecorder mRecorder;
private Camera mCamera;
private boolean mPreviewRunning = false;
private boolean mCaptureFrame = false;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.e("", "Begin onCreate");
    setContentView(R.layout.main);

    preview = (SurfaceView) findViewById(R.id.surfaceView1);
    previewHolder = preview.getHolder();
    previewHolder.addCallback(this);
    previewHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

    mRecorder = new MediaRecorder();
}

@Override
public void onResume() {
    super.onResume();
}

@Override
public void onPause() {
    super.onPause();
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.capture_menu, menu);
    return true;
}

public void startRecording() {
    Log.e("", "Begin StartRecording");
    mCaptureFrame = true;
    mRecorder.start();
}

public void stopRecording() {
    Log.e("", "Begin StopChange");
    mRecorder.stop();
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle item selection
    switch (item.getItemId()) {
    case R.id.startRecording:
        startRecording();
        return true;
    case R.id.stopRecording:
        stopRecording();
        return true;
    default:
        return super.onOptionsItemSelected(item);
    }
}

@Override
public void surfaceCreated(SurfaceHolder holder) {
    Log.e("", "Begin surfaceDestroy");
    mCamera = Camera.open();
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    mCamera.stopPreview();
    mPreviewRunning = false;
    mCamera.release();

    mRecorder.reset();
    mRecorder.release();
}

@Override
public void onAutoFocus(boolean success, Camera camera) {
    // TODO Auto-generated method stub

}

/*
 * PreviewCallback()
 * 
 * this callback captures the preview at every frame and puts it in a byte
 * buffer. we will evaluate if this is a frame that we want to process, and
 * if so, we will send it to an asynchronous thread that will process it to
 * an ARGB Bitmap and POST it to the server
 */
PreviewCallback previewCallback = new PreviewCallback() {
    public void onPreviewFrame(byte[] data, Camera camera) {
        Log.e("", "onPreviewFrame pass");
        if (mCaptureFrame) {
            mCaptureFrame = false;
            // new FrameHandler().execute(data);
        }
    }
};

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
        int height) {
    Log.e("", "Begin SurfaceChange");

    mRecorder.reset();
    mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
    mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
    mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);
    mRecorder.setOutputFile("/sdcard/videotest2.mp4");
    mRecorder.setVideoFrameRate(30);

    mRecorder.setPreviewDisplay(previewHolder.getSurface());
    try {
        mRecorder.prepare();
    } catch (IllegalStateException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

    if (mPreviewRunning)
        mCamera.stopPreview();

    Camera.Parameters p = mCamera.getParameters();
    // p.setPreviewSize(width, height);
    mCamera.setParameters(p);

    try {
        mCamera.setPreviewDisplay(holder);
    } catch (IOException e) {
        e.printStackTrace();
    }

    mCamera.setPreviewCallback(previewCallback);

    mCamera.startPreview();
    mPreviewRunning = true;

}

}

同时,XML 文件中的权限:

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

我已经添加了我的代码,但是出现了以下错误:W/System.err( 5476): java.io.IOException: prepare failed。应用程序在第一个错误后运行,直到我开始录制视频。然后我遇到了java.lang.IllegalStateException E/AndroidRuntime( 5476): at android.media.MediaRecorder.start(Native Method)。 - Bob Strak
你好,你能做到这个吗?我也卡在这一点上了。 - Bipin Vayalu
嗨,我也因为这个问题卡住了,你找到解决方案了吗? - Gaurav Prajapati
4个回答

6

太好了!关键在于将您的PreviewCallback附加到surfaceChanged(...)SurfaceHolder.Callback中!这样做后,即使MediaRecorder正在运行,您仍将继续获取预览帧数据!

例如:

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    mCamera.setPreviewCallback(new PreviewCallback() {
            public void onPreviewFrame(byte[] _data, Camera _camera) {
                Log.d("onPreviewFrame-surfaceChanged",String.format("Got %d bytes of camera data", _data.length));
            }
        });

}

1
我按照上面的方法做了,但在MediaRecorder运行后仍然无法获取预览帧数据!这对你真的有效吗? - whutdyp
是的,那时我正在使用Android 4.2。在启动MediaRecorder之前,请确保附加SufaceHolder.Callback。发布你的源代码,我会看一下。 - dbro
@dbro,不好意思,你的方法行不通。如果我调用mMediaRecorder.setCamera(mCamera)方法,那么我必须在之前调用mCamera.unlock(),这会导致无法获取预览帧。如果我不调用mMediaRecorder.setCamera(mCamera)方法,mMediaRecorder.start()会因为摄像头已经在使用而崩溃并抛出IllegalStateException异常。你能否提供更多或完整的代码,让我了解你的“窍门”是怎样实现的呢? - nick

4

录制视频时无法访问视频流,开始录制后,onPreviewFrame将不会被调用。奇怪的是,录制后似乎也不会调用onPreviewFrame...


1
那是错误的。你只需要在录制后重新开始听取即可。 - Eran Boudjnah

0
如果您正在使用2.2或更高版本的操作系统,则可以使用此方法来解决prepare失败和其他异常。
 public boolean startRecording() {
    try {
        camera.unlock();

        mediaRecorder = new MediaRecorder();
        mediaRecorder.setCamera(camera);
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
        mediaRecorder.setProfile(CamcorderProfile
                .get(CamcorderProfile.QUALITY_HIGH));

        File tempFile = new File(getOutputMediaFile(MEDIA_TYPE_VIDEO)
                .toString());

        mediaRecorder.setOutputFile(tempFile.getPath());
        mediaRecorder.setVideoFrameRate(videoFramesPerSecond);
        mediaRecorder.setVideoSize(surfaceView.getWidth(),
                surfaceView.getHeight());
        mediaRecorder.setPreviewDisplay(surfaceHolder.getSurface());
        mediaRecorder.setMaxFileSize(maxFileSizeInBytes);
        mediaRecorder.prepare();
        mediaRecorder.start();

        return true;
    } catch (IllegalStateException e) {
        Log.e(TAG, e.getMessage());
        e.printStackTrace();
        return false;
    } catch (IOException e) {
        Log.e(TAG, e.getMessage());
        e.printStackTrace();
        return false;
    }
}

0

您只需在清单文件中添加标签即可。这样它就会起作用。下面是示例代码。

例如:

   </application>
    <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.RECORD_VIDEO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    ***<uses-feature android:name="android.hardware.camera" />***
</manifest>

嗨nikhilkilivayil!感谢您的帮助!不幸的是,我已经添加了<uses-feature android:name="android.hardware.camera" />这一行,但似乎它也没有起作用。我发现当录制开始时,onFramePreview停止被调用!也许它们可以同时运行!? - Bob Strak
我也有这个问题。也许我们可以尝试同时解码视频,以实时获取原始图像? - Zhou Chang
@nikhilkilivayil:你有什么解决方案吗?你是如何使用MediaRecorder在录制时实时获取相机预览的? - Junaid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接