EffectsRecorder.java revision 07d5f8c27d144f7e72820b48fe5cb08f691435ad
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 17package com.android.camera; 18 19import android.content.Context; 20import android.filterfw.GraphEnvironment; 21import android.filterfw.core.Filter; 22import android.filterfw.core.GLEnvironment; 23import android.filterfw.core.GraphRunner; 24import android.filterfw.core.GraphRunner.OnRunnerDoneListener; 25import android.filterfw.geometry.Point; 26import android.filterfw.geometry.Quad; 27import android.filterpacks.videosrc.SurfaceTextureSource.SurfaceTextureSourceListener; 28import android.filterpacks.videoproc.BackDropperFilter; 29import android.filterpacks.videoproc.BackDropperFilter.LearningDoneListener; 30 31import android.graphics.SurfaceTexture; 32import android.hardware.Camera; 33import android.media.MediaRecorder; 34import android.media.CamcorderProfile; 35import android.os.ConditionVariable; 36import android.os.Handler; 37import android.os.Looper; 38import android.os.ParcelFileDescriptor; 39import android.util.Log; 40import android.view.Surface; 41import android.view.SurfaceHolder; 42 43import java.io.IOException; 44import java.lang.Runnable; 45 46/** 47 * Encapsulates the mobile filter framework components needed to record video with 48 * effects applied. Modeled after MediaRecorder. 49 */ 50public class EffectsRecorder { 51 52 public static final int EFFECT_NONE = 0; 53 public static final int EFFECT_GOOFY_FACE = 1; 54 public static final int EFFECT_BACKDROPPER = 2; 55 56 public static final int EFFECT_GF_SQUEEZE = 0; 57 public static final int EFFECT_GF_BIG_EYES = 1; 58 public static final int EFFECT_GF_BIG_MOUTH = 2; 59 public static final int EFFECT_GF_SMALL_MOUTH = 3; 60 public static final int EFFECT_GF_BIG_NOSE = 4; 61 public static final int EFFECT_GF_SMALL_EYES = 5; 62 public static final int NUM_OF_GF_EFFECTS = EFFECT_GF_SMALL_EYES + 1; 63 64 public static final int EFFECT_MSG_STARTED_LEARNING = 0; 65 public static final int EFFECT_MSG_DONE_LEARNING = 1; 66 public static final int EFFECT_MSG_SWITCHING_EFFECT = 2; 67 public static final int EFFECT_MSG_EFFECTS_STOPPED = 3; 68 69 private Context mContext; 70 private Handler mHandler; 71 private boolean mReleased; 72 73 private Camera mCameraDevice; 74 private CamcorderProfile mProfile; 75 private SurfaceHolder mPreviewSurfaceHolder; 76 private int mPreviewWidth; 77 private int mPreviewHeight; 78 private MediaRecorder.OnInfoListener mInfoListener; 79 private MediaRecorder.OnErrorListener mErrorListener; 80 81 private String mOutputFile; 82 private int mOrientationHint = 0; 83 private int mCameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK; 84 85 private int mEffect = EFFECT_NONE; 86 private int mCurrentEffect = EFFECT_NONE; 87 private EffectsListener mEffectsListener; 88 89 private Object mEffectParameter; 90 91 private GraphEnvironment mGraphEnv; 92 private int mGraphId; 93 private GraphRunner mRunner = null; 94 private GraphRunner mOldRunner = null; 95 96 private SurfaceTexture mTextureSource; 97 98 private static final int STATE_CONFIGURE = 0; 99 private static final int STATE_WAITING_FOR_SURFACE = 1; 100 private static final int STATE_PREVIEW = 2; 101 private static final int STATE_RECORD = 3; 102 private static final int STATE_RELEASED = 4; 103 private int mState = STATE_CONFIGURE; 104 105 private boolean mLogVerbose = true; //Log.isLoggable(TAG, Log.VERBOSE); 106 private static final String TAG = "effectsrecorder"; 107 108 /** Determine if a given effect is supported at runtime 109 * Some effects require libraries not available on all devices 110 */ 111 public static boolean isEffectSupported(int effectId) { 112 switch (effectId) { 113 case EFFECT_GOOFY_FACE: 114 return Filter.isAvailable("com.google.android.filterpacks.facedetect.GoofyRenderFilter"); 115 case EFFECT_BACKDROPPER: 116 return Filter.isAvailable("android.filterpacks.videoproc.BackDropperFilter"); 117 default: 118 return false; 119 } 120 } 121 122 public EffectsRecorder(Context context) { 123 if (mLogVerbose) Log.v(TAG, "EffectsRecorder created (" + this + ")"); 124 mContext = context; 125 mHandler = new Handler(Looper.getMainLooper()); 126 } 127 128 public void setCamera(Camera cameraDevice) { 129 switch (mState) { 130 case STATE_PREVIEW: 131 throw new RuntimeException("setCamera cannot be called while previewing!"); 132 case STATE_RECORD: 133 throw new RuntimeException("setCamera cannot be called while recording!"); 134 case STATE_RELEASED: 135 throw new RuntimeException("setCamera called on an already released recorder!"); 136 default: 137 break; 138 } 139 140 mCameraDevice = cameraDevice; 141 } 142 143 public void setProfile(CamcorderProfile profile) { 144 switch (mState) { 145 case STATE_RECORD: 146 throw new RuntimeException("setProfile cannot be called while recording!"); 147 case STATE_RELEASED: 148 throw new RuntimeException("setProfile called on an already released recorder!"); 149 default: 150 break; 151 } 152 mProfile = profile; 153 } 154 155 public void setOutputFile(String outputFile) { 156 switch (mState) { 157 case STATE_RECORD: 158 throw new RuntimeException("setOutputFile cannot be called while recording!"); 159 case STATE_RELEASED: 160 throw new RuntimeException("setOutputFile called on an already released recorder!"); 161 default: 162 break; 163 } 164 165 mOutputFile = outputFile; 166 } 167 168 public void setPreviewDisplay(SurfaceHolder previewSurfaceHolder, 169 int previewWidth, 170 int previewHeight) { 171 if (mLogVerbose) Log.v(TAG, "setPreviewDisplay (" + this + ")"); 172 switch (mState) { 173 case STATE_RECORD: 174 throw new RuntimeException("setPreviewDisplay cannot be called while recording!"); 175 case STATE_RELEASED: 176 throw new RuntimeException("setPreviewDisplay called on an already released recorder!"); 177 default: 178 break; 179 } 180 181 mPreviewSurfaceHolder = previewSurfaceHolder; 182 mPreviewWidth = previewWidth; 183 mPreviewHeight = previewHeight; 184 185 switch (mState) { 186 case STATE_WAITING_FOR_SURFACE: 187 startPreview(); 188 break; 189 case STATE_PREVIEW: 190 initializeEffect(true); 191 break; 192 } 193 } 194 195 public void setEffect(int effect, Object effectParameter) { 196 if (mLogVerbose) Log.v(TAG, 197 "setEffect: effect ID " + effect + 198 ", parameter " + effectParameter.toString() ); 199 switch (mState) { 200 case STATE_RECORD: 201 throw new RuntimeException("setEffect cannot be called while recording!"); 202 case STATE_RELEASED: 203 throw new RuntimeException("setEffect called on an already released recorder!"); 204 default: 205 break; 206 } 207 208 mEffect = effect; 209 mEffectParameter = effectParameter; 210 211 if (mState == STATE_PREVIEW) { 212 initializeEffect(false); 213 } 214 } 215 216 public interface EffectsListener { 217 public void onEffectsUpdate(int effectId, int effectMsg); 218 public void onEffectsError(Exception exception, String filePath); 219 } 220 221 public void setEffectsListener(EffectsListener listener) { 222 mEffectsListener = listener; 223 } 224 225 private void setFaceDetectOrientation(int degrees) { 226 if (mCurrentEffect == EFFECT_GOOFY_FACE) { 227 Filter rotateFilter = mRunner.getGraph().getFilter("rotate"); 228 Filter metaRotateFilter = mRunner.getGraph().getFilter("metarotate"); 229 rotateFilter.setInputValue("rotation", degrees); 230 int reverseDegrees = (360 - degrees) % 360; 231 metaRotateFilter.setInputValue("rotation", reverseDegrees); 232 } 233 } 234 235 private void setRecordingOrientation() { 236 if ( mState <= STATE_PREVIEW && mRunner != null ) { 237 Point bl = new Point(0, 0); 238 Point br = new Point(1, 0); 239 Point tl = new Point(0, 1); 240 Point tr = new Point(1, 1); 241 Quad recordingRegion; 242 if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK) { 243 // The back camera is not mirrored, so use a identity transform 244 recordingRegion = new Quad(bl, br, tl, tr); 245 } else { 246 // Recording region needs to be tweaked for front cameras, since they 247 // mirror their preview 248 if (mOrientationHint == 0 || mOrientationHint == 180) { 249 // Horizontal flip in landscape 250 recordingRegion = new Quad(br, bl, tr, tl); 251 } else { 252 // Horizontal flip in portrait 253 recordingRegion = new Quad(tl, tr, bl, br); 254 } 255 } 256 Filter recorder = mRunner.getGraph().getFilter("recorder"); 257 recorder.setInputValue("inputRegion", recordingRegion); 258 } 259 } 260 public void setOrientationHint(int degrees) { 261 switch (mState) { 262 case STATE_RELEASED: 263 throw new RuntimeException( 264 "setOrientationHint called on an already released recorder!"); 265 default: 266 break; 267 } 268 if (mLogVerbose) Log.v(TAG, "Setting orientation hint to: " + degrees); 269 270 mOrientationHint = degrees; 271 setFaceDetectOrientation(degrees); 272 setRecordingOrientation(); 273 } 274 275 public void setCameraFacing(int facing) { 276 switch (mState) { 277 case STATE_RELEASED: 278 throw new RuntimeException( 279 "setCameraFacing called on alrady released recorder!"); 280 default: 281 break; 282 } 283 mCameraFacing = facing; 284 setRecordingOrientation(); 285 } 286 287 public void setOnInfoListener(MediaRecorder.OnInfoListener infoListener) { 288 switch (mState) { 289 case STATE_RECORD: 290 throw new RuntimeException("setInfoListener cannot be called while recording!"); 291 case STATE_RELEASED: 292 throw new RuntimeException("setInfoListener called on an already released recorder!"); 293 default: 294 break; 295 } 296 mInfoListener = infoListener; 297 } 298 299 public void setOnErrorListener(MediaRecorder.OnErrorListener errorListener) { 300 switch (mState) { 301 case STATE_RECORD: 302 throw new RuntimeException("setErrorListener cannot be called while recording!"); 303 case STATE_RELEASED: 304 throw new RuntimeException("setErrorListener called on an already released recorder!"); 305 default: 306 break; 307 } 308 mErrorListener = errorListener; 309 } 310 311 private void initializeFilterFramework() { 312 mGraphEnv = new GraphEnvironment(); 313 mGraphEnv.createGLEnvironment(); 314 315 if (mLogVerbose) { 316 Log.v(TAG, "Effects framework initializing. Recording size " 317 + mProfile.videoFrameWidth + ", " + mProfile.videoFrameHeight); 318 } 319 320 mGraphEnv.addReferences( 321 "textureSourceCallback", mSourceReadyCallback, 322 "recordingWidth", mProfile.videoFrameWidth, 323 "recordingHeight", mProfile.videoFrameHeight, 324 "recordingProfile", mProfile, 325 "audioSource", MediaRecorder.AudioSource.CAMCORDER, 326 "learningDoneListener", mLearningDoneListener); 327 328 mRunner = null; 329 mGraphId = -1; 330 mCurrentEffect = EFFECT_NONE; 331 } 332 333 private synchronized void initializeEffect(boolean forceReset) { 334 if (forceReset || 335 mCurrentEffect != mEffect || 336 mCurrentEffect == EFFECT_BACKDROPPER) { 337 if (mLogVerbose) { 338 Log.v(TAG, "Effect initializing. Preview size " 339 + mPreviewWidth + ", " + mPreviewHeight); 340 } 341 342 mGraphEnv.addReferences( 343 "previewSurface", mPreviewSurfaceHolder.getSurface(), 344 "previewWidth", mPreviewWidth, 345 "previewHeight", mPreviewHeight, 346 "orientation", mOrientationHint); 347 if (mState == STATE_PREVIEW) { 348 // Switching effects while running. Inform video camera. 349 sendMessage(mCurrentEffect, EFFECT_MSG_SWITCHING_EFFECT); 350 } 351 352 switch (mEffect) { 353 case EFFECT_GOOFY_FACE: 354 mGraphId = mGraphEnv.loadGraph(mContext, R.raw.goofy_face); 355 break; 356 case EFFECT_BACKDROPPER: 357 sendMessage(EFFECT_BACKDROPPER, EFFECT_MSG_STARTED_LEARNING); 358 mGraphId = mGraphEnv.loadGraph(mContext, R.raw.backdropper); 359 break; 360 default: 361 throw new RuntimeException("Unknown effect ID" + mEffect + "!"); 362 } 363 mCurrentEffect = mEffect; 364 365 mOldRunner = mRunner; 366 mRunner = mGraphEnv.getRunner(mGraphId, GraphEnvironment.MODE_ASYNCHRONOUS); 367 mRunner.setDoneCallback(mRunnerDoneCallback); 368 if (mLogVerbose) { 369 Log.v(TAG, "New runner: " + mRunner 370 + ". Old runner: " + mOldRunner); 371 } 372 if (mState == STATE_PREVIEW) { 373 // Switching effects while running. Stop existing runner. 374 // The stop callback will take care of starting new runner. 375 mCameraDevice.stopPreview(); 376 try { 377 mCameraDevice.setPreviewTexture(null); 378 } catch(IOException e) { 379 throw new RuntimeException("Unable to connect camera to effect input", e); 380 } 381 mOldRunner.stop(); 382 } 383 } 384 385 switch (mCurrentEffect) { 386 case EFFECT_GOOFY_FACE: 387 tryEnableVideoStabilization(true); 388 Filter goofyFilter = mRunner.getGraph().getFilter("goofyrenderer"); 389 goofyFilter.setInputValue("currentEffect", 390 ((Integer)mEffectParameter).intValue()); 391 break; 392 case EFFECT_BACKDROPPER: 393 tryEnableVideoStabilization(false); 394 Filter backgroundSrc = mRunner.getGraph().getFilter("background"); 395 backgroundSrc.setInputValue("sourceUrl", 396 (String)mEffectParameter); 397 break; 398 default: 399 break; 400 } 401 setFaceDetectOrientation(mOrientationHint); 402 setRecordingOrientation(); 403 } 404 405 public synchronized void startPreview() { 406 if (mLogVerbose) Log.v(TAG, "Starting preview (" + this + ")"); 407 408 switch (mState) { 409 case STATE_PREVIEW: 410 // Already running preview 411 Log.w(TAG, "startPreview called when already running preview"); 412 return; 413 case STATE_RECORD: 414 throw new RuntimeException("Cannot start preview when already recording!"); 415 case STATE_RELEASED: 416 throw new RuntimeException("setEffect called on an already released recorder!"); 417 default: 418 break; 419 } 420 421 if (mEffect == EFFECT_NONE) { 422 throw new RuntimeException("No effect selected!"); 423 } 424 if (mEffectParameter == null) { 425 throw new RuntimeException("No effect parameter provided!"); 426 } 427 if (mProfile == null) { 428 throw new RuntimeException("No recording profile provided!"); 429 } 430 if (mPreviewSurfaceHolder == null) { 431 if (mLogVerbose) Log.v(TAG, "Passed a null surface holder; waiting for valid one"); 432 mState = STATE_WAITING_FOR_SURFACE; 433 return; 434 } 435 if (mCameraDevice == null) { 436 throw new RuntimeException("No camera to record from!"); 437 } 438 439 if (mLogVerbose) Log.v(TAG, "Initializing filter graph"); 440 441 initializeFilterFramework(); 442 443 initializeEffect(true); 444 445 if (mLogVerbose) Log.v(TAG, "Starting filter graph"); 446 447 mRunner.run(); 448 // Rest of preview startup handled in mSourceReadyCallback 449 } 450 451 private SurfaceTextureSourceListener mSourceReadyCallback = 452 new SurfaceTextureSourceListener() { 453 public void onSurfaceTextureSourceReady(SurfaceTexture source) { 454 if (mLogVerbose) Log.v(TAG, "SurfaceTexture ready callback received"); 455 synchronized(EffectsRecorder.this) { 456 mTextureSource = source; 457 458 // When shutting down a graph, we receive a null SurfaceTexture to 459 // indicate that. Don't want to connect up the camera in that case. 460 if (source == null) return; 461 462 if (mState == STATE_RELEASED) return; 463 464 // Lock AE/AWB to reduce transition flicker 465 tryEnable3ALocks(true); 466 467 mCameraDevice.stopPreview(); 468 if (mLogVerbose) Log.v(TAG, "Runner active, connecting effects preview"); 469 try { 470 mCameraDevice.setPreviewTexture(mTextureSource); 471 } catch(IOException e) { 472 throw new RuntimeException("Unable to connect camera to effect input", e); 473 } 474 475 mCameraDevice.startPreview(); 476 477 // Unlock AE/AWB after preview started 478 tryEnable3ALocks(false); 479 480 mState = STATE_PREVIEW; 481 482 if (mLogVerbose) Log.v(TAG, "Start preview/effect switch complete"); 483 } 484 } 485 }; 486 487 private LearningDoneListener mLearningDoneListener = 488 new LearningDoneListener() { 489 public void onLearningDone(BackDropperFilter filter) { 490 if (mLogVerbose) Log.v(TAG, "Learning done callback triggered"); 491 // Called in a processing thread, so have to post message back to UI 492 // thread 493 sendMessage(EFFECT_BACKDROPPER, EFFECT_MSG_DONE_LEARNING); 494 enable3ALocks(true); 495 } 496 }; 497 498 public synchronized void startRecording() { 499 if (mLogVerbose) Log.v(TAG, "Starting recording (" + this + ")"); 500 501 switch (mState) { 502 case STATE_RECORD: 503 throw new RuntimeException("Already recording, cannot begin anew!"); 504 case STATE_RELEASED: 505 throw new RuntimeException("startRecording called on an already released recorder!"); 506 default: 507 break; 508 } 509 510 if (mOutputFile == null) { 511 throw new RuntimeException("No output file name provided!"); 512 } 513 514 if (mState == STATE_CONFIGURE) { 515 startPreview(); 516 } 517 Filter recorder = mRunner.getGraph().getFilter("recorder"); 518 recorder.setInputValue("outputFile", mOutputFile); 519 recorder.setInputValue("orientationHint", mOrientationHint); 520 if (mInfoListener != null) { 521 recorder.setInputValue("infoListener", mInfoListener); 522 } 523 if (mErrorListener != null) { 524 recorder.setInputValue("errorListener", mErrorListener); 525 } 526 recorder.setInputValue("recording", true); 527 mState = STATE_RECORD; 528 } 529 530 public synchronized void stopRecording() { 531 if (mLogVerbose) Log.v(TAG, "Stop recording (" + this + ")"); 532 533 switch (mState) { 534 case STATE_CONFIGURE: 535 case STATE_PREVIEW: 536 Log.w(TAG, "StopRecording called when recording not active!"); 537 return; 538 case STATE_RELEASED: 539 throw new RuntimeException("stopRecording called on released EffectsRecorder!"); 540 default: 541 break; 542 } 543 Filter recorder = mRunner.getGraph().getFilter("recorder"); 544 recorder.setInputValue("recording", false); 545 mState = STATE_PREVIEW; 546 } 547 548 // Stop and release effect resources 549 public synchronized void stopPreview() { 550 if (mLogVerbose) Log.v(TAG, "Stopping preview (" + this + ")"); 551 552 switch (mState) { 553 case STATE_CONFIGURE: 554 Log.w(TAG, "StopPreview called when preview not active!"); 555 return; 556 case STATE_RELEASED: 557 throw new RuntimeException("stopPreview called on released EffectsRecorder!"); 558 default: 559 break; 560 } 561 562 if (mState == STATE_RECORD) { 563 stopRecording(); 564 } 565 566 mCurrentEffect = EFFECT_NONE; 567 568 mCameraDevice.stopPreview(); 569 try { 570 mCameraDevice.setPreviewTexture(null); 571 } catch(IOException e) { 572 throw new RuntimeException("Unable to connect camera to effect input", e); 573 } 574 575 mState = STATE_CONFIGURE; 576 mOldRunner = mRunner; 577 mRunner.stop(); 578 mRunner = null; 579 // Rest of stop and release handled in mRunnerDoneCallback 580 } 581 582 // Try to enable/disable video stabilization if supported; otherwise return false 583 boolean tryEnableVideoStabilization(boolean toggle) { 584 Camera.Parameters params = mCameraDevice.getParameters(); 585 586 String vstabSupported = params.get("video-stabilization-supported"); 587 if ("true".equals(vstabSupported)) { 588 if (mLogVerbose) Log.v(TAG, "Setting video stabilization to " + toggle); 589 params.set("video-stabilization", toggle ? "true" : "false"); 590 mCameraDevice.setParameters(params); 591 return true; 592 } 593 if (mLogVerbose) Log.v(TAG, "Video stabilization not supported"); 594 return false; 595 } 596 597 // Try to enable/disable 3A locks if supported; otherwise return false 598 boolean tryEnable3ALocks(boolean toggle) { 599 Camera.Parameters params = mCameraDevice.getParameters(); 600 if (params.isAutoExposureLockSupported() && 601 params.isAutoWhiteBalanceLockSupported() ) { 602 params.setAutoExposureLock(toggle); 603 params.setAutoWhiteBalanceLock(toggle); 604 mCameraDevice.setParameters(params); 605 return true; 606 } 607 return false; 608 } 609 610 // Try to enable/disable 3A locks if supported; otherwise, throw error 611 // Use this when locks are essential to success 612 void enable3ALocks(boolean toggle) { 613 Camera.Parameters params = mCameraDevice.getParameters(); 614 if (!tryEnable3ALocks(toggle)) { 615 throw new RuntimeException("Attempt to lock 3A on camera with no locking support!"); 616 } 617 } 618 619 private OnRunnerDoneListener mRunnerDoneCallback = 620 new OnRunnerDoneListener() { 621 public void onRunnerDone(int result) { 622 synchronized(EffectsRecorder.this) { 623 if (mLogVerbose) { 624 Log.v(TAG, 625 "Graph runner done (" + EffectsRecorder.this 626 + ", mRunner " + mRunner 627 + ", mOldRunner " + mOldRunner + ")"); 628 } 629 if (result == GraphRunner.RESULT_ERROR) { 630 // Handle error case 631 Log.e(TAG, "Error running filter graph!"); 632 raiseError(mRunner == null ? null : mRunner.getError()); 633 } 634 if (mOldRunner != null) { 635 // Tear down old graph if available 636 if (mLogVerbose) Log.v(TAG, "Tearing down old graph."); 637 GLEnvironment glEnv = mGraphEnv.getContext().getGLEnvironment(); 638 if (glEnv != null && !glEnv.isActive()) { 639 glEnv.activate(); 640 } 641 mOldRunner.getGraph().tearDown(mGraphEnv.getContext()); 642 if (glEnv != null && glEnv.isActive()) { 643 glEnv.deactivate(); 644 } 645 mOldRunner = null; 646 } 647 if (mState == STATE_PREVIEW) { 648 // Switching effects, start up the new runner 649 if (mLogVerbose) Log.v(TAG, "Previous effect halted, starting new effect."); 650 tryEnable3ALocks(false); 651 mRunner.run(); 652 } else if (mState != STATE_RELEASED) { 653 // Shutting down effects 654 if (mLogVerbose) Log.v(TAG, "Runner halted, restoring direct preview"); 655 sendMessage(EFFECT_NONE, EFFECT_MSG_EFFECTS_STOPPED); 656 } else { 657 // STATE_RELEASED - camera will be/has been released as well, do nothing. 658 } 659 } 660 } 661 }; 662 663 // Indicates that all camera/recording activity needs to halt 664 public synchronized void release() { 665 if (mLogVerbose) Log.v(TAG, "Releasing (" + this + ")"); 666 667 switch (mState) { 668 case STATE_RECORD: 669 case STATE_PREVIEW: 670 stopPreview(); 671 // Fall-through 672 default: 673 mState = STATE_RELEASED; 674 break; 675 } 676 } 677 678 private void sendMessage(final int effect, final int msg) { 679 if (mEffectsListener != null) { 680 mHandler.post(new Runnable() { 681 public void run() { 682 mEffectsListener.onEffectsUpdate(effect, msg); 683 } 684 }); 685 } 686 } 687 688 private void raiseError(final Exception exception) { 689 if (mEffectsListener != null) { 690 mHandler.post(new Runnable() { 691 public void run() { 692 mEffectsListener.onEffectsError(exception, mOutputFile); 693 } 694 }); 695 } 696 } 697} 698