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.MediaActionSound; 36 import android.media.MediaRecorder; 37 import android.media.CamcorderProfile; 38 import android.os.Handler; 39 import android.os.Looper; 40 import android.os.ParcelFileDescriptor; 41 import android.util.Log; 42 import android.view.Surface; 43 import android.view.SurfaceHolder; 44 45 import java.io.IOException; 46 import java.io.FileNotFoundException; 47 import java.io.File; 48 import java.lang.Runnable; 49 import java.io.FileDescriptor; 50 51 52 /** 53 * Encapsulates the mobile filter framework components needed to record video with 54 * effects applied. Modeled after MediaRecorder. 55 */ 56 public class EffectsRecorder { 57 58 public static final int EFFECT_NONE = 0; 59 public static final int EFFECT_GOOFY_FACE = 1; 60 public static final int EFFECT_BACKDROPPER = 2; 61 62 public static final int EFFECT_GF_SQUEEZE = 0; 63 public static final int EFFECT_GF_BIG_EYES = 1; 64 public static final int EFFECT_GF_BIG_MOUTH = 2; 65 public static final int EFFECT_GF_SMALL_MOUTH = 3; 66 public static final int EFFECT_GF_BIG_NOSE = 4; 67 public static final int EFFECT_GF_SMALL_EYES = 5; 68 public static final int NUM_OF_GF_EFFECTS = EFFECT_GF_SMALL_EYES + 1; 69 70 public static final int EFFECT_MSG_STARTED_LEARNING = 0; 71 public static final int EFFECT_MSG_DONE_LEARNING = 1; 72 public static final int EFFECT_MSG_SWITCHING_EFFECT = 2; 73 public static final int EFFECT_MSG_EFFECTS_STOPPED = 3; 74 public static final int EFFECT_MSG_RECORDING_DONE = 4; 75 public static final int EFFECT_MSG_PREVIEW_RUNNING = 5; 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 private boolean mAppIsLandscape; 97 98 private int mEffect = EFFECT_NONE; 99 private int mCurrentEffect = EFFECT_NONE; 100 private EffectsListener mEffectsListener; 101 102 private Object mEffectParameter; 103 104 private GraphEnvironment mGraphEnv; 105 private int mGraphId; 106 private GraphRunner mRunner = null; 107 private GraphRunner mOldRunner = null; 108 109 private SurfaceTexture mTextureSource; 110 111 private static final int STATE_CONFIGURE = 0; 112 private static final int STATE_WAITING_FOR_SURFACE = 1; 113 private static final int STATE_STARTING_PREVIEW = 2; 114 private static final int STATE_PREVIEW = 3; 115 private static final int STATE_RECORD = 4; 116 private static final int STATE_RELEASED = 5; 117 private int mState = STATE_CONFIGURE; 118 119 private boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); 120 private static final String TAG = "effectsrecorder"; 121 private MediaActionSound mCameraSound; 122 123 /** Determine if a given effect is supported at runtime 124 * Some effects require libraries not available on all devices 125 */ isEffectSupported(int effectId)126 public static boolean isEffectSupported(int effectId) { 127 switch (effectId) { 128 case EFFECT_GOOFY_FACE: 129 return Filter.isAvailable("com.google.android.filterpacks.facedetect.GoofyRenderFilter"); 130 case EFFECT_BACKDROPPER: 131 return Filter.isAvailable("android.filterpacks.videoproc.BackDropperFilter"); 132 default: 133 return false; 134 } 135 } 136 EffectsRecorder(Context context)137 public EffectsRecorder(Context context) { 138 if (mLogVerbose) Log.v(TAG, "EffectsRecorder created (" + this + ")"); 139 mContext = context; 140 mHandler = new Handler(Looper.getMainLooper()); 141 mCameraSound = new MediaActionSound(); 142 } 143 setCamera(Camera cameraDevice)144 public void setCamera(Camera cameraDevice) { 145 switch (mState) { 146 case STATE_PREVIEW: 147 throw new RuntimeException("setCamera cannot be called while previewing!"); 148 case STATE_RECORD: 149 throw new RuntimeException("setCamera cannot be called while recording!"); 150 case STATE_RELEASED: 151 throw new RuntimeException("setCamera called on an already released recorder!"); 152 default: 153 break; 154 } 155 156 mCameraDevice = cameraDevice; 157 } 158 setProfile(CamcorderProfile profile)159 public void setProfile(CamcorderProfile profile) { 160 switch (mState) { 161 case STATE_RECORD: 162 throw new RuntimeException("setProfile cannot be called while recording!"); 163 case STATE_RELEASED: 164 throw new RuntimeException("setProfile called on an already released recorder!"); 165 default: 166 break; 167 } 168 mProfile = profile; 169 } 170 setOutputFile(String outputFile)171 public void setOutputFile(String outputFile) { 172 switch (mState) { 173 case STATE_RECORD: 174 throw new RuntimeException("setOutputFile cannot be called while recording!"); 175 case STATE_RELEASED: 176 throw new RuntimeException("setOutputFile called on an already released recorder!"); 177 default: 178 break; 179 } 180 181 mOutputFile = outputFile; 182 mFd = null; 183 } 184 setOutputFile(FileDescriptor fd)185 public void setOutputFile(FileDescriptor fd) { 186 switch (mState) { 187 case STATE_RECORD: 188 throw new RuntimeException("setOutputFile cannot be called while recording!"); 189 case STATE_RELEASED: 190 throw new RuntimeException("setOutputFile called on an already released recorder!"); 191 default: 192 break; 193 } 194 195 mOutputFile = null; 196 mFd = fd; 197 } 198 199 /** 200 * Sets the maximum filesize (in bytes) of the recording session. 201 * This will be passed on to the MediaEncoderFilter and then to the 202 * MediaRecorder ultimately. If zero or negative, the MediaRecorder will 203 * disable the limit 204 */ setMaxFileSize(long maxFileSize)205 public synchronized void setMaxFileSize(long maxFileSize) { 206 switch (mState) { 207 case STATE_RECORD: 208 throw new RuntimeException("setMaxFileSize cannot be called while recording!"); 209 case STATE_RELEASED: 210 throw new RuntimeException("setMaxFileSize called on an already released recorder!"); 211 default: 212 break; 213 } 214 mMaxFileSize = maxFileSize; 215 } 216 217 /** 218 * Sets the maximum recording duration (in ms) for the next recording session 219 * Setting it to zero (the default) disables the limit. 220 */ setMaxDuration(int maxDurationMs)221 public synchronized void setMaxDuration(int maxDurationMs) { 222 switch (mState) { 223 case STATE_RECORD: 224 throw new RuntimeException("setMaxDuration cannot be called while recording!"); 225 case STATE_RELEASED: 226 throw new RuntimeException("setMaxDuration called on an already released recorder!"); 227 default: 228 break; 229 } 230 mMaxDurationMs = maxDurationMs; 231 } 232 233 setCaptureRate(double fps)234 public void setCaptureRate(double fps) { 235 switch (mState) { 236 case STATE_RECORD: 237 throw new RuntimeException("setCaptureRate cannot be called while recording!"); 238 case STATE_RELEASED: 239 throw new RuntimeException("setCaptureRate called on an already released recorder!"); 240 default: 241 break; 242 } 243 244 if (mLogVerbose) Log.v(TAG, "Setting time lapse capture rate to " + fps + " fps"); 245 mCaptureRate = fps; 246 } 247 setPreviewDisplay(SurfaceHolder previewSurfaceHolder, int previewWidth, int previewHeight)248 public void setPreviewDisplay(SurfaceHolder previewSurfaceHolder, 249 int previewWidth, 250 int previewHeight) { 251 if (mLogVerbose) Log.v(TAG, "setPreviewDisplay (" + this + ")"); 252 switch (mState) { 253 case STATE_RECORD: 254 throw new RuntimeException("setPreviewDisplay cannot be called while recording!"); 255 case STATE_RELEASED: 256 throw new RuntimeException("setPreviewDisplay called on an already released recorder!"); 257 default: 258 break; 259 } 260 261 mPreviewSurfaceHolder = previewSurfaceHolder; 262 mPreviewWidth = previewWidth; 263 mPreviewHeight = previewHeight; 264 265 switch (mState) { 266 case STATE_WAITING_FOR_SURFACE: 267 startPreview(); 268 break; 269 case STATE_STARTING_PREVIEW: 270 case STATE_PREVIEW: 271 initializeEffect(true); 272 break; 273 } 274 } 275 setEffect(int effect, Object effectParameter)276 public void setEffect(int effect, Object effectParameter) { 277 if (mLogVerbose) Log.v(TAG, 278 "setEffect: effect ID " + effect + 279 ", parameter " + effectParameter.toString() ); 280 switch (mState) { 281 case STATE_RECORD: 282 throw new RuntimeException("setEffect cannot be called while recording!"); 283 case STATE_RELEASED: 284 throw new RuntimeException("setEffect called on an already released recorder!"); 285 default: 286 break; 287 } 288 289 mEffect = effect; 290 mEffectParameter = effectParameter; 291 292 if (mState == STATE_PREVIEW || 293 mState == STATE_STARTING_PREVIEW) { 294 initializeEffect(false); 295 } 296 } 297 298 public interface EffectsListener { onEffectsUpdate(int effectId, int effectMsg)299 public void onEffectsUpdate(int effectId, int effectMsg); onEffectsError(Exception exception, String filePath)300 public void onEffectsError(Exception exception, String filePath); 301 } 302 setEffectsListener(EffectsListener listener)303 public void setEffectsListener(EffectsListener listener) { 304 mEffectsListener = listener; 305 } 306 setFaceDetectOrientation()307 private void setFaceDetectOrientation() { 308 if (mCurrentEffect == EFFECT_GOOFY_FACE) { 309 Filter rotateFilter = mRunner.getGraph().getFilter("rotate"); 310 Filter metaRotateFilter = mRunner.getGraph().getFilter("metarotate"); 311 rotateFilter.setInputValue("rotation", mOrientationHint); 312 int reverseDegrees = (360 - mOrientationHint) % 360; 313 metaRotateFilter.setInputValue("rotation", reverseDegrees); 314 } 315 } 316 setRecordingOrientation()317 private void setRecordingOrientation() { 318 if ( mState != STATE_RECORD && mRunner != null) { 319 Point bl = new Point(0, 0); 320 Point br = new Point(1, 0); 321 Point tl = new Point(0, 1); 322 Point tr = new Point(1, 1); 323 Quad recordingRegion; 324 if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK) { 325 // The back camera is not mirrored, so use a identity transform 326 recordingRegion = new Quad(bl, br, tl, tr); 327 } else { 328 // Recording region needs to be tweaked for front cameras, since they 329 // mirror their preview 330 if (mOrientationHint == 0 || mOrientationHint == 180) { 331 // Horizontal flip in landscape 332 recordingRegion = new Quad(br, bl, tr, tl); 333 } else { 334 // Horizontal flip in portrait 335 recordingRegion = new Quad(tl, tr, bl, br); 336 } 337 } 338 Filter recorder = mRunner.getGraph().getFilter("recorder"); 339 recorder.setInputValue("inputRegion", recordingRegion); 340 } 341 } setOrientationHint(int degrees)342 public void setOrientationHint(int degrees) { 343 switch (mState) { 344 case STATE_RELEASED: 345 throw new RuntimeException( 346 "setOrientationHint called on an already released recorder!"); 347 default: 348 break; 349 } 350 if (mLogVerbose) Log.v(TAG, "Setting orientation hint to: " + degrees); 351 mOrientationHint = degrees; 352 setFaceDetectOrientation(); 353 setRecordingOrientation(); 354 } 355 356 /** Passes the native orientation of the Camera app (device dependent) 357 * to allow for correct output aspect ratio. Defaults to portrait */ setAppToLandscape(boolean landscape)358 public void setAppToLandscape(boolean landscape) { 359 if (mState != STATE_CONFIGURE) { 360 throw new RuntimeException( 361 "setAppToLandscape called after configuration!"); 362 } 363 mAppIsLandscape = landscape; 364 } 365 setCameraFacing(int facing)366 public void setCameraFacing(int facing) { 367 switch (mState) { 368 case STATE_RELEASED: 369 throw new RuntimeException( 370 "setCameraFacing called on alrady released recorder!"); 371 default: 372 break; 373 } 374 mCameraFacing = facing; 375 setRecordingOrientation(); 376 } 377 setOnInfoListener(MediaRecorder.OnInfoListener infoListener)378 public void setOnInfoListener(MediaRecorder.OnInfoListener infoListener) { 379 switch (mState) { 380 case STATE_RECORD: 381 throw new RuntimeException("setInfoListener cannot be called while recording!"); 382 case STATE_RELEASED: 383 throw new RuntimeException("setInfoListener called on an already released recorder!"); 384 default: 385 break; 386 } 387 mInfoListener = infoListener; 388 } 389 setOnErrorListener(MediaRecorder.OnErrorListener errorListener)390 public void setOnErrorListener(MediaRecorder.OnErrorListener errorListener) { 391 switch (mState) { 392 case STATE_RECORD: 393 throw new RuntimeException("setErrorListener cannot be called while recording!"); 394 case STATE_RELEASED: 395 throw new RuntimeException("setErrorListener called on an already released recorder!"); 396 default: 397 break; 398 } 399 mErrorListener = errorListener; 400 } 401 initializeFilterFramework()402 private void initializeFilterFramework() { 403 mGraphEnv = new GraphEnvironment(); 404 mGraphEnv.createGLEnvironment(); 405 406 if (mLogVerbose) { 407 Log.v(TAG, "Effects framework initializing. Recording size " 408 + mProfile.videoFrameWidth + ", " + mProfile.videoFrameHeight); 409 } 410 if (!mAppIsLandscape) { 411 int tmp; 412 tmp = mProfile.videoFrameWidth; 413 mProfile.videoFrameWidth = mProfile.videoFrameHeight; 414 mProfile.videoFrameHeight = tmp; 415 } 416 mGraphEnv.addReferences( 417 "textureSourceCallback", mSourceReadyCallback, 418 "recordingWidth", mProfile.videoFrameWidth, 419 "recordingHeight", mProfile.videoFrameHeight, 420 "recordingProfile", mProfile, 421 "learningDoneListener", mLearningDoneListener, 422 "recordingDoneListener", mRecordingDoneListener); 423 mRunner = null; 424 mGraphId = -1; 425 mCurrentEffect = EFFECT_NONE; 426 } 427 initializeEffect(boolean forceReset)428 private synchronized void initializeEffect(boolean forceReset) { 429 if (forceReset || 430 mCurrentEffect != mEffect || 431 mCurrentEffect == EFFECT_BACKDROPPER) { 432 if (mLogVerbose) { 433 Log.v(TAG, "Effect initializing. Preview size " 434 + mPreviewWidth + ", " + mPreviewHeight); 435 } 436 437 mGraphEnv.addReferences( 438 "previewSurface", mPreviewSurfaceHolder.getSurface(), 439 "previewWidth", mPreviewWidth, 440 "previewHeight", mPreviewHeight, 441 "orientation", mOrientationHint); 442 if (mState == STATE_PREVIEW || 443 mState == STATE_STARTING_PREVIEW) { 444 // Switching effects while running. Inform video camera. 445 sendMessage(mCurrentEffect, EFFECT_MSG_SWITCHING_EFFECT); 446 } 447 448 switch (mEffect) { 449 case EFFECT_GOOFY_FACE: 450 mGraphId = mGraphEnv.loadGraph(mContext, R.raw.goofy_face); 451 break; 452 case EFFECT_BACKDROPPER: 453 sendMessage(EFFECT_BACKDROPPER, EFFECT_MSG_STARTED_LEARNING); 454 mGraphId = mGraphEnv.loadGraph(mContext, R.raw.backdropper); 455 break; 456 default: 457 throw new RuntimeException("Unknown effect ID" + mEffect + "!"); 458 } 459 mCurrentEffect = mEffect; 460 461 mOldRunner = mRunner; 462 mRunner = mGraphEnv.getRunner(mGraphId, GraphEnvironment.MODE_ASYNCHRONOUS); 463 mRunner.setDoneCallback(mRunnerDoneCallback); 464 if (mLogVerbose) { 465 Log.v(TAG, "New runner: " + mRunner 466 + ". Old runner: " + mOldRunner); 467 } 468 if (mState == STATE_PREVIEW || 469 mState == STATE_STARTING_PREVIEW) { 470 // Switching effects while running. Stop existing runner. 471 // The stop callback will take care of starting new runner. 472 mCameraDevice.stopPreview(); 473 try { 474 mCameraDevice.setPreviewTexture(null); 475 } catch(IOException e) { 476 throw new RuntimeException("Unable to connect camera to effect input", e); 477 } 478 mOldRunner.stop(); 479 } 480 } 481 482 switch (mCurrentEffect) { 483 case EFFECT_GOOFY_FACE: 484 tryEnableVideoStabilization(true); 485 Filter goofyFilter = mRunner.getGraph().getFilter("goofyrenderer"); 486 goofyFilter.setInputValue("currentEffect", 487 ((Integer)mEffectParameter).intValue()); 488 break; 489 case EFFECT_BACKDROPPER: 490 tryEnableVideoStabilization(false); 491 Filter backgroundSrc = mRunner.getGraph().getFilter("background"); 492 backgroundSrc.setInputValue("sourceUrl", 493 (String)mEffectParameter); 494 // For front camera, the background video needs to be mirrored in the 495 // backdropper filter 496 if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 497 Filter replacer = mRunner.getGraph().getFilter("replacer"); 498 replacer.setInputValue("mirrorBg", true); 499 if (mLogVerbose) Log.v(TAG, "Setting the background to be mirrored"); 500 } 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 // Sending a message to listener that preview is complete 616 sendMessage(mCurrentEffect, EFFECT_MSG_PREVIEW_RUNNING); 617 } 618 } 619 }; 620 621 private LearningDoneListener mLearningDoneListener = 622 new LearningDoneListener() { 623 public void onLearningDone(BackDropperFilter filter) { 624 if (mLogVerbose) Log.v(TAG, "Learning done callback triggered"); 625 // Called in a processing thread, so have to post message back to UI 626 // thread 627 sendMessage(EFFECT_BACKDROPPER, EFFECT_MSG_DONE_LEARNING); 628 enable3ALocks(true); 629 } 630 }; 631 632 // A callback to finalize the media after the recording is done. 633 private OnRecordingDoneListener mRecordingDoneListener = 634 new OnRecordingDoneListener() { 635 // Forward the callback to the VideoCamera object (as an asynchronous event). 636 public void onRecordingDone() { 637 if (mLogVerbose) Log.v(TAG, "Recording done callback triggered"); 638 sendMessage(EFFECT_NONE, EFFECT_MSG_RECORDING_DONE); 639 } 640 }; 641 startRecording()642 public synchronized void startRecording() { 643 if (mLogVerbose) Log.v(TAG, "Starting recording (" + this + ")"); 644 645 switch (mState) { 646 case STATE_RECORD: 647 throw new RuntimeException("Already recording, cannot begin anew!"); 648 case STATE_RELEASED: 649 throw new RuntimeException("startRecording called on an already released recorder!"); 650 default: 651 break; 652 } 653 654 if ((mOutputFile == null) && (mFd == null)) { 655 throw new RuntimeException("No output file name or descriptor provided!"); 656 } 657 658 if (mState == STATE_CONFIGURE) { 659 startPreview(); 660 } 661 662 Filter recorder = mRunner.getGraph().getFilter("recorder"); 663 if (mFd != null) { 664 recorder.setInputValue("outputFileDescriptor", mFd); 665 } else { 666 recorder.setInputValue("outputFile", mOutputFile); 667 } 668 // It is ok to set the audiosource without checking for timelapse here 669 // since that check will be done in the MediaEncoderFilter itself 670 recorder.setInputValue("audioSource", MediaRecorder.AudioSource.CAMCORDER); 671 672 recorder.setInputValue("recordingProfile", mProfile); 673 recorder.setInputValue("orientationHint", mOrientationHint); 674 // Important to set the timelapseinterval to 0 if the capture rate is not >0 675 // since the recorder does not get created every time the recording starts. 676 // The recorder infers whether the capture is timelapsed based on the value of 677 // this interval 678 boolean captureTimeLapse = mCaptureRate > 0; 679 if (captureTimeLapse) { 680 double timeBetweenFrameCapture = 1 / mCaptureRate; 681 recorder.setInputValue("timelapseRecordingIntervalUs", 682 (long) (1000000 * timeBetweenFrameCapture)); 683 } else { 684 recorder.setInputValue("timelapseRecordingIntervalUs", 0L); 685 } 686 687 if (mInfoListener != null) { 688 recorder.setInputValue("infoListener", mInfoListener); 689 } 690 if (mErrorListener != null) { 691 recorder.setInputValue("errorListener", mErrorListener); 692 } 693 recorder.setInputValue("maxFileSize", mMaxFileSize); 694 recorder.setInputValue("maxDurationMs", mMaxDurationMs); 695 recorder.setInputValue("recording", true); 696 mCameraSound.play(MediaActionSound.START_VIDEO_RECORDING); 697 mState = STATE_RECORD; 698 } 699 stopRecording()700 public synchronized void stopRecording() { 701 if (mLogVerbose) Log.v(TAG, "Stop recording (" + this + ")"); 702 703 switch (mState) { 704 case STATE_CONFIGURE: 705 case STATE_STARTING_PREVIEW: 706 case STATE_PREVIEW: 707 Log.w(TAG, "StopRecording called when recording not active!"); 708 return; 709 case STATE_RELEASED: 710 throw new RuntimeException("stopRecording called on released EffectsRecorder!"); 711 default: 712 break; 713 } 714 Filter recorder = mRunner.getGraph().getFilter("recorder"); 715 recorder.setInputValue("recording", false); 716 mCameraSound.play(MediaActionSound.STOP_VIDEO_RECORDING); 717 mState = STATE_PREVIEW; 718 } 719 720 // Stop and release effect resources stopPreview()721 public synchronized void stopPreview() { 722 if (mLogVerbose) Log.v(TAG, "Stopping preview (" + this + ")"); 723 724 switch (mState) { 725 case STATE_CONFIGURE: 726 Log.w(TAG, "StopPreview called when preview not active!"); 727 return; 728 case STATE_RELEASED: 729 throw new RuntimeException("stopPreview called on released EffectsRecorder!"); 730 default: 731 break; 732 } 733 734 if (mState == STATE_RECORD) { 735 stopRecording(); 736 } 737 738 mCurrentEffect = EFFECT_NONE; 739 740 mCameraDevice.stopPreview(); 741 try { 742 mCameraDevice.setPreviewTexture(null); 743 } catch(IOException e) { 744 throw new RuntimeException("Unable to connect camera to effect input", e); 745 } 746 mCameraSound.release(); 747 748 mState = STATE_CONFIGURE; 749 mOldRunner = mRunner; 750 mRunner.stop(); 751 mRunner = null; 752 // Rest of stop and release handled in mRunnerDoneCallback 753 } 754 755 // Try to enable/disable video stabilization if supported; otherwise return false tryEnableVideoStabilization(boolean toggle)756 boolean tryEnableVideoStabilization(boolean toggle) { 757 Camera.Parameters params = mCameraDevice.getParameters(); 758 759 String vstabSupported = params.get("video-stabilization-supported"); 760 if ("true".equals(vstabSupported)) { 761 if (mLogVerbose) Log.v(TAG, "Setting video stabilization to " + toggle); 762 params.set("video-stabilization", toggle ? "true" : "false"); 763 mCameraDevice.setParameters(params); 764 return true; 765 } 766 if (mLogVerbose) Log.v(TAG, "Video stabilization not supported"); 767 return false; 768 } 769 770 // Try to enable/disable 3A locks if supported; otherwise return false tryEnable3ALocks(boolean toggle)771 boolean tryEnable3ALocks(boolean toggle) { 772 Camera.Parameters params = mCameraDevice.getParameters(); 773 if (params.isAutoExposureLockSupported() && 774 params.isAutoWhiteBalanceLockSupported() ) { 775 params.setAutoExposureLock(toggle); 776 params.setAutoWhiteBalanceLock(toggle); 777 mCameraDevice.setParameters(params); 778 return true; 779 } 780 return false; 781 } 782 783 // Try to enable/disable 3A locks if supported; otherwise, throw error 784 // Use this when locks are essential to success enable3ALocks(boolean toggle)785 void enable3ALocks(boolean toggle) { 786 Camera.Parameters params = mCameraDevice.getParameters(); 787 if (!tryEnable3ALocks(toggle)) { 788 throw new RuntimeException("Attempt to lock 3A on camera with no locking support!"); 789 } 790 } 791 792 private OnRunnerDoneListener mRunnerDoneCallback = 793 new OnRunnerDoneListener() { 794 public void onRunnerDone(int result) { 795 synchronized(EffectsRecorder.this) { 796 if (mLogVerbose) { 797 Log.v(TAG, 798 "Graph runner done (" + EffectsRecorder.this 799 + ", mRunner " + mRunner 800 + ", mOldRunner " + mOldRunner + ")"); 801 } 802 if (result == GraphRunner.RESULT_ERROR) { 803 // Handle error case 804 Log.e(TAG, "Error running filter graph!"); 805 Exception e = null; 806 if (mRunner != null) { 807 e = mRunner.getError(); 808 } else if (mOldRunner != null) { 809 e = mOldRunner.getError(); 810 } 811 raiseError(e); 812 } 813 if (mOldRunner != null) { 814 // Tear down old graph if available 815 if (mLogVerbose) Log.v(TAG, "Tearing down old graph."); 816 GLEnvironment glEnv = mGraphEnv.getContext().getGLEnvironment(); 817 if (glEnv != null && !glEnv.isActive()) { 818 glEnv.activate(); 819 } 820 mOldRunner.getGraph().tearDown(mGraphEnv.getContext()); 821 if (glEnv != null && glEnv.isActive()) { 822 glEnv.deactivate(); 823 } 824 mOldRunner = null; 825 } 826 if (mState == STATE_PREVIEW || 827 mState == STATE_STARTING_PREVIEW) { 828 // Switching effects, start up the new runner 829 if (mLogVerbose) Log.v(TAG, "Previous effect halted, starting new effect."); 830 tryEnable3ALocks(false); 831 mRunner.run(); 832 } else if (mState != STATE_RELEASED) { 833 // Shutting down effects 834 if (mLogVerbose) Log.v(TAG, "Runner halted, restoring direct preview"); 835 tryEnable3ALocks(false); 836 sendMessage(EFFECT_NONE, EFFECT_MSG_EFFECTS_STOPPED); 837 } else { 838 // STATE_RELEASED - camera will be/has been released as well, do nothing. 839 } 840 } 841 } 842 }; 843 844 // Indicates that all camera/recording activity needs to halt release()845 public synchronized void release() { 846 if (mLogVerbose) Log.v(TAG, "Releasing (" + this + ")"); 847 848 switch (mState) { 849 case STATE_RECORD: 850 case STATE_STARTING_PREVIEW: 851 case STATE_PREVIEW: 852 stopPreview(); 853 // Fall-through 854 default: 855 mState = STATE_RELEASED; 856 break; 857 } 858 } 859 sendMessage(final int effect, final int msg)860 private void sendMessage(final int effect, final int msg) { 861 if (mEffectsListener != null) { 862 mHandler.post(new Runnable() { 863 public void run() { 864 mEffectsListener.onEffectsUpdate(effect, msg); 865 } 866 }); 867 } 868 } 869 raiseError(final Exception exception)870 private void raiseError(final Exception exception) { 871 if (mEffectsListener != null) { 872 mHandler.post(new Runnable() { 873 public void run() { 874 if (mFd != null) { 875 mEffectsListener.onEffectsError(exception, null); 876 } else { 877 mEffectsListener.onEffectsError(exception, mOutputFile); 878 } 879 } 880 }); 881 } 882 } 883 884 } 885