Android App Development: How to Capture Video

796

In previous tutorials we looked at writing code to access the onboard camera, preview the display, and save an image. Many Android device cameras will also capture video, but the code to manage this needs to be a little more complicated.

Android mediarecorder diagramThe MediaRecorder API provides recording control for recording audio and video, and is the bedrock of any video recording you do on Android. Check out the API documentation for the full diagram of the state machine. While MediaRecorder does the heavy lifting for you, to get it to work correctly, there are a set of steps that you need to do in a particular order, which we’ll go over in the rest of this tutorial. We’ll be using the preview XML and the basic app structure from the previous tutorials, so refer back to those if you need to.

How to Call the Camera in Android App Development

How to Call the Camera in Android Apps Part 2: Capture and Store Photos

Setting up MediaRecorder

First, open up AndroidManifest.xml, to add audio recording permissions to the camera permissions set up in previous tutorials:

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

We’ll write a separate method in MyCameraActivity.java to deal with video. The first steps are to unlock the camera, and to create a new MediaRecorder. We need this method to return true/false so that when later on we try to start recording, we can check that it’s all been set up successfully before recording (thus avoiding various possible errors):

private MediaRecorder mr;
protected boolean prepareForVideoRecording() {
  camera.unlock();
  mr = new MediaRecorder();
  mr.setCamera(camera);
}

Unlocking the camera is necessary to allow the media process to access the camera. (If you’re just taking still photos, the Camera API handles all of this automatically for you; you only need to worry about it for video.)

Next, we need to set various aspects of the MediaRecorder: the source (both audio and video), the profile, the output file, and the preview display:

private static final int MEDIA_TYPE_VIDEO = 1;
protected boolean prepareForVideoRecording() {
  camera.unlock();
  mr = new MediaRecorder();
  mr.setCamera(camera);
  mr.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
  mr.setVideoSource(MediaRecorder.VideoSource.CAMERA);
  mr.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));
  mr.setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());
  mr.setPreviewDisplay(preview.getHolder().getSurface());
}

The AudioSource and VideoSource do what you’d expect. There are a bunch of options available for audio (including VOICE_CALL and MIC), but here we use CAMCORDER (which matches the camera if available, or uses the main microphone otherwise). For video your only options are CAMERA or DEFAULT.

setProfile() is a shortcut function (only available since API 8) which allows you to set a bunch of video and audio encoding and format information at once. If you want to do this manually, either to run against earlier APIs, or because you want more fine-grained control, here are some sample lines:

mr.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
mr.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mr.setVideoEncoder(MediaRecorder.VideoEncoder.H264);

Check out the MediaRecorder API docs for more options, and the CamcorderProfile API docs for information on what is automatically set if you use that option.

Note for Samsung phones: There’s a problem with some Samsung phones which means that they need a few more settings in order to work correctly. Check out this StackOverflow thread and this XDA Developers thread for more details.

You’ll see that the call to getOutputMediaFile() specifies a video output filetype, which our existing method doesn’t handle. Here’s the extra code to manage that:

private File getOutputMediaFile(int type) {
// Get directory and timestamp as before
  if (type == MEDIA_TYPE_IMAGE) {
    return new File(dir.getPath() + File.separator + "IMG_"  
                    + timeStamp + ".jpg");
  } else if (type == MEDIA_TYPE_VIDEO) {
    return new File(dir.getPath() + File.separator + "VID_"  
                    + timeStamp + ".3gp"); 
  } else {
    return null;
  }
}

You may need to change the file type ending if you’ve specified a different encoding.

Finally, the preview is the same preview as we set up before, and we just call the appropriate methods to get the surface it uses so the MediaRecorder can talk to that surface.

Recording

The last thing to do before recording is to prepare the MediaRecorder:

protected boolean prepareForVideoRecording() {
  // as above 
  try {
	mr.prepare();
  } catch (IllegalStateException e) {
	Log.e(TAG, "IllegalStateException when preparing MediaRecorder " 
          + e.getMessage());
	e.getStackTrace();
	releaseMediaRecorder();
	return false;
  } catch (IOException e) {
    Log.e(TAG, "IOException when preparing MediaRecorder " 
          + e.getMessage());
    e.getStackTrace();
	releaseMediaRecorder();
	return false;
  }
  return true;
}
private void releaseMediaRecorder() {
  if (mr != null) {
    mr.reset();
    mr.release();
    mr = null;
    camera.lock();
  }
}

This is straightforward; we just call the API’s prepare() method on our MediaRecorder, and deal with any exceptions. Note that it’s important to release the MediaRecorder, and to return false, if there are any exceptions. After resetting and releasing the MediaRecorder, we also need to relock the camera so that the app retains control of it. (Remember that you need to release the camera on pause! We already dealt with this in the code in previous tutorials.)

To start recording, we’ll need to add a button to the preview pane, and set the button up to stop and start recording:

private Button recordVideoButton;
private boolean isRecording = false; 
private void setUpLayout() {
  // as in previous tutorials
  setUpVideoButton();
}
private void setUpVideoButton() {
  Button recordVideoButton = (Button) findViewById(R.id.button_video);
  setUpButton(recordVideoButton, "Start video");
  recordVideoButton.setOnClickListener(
    new View.OnClickListener() {
      public void onClick(View v) {
        if (isRecording) {
          mr.stop();
          releaseMediaRecorder();
          camera.lock();
          recordVideoButton.setText("Start video");
          isRecording = false;
        } else {
          if (prepareForVideoRecording()) {
             mr.start();
             recordVideoButton.setText("Stop video");
             isRecording = true;
          } else {
             // Something has gone wrong! Release the camera
             releaseMediaRecorder();
             Toast.makeText(MyCameraActivity.this, 
                            "Sorry: couldn't start video", 
                            Toast.LENGTH_LONG).show();
          }
        }
      }
    }
  );	
}

You’ll also need to add a button to the XML (and you will want to edit the XML a bit to improve the appearance of the whole thing).

This code is written so that we use the same button for starting and stopping recording. This also means that the button itself shows whether you’re currently recording or not, which is a handy way of maximising user information with minimal screen real estate usage. If the app is already recording, when the button is clicked, the user wants to stop recording. So we stop and release the MediaRecorder, relock the camera, change the button text, and reset isRecording.

If the app isn’t already recording, we want to start recording. If the setup method returns true (i.e. the MediaRecorder has successfully been prepared), we start recording, change the button text, and reset isRecording. If the MediaRecorder is not successfully prepared, we release it again, and show a Toast message to let the user know that something has gone wrong.

Note that unlike with the Camera photo storage, with MediaRecorder, passing in the filename for storage automatically takes care of saving the video; you don’t have to handle saving it yourself. However, all it does here is save it to the filename you provide (check this out via a file explorer app); if you want to do anything else with the footage (such as show it to the user or allow them to edit it) you’ll need to extend your app further to do that.

In further tutorials, we’ll take a look at some of the optional capabilities of the Camera, and how to use dynamic layout to offer the user options only when they’re available.