The wonder world of Android’s video recording

More than a year ago I started to develop an an app that looked very simple and straight forward to do: The user starts a timer that counts down the time and and when the timer reaches zero the app records a video whose maximum file size and length can be preconfigured. Sounds easy, doesn’t it? I can tell you it was not. But I finally got an app who does that (see here), runs on all Android versions from 2.1 on and seems to work even well with the newest generation of Samsung devices (here I still keep my fingers crossed). But it was a bunch of work to find out how to do it. Here is the story.

As this was my first video recording app I started with reading the guide on Android Developers. This guide tells you the steps that you have to follow. It does not tell you about the caveats. So please read this guide first (if you haven’t already) and then continue reading here.

Opening the camera with Camera.open() contains already a small trap. The guide does not mention that Camera.open() returns a null pointer in case that the device has only a front facing camera which is for example true for the first generation Nexus 7. In this case you have to use Camera.open(id) with the correct camera id. But Camera.open(id) was only introduced with Android 2.3 (API level 9). This was the first case where I was forced to write version dependent code. This code snippet opens the first back facing camera it finds and opens the first camera it can get otherwise.

Camera cam = null;

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD){
  Camera.CameraInfo info = new Camera.CameraInfo();
  for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
    Camera.getCameraInfo(i, info);
    if (info.facing == CameraInfo.CAMERA_FACING_BACK){
    	cam = Camera.open(i);
    }
  }
  if (cam == null){
    cam = Camera.open(0);
  }
}
else {
  camera = Camera.open();
}

When it comes to setting up the media recorder, setting up the video output format and encoding is to my experience the main source of trouble for recording a video in Android. The guide on Android Developer says here:

Set the video output format and encoding. For Android 2.2 (API Level 8) and higher, use the MediaRecorder.setProfile method, and get a profile instance using CamcorderProfile.get(). For versions of Android prior to 2.2, you must set the video output format and encoding parameters: …

This leaves a bunch of problems unmentioned:

  • Android 2.2 (API Level 8) did not bring the full class with all its methods:
    • CamcorderProfile.get(quality) was already available with Android 2.2 (API Level 8), but CamcorderProfile.get(cameraId, quality) came with Android 2.3 (API Level 9).
    • Initially only constants for CamcorderProfile.QUALITY_HIGH and CamcorderProfile.QUALITY_LOW were defined. Most other constants came with Android 3.0 (API Level 11), CamcorderProfile.QUALITY_QVGA and CamcorderProfile.QUALITY_TIME_LAPSE_QVGA came with Android 4.0.3 (API Level 15). I used Java’s introspection API on CamcorderProfile to get the available profile constant in order be prepared for future extensions like e.g. 4k.
    • Checking, which profiles are actually supported with CamcorderProfile.hasProfile (cameraId, quality) came with Android 3.0 (API Level 11).
  • I know at least of one device (Sony Ericsson Xperia Ray with Android 4.0.4) where setting up the MediaRecorder with  MediaRecorder.setProfile() results in an exception when starting the MediaRecorder.
  • On some devices some possible output formats are not available through MediaRecorder profiles, but only by manually setting the output format parameters. For example the Intenso Tab 814 (Android 4.1.1) offers a 320×240 video format which is QVGA for which there is no profile constant in Android 4.1.1.
  • The guide mentions nowhere that you must specify width and height of the video with MediaRecoder.setVideoSize and the video frame rate with MediaRecorder.setVideoFrameRate in case you don’t use camcorder profiles.

So using camcorder profiles is only really well suited for devices with Android 3.0 (API Level 11) at minimum. Nevertheless it seems as if for the newest Generation of Samsung phones (Galaxy Note 3, Galaxy S4, etc.) using camcorder profiles is the only way to get the MediaRecorder up and running. Setting up the video output format values manually resulted either in a completely failed start of the recorder (e.g. on a Galaxy Note 3) or in a low quality video (e.g. on a Galaxy Tab 3 7.1).

In parallel to this approach I implemented two other ways to set up the video output format values:

The “automatic” approach

1) Get the preview size from the camera object (which can e.g on the Xperia Ray be different from the size of the holder of the preview surface!):

videoWidth = cam.getParameters().getPreviewSize().width; 
videoHeight = cam.getParameters().getPreviewSize().height;

This must be done after the preview surface has been created and the surface holder of the preview has been set in the camera object but before unlocking the camera when starting to set up theMediaRecorder.

2) Use these values and some parameters that officially every Android device with a camera must support to set up the media recorder

recorder = new MediaRecorder();
videoFramesPerSecond = 20;
...
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
recorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);
recorder.setVideoFrameRate(videoFramesPerSecond);
recorder.setVideoSize(videoWidth, videoHeight);

Version 1.0 of my app published more than a year ago used only this approach for video recording and at that time I had next to no crash reports. Interestingly it worked even on the Samsung Galaxy S3. The videoFramesPerSecond parameter is a “high enough” value. From my experience it seems that the cameras take this only as a maximum value that they decrease for the given conditions during the recording.

The “explicit frame size” approach

This method requires Android 3.0 (API Level 11) and above.

1) Let the user choose one of the supported video sizes that you get from Camera.getParameters().getSupportedVideoSizes() if this function does not return null (!!!). If it does return null (an example is here again the Xperia Ray) this means that the size of the preview and the frame size of the recorded video must be identical and you can let the user choose one of the video sizes from Camera.getParameters().getSupportedPreviewSizes(). What does not work is to use one of the supported preview sizes without checking whether or not it is a supported video size. For example the Nexus 7 (first generation with Android 4.4.2) does support a preview size of 480×480 which makes the MediaRecorder start fail when used as a video size. The same Nexus 7 produces by the way some supported video sizes that are way beyond the known resolution of the camera, for example 1280×720. In this case the recording starts but stops after a few seconds and produces a corrupted video.

2) My app chooses values from Camera.getParameters().getSupportedPreviewSizes() that are as close as possible (which means normally identical) to the selected video sizes and sets them as preview sizes in the camera parameters directly after opening the camera, which means before you attach the camera to the surface view holder for the preview:

int previewWidth = ...
previewHeight = ...
Camera.Parameters  camPara = cam.getParameters();
camPara.setPreviewSize(previewWidth, previewHeight);
cam.setParameters(camPara);

Just leaving the preview size parameters in the camera as it is would probably work as well but I did not test it.

3) Set up the video recorder in the same way as with the “automatic” approach but use the selected width and height parameters instead.

The advantage of this method is that you get access to all available video sizes. The downside is that even on devices where this method works in principal there can be sizes that don’t work.

My app supports all three methods. Any feedback on which parameters work on which device would be highly welcome.

Finally one last hint that – if I had know this before – would have saved me some hours of debugging. In Android’s Media Framework the process running the app communicates via Interprocess Communication with a another process who runs the media recorder. Detailed warnings and error message (for example “Unsupported video size: 480×480”) come mostly from this other process, the Media Framework layers of the app quite often throw only a run time exception with an integer parameter like -19 whose meaning is nowhere documented. This means that on a Development System like Eclipse it may be sometimes extremely helpful to look at the complete LogCat without the filter set for the messages of your app.