• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package com.android.camera;
18 
19 import android.content.Context;
20 import android.content.res.AssetFileDescriptor;
21 import android.filterfw.GraphEnvironment;
22 import android.filterfw.core.Filter;
23 import android.filterfw.core.GLEnvironment;
24 import android.filterfw.core.GraphRunner;
25 import android.filterfw.core.GraphRunner.OnRunnerDoneListener;
26 import android.filterfw.geometry.Point;
27 import android.filterfw.geometry.Quad;
28 import android.filterpacks.videoproc.BackDropperFilter;
29 import android.filterpacks.videoproc.BackDropperFilter.LearningDoneListener;
30 import android.filterpacks.videosink.MediaEncoderFilter.OnRecordingDoneListener;
31 import android.filterpacks.videosrc.SurfaceTextureSource.SurfaceTextureSourceListener;
32 
33 import android.graphics.SurfaceTexture;
34 import android.hardware.Camera;
35 import android.media.MediaRecorder;
36 import android.media.CamcorderProfile;
37 import android.os.ConditionVariable;
38 import android.os.Handler;
39 import android.os.Looper;
40 import android.os.ParcelFileDescriptor;
41 import android.os.SystemProperties;
42 import android.util.Log;
43 import android.view.Surface;
44 import android.view.SurfaceHolder;
45 
46 import java.io.IOException;
47 import java.io.FileNotFoundException;
48 import java.io.File;
49 import java.lang.Runnable;
50 import java.io.FileDescriptor;
51 
52 
53 /**
54  * Encapsulates the mobile filter framework components needed to record video with
55  * effects applied. Modeled after MediaRecorder.
56  */
57 public class EffectsRecorder {
58 
59     public static final int  EFFECT_NONE        = 0;
60     public static final int  EFFECT_GOOFY_FACE  = 1;
61     public static final int  EFFECT_BACKDROPPER = 2;
62 
63     public static final int  EFFECT_GF_SQUEEZE     = 0;
64     public static final int  EFFECT_GF_BIG_EYES    = 1;
65     public static final int  EFFECT_GF_BIG_MOUTH   = 2;
66     public static final int  EFFECT_GF_SMALL_MOUTH = 3;
67     public static final int  EFFECT_GF_BIG_NOSE    = 4;
68     public static final int  EFFECT_GF_SMALL_EYES  = 5;
69     public static final int  NUM_OF_GF_EFFECTS = EFFECT_GF_SMALL_EYES + 1;
70 
71     public static final int  EFFECT_MSG_STARTED_LEARNING = 0;
72     public static final int  EFFECT_MSG_DONE_LEARNING    = 1;
73     public static final int  EFFECT_MSG_SWITCHING_EFFECT = 2;
74     public static final int  EFFECT_MSG_EFFECTS_STOPPED  = 3;
75     public static final int  EFFECT_MSG_RECORDING_DONE   = 4;
76 
77     private Context mContext;
78     private Handler mHandler;
79     private boolean mReleased;
80 
81     private Camera mCameraDevice;
82     private CamcorderProfile mProfile;
83     private double mCaptureRate = 0;
84     private SurfaceHolder mPreviewSurfaceHolder;
85     private int mPreviewWidth;
86     private int mPreviewHeight;
87     private MediaRecorder.OnInfoListener mInfoListener;
88     private MediaRecorder.OnErrorListener mErrorListener;
89 
90     private String mOutputFile;
91     private FileDescriptor mFd;
92     private int mOrientationHint = 0;
93     private long mMaxFileSize = 0;
94     private int mMaxDurationMs = 0;
95     private int mCameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK;
96 
97     private int mEffect = EFFECT_NONE;
98     private int mCurrentEffect = EFFECT_NONE;
99     private EffectsListener mEffectsListener;
100 
101     private Object mEffectParameter;
102 
103     private GraphEnvironment mGraphEnv;
104     private int mGraphId;
105     private GraphRunner mRunner = null;
106     private GraphRunner mOldRunner = null;
107 
108     private SurfaceTexture mTextureSource;
109 
110     private static final String mVideoRecordSound = "/system/media/audio/ui/VideoRecord.ogg";
111     private SoundPlayer mRecordSound;
112 
113     private static final int STATE_CONFIGURE              = 0;
114     private static final int STATE_WAITING_FOR_SURFACE    = 1;
115     private static final int STATE_STARTING_PREVIEW       = 2;
116     private static final int STATE_PREVIEW                = 3;
117     private static final int STATE_RECORD                 = 4;
118     private static final int STATE_RELEASED               = 5;
119     private int mState = STATE_CONFIGURE;
120 
121     private boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
122     private static final String TAG = "effectsrecorder";
123 
124     /** Determine if a given effect is supported at runtime
125      * Some effects require libraries not available on all devices
126      */
isEffectSupported(int effectId)127     public static boolean isEffectSupported(int effectId) {
128         switch (effectId) {
129             case EFFECT_GOOFY_FACE:
130                 return Filter.isAvailable("com.google.android.filterpacks.facedetect.GoofyRenderFilter");
131             case EFFECT_BACKDROPPER:
132                 return Filter.isAvailable("android.filterpacks.videoproc.BackDropperFilter");
133             default:
134                 return false;
135         }
136     }
137 
EffectsRecorder(Context context)138     public EffectsRecorder(Context context) {
139         if (mLogVerbose) Log.v(TAG, "EffectsRecorder created (" + this + ")");
140         mContext = context;
141         mHandler = new Handler(Looper.getMainLooper());
142 
143         // Construct sound player; use enforced sound output if necessary
144         File recordSoundFile = new File(mVideoRecordSound);
145         try {
146             ParcelFileDescriptor recordSoundParcel =
147                     ParcelFileDescriptor.open(recordSoundFile,
148                             ParcelFileDescriptor.MODE_READ_ONLY);
149             AssetFileDescriptor recordSoundAsset =
150                     new AssetFileDescriptor(recordSoundParcel, 0,
151                                             AssetFileDescriptor.UNKNOWN_LENGTH);
152             if (SystemProperties.get("ro.camera.sound.forced", "0").equals("0")) {
153                 if (mLogVerbose) Log.v(TAG, "Standard recording sound");
154                 mRecordSound = new SoundPlayer(recordSoundAsset, false);
155             } else {
156                 if (mLogVerbose) Log.v(TAG, "Forced recording sound");
157                 mRecordSound = new SoundPlayer(recordSoundAsset, true);
158             }
159         } catch (java.io.FileNotFoundException e) {
160             Log.e(TAG, "System video record sound not found");
161             mRecordSound = null;
162         }
163 
164     }
165 
setCamera(Camera cameraDevice)166     public void setCamera(Camera cameraDevice) {
167         switch (mState) {
168             case STATE_PREVIEW:
169                 throw new RuntimeException("setCamera cannot be called while previewing!");
170             case STATE_RECORD:
171                 throw new RuntimeException("setCamera cannot be called while recording!");
172             case STATE_RELEASED:
173                 throw new RuntimeException("setCamera called on an already released recorder!");
174             default:
175                 break;
176         }
177 
178         mCameraDevice = cameraDevice;
179     }
180 
setProfile(CamcorderProfile profile)181     public void setProfile(CamcorderProfile profile) {
182         switch (mState) {
183             case STATE_RECORD:
184                 throw new RuntimeException("setProfile cannot be called while recording!");
185             case STATE_RELEASED:
186                 throw new RuntimeException("setProfile called on an already released recorder!");
187             default:
188                 break;
189         }
190         mProfile = profile;
191     }
192 
setOutputFile(String outputFile)193     public void setOutputFile(String outputFile) {
194         switch (mState) {
195             case STATE_RECORD:
196                 throw new RuntimeException("setOutputFile cannot be called while recording!");
197             case STATE_RELEASED:
198                 throw new RuntimeException("setOutputFile called on an already released recorder!");
199             default:
200                 break;
201         }
202 
203         mOutputFile = outputFile;
204         mFd = null;
205     }
206 
setOutputFile(FileDescriptor fd)207     public void setOutputFile(FileDescriptor fd) {
208         switch (mState) {
209             case STATE_RECORD:
210                 throw new RuntimeException("setOutputFile cannot be called while recording!");
211             case STATE_RELEASED:
212                 throw new RuntimeException("setOutputFile called on an already released recorder!");
213             default:
214                 break;
215         }
216 
217         mOutputFile = null;
218         mFd = fd;
219     }
220 
221     /**
222      * Sets the maximum filesize (in bytes) of the recording session.
223      * This will be passed on to the MediaEncoderFilter and then to the
224      * MediaRecorder ultimately. If zero or negative, the MediaRecorder will
225      * disable the limit
226     */
setMaxFileSize(long maxFileSize)227     public synchronized void setMaxFileSize(long maxFileSize) {
228         switch (mState) {
229             case STATE_RECORD:
230                 throw new RuntimeException("setMaxFileSize cannot be called while recording!");
231             case STATE_RELEASED:
232                 throw new RuntimeException("setMaxFileSize called on an already released recorder!");
233             default:
234                 break;
235         }
236         mMaxFileSize = maxFileSize;
237     }
238 
239     /**
240     * Sets the maximum recording duration (in ms) for the next recording session
241     * Setting it to zero (the default) disables the limit.
242     */
setMaxDuration(int maxDurationMs)243     public synchronized void setMaxDuration(int maxDurationMs) {
244         switch (mState) {
245             case STATE_RECORD:
246                 throw new RuntimeException("setMaxDuration cannot be called while recording!");
247             case STATE_RELEASED:
248                 throw new RuntimeException("setMaxDuration called on an already released recorder!");
249             default:
250                 break;
251         }
252         mMaxDurationMs = maxDurationMs;
253     }
254 
255 
setCaptureRate(double fps)256     public void setCaptureRate(double fps) {
257         switch (mState) {
258             case STATE_RECORD:
259                 throw new RuntimeException("setCaptureRate cannot be called while recording!");
260             case STATE_RELEASED:
261                 throw new RuntimeException("setCaptureRate called on an already released recorder!");
262             default:
263                 break;
264         }
265 
266         if (mLogVerbose) Log.v(TAG, "Setting time lapse capture rate to " + fps + " fps");
267         mCaptureRate = fps;
268     }
269 
setPreviewDisplay(SurfaceHolder previewSurfaceHolder, int previewWidth, int previewHeight)270     public void setPreviewDisplay(SurfaceHolder previewSurfaceHolder,
271                                   int previewWidth,
272                                   int previewHeight) {
273         if (mLogVerbose) Log.v(TAG, "setPreviewDisplay (" + this + ")");
274         switch (mState) {
275             case STATE_RECORD:
276                 throw new RuntimeException("setPreviewDisplay cannot be called while recording!");
277             case STATE_RELEASED:
278                 throw new RuntimeException("setPreviewDisplay called on an already released recorder!");
279             default:
280                 break;
281         }
282 
283         mPreviewSurfaceHolder = previewSurfaceHolder;
284         mPreviewWidth = previewWidth;
285         mPreviewHeight = previewHeight;
286 
287         switch (mState) {
288             case STATE_WAITING_FOR_SURFACE:
289                 startPreview();
290                 break;
291             case STATE_STARTING_PREVIEW:
292             case STATE_PREVIEW:
293                 initializeEffect(true);
294                 break;
295         }
296     }
297 
setEffect(int effect, Object effectParameter)298     public void setEffect(int effect, Object effectParameter) {
299         if (mLogVerbose) Log.v(TAG,
300                                "setEffect: effect ID " + effect +
301                                ", parameter " + effectParameter.toString() );
302         switch (mState) {
303             case STATE_RECORD:
304                 throw new RuntimeException("setEffect cannot be called while recording!");
305             case STATE_RELEASED:
306                 throw new RuntimeException("setEffect called on an already released recorder!");
307             default:
308                 break;
309         }
310 
311         mEffect = effect;
312         mEffectParameter = effectParameter;
313 
314         if (mState == STATE_PREVIEW ||
315                 mState == STATE_STARTING_PREVIEW) {
316             initializeEffect(false);
317         }
318     }
319 
320     public interface EffectsListener {
onEffectsUpdate(int effectId, int effectMsg)321         public void onEffectsUpdate(int effectId, int effectMsg);
onEffectsError(Exception exception, String filePath)322         public void onEffectsError(Exception exception, String filePath);
323     }
324 
setEffectsListener(EffectsListener listener)325     public void setEffectsListener(EffectsListener listener) {
326         mEffectsListener = listener;
327     }
328 
setFaceDetectOrientation()329     private void setFaceDetectOrientation() {
330         if (mCurrentEffect == EFFECT_GOOFY_FACE) {
331             Filter rotateFilter = mRunner.getGraph().getFilter("rotate");
332             Filter metaRotateFilter = mRunner.getGraph().getFilter("metarotate");
333             rotateFilter.setInputValue("rotation", mOrientationHint);
334             int reverseDegrees = (360 - mOrientationHint) % 360;
335             metaRotateFilter.setInputValue("rotation", reverseDegrees);
336         }
337     }
338 
setRecordingOrientation()339     private void setRecordingOrientation() {
340         if ( mState != STATE_RECORD && mRunner != null) {
341             Point bl = new Point(0, 0);
342             Point br = new Point(1, 0);
343             Point tl = new Point(0, 1);
344             Point tr = new Point(1, 1);
345             Quad recordingRegion;
346             if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK) {
347                 // The back camera is not mirrored, so use a identity transform
348                 recordingRegion = new Quad(bl, br, tl, tr);
349             } else {
350                 // Recording region needs to be tweaked for front cameras, since they
351                 // mirror their preview
352                 if (mOrientationHint == 0 || mOrientationHint == 180) {
353                     // Horizontal flip in landscape
354                     recordingRegion = new Quad(br, bl, tr, tl);
355                 } else {
356                     // Horizontal flip in portrait
357                     recordingRegion = new Quad(tl, tr, bl, br);
358                 }
359             }
360             Filter recorder = mRunner.getGraph().getFilter("recorder");
361             recorder.setInputValue("inputRegion", recordingRegion);
362         }
363     }
setOrientationHint(int degrees)364     public void setOrientationHint(int degrees) {
365         switch (mState) {
366             case STATE_RELEASED:
367                 throw new RuntimeException(
368                         "setOrientationHint called on an already released recorder!");
369             default:
370                 break;
371         }
372         if (mLogVerbose) Log.v(TAG, "Setting orientation hint to: " + degrees);
373         mOrientationHint = degrees;
374         setFaceDetectOrientation();
375         setRecordingOrientation();
376     }
377 
setCameraFacing(int facing)378     public void setCameraFacing(int facing) {
379         switch (mState) {
380             case STATE_RELEASED:
381                 throw new RuntimeException(
382                     "setCameraFacing called on alrady released recorder!");
383             default:
384                 break;
385         }
386         mCameraFacing = facing;
387         setRecordingOrientation();
388     }
389 
setOnInfoListener(MediaRecorder.OnInfoListener infoListener)390     public void setOnInfoListener(MediaRecorder.OnInfoListener infoListener) {
391         switch (mState) {
392             case STATE_RECORD:
393                 throw new RuntimeException("setInfoListener cannot be called while recording!");
394             case STATE_RELEASED:
395                 throw new RuntimeException("setInfoListener called on an already released recorder!");
396             default:
397                 break;
398         }
399         mInfoListener = infoListener;
400     }
401 
setOnErrorListener(MediaRecorder.OnErrorListener errorListener)402     public void setOnErrorListener(MediaRecorder.OnErrorListener errorListener) {
403         switch (mState) {
404             case STATE_RECORD:
405                 throw new RuntimeException("setErrorListener cannot be called while recording!");
406             case STATE_RELEASED:
407                 throw new RuntimeException("setErrorListener called on an already released recorder!");
408             default:
409                 break;
410         }
411         mErrorListener = errorListener;
412     }
413 
initializeFilterFramework()414     private void initializeFilterFramework() {
415         mGraphEnv = new GraphEnvironment();
416         mGraphEnv.createGLEnvironment();
417 
418         if (mLogVerbose) {
419             Log.v(TAG, "Effects framework initializing. Recording size "
420                   + mProfile.videoFrameWidth + ", " + mProfile.videoFrameHeight);
421         }
422 
423         mGraphEnv.addReferences(
424                 "textureSourceCallback", mSourceReadyCallback,
425                 "recordingWidth", mProfile.videoFrameWidth,
426                 "recordingHeight", mProfile.videoFrameHeight,
427                 "recordingProfile", mProfile,
428                 "learningDoneListener", mLearningDoneListener,
429                 "recordingDoneListener", mRecordingDoneListener);
430         mRunner = null;
431         mGraphId = -1;
432         mCurrentEffect = EFFECT_NONE;
433     }
434 
initializeEffect(boolean forceReset)435     private synchronized void initializeEffect(boolean forceReset) {
436         if (forceReset ||
437             mCurrentEffect != mEffect ||
438             mCurrentEffect == EFFECT_BACKDROPPER) {
439             if (mLogVerbose) {
440                 Log.v(TAG, "Effect initializing. Preview size "
441                        + mPreviewWidth + ", " + mPreviewHeight);
442             }
443 
444             mGraphEnv.addReferences(
445                     "previewSurface", mPreviewSurfaceHolder.getSurface(),
446                     "previewWidth", mPreviewWidth,
447                     "previewHeight", mPreviewHeight,
448                     "orientation", mOrientationHint);
449             if (mState == STATE_PREVIEW ||
450                     mState == STATE_STARTING_PREVIEW) {
451                 // Switching effects while running. Inform video camera.
452                 sendMessage(mCurrentEffect, EFFECT_MSG_SWITCHING_EFFECT);
453             }
454 
455             switch (mEffect) {
456                 case EFFECT_GOOFY_FACE:
457                     mGraphId = mGraphEnv.loadGraph(mContext, R.raw.goofy_face);
458                     break;
459                 case EFFECT_BACKDROPPER:
460                     sendMessage(EFFECT_BACKDROPPER, EFFECT_MSG_STARTED_LEARNING);
461                     mGraphId = mGraphEnv.loadGraph(mContext, R.raw.backdropper);
462                     break;
463                 default:
464                     throw new RuntimeException("Unknown effect ID" + mEffect + "!");
465             }
466             mCurrentEffect = mEffect;
467 
468             mOldRunner = mRunner;
469             mRunner = mGraphEnv.getRunner(mGraphId, GraphEnvironment.MODE_ASYNCHRONOUS);
470             mRunner.setDoneCallback(mRunnerDoneCallback);
471             if (mLogVerbose) {
472                 Log.v(TAG, "New runner: " + mRunner
473                       + ". Old runner: " + mOldRunner);
474             }
475             if (mState == STATE_PREVIEW ||
476                     mState == STATE_STARTING_PREVIEW) {
477                 // Switching effects while running. Stop existing runner.
478                 // The stop callback will take care of starting new runner.
479                 mCameraDevice.stopPreview();
480                 try {
481                     mCameraDevice.setPreviewTexture(null);
482                 } catch(IOException e) {
483                     throw new RuntimeException("Unable to connect camera to effect input", e);
484                 }
485                 mOldRunner.stop();
486             }
487         }
488 
489         switch (mCurrentEffect) {
490             case EFFECT_GOOFY_FACE:
491                 tryEnableVideoStabilization(true);
492                 Filter goofyFilter = mRunner.getGraph().getFilter("goofyrenderer");
493                 goofyFilter.setInputValue("currentEffect",
494                                           ((Integer)mEffectParameter).intValue());
495                 break;
496             case EFFECT_BACKDROPPER:
497                 tryEnableVideoStabilization(false);
498                 Filter backgroundSrc = mRunner.getGraph().getFilter("background");
499                 backgroundSrc.setInputValue("sourceUrl",
500                                             (String)mEffectParameter);
501                 break;
502             default:
503                 break;
504         }
505         setFaceDetectOrientation();
506         setRecordingOrientation();
507     }
508 
startPreview()509     public synchronized void startPreview() {
510         if (mLogVerbose) Log.v(TAG, "Starting preview (" + this + ")");
511 
512         switch (mState) {
513             case STATE_STARTING_PREVIEW:
514             case STATE_PREVIEW:
515                 // Already running preview
516                 Log.w(TAG, "startPreview called when already running preview");
517                 return;
518             case STATE_RECORD:
519                 throw new RuntimeException("Cannot start preview when already recording!");
520             case STATE_RELEASED:
521                 throw new RuntimeException("setEffect called on an already released recorder!");
522             default:
523                 break;
524         }
525 
526         if (mEffect == EFFECT_NONE) {
527             throw new RuntimeException("No effect selected!");
528         }
529         if (mEffectParameter == null) {
530             throw new RuntimeException("No effect parameter provided!");
531         }
532         if (mProfile == null) {
533             throw new RuntimeException("No recording profile provided!");
534         }
535         if (mPreviewSurfaceHolder == null) {
536             if (mLogVerbose) Log.v(TAG, "Passed a null surface holder; waiting for valid one");
537             mState = STATE_WAITING_FOR_SURFACE;
538             return;
539         }
540         if (mCameraDevice == null) {
541             throw new RuntimeException("No camera to record from!");
542         }
543 
544         if (mLogVerbose) Log.v(TAG, "Initializing filter graph");
545 
546         initializeFilterFramework();
547 
548         initializeEffect(true);
549 
550         if (mLogVerbose) Log.v(TAG, "Starting filter graph");
551 
552         mState = STATE_STARTING_PREVIEW;
553         mRunner.run();
554         // Rest of preview startup handled in mSourceReadyCallback
555     }
556 
557     private SurfaceTextureSourceListener mSourceReadyCallback =
558             new SurfaceTextureSourceListener() {
559         public void onSurfaceTextureSourceReady(SurfaceTexture source) {
560             if (mLogVerbose) Log.v(TAG, "SurfaceTexture ready callback received");
561             synchronized(EffectsRecorder.this) {
562                 mTextureSource = source;
563 
564                 if (mState == STATE_CONFIGURE) {
565                     // Stop preview happened while the runner was doing startup tasks
566                     // Since we haven't started anything up, don't do anything
567                     // Rest of cleanup will happen in onRunnerDone
568                     if (mLogVerbose) Log.v(TAG, "Ready callback: Already stopped, skipping.");
569                     return;
570                 }
571                 if (mState == STATE_RELEASED) {
572                     // EffectsRecorder has been released, so don't touch the camera device
573                     // or anything else
574                     if (mLogVerbose) Log.v(TAG, "Ready callback: Already released, skipping.");
575                     return;
576                 }
577                 if (source == null) {
578                     if (mState == STATE_PREVIEW ||
579                             mState == STATE_STARTING_PREVIEW ||
580                             mState == STATE_RECORD) {
581                         // A null source here means the graph is shutting down
582                         // unexpectedly, so we need to turn off preview before
583                         // the surface texture goes away.
584                         mCameraDevice.stopPreview();
585                         try {
586                             mCameraDevice.setPreviewTexture(null);
587                         } catch(IOException e) {
588                             throw new RuntimeException("Unable to disconnect " +
589                                     "camera from effect input", e);
590                         }
591                     }
592                     return;
593                 }
594 
595                 // Lock AE/AWB to reduce transition flicker
596                 tryEnable3ALocks(true);
597 
598                 mCameraDevice.stopPreview();
599                 if (mLogVerbose) Log.v(TAG, "Runner active, connecting effects preview");
600                 try {
601                     mCameraDevice.setPreviewTexture(mTextureSource);
602                 } catch(IOException e) {
603                     throw new RuntimeException("Unable to connect camera to effect input", e);
604                 }
605 
606                 mCameraDevice.startPreview();
607 
608                 // Unlock AE/AWB after preview started
609                 tryEnable3ALocks(false);
610 
611                 mState = STATE_PREVIEW;
612 
613                 if (mLogVerbose) Log.v(TAG, "Start preview/effect switch complete");
614             }
615         }
616     };
617 
618     private LearningDoneListener mLearningDoneListener =
619             new LearningDoneListener() {
620         public void onLearningDone(BackDropperFilter filter) {
621             if (mLogVerbose) Log.v(TAG, "Learning done callback triggered");
622             // Called in a processing thread, so have to post message back to UI
623             // thread
624             sendMessage(EFFECT_BACKDROPPER, EFFECT_MSG_DONE_LEARNING);
625             enable3ALocks(true);
626         }
627     };
628 
629     // A callback to finalize the media after the recording is done.
630     private OnRecordingDoneListener mRecordingDoneListener =
631             new OnRecordingDoneListener() {
632         // Forward the callback to the VideoCamera object (as an asynchronous event).
633         public void onRecordingDone() {
634             if (mLogVerbose) Log.v(TAG, "Recording done callback triggered");
635             sendMessage(EFFECT_NONE, EFFECT_MSG_RECORDING_DONE);
636         }
637     };
638 
startRecording()639     public synchronized void startRecording() {
640         if (mLogVerbose) Log.v(TAG, "Starting recording (" + this + ")");
641 
642         switch (mState) {
643             case STATE_RECORD:
644                 throw new RuntimeException("Already recording, cannot begin anew!");
645             case STATE_RELEASED:
646                 throw new RuntimeException("startRecording called on an already released recorder!");
647             default:
648                 break;
649         }
650 
651         if ((mOutputFile == null) && (mFd == null)) {
652             throw new RuntimeException("No output file name or descriptor provided!");
653         }
654 
655         if (mState == STATE_CONFIGURE) {
656             startPreview();
657         }
658 
659         Filter recorder = mRunner.getGraph().getFilter("recorder");
660         if (mFd != null) {
661             recorder.setInputValue("outputFileDescriptor", mFd);
662         } else {
663             recorder.setInputValue("outputFile", mOutputFile);
664         }
665         // It is ok to set the audiosource without checking for timelapse here
666         // since that check will be done in the MediaEncoderFilter itself
667         recorder.setInputValue("audioSource", MediaRecorder.AudioSource.CAMCORDER);
668 
669         recorder.setInputValue("recordingProfile", mProfile);
670         recorder.setInputValue("orientationHint", mOrientationHint);
671         // Important to set the timelapseinterval to 0 if the capture rate is not >0
672         // since the recorder does not get created every time the recording starts.
673         // The recorder infers whether the capture is timelapsed based on the value of
674         // this interval
675         boolean captureTimeLapse = mCaptureRate > 0;
676         if (captureTimeLapse) {
677             double timeBetweenFrameCapture = 1 / mCaptureRate;
678             recorder.setInputValue("timelapseRecordingIntervalUs",
679                     (long) (1000000 * timeBetweenFrameCapture));
680         } else {
681             recorder.setInputValue("timelapseRecordingIntervalUs", 0L);
682         }
683 
684         if (mInfoListener != null) {
685             recorder.setInputValue("infoListener", mInfoListener);
686         }
687         if (mErrorListener != null) {
688             recorder.setInputValue("errorListener", mErrorListener);
689         }
690         recorder.setInputValue("maxFileSize", mMaxFileSize);
691         recorder.setInputValue("maxDurationMs", mMaxDurationMs);
692         recorder.setInputValue("recording", true);
693         if (mRecordSound != null) mRecordSound.play();
694         mState = STATE_RECORD;
695     }
696 
stopRecording()697     public synchronized void stopRecording() {
698         if (mLogVerbose) Log.v(TAG, "Stop recording (" + this + ")");
699 
700         switch (mState) {
701             case STATE_CONFIGURE:
702             case STATE_STARTING_PREVIEW:
703             case STATE_PREVIEW:
704                 Log.w(TAG, "StopRecording called when recording not active!");
705                 return;
706             case STATE_RELEASED:
707                 throw new RuntimeException("stopRecording called on released EffectsRecorder!");
708             default:
709                 break;
710         }
711         Filter recorder = mRunner.getGraph().getFilter("recorder");
712         recorder.setInputValue("recording", false);
713         if (mRecordSound != null) mRecordSound.play();
714         mState = STATE_PREVIEW;
715     }
716 
717     // Stop and release effect resources
stopPreview()718     public synchronized void stopPreview() {
719         if (mLogVerbose) Log.v(TAG, "Stopping preview (" + this + ")");
720 
721         switch (mState) {
722             case STATE_CONFIGURE:
723                 Log.w(TAG, "StopPreview called when preview not active!");
724                 return;
725             case STATE_RELEASED:
726                 throw new RuntimeException("stopPreview called on released EffectsRecorder!");
727             default:
728                 break;
729         }
730 
731         if (mState == STATE_RECORD) {
732             stopRecording();
733         }
734 
735         mCurrentEffect = EFFECT_NONE;
736 
737         mCameraDevice.stopPreview();
738         try {
739             mCameraDevice.setPreviewTexture(null);
740         } catch(IOException e) {
741             throw new RuntimeException("Unable to connect camera to effect input", e);
742         }
743 
744         mState = STATE_CONFIGURE;
745         mOldRunner = mRunner;
746         mRunner.stop();
747         mRunner = null;
748         // Rest of stop and release handled in mRunnerDoneCallback
749     }
750 
751     // Try to enable/disable video stabilization if supported; otherwise return false
tryEnableVideoStabilization(boolean toggle)752     boolean tryEnableVideoStabilization(boolean toggle) {
753         Camera.Parameters params = mCameraDevice.getParameters();
754 
755         String vstabSupported = params.get("video-stabilization-supported");
756         if ("true".equals(vstabSupported)) {
757             if (mLogVerbose) Log.v(TAG, "Setting video stabilization to " + toggle);
758             params.set("video-stabilization", toggle ? "true" : "false");
759             mCameraDevice.setParameters(params);
760             return true;
761         }
762         if (mLogVerbose) Log.v(TAG, "Video stabilization not supported");
763         return false;
764     }
765 
766     // Try to enable/disable 3A locks if supported; otherwise return false
tryEnable3ALocks(boolean toggle)767     boolean tryEnable3ALocks(boolean toggle) {
768         Camera.Parameters params = mCameraDevice.getParameters();
769         if (params.isAutoExposureLockSupported() &&
770             params.isAutoWhiteBalanceLockSupported() ) {
771             params.setAutoExposureLock(toggle);
772             params.setAutoWhiteBalanceLock(toggle);
773             mCameraDevice.setParameters(params);
774             return true;
775         }
776         return false;
777     }
778 
779     // Try to enable/disable 3A locks if supported; otherwise, throw error
780     // Use this when locks are essential to success
enable3ALocks(boolean toggle)781     void enable3ALocks(boolean toggle) {
782         Camera.Parameters params = mCameraDevice.getParameters();
783         if (!tryEnable3ALocks(toggle)) {
784             throw new RuntimeException("Attempt to lock 3A on camera with no locking support!");
785         }
786     }
787 
788     private OnRunnerDoneListener mRunnerDoneCallback =
789             new OnRunnerDoneListener() {
790         public void onRunnerDone(int result) {
791             synchronized(EffectsRecorder.this) {
792                 if (mLogVerbose) {
793                     Log.v(TAG,
794                           "Graph runner done (" + EffectsRecorder.this
795                           + ", mRunner " + mRunner
796                           + ", mOldRunner " + mOldRunner + ")");
797                 }
798                 if (result == GraphRunner.RESULT_ERROR) {
799                     // Handle error case
800                     Log.e(TAG, "Error running filter graph!");
801                     raiseError(mRunner == null ? null : mRunner.getError());
802                 }
803                 if (mOldRunner != null) {
804                     // Tear down old graph if available
805                     if (mLogVerbose) Log.v(TAG, "Tearing down old graph.");
806                     GLEnvironment glEnv = mGraphEnv.getContext().getGLEnvironment();
807                     if (glEnv != null && !glEnv.isActive()) {
808                         glEnv.activate();
809                     }
810                     mOldRunner.getGraph().tearDown(mGraphEnv.getContext());
811                     if (glEnv != null && glEnv.isActive()) {
812                         glEnv.deactivate();
813                     }
814                     mOldRunner = null;
815                 }
816                 if (mState == STATE_PREVIEW ||
817                         mState == STATE_STARTING_PREVIEW) {
818                     // Switching effects, start up the new runner
819                     if (mLogVerbose) Log.v(TAG, "Previous effect halted, starting new effect.");
820                     tryEnable3ALocks(false);
821                     mRunner.run();
822                 } else if (mState != STATE_RELEASED) {
823                     // Shutting down effects
824                     if (mLogVerbose) Log.v(TAG, "Runner halted, restoring direct preview");
825                     tryEnable3ALocks(false);
826                     sendMessage(EFFECT_NONE, EFFECT_MSG_EFFECTS_STOPPED);
827                 } else {
828                     // STATE_RELEASED - camera will be/has been released as well, do nothing.
829                 }
830             }
831         }
832     };
833 
834     // Indicates that all camera/recording activity needs to halt
release()835     public synchronized void release() {
836         if (mLogVerbose) Log.v(TAG, "Releasing (" + this + ")");
837 
838         switch (mState) {
839             case STATE_RECORD:
840             case STATE_STARTING_PREVIEW:
841             case STATE_PREVIEW:
842                 stopPreview();
843                 // Fall-through
844             default:
845                 mRecordSound.release();
846                 mState = STATE_RELEASED;
847                 break;
848         }
849     }
850 
sendMessage(final int effect, final int msg)851     private void sendMessage(final int effect, final int msg) {
852         if (mEffectsListener != null) {
853             mHandler.post(new Runnable() {
854                 public void run() {
855                     mEffectsListener.onEffectsUpdate(effect, msg);
856                 }
857             });
858         }
859     }
860 
raiseError(final Exception exception)861     private void raiseError(final Exception exception) {
862         if (mEffectsListener != null) {
863             mHandler.post(new Runnable() {
864                 public void run() {
865                     if (mFd != null) {
866                         mEffectsListener.onEffectsError(exception, null);
867                     } else {
868                         mEffectsListener.onEffectsError(exception, mOutputFile);
869                     }
870                 }
871             });
872         }
873     }
874 
875 }
876