如何在 Android 应用程序中录制视频?

如何在 Android 上捕捉录像?

230071 次浏览

使用相同的 MediaRecorder 类录制音频和视频,非常简单。 这是 例子

查看此示例相机预览代码,CameraPreview。这将帮助您开发视频预览的视频录制代码,创建 MediaRecorder对象,并设置视频录制参数。

下面是一个使用 MediaRecorder 录制视频的简单例子:

public class VideoCapture extends Activity implements OnClickListener, SurfaceHolder.Callback {
MediaRecorder recorder;
SurfaceHolder holder;
boolean recording = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);


recorder = new MediaRecorder();
initRecorder();
setContentView(R.layout.main);


SurfaceView cameraView = (SurfaceView) findViewById(R.id.CameraView);
holder = cameraView.getHolder();
holder.addCallback(this);
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);


cameraView.setClickable(true);
cameraView.setOnClickListener(this);
}


private void initRecorder() {
recorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
recorder.setVideoSource(MediaRecorder.VideoSource.DEFAULT);


CamcorderProfile cpHigh = CamcorderProfile
.get(CamcorderProfile.QUALITY_HIGH);
recorder.setProfile(cpHigh);
recorder.setOutputFile("/sdcard/videocapture_example.mp4");
recorder.setMaxDuration(50000); // 50 seconds
recorder.setMaxFileSize(5000000); // Approximately 5 megabytes
}


private void prepareRecorder() {
recorder.setPreviewDisplay(holder.getSurface());


try {
recorder.prepare();
} catch (IllegalStateException e) {
e.printStackTrace();
finish();
} catch (IOException e) {
e.printStackTrace();
finish();
}
}


public void onClick(View v) {
if (recording) {
recorder.stop();
recording = false;


// Let's initRecorder so we can record again
initRecorder();
prepareRecorder();
} else {
recording = true;
recorder.start();
}
}


public void surfaceCreated(SurfaceHolder holder) {
prepareRecorder();
}


public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}


public void surfaceDestroyed(SurfaceHolder holder) {
if (recording) {
recorder.stop();
recording = false;
}
recorder.release();
finish();
}
}

这是从我的书: Pro Android Media: 为智能手机和平板电脑开发图形、音乐、视频和富媒体应用程序

另外,不要忘记在清单中包含这些权限:

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

这是另一个正在起作用的例子

public class EnregistrementVideoStackActivity extends Activity implements SurfaceHolder.Callback {
private SurfaceHolder surfaceHolder;
private SurfaceView surfaceView;
public MediaRecorder mrec = new MediaRecorder();
private Button startRecording = null;


File video;
private Camera mCamera;


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camera_surface);
Log.i(null , "Video starting");
startRecording = (Button)findViewById(R.id.buttonstart);
mCamera = Camera.open();
surfaceView = (SurfaceView) findViewById(R.id.surface_camera);
surfaceHolder = surfaceView.getHolder();
surfaceHolder.addCallback(this);
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}


@Override
public boolean onCreateOptionsMenu(Menu menu)
{
menu.add(0, 0, 0, "StartRecording");
menu.add(0, 1, 0, "StopRecording");
return super.onCreateOptionsMenu(menu);
}


@Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
{
case 0:
try {
startRecording();
} catch (Exception e) {
String message = e.getMessage();
Log.i(null, "Problem Start"+message);
mrec.release();
}
break;


case 1: //GoToAllNotes
mrec.stop();
mrec.release();
mrec = null;
break;


default:
break;
}
return super.onOptionsItemSelected(item);
}


protected void startRecording() throws IOException
{
mrec = new MediaRecorder();  // Works well
mCamera.unlock();


mrec.setCamera(mCamera);


mrec.setPreviewDisplay(surfaceHolder.getSurface());
mrec.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mrec.setAudioSource(MediaRecorder.AudioSource.MIC);


mrec.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));
mrec.setPreviewDisplay(surfaceHolder.getSurface());
mrec.setOutputFile("/sdcard/zzzz.3gp");


mrec.prepare();
mrec.start();
}


protected void stopRecording() {
mrec.stop();
mrec.release();
mCamera.release();
}


private void releaseMediaRecorder(){
if (mrec != null) {
mrec.reset();   // clear recorder configuration
mrec.release(); // release the recorder object
mrec = null;
mCamera.lock();           // lock camera for later use
}
}


private void releaseCamera(){
if (mCamera != null){
mCamera.release();        // release the camera for other applications
mCamera = null;
}
}


@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}


@Override
public void surfaceCreated(SurfaceHolder holder) {
if (mCamera != null){
Parameters params = mCamera.getParameters();
mCamera.setParameters(params);
}
else {
Toast.makeText(getApplicationContext(), "Camera not available!", Toast.LENGTH_LONG).show();
finish();
}
}


@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mCamera.stopPreview();
mCamera.release();
}
}

Camera _ Surface. xml

<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >


<SurfaceView
android:id="@+id/surface_camera"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1" />


<Button
android:id="@+id/buttonstart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/record_start" />


</RelativeLayout>

当然包括这些许可在清单:

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

这个演示将有助于你... 。

Xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >


<ToggleButton
android:id="@+id/toggleRecordingButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true" />


<SurfaceView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/surface_camera"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_centerInParent="true"
android:layout_weight="1" >
</SurfaceView>

你的主要活动: Video.java

 public class Video extends Activity implements OnClickListener,
SurfaceHolder.Callback {


private static final String TAG = "CAMERA_TUTORIAL";


private SurfaceView mSurfaceView;
private SurfaceHolder mHolder;
private Camera mCamera;
private boolean previewRunning;
private MediaRecorder mMediaRecorder;
private final int maxDurationInMs = 20000;
private final long maxFileSizeInBytes = 500000;
private final int videoFramesPerSecond = 20;
Button btn_record;
boolean mInitSuccesful = false;
File file;
ToggleButton mToggleButton;


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.video);


setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);


mSurfaceView = (SurfaceView) findViewById(R.id.surface_camera);
mHolder = mSurfaceView.getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);


mToggleButton = (ToggleButton) findViewById(R.id.toggleRecordingButton);
mToggleButton.setOnClickListener(new OnClickListener() {
@Override
// toggle video recording
public void onClick(View v) {
if (((ToggleButton) v).isChecked())
mMediaRecorder.start();
else {
mMediaRecorder.stop();
mMediaRecorder.reset();
try {
initRecorder(mHolder.getSurface());
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
}


private void initRecorder(Surface surface) throws IOException {
// It is very important to unlock the camera before doing setCamera
// or it will results in a black preview
if (mCamera == null)
{
mCamera = Camera.open();
mCamera.unlock();
}


if (mMediaRecorder == null)
mMediaRecorder = new MediaRecorder();


mMediaRecorder.setPreviewDisplay(surface);
mMediaRecorder.setCamera(mCamera);


mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);


mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);


mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);


mMediaRecorder.setOutputFile(this.initFile().getAbsolutePath());


// No limit. Don't forget to check the space on disk.
mMediaRecorder.setMaxDuration(50000);
mMediaRecorder.setVideoFrameRate(24);
mMediaRecorder.setVideoSize(1280, 720);
mMediaRecorder.setVideoEncodingBitRate(3000000);
mMediaRecorder.setAudioEncodingBitRate(8000);


mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);


try {
mMediaRecorder.prepare();
} catch (IllegalStateException e) {
// This is thrown if the previous calls are not called with the
// proper order
e.printStackTrace();
}


mInitSuccesful = true;
}


private File initFile() {
// File dir = new
// File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES),
// this
File dir = new File(Environment.getExternalStorageDirectory(), this
.getClass().getPackage().getName());




if (!dir.exists() && !dir.mkdirs()) {
Log.wtf(TAG,
"Failed to create storage directory: "
+ dir.getAbsolutePath());
Toast.makeText(Video.this, "not record", Toast.LENGTH_SHORT);
file = null;
} else {
file = new File(dir.getAbsolutePath(), new SimpleDateFormat(
"'IMG_'yyyyMMddHHmmss'.mp4'").format(new Date()));
}
return file;
}


@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
if (!mInitSuccesful)
initRecorder(mHolder.getSurface());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}


private void shutdown() {
// Release MediaRecorder and especially the Camera as it's a shared
// object that can be used by other applications
mMediaRecorder.reset();
mMediaRecorder.release();
mCamera.release();


// once the objects have been released they can't be reused
mMediaRecorder = null;
mCamera = null;
}


@Override
public void surfaceDestroyed(SurfaceHolder holder) {
shutdown();
}


@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub


}


@Override
public void onClick(View v) {
// TODO Auto-generated method stub


}


}

public class MediaMetadataRetriever {


static {
System.loadLibrary("media_jni");
native_init();
}


// The field below is accessed by native methods
@SuppressWarnings("unused")
private int mNativeContext;


public MediaMetadataRetriever() {
native_setup();
}


/**
* Call this method before setDataSource() so that the mode becomes
* effective for subsequent operations. This method can be called only once
* at the beginning if the intended mode of operation for a
* MediaMetadataRetriever object remains the same for its whole lifetime,
* and thus it is unnecessary to call this method each time setDataSource()
* is called. If this is not never called (which is allowed), by default the
* intended mode of operation is to both capture frame and retrieve meta
* data (i.e., MODE_GET_METADATA_ONLY | MODE_CAPTURE_FRAME_ONLY).
* Often, this may not be what one wants, since doing this has negative
* performance impact on execution time of a call to setDataSource(), since
* both types of operations may be time consuming.
*
* @param mode The intended mode of operation. Can be any combination of
* MODE_GET_METADATA_ONLY and MODE_CAPTURE_FRAME_ONLY:
* 1. MODE_GET_METADATA_ONLY & MODE_CAPTURE_FRAME_ONLY:
*    For neither frame capture nor meta data retrieval
* 2. MODE_GET_METADATA_ONLY: For meta data retrieval only
* 3. MODE_CAPTURE_FRAME_ONLY: For frame capture only
* 4. MODE_GET_METADATA_ONLY | MODE_CAPTURE_FRAME_ONLY:
*    For both frame capture and meta data retrieval
*/
public native void setMode(int mode);


/**
* @return the current mode of operation. A negative return value indicates
* some runtime error has occurred.
*/
public native int getMode();


/**
* Sets the data source (file pathname) to use. Call this
* method before the rest of the methods in this class. This method may be
* time-consuming.
*
* @param path The path of the input media file.
* @throws IllegalArgumentException If the path is invalid.
*/
public native void setDataSource(String path) throws IllegalArgumentException;


/**
* Sets the data source (FileDescriptor) to use.  It is the caller's
* responsibility to close the file descriptor. It is safe to do so as soon
* as this call returns. Call this method before the rest of the methods in
* this class. This method may be time-consuming.
*
* @param fd the FileDescriptor for the file you want to play
* @param offset the offset into the file where the data to be played starts,
* in bytes. It must be non-negative
* @param length the length in bytes of the data to be played. It must be
* non-negative.
* @throws IllegalArgumentException if the arguments are invalid
*/
public native void setDataSource(FileDescriptor fd, long offset, long length)
throws IllegalArgumentException;


/**
* Sets the data source (FileDescriptor) to use. It is the caller's
* responsibility to close the file descriptor. It is safe to do so as soon
* as this call returns. Call this method before the rest of the methods in
* this class. This method may be time-consuming.
*
* @param fd the FileDescriptor for the file you want to play
* @throws IllegalArgumentException if the FileDescriptor is invalid
*/
public void setDataSource(FileDescriptor fd)
throws IllegalArgumentException {
// intentionally less than LONG_MAX
setDataSource(fd, 0, 0x7ffffffffffffffL);
}


/**
* Sets the data source as a content Uri. Call this method before
* the rest of the methods in this class. This method may be time-consuming.
*
* @param context the Context to use when resolving the Uri
* @param uri the Content URI of the data you want to play
* @throws IllegalArgumentException if the Uri is invalid
* @throws SecurityException if the Uri cannot be used due to lack of
* permission.
*/
public void setDataSource(Context context, Uri uri)
throws IllegalArgumentException, SecurityException {
if (uri == null) {
throw new IllegalArgumentException();
}


String scheme = uri.getScheme();
if(scheme == null || scheme.equals("file")) {
setDataSource(uri.getPath());
return;
}


AssetFileDescriptor fd = null;
try {
ContentResolver resolver = context.getContentResolver();
try {
fd = resolver.openAssetFileDescriptor(uri, "r");
} catch(FileNotFoundException e) {
throw new IllegalArgumentException();
}
if (fd == null) {
throw new IllegalArgumentException();
}
FileDescriptor descriptor = fd.getFileDescriptor();
if (!descriptor.valid()) {
throw new IllegalArgumentException();
}
// Note: using getDeclaredLength so that our behavior is the same
// as previous versions when the content provider is returning
// a full file.
if (fd.getDeclaredLength() < 0) {
setDataSource(descriptor);
} else {
setDataSource(descriptor, fd.getStartOffset(), fd.getDeclaredLength());
}
return;
} catch (SecurityException ex) {
} finally {
try {
if (fd != null) {
fd.close();
}
} catch(IOException ioEx) {
}
}
setDataSource(uri.toString());
}


/**
* Call this method after setDataSource(). This method retrieves the
* meta data value associated with the keyCode.
*
* The keyCode currently supported is listed below as METADATA_XXX
* constants. With any other value, it returns a null pointer.
*
* @param keyCode One of the constants listed below at the end of the class.
* @return The meta data value associate with the given keyCode on success;
* null on failure.
*/
public native String extractMetadata(int keyCode);


/**
* Call this method after setDataSource(). This method finds a
* representative frame if successful and returns it as a bitmap. This is
* useful for generating a thumbnail for an input media source.
*
* @return A Bitmap containing a representative video frame, which
*         can be null, if such a frame cannot be retrieved.
*/
public native Bitmap captureFrame();


/**
* Call this method after setDataSource(). This method finds the optional
* graphic or album art associated (embedded or external url linked) the
* related data source.
*
* @return null if no such graphic is found.
*/
public native byte[] extractAlbumArt();


/**
* Call it when one is done with the object. This method releases the memory
* allocated internally.
*/
public native void release();
private native void native_setup();
private static native void native_init();


private native final void native_finalize();


@Override
protected void finalize() throws Throwable {
try {
native_finalize();
} finally {
super.finalize();
}
}


public static final int MODE_GET_METADATA_ONLY  = 0x01;
public static final int MODE_CAPTURE_FRAME_ONLY = 0x02;


/*
* Do not change these values without updating their counterparts
* in include/media/mediametadataretriever.h!
*/
public static final int METADATA_KEY_CD_TRACK_NUMBER = 0;
public static final int METADATA_KEY_ALBUM           = 1;
public static final int METADATA_KEY_ARTIST          = 2;
public static final int METADATA_KEY_AUTHOR          = 3;
public static final int METADATA_KEY_COMPOSER        = 4;
public static final int METADATA_KEY_DATE            = 5;
public static final int METADATA_KEY_GENRE           = 6;
public static final int METADATA_KEY_TITLE           = 7;
public static final int METADATA_KEY_YEAR            = 8;
public static final int METADATA_KEY_DURATION        = 9;
public static final int METADATA_KEY_NUM_TRACKS      = 10;
public static final int METADATA_KEY_IS_DRM_CRIPPLED = 11;
public static final int METADATA_KEY_CODEC           = 12;
public static final int METADATA_KEY_RATING          = 13;
public static final int METADATA_KEY_COMMENT         = 14;
public static final int METADATA_KEY_COPYRIGHT       = 15;
public static final int METADATA_KEY_BIT_RATE        = 16;
public static final int METADATA_KEY_FRAME_RATE      = 17;
public static final int METADATA_KEY_VIDEO_FORMAT    = 18;
public static final int METADATA_KEY_VIDEO_HEIGHT    = 19;
public static final int METADATA_KEY_VIDEO_WIDTH     = 20;
public static final int METADATA_KEY_WRITER          = 21;
// Add more here...
}

顺便说一句—— Android API 似乎有一个 bug,或者文档有问题,或者我只是太笨了。谷歌文档明确指出:

注意: 从 Android 4.0(API 级别14)开始,Camera.lock ()和 将自动为您管理 Camera.lock ()调用。

见: http://developer.android.com/guide/topics/media/camera.html

事实似乎并非如此!

在经历了几天毫无进展的斗争和诸如“无法启动”之类的小问题之后,我决定手动实现锁定和 BAM!一切正常。

我使用 genymotion 模拟器为一个4.1.1设备,最小 sdk 为14。

为了方便搜索者,这个示例将提供一个活动预览,带有一个用于记录的开始/停止按钮。它是从这个机器人 博客修改的,看起来相当可靠。

Java 类(VideoWithSurfaceVw)

package <<your packagename here>>;


import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;


import android.app.Activity;
import android.content.Context;
import android.hardware.Camera;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.Toast;


public class VideoWithSurfaceVw extends Activity{


// Adapted from http://sandyandroidtutorials.blogspot.co.uk/2013/05/android-video-capture-tutorial.html




private Camera myCamera;
private MyCameraSurfaceView myCameraSurfaceView;
private MediaRecorder mediaRecorder;


Button myButton;
SurfaceHolder surfaceHolder;
boolean recording;


/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);


recording = false;


setContentView(R.layout.activity_video_with_surface_vw);


//Get Camera for preview
myCamera = getCameraInstance();
if(myCamera == null){
Toast.makeText(VideoWithSurfaceVw.this,
"Fail to get Camera",
Toast.LENGTH_LONG).show();
}


myCameraSurfaceView = new MyCameraSurfaceView(this, myCamera);
FrameLayout myCameraPreview = (FrameLayout)findViewById(R.id.videoview);
myCameraPreview.addView(myCameraSurfaceView);


myButton = (Button)findViewById(R.id.mybutton);
myButton.setOnClickListener(myButtonOnClickListener);
}


Button.OnClickListener myButtonOnClickListener
= new Button.OnClickListener(){


@Override
public void onClick(View v) {
// TODO Auto-generated method stub


try{
if(recording){
// stop recording and release camera
mediaRecorder.stop();  // stop the recording
releaseMediaRecorder(); // release the MediaRecorder object


//Exit after saved
//finish();
myButton.setText("REC");
recording = false;
}else{


//Release Camera before MediaRecorder start
releaseCamera();


if(!prepareMediaRecorder()){
Toast.makeText(VideoWithSurfaceVw.this,
"Fail in prepareMediaRecorder()!\n - Ended -",
Toast.LENGTH_LONG).show();
finish();
}


mediaRecorder.start();
recording = true;
myButton.setText("STOP");
}
}catch (Exception ex){
ex.printStackTrace();
}
}};


private Camera getCameraInstance(){
// TODO Auto-generated method stub
Camera c = null;
try {
c = Camera.open(); // attempt to get a Camera instance
}
catch (Exception e){
// Camera is not available (in use or does not exist)
}
return c; // returns null if camera is unavailable
}


private String getFileName_CustomFormat() {
SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd HH_mm_ss");
Date now = new Date();
String strDate = sdfDate.format(now);
return strDate;
}




private boolean prepareMediaRecorder(){
myCamera = getCameraInstance();
mediaRecorder = new MediaRecorder();


myCamera.unlock();
mediaRecorder.setCamera(myCamera);


mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);


mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));






mediaRecorder.setOutputFile("/sdcard/" + getFileName_CustomFormat() + ".mp4");
//mediaRecorder.setOutputFile("/sdcard/myvideo1.mp4");
mediaRecorder.setMaxDuration(60000); // Set max duration 60 sec.
mediaRecorder.setMaxFileSize(50000000); // Set max file size 50M


mediaRecorder.setPreviewDisplay(myCameraSurfaceView.getHolder().getSurface());


try {
mediaRecorder.prepare();
} catch (IllegalStateException e) {
releaseMediaRecorder();
return false;
} catch (IOException e) {
releaseMediaRecorder();
return false;
}
return true;


}


@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 (mediaRecorder != null) {
mediaRecorder.reset();   // clear recorder configuration
mediaRecorder.release(); // release the recorder object
mediaRecorder = new MediaRecorder();
myCamera.lock();           // lock camera for later use
}
}


private void releaseCamera(){
if (myCamera != null){
myCamera.release();        // release the camera for other applications
myCamera = null;
}
}


public class MyCameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback{


private SurfaceHolder mHolder;
private Camera mCamera;


public MyCameraSurfaceView(Context context, Camera camera) {
super(context);
mCamera = camera;


// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
mHolder.addCallback(this);
// deprecated setting, but required on Android versions prior to 3.0
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}


@Override
public void surfaceChanged(SurfaceHolder holder, int format, int weight,
int height) {
// If your preview can change or rotate, take care of those events here.
// Make sure to stop the preview before resizing or reformatting it.


if (mHolder.getSurface() == null){
// preview surface does not exist
return;
}


// stop preview before making changes
try {
mCamera.stopPreview();
} catch (Exception e){
// ignore: tried to stop a non-existent preview
}


// make any resize, rotate or reformatting changes here


// start preview with new settings
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();


} catch (Exception e){
}
}


@Override
public void surfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stub
// The Surface has been created, now tell the camera where to draw the preview.
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
}
}


@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub


}
}
}

活动(activity _ video _ with _ Surface _ vw)

<RelativeLayout android:id="@+id/surface_camera"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_centerInParent="true"
android:layout_weight="1"
>


<RelativeLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<FrameLayout
android:id="@+id/videoview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
<Button
android:id="@+id/mybutton"
android:layout_width="100dp"
android:layout_height="50dp"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:text="REC"
android:textSize="12dp"/>
</RelativeLayout>


</RelativeLayout>

上面的例子将工作,如果你正在使用后置摄像头。如果你使用前置摄像头,你需要调整一些东西:

首先,您需要在清单中添加新的权限。

<uses-feature android:name="android.hardware.camera.front" android:required="false" />

initRecorder方法中,而不是

CamcorderProfile cpHigh = CamcorderProfile
.get(CamcorderProfile.QUALITY_HIGH);
recorder.setProfile(cpHigh);

你需要使用:

CamcorderProfile profile = CamcorderProfile.get(Camera.CameraInfo.CAMERA_FACING_FRONT, CamcorderProfile.QUALITY_LOW);
recorder.setProfile(profile);

因为 CamcorderProfile.QUALITY_HIGH是留给后置摄像头的。

您还必须为媒体录像机设置视频大小,因为它在您的表面视图中。

下面是一个从前置摄像机录制视频的完整例子,只有一个小小的预览显示:

机器人,清单

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.front" android:required="false" />

Activity _ Camera. xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
tools:context="CameraActivity">


<SurfaceView
android:layout_width="200dp"
android:layout_height="wrap_content"
android:id="@+id/surfaceView"/>


<Button
android:layout_width="100dp"
android:layout_height="100dp"
android:text="REC"
android:id="@+id/btnRecord"
android:layout_alignParentBottom="true"
android:layout_marginBottom="25dp" />
</RelativeLayout>

Java

public class SongVideoActivity extends BaseActivity implements SurfaceHolder.Callback {


private int mCameraContainerWidth = 0;
private SurfaceView mSurfaceView = null;
private SurfaceHolder mSurfaceHolder = null;


private Camera mCamera = null;
private boolean mIsRecording = false;


private int mPreviewHeight;
private int mPreviewWidth;


MediaRecorder mRecorder;


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


Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable ex) {
releaseMediaRecorder();
releaseCamera();
}
});


mCamera = getCamera();


//camera preview
mSurfaceView = (SurfaceView) findViewById(R.id.surfaceView);
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.addCallback(this);
// deprecated setting, but required on Android versions prior to 3.0
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);


mCameraContainerWidth = mSurfaceView.getLayoutParams().width;


findViewById(R.id.btnRecord).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mIsRecording) {
stopRecording();
} else {
// initialize video camera
if (prepareVideoRecorder()) {


// Camera is available and unlocked, MediaRecorder is prepared,
// now you can start recording
mRecorder.start();


// inform the user that recording has started
Toast.makeText(getApplicationContext(), "Started recording", Toast.LENGTH_SHORT).show();
mIsRecording = true;
} else {
// prepare didn't work, release the camera
releaseMediaRecorder();
// inform user
}
}
}
});
}


private void stopRecording() {
mRecorder.stop();  // stop the recording
releaseMediaRecorder(); // release the MediaRecorder object
mCamera.lock();         // take camera access back from MediaRecorder


// inform the user that recording has stopped
Toast.makeText(this, "Recording complete", Toast.LENGTH_SHORT).show();
mIsRecording = false;
}


@Override
protected void onDestroy() {
super.onDestroy();
releaseMediaRecorder();       // if you are using MediaRecorder, release it first
releaseCamera();              // release the camera immediately on pause event
}


private Camera getCamera() {


Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
for (int camIdx = 0; camIdx < Camera.getNumberOfCameras(); camIdx++) {
Camera.getCameraInfo(camIdx, cameraInfo);
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
try {
return mCamera = Camera.open(camIdx);
} catch (RuntimeException e) {
Log.e("cameras", "Camera failed to open: " + e.getLocalizedMessage());
}
}
}
return null;
}


@Override
protected void onPause() {
super.onPause();
releaseMediaRecorder();       // if you are using MediaRecorder, release it first
releaseCamera();              // release the camera immediately on pause event
}


private Camera.Size getBestPreviewSize(Camera.Parameters parameters) {
Camera.Size result=null;


for (Camera.Size size : parameters.getSupportedPreviewSizes()) {
if(size.width < size.height) continue; //we are only interested in landscape variants


if (result == null) {
result = size;
}
else {
int resultArea = result.width*result.height;
int newArea = size.width*size.height;


if (newArea > resultArea) {
result = size;
}
}
}


return(result);
}


private boolean prepareVideoRecorder(){
mRecorder = new MediaRecorder();


// Step 1: Unlock and set camera to MediaRecorder
mCamera.unlock();
mRecorder.setCamera(mCamera);


// Step 2: Set sources
mRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
mRecorder.setVideoSource(MediaRecorder.VideoSource.DEFAULT);
//recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);


// Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
// Customise your profile based on a pre-existing profile
CamcorderProfile profile = CamcorderProfile.get(Camera.CameraInfo.CAMERA_FACING_FRONT, CamcorderProfile.QUALITY_LOW);
mRecorder.setProfile(profile);


// Step 4: Set output file
mRecorder.setOutputFile(new File(getFilesDir(), "movie-" + UUID.randomUUID().toString()).getAbsolutePath());
//recorder.setMaxDuration(50000); // 50 seconds
//recorder.setMaxFileSize(500000000); // Approximately 500 megabytes


mRecorder.setVideoSize(mPreviewWidth, mPreviewHeight);


// Step 5: Set the preview output
mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());


// Step 6: Prepare configured MediaRecorder
try {
mRecorder.prepare();
} catch (IllegalStateException e) {
Toast.makeText(getApplicationContext(), "exception: " + e.getMessage(), Toast.LENGTH_LONG).show();
releaseMediaRecorder();
return false;
} catch (IOException e) {
Toast.makeText(getApplicationContext(), "exception: " + e.getMessage(), Toast.LENGTH_LONG).show();
releaseMediaRecorder();
return false;
}
return true;
}


private void releaseMediaRecorder(){
if (mRecorder != null) {
mRecorder.reset();   // clear recorder configuration
mRecorder.release(); // release the recorder object
mRecorder = null;
mCamera.lock();           // lock camera for later use
}
}


private void releaseCamera(){
if (mCamera != null){
mCamera.release();        // release the camera for other applications
mCamera = null;
}
}


@Override
public void surfaceCreated(SurfaceHolder holder) {
// The Surface has been created, now tell the camera where to draw the preview.
Camera.Parameters parameters = mCamera.getParameters();
parameters.setRecordingHint(true);
Camera.Size size = getBestPreviewSize(parameters);
mCamera.setParameters(parameters);


//resize the view to the specified surface view width in layout
int newHeight = size.height / (size.width / mCameraContainerWidth);
mSurfaceView.getLayoutParams().height = newHeight;
}


@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
mPreviewHeight = mCamera.getParameters().getPreviewSize().height;
mPreviewWidth = mCamera.getParameters().getPreviewSize().width;


mCamera.stopPreview();
try {
mCamera.setPreviewDisplay(mSurfaceHolder);
} catch (IOException e) {
e.printStackTrace();
}
mCamera.startPreview();
}


@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (mIsRecording) {
stopRecording();
}


releaseMediaRecorder();
releaseCamera();
}
}

截至2017年12月,已有一些更新,例如 android.hardware.Camera的使用现在已被弃用。而较新的 android.hardware.camera2配备了像 CameraManager这样方便的东西。

我个人非常喜欢这个例子,它充分利用了当前的 API,工作起来非常出色: Https://github.com/googlesamples/android-camera2video

它还包括在启动时要求用户获得所需的权限,并在开始录制视频之前提供视频预览。

(另外,我发现代码非常漂亮(这对我来说非常罕见 ^ ^) ,但这只是我的主观看法。)

您可以在 GitHub 上使用库,而不用从草图中编写代码。例如: https://github.com/CameraKit/camerakit-android(或者 https://github.com/google/cameraview,或者 https://github.com/hujiaweibujidao/CameraView等等)。那么你只需要:

private CameraKitView cameraKitView;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
cameraKitView = findViewById(R.id.camera);
}


@Override
protected void onStart() {
super.onStart();
cameraKitView.onStart();
}


@Override
protected void onResume() {
super.onResume();
cameraKitView.onResume();
}


@Override
protected void onPause() {
cameraKitView.onPause();
super.onPause();
}


@Override
protected void onStop() {
cameraKitView.onStop();
super.onStop();
}


@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
cameraKitView.onRequestPermissionsResult(requestCode, permissions, grantResults);
}

目前(在2022年)应该是最具未来性的解决方案是 Android CameraX 库。译自: 美国《科学》杂志网站(ref) https://developer.android.com/jetpack/androidx/releases/camera : 示例代码可以在这里找到: < a href = “ https://github.com/android/Camera-Sample”rel = “ nofollow norefrer”> https://github.com/android/camera-samples

它仍然是在阿尔法状态,但对我的样本代码工程没有问题的像素4a