/** * * Funf: Open Sensing Framework * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. * Acknowledgments: Alan Gardner * Contact: nadav@media.mit.edu * * Author(s): Pararth Shah (pararthshah717@gmail.com) * * This file is part of Funf. * * Funf is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * Funf is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Funf. If not, see <http://www.gnu.org/licenses/>. * */ package edu.mit.media.funf.probe.builtin; import java.io.File; import java.io.IOException; import android.content.Context; import android.graphics.PixelFormat; import android.media.CamcorderProfile; import android.media.MediaRecorder; import android.media.MediaRecorder.OutputFormat; import android.os.CountDownTimer; import android.os.Environment; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import com.google.gson.JsonObject; import edu.mit.media.funf.Schedule; import edu.mit.media.funf.config.Configurable; import edu.mit.media.funf.probe.Probe.DisplayName; import edu.mit.media.funf.probe.Probe.PassiveProbe; import edu.mit.media.funf.probe.Probe.RequiredFeatures; import edu.mit.media.funf.probe.Probe.RequiredPermissions; import edu.mit.media.funf.probe.builtin.ProbeKeys.HighBandwidthKeys; import edu.mit.media.funf.time.TimeUtil; import edu.mit.media.funf.util.CameraUtil; import edu.mit.media.funf.util.LogUtil; import edu.mit.media.funf.util.NameGenerator; import edu.mit.media.funf.util.NameGenerator.SystemUniqueTimestampNameGenerator; @DisplayName("Video Capture Probe") @RequiredPermissions({android.Manifest.permission.WRITE_EXTERNAL_STORAGE, android.Manifest.permission.CAMERA}) @RequiredFeatures("android.hardware.camera") @Schedule.DefaultSchedule(interval=1800) public class VideoCaptureProbe extends ImpulseProbe implements PassiveProbe, HighBandwidthKeys, SurfaceHolder.Callback { @Configurable private String fileNameBase = "videorec"; @Configurable private String folderName = "videocapturetest"; @Configurable private int selectedCamera = 1; // BACK_FACING = 0, FRONT_FACING = 1 @Configurable private int cameraOpenDelay = 1; // allow delay for camera open in seconds @Configurable private int recordingLength = 5; // Duration of recording in seconds @Configurable private String videoProfile = "LOW"; // LOW, HIGH, QCIF, CIF, 480P, 720P, 1080P, QVGA // If the camera does not support the configured profile, // by default the "LOW" profile will be selected @Configurable private boolean timeLapse = false; @Configurable private double captureRate = 24; private enum CameraProfile { QUALITY_LOW, QUALITY_QCIF, QUALITY_CIF, QUALITY_480P, QUALITY_720P, QUALITY_1080P, QUALITY_QVGA, QUALITY_HIGH, QUALITY_TIME_LAPSE_LOW, QUALITY_TIME_LAPSE_QCIF, QUALITY_TIME_LAPSE_CIF, QUALITY_TIME_LAPSE_480P, QUALITY_TIME_LAPSE_720P, QUALITY_TIME_LAPSE_1080P, QUALITY_TIME_LAPSE_QVGA, QUALITY_TIME_LAPSE_HIGH } private String mFileName; private String mFolderPath; private NameGenerator mNameGenerator; private SurfaceView mSurfaceView; private SurfaceHolder mHolder; private WindowManager mWindowManager; private LayoutParams mParams; private MediaRecorder mRecorder; private class RecordingCountDown extends CountDownTimer { public RecordingCountDown(long millisInFuture, long countDownInterval) { super(millisInFuture, countDownInterval); } @Override public void onFinish() { stopRecording(); } @Override public void onTick(long millisUntilFinished) { //Log.d(LogUtil.TAG, "Video capture: seconds remaining = " + millisUntilFinished / 1000); } } private RecordingCountDown mCountDown; @Override protected void onEnable() { super.onEnable(); mNameGenerator = new SystemUniqueTimestampNameGenerator(getContext()); mFolderPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + folderName; File folder = new File(mFolderPath); if (!folder.exists()) { folder.mkdirs(); } else if (!folder.isDirectory()) { folder.delete(); folder.mkdirs(); } mWindowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); mParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, PixelFormat.TRANSLUCENT); } @Override protected void onStart() { super.onStart(); Log.d(LogUtil.TAG, "VideoCaptureProbe: Probe initialization"); mSurfaceView = new SurfaceView(getContext()); mHolder = mSurfaceView.getHolder(); mHolder.addCallback(this); mWindowManager.addView(mSurfaceView, mParams); mSurfaceView.setZOrderOnTop(false); mHolder.setFormat(PixelFormat.TRANSPARENT); } @Override public void surfaceCreated(SurfaceHolder holder) { //Log.d(LogUtil.TAG, "Video capture: surfaceCreated"); if (CameraUtil.safeCameraOpen(selectedCamera)) { try { CameraUtil.getCamera().setPreviewDisplay(mHolder); } catch (IOException exception) { CameraUtil.safeCameraRelease(); Log.e(LogUtil.TAG, "VideoCaptureProbe: error in surface initialization"); Log.e(LogUtil.TAG, exception.getLocalizedMessage()); } } else { abortRecording(); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { //Log.d(LogUtil.TAG, "Video capture: surfaceChanged"); CameraUtil.configureCameraParameters(getContext(), mWindowManager.getDefaultDisplay().getRotation()); CameraUtil.getCamera().startPreview(); // to allow time for camera initiation on certain devices try { Thread.sleep(TimeUtil.secondsToMillis(cameraOpenDelay)); } catch (InterruptedException e) { e.printStackTrace(); } mCountDown = new RecordingCountDown(TimeUtil.secondsToMillis(recordingLength), 1000); CameraUtil.getCamera().unlock(); if (startRecording()) mCountDown.start(); else { abortRecording(); } } @Override public void surfaceDestroyed(SurfaceHolder holder) { //Log.d(LogUtil.TAG, "Video capture: surfaceDestroyed"); CameraUtil.safeCameraClose(); } private boolean startRecording() { Log.d(LogUtil.TAG, "VideoCaptureProbe: Recording video init"); mRecorder = new MediaRecorder(); mRecorder.setCamera(CameraUtil.getCamera()); CamcorderProfile camProfile; int videoQuality = getConfiguredProfile(timeLapse, videoProfile); if (CamcorderProfile.hasProfile(CameraUtil.getCameraId(), videoQuality)) { Log.d(LogUtil.TAG, "VideoCaptureProbe: using camcorder profile " + videoProfile); camProfile = CamcorderProfile.get(CameraUtil.getCameraId(), videoQuality); } else { Log.d(LogUtil.TAG, "VideoCaptureProbe: profile unavailable, using LOW"); camProfile = CamcorderProfile.get(CameraUtil.getCameraId(), CamcorderProfile.QUALITY_LOW); timeLapse = false; } mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); if (!timeLapse) mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); mRecorder.setProfile(camProfile); if (timeLapse) mRecorder.setCaptureRate(captureRate); mFileName = mFolderPath + "/" + mNameGenerator.generateName(fileNameBase); mFileName += getFileFormat(camProfile.fileFormat); mRecorder.setOutputFile(mFileName); mRecorder.setOrientationHint(CameraUtil.computePictureRotation()); try { mRecorder.prepare(); Log.d(LogUtil.TAG, "VideoCaptureProbe: Recording video start"); mRecorder.start(); } catch (Exception e) { e.printStackTrace(); Log.e(LogUtil.TAG, "VideoCaptureProbe: Error in preparing mediaRecorder"); stop(); return false; } return true; } private void stopRecording() { mRecorder.stop(); mRecorder.reset(); mRecorder.release(); mRecorder = null; mWindowManager.removeView(mSurfaceView); Log.d(LogUtil.TAG, "VideoCaptureProbe: Recording video stop"); JsonObject data = new JsonObject(); data.addProperty(FILENAME, mFileName); sendData(data); stop(); } private void abortRecording() { Log.e(LogUtil.TAG, "VideoCaptureProbe: Recording video abort"); CameraUtil.safeCameraClose(); if (mHolder != null) { mHolder.removeCallback(this); } if (mWindowManager != null) { mWindowManager.removeView(mSurfaceView); } stop(); } public static int getConfiguredProfile(boolean isTimeLapse, String profileType) { if (isTimeLapse) { String profileName = "QUALITY_TIME_LAPSE_" + profileType; switch (CameraProfile.valueOf(profileName)){ case QUALITY_TIME_LAPSE_LOW: return CamcorderProfile.QUALITY_TIME_LAPSE_LOW; case QUALITY_TIME_LAPSE_HIGH: return CamcorderProfile.QUALITY_TIME_LAPSE_HIGH; case QUALITY_TIME_LAPSE_QCIF: return CamcorderProfile.QUALITY_TIME_LAPSE_QCIF; case QUALITY_TIME_LAPSE_CIF: return CamcorderProfile.QUALITY_TIME_LAPSE_CIF; case QUALITY_TIME_LAPSE_480P: return CamcorderProfile.QUALITY_TIME_LAPSE_480P; case QUALITY_TIME_LAPSE_720P: return CamcorderProfile.QUALITY_TIME_LAPSE_720P; case QUALITY_TIME_LAPSE_1080P: return CamcorderProfile.QUALITY_TIME_LAPSE_1080P; case QUALITY_TIME_LAPSE_QVGA: return CamcorderProfile.QUALITY_TIME_LAPSE_QVGA; default: return CamcorderProfile.QUALITY_TIME_LAPSE_LOW; } } else { String profileName = "QUALITY_" + profileType; switch (CameraProfile.valueOf(profileName)){ case QUALITY_LOW: return CamcorderProfile.QUALITY_LOW; case QUALITY_HIGH: return CamcorderProfile.QUALITY_HIGH; case QUALITY_QCIF: return CamcorderProfile.QUALITY_QCIF; case QUALITY_CIF: return CamcorderProfile.QUALITY_CIF; case QUALITY_480P: return CamcorderProfile.QUALITY_480P; case QUALITY_720P: return CamcorderProfile.QUALITY_720P; case QUALITY_1080P: return CamcorderProfile.QUALITY_1080P; case QUALITY_QVGA: return CamcorderProfile.QUALITY_QVGA; default: return CamcorderProfile.QUALITY_LOW; } } } private static String getFileFormat(int fileFormat){ switch(fileFormat){ case OutputFormat.MPEG_4: return ".mp4"; case OutputFormat.THREE_GPP: return ".3gp"; case OutputFormat.DEFAULT: return ".mp4"; default: return ".mp4"; } } }