In my last TechRepublic App Builder post, I presented my ideas for creating a simple home or SMB video surveillance system using an Android device. In this installment in my three-part series, I explain how I created the application and describe the problems I encountered along the way.
Getting started
I broke the problem down into three main tasks:
- Figure out how to record a video in Android.
- Adapt the simple video recorder solution to record back-to-back videos.
- Integrate Dropbox to upload the videos from the Android device as they finish recording.
Recording video in Android
First, I started my research on the Android Developers site. Google provides a relatively concise guide on properly recording video in Android in the Camera documentation.
The first sentence of the documentation states that the two main classes you have to deal with are Camera and MediaRecorder. By following Google’s documentation and code samples, I was able to successfully capture a sample video in relatively short order. I will spare you the details for now because I learned there was a much simpler approach, which I will cover later in this post.
Recording consecutive videos in Android
Next, I had to figure out how to automatically stop and re-start the video recording, as well as decide the length of each video. By testing a couple of video samples, I found that it was best to stick with one-minute videos for now or risk making the files too large and more prone for upload errors. Each video was averaging 0.5 MB to 2 MB depending on the amount of light and motion in the video.
As far as the stop/re-start problem, I initially used a Timer object and found the code worked as long as I called Looper.prepare() in the TimerTask (because it isn’t executed on the main UI thread). Later I stumbled across the MediaRecorder.setMaxDuration and MediaRecorder.setOnInfoListener calls, which could replace the Timer object I was using, and I started using those methods instead. By setting a maximum duration on the MediaRecorder, the infoListener object is called when the time limit has been reached, which should be used as the trigger to stop/reset the video.
Hitting a roadblock
I had the first two major chunks of my problem solved, so it was time to start testing. I set up my Android device in a little corner and started recording videos. The first couple of videos recorded without a hitch, and then the app froze. I assumed it was a fluke, so I started the app again. The app froze again a couple of videos into the test. After several frustrating hours of searching, tweaking, and testing, I still hadn’t come up with a solution.
During this process, I found a code example online for recording video that was much less complicated. This particular solution didn’t involve the Camera class and only dealt with the MediaRecorder class. I assume that the MediaRecorder class takes care of establishing a connection with the Camera class and properly handles it.
I quickly whipped together a prototype with this new code and started testing again. The results were promising (I was able to record about 10 minutes of video), but much to my dismay the application froze, and I was right back to where I was before but with less code.
Finding a solution
The next several days I searched for answers, tested minor tweaks, and pulled my hair out until I finally found the solution (which I swear I tried before). By switching the VideoEncoder to MediaRecorder.VideoEncoder.MPEG_4_SP, the video would no longer randomly freeze, and I was able to record up to two hours of video before I considered it a success.
The Video Recorder Activity has a very basic XML layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:orientation="vertical"
android:gravity="center">
<SurfaceView android:id="@+id/surface_camera"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:layout_weight="1">
</SurfaceView>
</LinearLayout>
The Activity used for recording video implements OnClickListener (for starting/stopping the video) and SurfaceHolder.Callback (for determining when the SurfaceView is ready for initiating and preparing the MediaRecorder).
In the onCreate method of the recording Activity, the first thing to do is set a flag that will allow the Activity to keep the screen on:
getWindow().addFlags( WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON );
Next, create a method-wide instance of MediaRecorder and initialize it (initRecorder will be explained after onCreate):
mRecorder = new MediaRecorder();
initRecorder();
setContentView(R.layout.camera);
Then grab the SurfaceView from the XML layout, store a method-wide reference to its SurfaceHolder, set the activity as the SurfaceHolder callback, and set the holder’s type to SURFACE_TYPE_PUSH_BUFFERS:
SurfaceView cameraView = (SurfaceView) findViewById(R.id.surface_camera);
mHolder = cameraView.getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
Finally, make the SurfaceView clickable:
cameraView.setClickable(true);
cameraView.setOnClickListener(this);
Inside initRecoder all the properties of the MediaRecorder are set:
mRecorder.setVideoSource(MediaRecorder.VideoSource.DEFAULT);
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);
mRecorder.setVideoSize(320, 240);
mRecorder.setVideoFrameRate(15);
File mediaStorageDir = new File("/sdcard/VideoSurveillance/");
if ( !mediaStorageDir.exists() ) {
if ( !mediaStorageDir.mkdirs() ){
Logger.Debug("failed to create directory");
}
}
String filePath = "/sdcard/VideoSurveillance/" + "VID_"+ new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + ".mp4";
mRecorder.setOutputFile(filePath);
mRecorder.setPreviewDisplay(mHolder.getSurface());
The SurfaceHolder.Callback methods, surfaceCreated, surfaceChanged, and surfaceDestroyed, must be implemented. surfaceChanged can be ignored, but it must be implemented. surfaceDestroyed will be discussed in a bit. In surfaceCreated, the only thing to do is prepare the MediaRecorder:
mMediaRecorder.prepare();
The last main piece to look at is the onClick handler of the SurfaceView. If the MediaRecorder is not running, then call its start method and schedule a Timer (I gave up on the onInfoListener because of errors I saw in logcat):
mRecorder.start();
mStopTimer = new Timer();
mStopTimer.schedule( new TimerTask() {
@Override
public void run() {
Looper.prepare();
restartRecorder();
}
}, 60 * 1000 );
Alternately, if the MediaRecorder is running, we can treat the click as a trigger to stop the automation process by resetting the MediaRecorder, initializing and preparing it (in case you want to start recording again), and then canceling the Timer that is scheduled:
mRecorder.reset();
initRecorder();
mRecorder.prepare();
mStopTimer.cancel();
Inside the restartRecorder method (from above), the MediaRecorder is reset, initialized, prepared, and started. Then a new stop Timer is set:
mRecorder.reset();
initRecorder();
mRecorder.prepare();
mRecorder.start();
mStopTimer = new Timer();
mStopTimer.schedule( new TimerTask() {
@Override
public void run() {
Looper.prepare();
restartRecorder();
}
}, 60 * 1000 );
Part three
In the final part of this series, I will discuss how I went about Dropbox integration and provide the entire source code for this app.