Comment puis-je capturer un enregistrement vidéo sur Android?
Comment puis-je capturer un enregistrement vidéo sur Android?
Réponses:
Voici un exemple d'enregistrement vidéo simple à l'aide du 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();
}
}
C'est tiré de mon livre: Pro Android Media: Développement d'applications graphiques, musicales, vidéo et rich media pour smartphones et tablettes
N'oubliez pas non plus d'inclure ces autorisations dans le manifeste:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Voici un autre exemple qui fonctionne
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>
Et bien sûr, incluez ces autorisations dans le manifeste:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Vous enregistrez de l'audio et de la vidéo en utilisant la même classe MediaRecorder. C'est assez simple. Voici un exemple .
Cette démo vous sera utile ...
video.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>
Votre activité principale: 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
}
}
Classe MediaMetadataRetriever
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...
}
mHolder.addCallback(this);
attache la classe Activity pour gérer les événements de surface. Peut-être que votre IDE ne peut pas reconnaître cet appel, mais il devrait être appelé.
Consultez cet appareil photo Exemple de code Aperçu, CameraPreview
. Cela vous aiderait à développer un code d'enregistrement vidéo pour l'aperçu vidéo, à créer un MediaRecorder
objet et à définir les paramètres d'enregistrement vidéo.
Pour le bénéfice des chercheurs, cet exemple vous donnera un aperçu actif, avec un bouton marche / arrêt pour l'enregistrement. Il a été modifié à partir de ce blog Android et semble assez fiable.
classe 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
}
}
}
activité (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>
En remarque, il semble y avoir un bogue dans l'API Android ou une documentation défectueuse ou peut-être que je suis tout simplement stupide. La documentation Google indique clairement ce qui suit:
Remarque: à partir d'Android 4.0 (niveau d'API 14), les appels Camera.lock () et Camera.unlock () sont gérés automatiquement pour vous.
Voir: http://developer.android.com/guide/topics/media/camera.html
Cela ne semble pas être le cas!
Après avoir lutté pendant des jours sans succès et de nombreux petits problèmes comme des erreurs de type «échec de démarrage», j'ai décidé d'implémenter manuellement le verrouillage et le BAM! tout a bien fonctionné.
J'utilise l'émulateur genymotion pour un appareil 4.1.1 avec un sdk min de 14.
L'exemple ci-dessus fonctionnera si vous utilisez une caméra arrière. Si vous utilisez la caméra frontale, vous devrez ajuster certaines choses:
Tout d'abord, vous devrez ajouter une nouvelle autorisation dans le manifeste.
<uses-feature android:name="android.hardware.camera.front" android:required="false" />
Dans votre initRecorder
méthode, au lieu de
CamcorderProfile cpHigh = CamcorderProfile
.get(CamcorderProfile.QUALITY_HIGH);
recorder.setProfile(cpHigh);
Vous devez utiliser:
CamcorderProfile profile = CamcorderProfile.get(Camera.CameraInfo.CAMERA_FACING_FRONT, CamcorderProfile.QUALITY_LOW);
recorder.setProfile(profile);
car CamcorderProfile.QUALITY_HIGH
est réservé à la caméra arrière.
Vous devrez également définir la taille de la vidéo pour mediarecorder telle qu'elle est dans votre vue de surface.
Voici l'exemple complet d'enregistrement vidéo à partir de la caméra frontale avec un petit écran de prévisualisation:
Android.manifest
<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>
CameraActivity.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();
}
}
En décembre 2017, il y a eu quelques mises à jour, par exemple, l'utilisation de android.hardware.Camera
est désormais obsolète. Alors que le plus récent android.hardware.camera2
est livré avec des choses pratiques comme un CameraManager
.
Personnellement, j'aime beaucoup cet exemple, qui utilise cette API actuelle et fonctionne comme un charme: https://github.com/googlesamples/android-Camera2Video
Cela inclut également de demander à l'utilisateur les autorisations requises au démarrage et de présenter un aperçu vidéo avant de démarrer l'enregistrement vidéo.
(En plus, je trouve le code vraiment beau (et c'est très rare pour moi ^^), mais c'est juste mon avis subjectif.)
Au lieu d'écrire du code à partir de l'esquisse, vous pouvez utiliser une bibliothèque sur GitHub. Par exemple: https://github.com/CameraKit/camerakit-android (ou https://github.com/google/cameraview , ou https://github.com/hujiaweibujidao/CameraView et ainsi de suite). Ensuite, il vous suffit de:
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);
}