@ -16,13 +16,14 @@ import android.support.annotation.WorkerThread;
import android.view.SurfaceHolder ;
import java.io.File ;
import java.io.IOException ;
import java.util.ArrayList ;
import java.util.Collections ;
import java.util.List ;
@SuppressWarnings ( "deprecation" )
class Camera1 extends CameraController implements Camera . PreviewCallback {
class Camera1 extends CameraController implements Camera . PreviewCallback , Camera . ErrorCallback {
private static final String TAG = Camera1 . class . getSimpleName ( ) ;
private static final CameraLogger LOG = CameraLogger . create ( TAG ) ;
@ -40,144 +41,145 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
public void run ( ) {
if ( ! isCameraAvailable ( ) ) return ;
mCamera . cancelAutoFocus ( ) ;
synchronized ( mLock ) {
Camera . Parameters params = mCamera . getParameters ( ) ;
params . setFocusAreas ( null ) ;
params . setMeteringAreas ( null ) ;
applyDefaultFocus ( params ) ; // Revert to internal focus.
mCamera . setParameters ( params ) ;
}
}
} ;
private Mapper mMapper = new Mapper . Mapper1 ( ) ;
private boolean mIsSetup = false ;
private Mapper mMapper ;
private boolean mIsBound = false ;
private boolean mIsCapturingImage = false ;
private boolean mIsCapturingVideo = false ;
private final Object mLock = new Object ( ) ;
Camera1 ( CameraView . CameraCallbacks callback ) {
super ( callback ) ;
mMapper = new Mapper . Mapper1 ( ) ;
}
/ * *
* Preview surface is now available . If camera is open , set up .
* /
private void schedule ( @Nullable final Task < Void > task , final boolean ensureAvailable , final Runnable action ) {
mHandler . post ( new Runnable ( ) {
@Override
public void run ( ) {
if ( ensureAvailable & & ! isCameraAvailable ( ) ) {
if ( task ! = null ) task . end ( null ) ;
} else {
action . run ( ) ;
if ( task ! = null ) task . end ( null ) ;
}
}
} ) ;
}
// Preview surface is now available. If camera is open, set up.
@Override
public void onSurfaceAvailable ( ) {
LOG . i ( "onSurfaceAvailable:" , "Size is" , mPreview . getSurfaceSize ( ) ) ;
if ( ! shouldSetup ( ) ) return ;
mHandler . post ( new Runnable ( ) {
schedule ( null , false , new Runnable ( ) {
@Override
public void run ( ) {
if ( ! shouldSetup ( ) ) return ;
if ( shouldBindTo Surfac e ( ) ) {
LOG . i ( "onSurfaceAvailable:" , "Inside handler. About to bind." ) ;
try {
setup ( ) ;
bindToSurface ( ) ;
} catch ( Exception e ) {
LOG . w ( "onSurfaceAvailable:" , "Exception while binding camera to preview." , e ) ;
throw new RuntimeException ( e ) ;
LOG . e ( "onSurfaceAvailable:" , "Exception while binding camera to preview." , e ) ;
throw new CameraException ( e ) ;
}
}
}
} ) ;
}
/ * *
* Preview surface did change its size . Compute a new preview size .
* This requires stopping and restarting the preview .
* /
// Preview surface did change its size. Compute a new preview size.
// This requires stopping and restarting the preview.
@Override
public void onSurfaceChanged ( ) {
LOG . i ( "onSurfaceChanged, size is" , mPreview . getSurfaceSize ( ) ) ;
if ( mIsSetup ) {
schedule ( null , true , new Runnable ( ) {
@Override
public void run ( ) {
if ( ! mIsBound ) return ;
// Compute a new camera preview size.
Size newSize = computePreviewSize ( ) ;
if ( ! newSize . equals ( mPreviewSize ) ) {
LOG . i ( "onSurfaceChanged:" , "Computed a new preview size. Dispatching." ) ;
if ( newSize . equals ( mPreviewSize ) ) return ;
// Apply.
LOG . i ( "onSurfaceChanged:" , "Computed a new preview size. Going on." ) ;
mPreviewSize = newSize ;
mCameraCallbacks . onCameraPreviewSizeChanged ( ) ;
synchronized ( mLock ) {
LOG . i ( "onSurfaceChanged:" , "Stopping preview." ) ;
mCamera . stopPreview ( ) ;
LOG . i ( "onSurfaceChanged:" , "Stopped preview." ) ;
Camera . Parameters params = mCamera . getParameters ( ) ;
params . setPreviewSize ( mPreviewSize . getWidth ( ) , mPreviewSize . getHeight ( ) ) ;
mCamera . setParameters ( params ) ;
}
boolean invertPreviewSizes = shouldFlipSizes ( ) ;
mPreview . setDesiredSize (
invertPreviewSizes ? mPreviewSize . getHeight ( ) : mPreviewSize . getWidth ( ) ,
invertPreviewSizes ? mPreviewSize . getWidth ( ) : mPreviewSize . getHeight ( )
) ;
mCamera . setPreviewCallbackWithBuffer ( null ) ; // This clears the buffers
mCamera . setPreviewCallbackWithBuffer ( this ) ; // Reset
mFrameManager . allocate ( ImageFormat . getBitsPerPixel ( mPreviewFormat ) , mPreviewSize ) ;
LOG . i ( "onSurfaceChanged:" , "Restarting preview." ) ;
mCamera . startPreview ( ) ;
LOG . i ( "onSurfaceChanged:" , "Restarted preview." ) ;
}
applySizesAndStartPreview ( "onSurfaceChanged:" ) ;
}
} ) ;
}
private boolean shouldSetup ( ) {
return isCameraAvailable ( ) & & mPreview ! = null & & mPreview . isReady ( ) & & ! mIsSetup ;
private boolean shouldBindToSurface ( ) {
return isCameraAvailable ( ) & & mPreview ! = null & & mPreview . isReady ( ) & & ! mIsBound ;
}
// The act of binding an "open" camera to a "ready" preview.
// These can happen at different times but we want to end up here.
@WorkerThread
private void setup ( ) throws Exception {
LOG . i ( "setup :" , "Started" ) ;
private void bindToSurface ( ) {
LOG . i ( "bindToSurface:" , "Started" ) ;
Object output = mPreview . getOutput ( ) ;
try {
if ( mPreview . getOutputClass ( ) = = SurfaceHolder . class ) {
mCamera . setPreviewDisplay ( ( SurfaceHolder ) output ) ;
} else {
mCamera . setPreviewTexture ( ( SurfaceTexture ) output ) ;
}
} catch ( IOException e ) {
throw new CameraException ( e ) ;
}
boolean invertPreviewSizes = shouldFlipSizes ( ) ;
mCaptureSize = computeCaptureSize ( ) ;
mPreviewSize = computePreviewSize ( ) ;
LOG . i ( "setup:" , "Dispatching onCameraPreviewSizeChanged." ) ;
applySizesAndStartPreview ( "bindToSurface:" ) ;
mIsBound = true ;
}
// To be called when the preview size is setup or changed.
private void applySizesAndStartPreview ( String log ) {
LOG . i ( log , "Dispatching onCameraPreviewSizeChanged." ) ;
mCameraCallbacks . onCameraPreviewSizeChanged ( ) ;
boolean invertPreviewSizes = shouldFlipSizes ( ) ;
mPreview . setDesiredSize (
invertPreviewSizes ? mPreviewSize . getHeight ( ) : mPreviewSize . getWidth ( ) ,
invertPreviewSizes ? mPreviewSize . getWidth ( ) : mPreviewSize . getHeight ( )
) ;
synchronized ( mLock ) {
Camera . Parameters params = mCamera . getParameters ( ) ;
mPreviewFormat = params . getPreviewFormat ( ) ;
params . setPreviewSize ( mPreviewSize . getWidth ( ) , mPreviewSize . getHeight ( ) ) ; // <- not allowed during preview
params . setPictureSize ( mCaptureSize . getWidth ( ) , mCaptureSize . getHeight ( ) ) ; // <- allowed
mCamera . setParameters ( params ) ;
}
mCamera . setPreviewCallbackWithBuffer ( null ) ; // Release anything left
mCamera . setPreviewCallbackWithBuffer ( this ) ; // Add ourselves
mFrameManager . allocate ( ImageFormat . getBitsPerPixel ( mPreviewFormat ) , mPreviewSize ) ;
LOG . i ( "setup:" , "Starting preview with startPreview()." ) ;
LOG . i ( log , "Starting preview with startPreview()." ) ;
mCamera . startPreview ( ) ;
LOG . i ( "setup:" , "Started preview with startPreview()." ) ;
mIsSetup = true ;
LOG . i ( log , "Started preview." ) ;
}
@WorkerThread
@Override
void onStart ( ) throws Exception {
void onStart ( ) {
if ( isCameraAvailable ( ) ) {
LOG . w ( "onStart:" , "Camera not available. Should not happen." ) ;
onStop ( ) ; // Should not happen.
}
if ( collectCameraId ( ) ) {
mCamera = Camera . open ( mCameraId ) ;
mCamera . setErrorCallback ( this ) ;
// Set parameters that might have been set before the camera was opened.
synchronized ( mLock ) {
LOG . i ( "onStart:" , "Applying default parameters." ) ;
Camera . Parameters params = mCamera . getParameters ( ) ;
mExtraProperties = new ExtraProperties ( params ) ;
@ -186,29 +188,28 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
mergeFlash ( params , Flash . DEFAULT ) ;
mergeLocation ( params , null ) ;
mergeWhiteBalance ( params , WhiteBalance . DEFAULT ) ;
mergeHdr ( params , Hdr . DEFAULT ) ;
params . setRecordingHint ( mSessionType = = SessionType . VIDEO ) ;
mCamera . setParameters ( params ) ;
}
// Try starting preview.
mCamera . setDisplayOrientation ( computeSensorToDisplayOffset ( ) ) ; // <- not allowed during preview
if ( shouldSetup ( ) ) setup ( ) ;
if ( shouldBindToSurface ( ) ) bindToSurface ( ) ;
LOG . i ( "onStart:" , "Ended" ) ;
}
}
@WorkerThread
@Override
void onStop ( ) throws Exception {
void onStop ( ) {
Exception error = null ;
LOG . i ( "onStop:" , "About to clean up." ) ;
mHandler . get ( ) . removeCallbacks ( mPostFocusResetRunnable ) ;
mFrameManager . release ( ) ;
if ( mCamera ! = null ) {
LOG . i ( "onStop:" , "Clean up." , "Ending video?" , mIsCapturingVideo ) ;
if ( mIsCapturingVideo ) endVideo ( ) ;
LOG . i ( "onStop:" , "Clean up." , "Ending video." ) ;
endVideoImmediately ( ) ;
try {
LOG . i ( "onStop:" , "Clean up." , "Stopping preview." ) ;
@ -216,7 +217,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
mCamera . setPreviewCallbackWithBuffer ( null ) ;
LOG . i ( "onStop:" , "Clean up." , "Stopped preview." ) ;
} catch ( Exception e ) {
LOG . w ( "onStop:" , "Clean up." , "Exception while stopping preview." ) ;
LOG . w ( "onStop:" , "Clean up." , "Exception while stopping preview." , e ) ;
error = e ;
}
@ -225,7 +226,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
mCamera . release ( ) ;
LOG . i ( "onStop:" , "Clean up." , "Released camera." ) ;
} catch ( Exception e ) {
LOG . w ( "onStop:" , "Clean up." , "Exception while releasing camera." ) ;
LOG . w ( "onStop:" , "Clean up." , "Exception while releasing camera." , e ) ;
error = e ;
}
}
@ -234,9 +235,9 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
mCamera = null ;
mPreviewSize = null ;
mCaptureSize = null ;
mIsSetup = false ;
mIsBound = false ;
if ( error ! = null ) throw error ;
if ( error ! = null ) throw new CameraException ( error ) ;
}
private boolean collectCameraId ( ) {
@ -255,31 +256,50 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
@Override
public void onBufferAvailable ( byte [ ] buffer ) {
// TODO: sync with handler?
if ( isCameraAvailable ( ) ) {
mCamera . addCallbackBuffer ( buffer ) ;
}
}
@Override
public void onError ( int error , Camera camera ) {
if ( error = = Camera . CAMERA_ERROR_SERVER_DIED ) {
// Looks like this is recoverable.
LOG . w ( "Recoverable error inside the onError callback." , "CAMERA_ERROR_SERVER_DIED" ) ;
stopImmediately ( ) ;
start ( ) ;
return ;
}
LOG . e ( "Error inside the onError callback." , error ) ;
throw new CameraException ( new RuntimeException ( CameraLogger . lastMessage ) ) ;
}
@Override
void setSessionType ( SessionType sessionType ) {
if ( sessionType ! = mSessionType ) {
mSessionType = sessionType ;
if ( isCameraAvailable ( ) ) {
schedule ( null , true , new Runnable ( ) {
@Override
public void run ( ) {
restart ( ) ;
}
} ) ;
}
}
@Override
void setLocation ( Location location ) {
Location oldLocation = mLocation ;
final Location oldLocation = mLocation ;
mLocation = location ;
if ( isCameraAvailable ( ) ) {
synchronized ( mLock ) {
schedule ( mLocationTask , true , new Runnable ( ) {
@Override
public void run ( ) {
Camera . Parameters params = mCamera . getParameters ( ) ;
if ( mergeLocation ( params , oldLocation ) ) mCamera . setParameters ( params ) ;
}
}
} ) ;
}
private boolean mergeLocation ( Camera . Parameters params , Location oldLocation ) {
@ -302,22 +322,28 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
void setFacing ( Facing facing ) {
if ( facing ! = mFacing ) {
mFacing = facing ;
if ( collectCameraId ( ) & & isCameraAvailable ( ) ) {
schedule ( null , true , new Runnable ( ) {
@Override
public void run ( ) {
if ( collectCameraId ( ) ) {
restart ( ) ;
}
}
} ) ;
}
}
@Override
void setWhiteBalance ( WhiteBalance whiteBalance ) {
WhiteBalance old = mWhiteBalance ;
final WhiteBalance old = mWhiteBalance ;
mWhiteBalance = whiteBalance ;
if ( isCameraAvailable ( ) ) {
synchronized ( mLock ) {
schedule ( mWhiteBalanceTask , true , new Runnable ( ) {
@Override
public void run ( ) {
Camera . Parameters params = mCamera . getParameters ( ) ;
if ( mergeWhiteBalance ( params , old ) ) mCamera . setParameters ( params ) ;
}
}
} ) ;
}
private boolean mergeWhiteBalance ( Camera . Parameters params , WhiteBalance oldWhiteBalance ) {
@ -331,14 +357,15 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
@Override
void setHdr ( Hdr hdr ) {
Hdr old = mHdr ;
final Hdr old = mHdr ;
mHdr = hdr ;
if ( isCameraAvailable ( ) ) {
synchronized ( mLock ) {
schedule ( mHdrTask , true , new Runnable ( ) {
@Override
public void run ( ) {
Camera . Parameters params = mCamera . getParameters ( ) ;
if ( mergeHdr ( params , old ) ) mCamera . setParameters ( params ) ;
}
}
} ) ;
}
private boolean mergeHdr ( Camera . Parameters params , Hdr oldHdr ) {
@ -355,7 +382,8 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
void setAudio ( Audio audio ) {
if ( mAudio ! = audio ) {
if ( mIsCapturingVideo ) {
LOG . w ( "Changing audio mode while recording. Changes will take place starting from next video" ) ;
LOG . w ( "Audio setting was changed while recording. " +
"Changes will take place starting from next video" ) ;
}
mAudio = audio ;
}
@ -363,14 +391,15 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
@Override
void setFlash ( Flash flash ) {
Flash old = mFlash ;
final Flash old = mFlash ;
mFlash = flash ;
if ( isCameraAvailable ( ) ) {
synchronized ( mLock ) {
schedule ( mFlashTask , true , new Runnable ( ) {
@Override
public void run ( ) {
Camera . Parameters params = mCamera . getParameters ( ) ;
if ( mergeFlash ( params , old ) ) mCamera . setParameters ( params ) ;
}
}
} ) ;
}
@ -413,87 +442,102 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
@Override
void setVideoQuality ( VideoQuality videoQuality ) {
final VideoQuality old = mVideoQuality ;
mVideoQuality = videoQuality ;
schedule ( mVideoQualityTask , true , new Runnable ( ) {
@Override
public void run ( ) {
if ( mIsCapturingVideo ) {
// TODO: actually any call to getParameters() could fail while recording a video.
// See. https://stackoverflow.com/questions/14941625/correct-handling-of-exception-getparameters-failed-empty-parameters
// See. https://stackoverflow.com/questions/14941625/
mVideoQuality = old ;
throw new IllegalStateException ( "Can't change video quality while recording a video." ) ;
}
mVideoQuality = videoQuality ;
if ( isCameraAvailable ( ) & & mSessionType = = SessionType . VIDEO ) {
if ( mSessionType = = SessionType . VIDEO ) {
// Change capture size to a size that fits the video aspect ratio.
Size oldSize = mCaptureSize ;
mCaptureSize = computeCaptureSize ( ) ;
if ( ! mCaptureSize . equals ( oldSize ) ) {
// New video quality triggers a new aspect ratio.
// Go on and see if preview size should change also.
synchronized ( mLock ) {
Camera . Parameters params = mCamera . getParameters ( ) ;
params . setPictureSize ( mCaptureSize . getWidth ( ) , mCaptureSize . getHeight ( ) ) ;
mCamera . setParameters ( params ) ;
}
onSurfaceChanged ( ) ;
}
LOG . i ( "setVideoQuality:" , "captureSize:" , mCaptureSize ) ;
LOG . i ( "setVideoQuality:" , "previewSize:" , mPreviewSize ) ;
}
}
} ) ;
}
@Override
boolean capturePicture ( ) {
if ( mIsCapturingImage ) return false ;
if ( ! isCameraAvailable ( ) ) return false ;
if ( mSessionType = = SessionType . VIDEO & & mIsCapturingVideo ) {
if ( ! mOptions . isVideoSnapshotSupported ( ) ) return false ;
}
void capturePicture ( ) {
LOG . v ( "capturePicture: scheduling" ) ;
schedule ( null , true , new Runnable ( ) {
@Override
public void run ( ) {
LOG . v ( "capturePicture: performing." , mIsCapturingImage ) ;
if ( mIsCapturingImage ) return ;
if ( mIsCapturingVideo & & ! mOptions . isVideoSnapshotSupported ( ) ) return ;
// Set boolean to wait for image callback
mIsCapturingImage = true ;
final int exifRotation = computeExifRotation ( ) ;
final boolean exifFlip = computeExifFlip ( ) ;
final int sensorToDisplay = computeSensorToDisplayOffset ( ) ;
synchronized ( mLock ) {
Camera . Parameters params = mCamera . getParameters ( ) ;
params . setRotation ( exifRotation ) ;
mCamera . setParameters ( params ) ;
}
// Is the final picture (decoded respecting EXIF) consistent with CameraView orientation?
// We must consider exifOrientation to bring back the picture in the sensor world.
// Then use sensorToDisplay to move to the display world, where CameraView lives.
final boolean consistentWithView = ( exifRotation + sensorToDisplay + 180 ) % 180 = = 0 ;
mCamera . takePicture ( null , null , null ,
mCamera . takePicture (
new Camera . ShutterCallback ( ) {
@Override
public void onShutter ( ) {
mCameraCallbacks . onShutter ( false ) ;
}
} ,
null ,
null ,
new Camera . PictureCallback ( ) {
@Override
public void onPictureTaken ( byte [ ] data , final Camera camera ) {
mIsCapturingImage = false ;
mHandler . post ( new Runnable ( ) {
@Override
public void run ( ) {
// This is needed, read somewhere in the docs.
camera . startPreview ( ) ;
}
} ) ;
mCameraCallbacks . processImage ( data , consistentWithView , exifFlip ) ;
camera . startPreview ( ) ; // This is needed, read somewhere in the docs.
}
}
) ;
}
} ) ;
return true ;
}
@Override
boolean captureSnapshot ( ) {
if ( ! isCameraAvailable ( ) ) return false ;
if ( mIsCapturingImage ) return false ;
void captureSnapshot ( ) {
LOG . v ( "captureSnapshot: scheduling" ) ;
schedule ( null , true , new Runnable ( ) {
@Override
public void run ( ) {
LOG . v ( "captureSnapshot: performing." , mIsCapturingImage ) ;
if ( mIsCapturingImage ) return ;
// This won't work while capturing a video.
// Switch to capturePicture.
if ( mIsCapturingVideo ) {
capturePicture ( ) ;
return false ;
return ;
}
mIsCapturingImage = true ;
mCamera . setOneShotPreviewCallback ( new Camera . PreviewCallback ( ) {
@Override
public void onPreviewFrame ( final byte [ ] data , Camera camera ) {
mCameraCallbacks . onShutter ( true ) ;
// Got to rotate the preview frame, since byte[] data here does not include
// EXIF tags automatically set by camera. So either we add EXIF, or we rotate.
// Adding EXIF to a byte array, unfortunately, is hard.
@ -510,8 +554,10 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
@Override
public void run ( ) {
LOG . v ( "captureSnapshot: rotating." ) ;
final boolean consistentWithView = ( sensorToDevice + sensorToDisplay + 180 ) % 180 = = 0 ;
byte [ ] rotatedData = RotationHelper . rotate ( data , preWidth , preHeight , sensorToDevice ) ;
LOG . v ( "captureSnapshot: rotated." ) ;
YuvImage yuv = new YuvImage ( rotatedData , format , postWidth , postHeight , null ) ;
mCameraCallbacks . processSnapshot ( yuv , consistentWithView , exifFlip ) ;
mIsCapturingImage = false ;
@ -520,11 +566,12 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
// It seems that the buffers are already cleared here, so we need to allocate again.
mCamera . setPreviewCallbackWithBuffer ( null ) ; // Release anything left
mCamera . setPreviewCallbackWithBuffer ( this ) ; // Add ourselves
mFrameManager . allocate ( ImageFormat . getBitsPerPixel ( mPreviewFormat ) , mPreviewSize ) ; mCamera . setPreviewCallbackWithBuffer ( Camera1 . this ) ;
mCamera . setPreviewCallbackWithBuffer ( Camera1 . this ) ; // Add ourselves
mFrameManager . allocate ( ImageFormat . getBitsPerPixel ( mPreviewFormat ) , mPreviewSize ) ;
}
} ) ;
}
} ) ;
return true ;
}
@Override
@ -552,12 +599,11 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
// If we are going to be closed, don't act on camera.
// Even if mCamera != null, it might have been released.
case STATE_STOPPING : return false ;
// If we are started, act as long as there is no stop/restart scheduled.
// At this point mCamera should never be null.
case STATE_STARTED : return ! mScheduledForStop & & ! mScheduledForRestart ;
// If we are started, mCamera should never be null.
case STATE_STARTED : return true ;
// If we are starting, theoretically we could act.
// Just check that camera is available.
case STATE_STARTING : return mCamera ! = null & & ! mScheduledForStop & & ! mScheduledForRestart ;
case STATE_STARTING : return mCamera ! = null ;
}
return false ;
}
@ -621,7 +667,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
// The Camcorder internally checks for cameraParameters.getSupportedVideoSizes() etc.
// We want the picture size to be the max picture consistent with the video aspect ratio.
List < Size > captureSizes = sizesFromList ( params . getSupportedPictureSizes ( ) ) ;
CamcorderProfile profile = getCamcorderProfile ( mVideoQuality ) ;
CamcorderProfile profile = getCamcorderProfile ( mCameraId , m VideoQuality ) ;
AspectRatio targetRatio = AspectRatio . of ( profile . videoFrameWidth , profile . videoFrameHeight ) ;
LOG . i ( "size:" , "computeCaptureSize:" , "videoQuality:" , mVideoQuality , "targetRatio:" , targetRatio ) ;
return matchSize ( captureSizes , targetRatio , new Size ( 0 , 0 ) , true ) ;
@ -643,9 +689,11 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
@Override
boolean startVideo ( @NonNull File videoFile ) {
if ( mIsCapturingVideo ) return false ;
if ( ! isCameraAvailable ( ) ) return false ;
void startVideo ( @NonNull final File videoFile ) {
schedule ( mStartVideoTask , true , new Runnable ( ) {
@Override
public void run ( ) {
if ( mIsCapturingVideo ) return ;
if ( mSessionType = = SessionType . VIDEO ) {
mVideoFile = videoFile ;
mIsCapturingVideo = true ;
@ -653,62 +701,71 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
try {
mMediaRecorder . prepare ( ) ;
mMediaRecorder . start ( ) ;
return true ;
} catch ( Exception e ) {
LOG . e ( "Error while starting MediaRecorder. Swallowing." , e ) ;
mVideoFile = null ;
mCamera . lock ( ) ;
endVideo ( ) ;
return false ;
endVideoImmediately ( ) ;
}
} else {
throw new IllegalStateException ( "Can't record video while session type is picture" ) ;
}
}
} ) ;
}
@Override
boolean endVideo ( ) {
if ( mIsCapturingVideo ) {
void endVideo ( ) {
schedule ( null , false , new Runnable ( ) {
@Override
public void run ( ) {
endVideoImmediately ( ) ;
}
} ) ;
}
@WorkerThread
private void endVideoImmediately ( ) {
LOG . i ( "endVideoImmediately:" , "is capturing:" , mIsCapturingVideo ) ;
mIsCapturingVideo = false ;
if ( mMediaRecorder ! = null ) {
try {
mMediaRecorder . stop ( ) ;
mMediaRecorder . release ( ) ;
} catch ( Exception e ) {
// This can happen if endVideo() is called right after startVideo().
// We don't care.
LOG . w ( "Error while closing media recorder. Swallowing" , e ) ;
// This can happen if endVideo() is called right after startVideo(). We don't care.
LOG . w ( "endVideoImmediately:" , "Error while closing media recorder. Swallowing" , e ) ;
}
mMediaRecorder . release ( ) ;
mMediaRecorder = null ;
}
if ( mVideoFile ! = null ) {
mCameraCallbacks . dispatchOnVideoTaken ( mVideoFile ) ;
mVideoFile = null ;
}
return true ;
}
return false ;
}
@WorkerThread
private void initMediaRecorder ( ) {
mMediaRecorder = new MediaRecorder ( ) ;
mCamera . unlock ( ) ;
mMediaRecorder . setCamera ( mCamera ) ;
mMediaRecorder . setVideoSource ( MediaRecorder . VideoSource . CAMERA ) ;
CamcorderProfile profile = getCamcorderProfile ( mVideoQuality ) ;
if ( mAudio = = Audio . ON ) {
mMediaRecorder . setAudioSource ( MediaRecorder . AudioSource . CAMCORDER ) ;
mMediaRecorder . setProfile ( profile ) ;
} else {
// Set all values contained in profile except audio settings
// Must be called before setOutputFormat.
mMediaRecorder . setAudioSource ( MediaRecorder . AudioSource . DEFAULT ) ;
}
CamcorderProfile profile = getCamcorderProfile ( mCameraId , mVideoQuality ) ;
mMediaRecorder . setOutputFormat ( profile . fileFormat ) ;
mMediaRecorder . setVideoEncoder ( profile . videoCodec ) ;
mMediaRecorder . setVideoEncodingBitRate ( profile . videoBitRate ) ;
mMediaRecorder . setVideoFrameRate ( profile . videoFrameRate ) ;
mMediaRecorder . setVideoSize ( profile . videoFrameWidth , profile . videoFrameHeight ) ;
mMediaRecorder . setVideoEncoder ( profile . videoCodec ) ;
mMediaRecorder . setVideoEncodingBitRate ( profile . videoBitRate ) ;
if ( mAudio = = Audio . ON ) {
mMediaRecorder . setAudioChannels ( profile . audioChannels ) ;
mMediaRecorder . setAudioSamplingRate ( profile . audioSampleRate ) ;
mMediaRecorder . setAudioEncoder ( profile . audioCodec ) ;
mMediaRecorder . setAudioEncodingBitRate ( profile . audioBitRate ) ;
}
if ( mLocation ! = null ) {
@ -721,48 +778,47 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
// Not needed. mMediaRecorder.setPreviewDisplay(mPreview.getSurface());
}
@NonNull
private CamcorderProfile getCamcorderProfile ( VideoQuality videoQuality ) {
private static CamcorderProfile getCamcorderProfile ( int cameraId , VideoQuality videoQuality ) {
switch ( videoQuality ) {
case HIGHEST :
return CamcorderProfile . get ( mC ameraId, CamcorderProfile . QUALITY_HIGH ) ;
return CamcorderProfile . get ( c ameraId, CamcorderProfile . QUALITY_HIGH ) ;
case MAX_2160P :
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . LOLLIPOP & &
CamcorderProfile . hasProfile ( CamcorderProfile . QUALITY_2160P ) ) {
return CamcorderProfile . get ( mC ameraId, CamcorderProfile . QUALITY_2160P ) ;
return CamcorderProfile . get ( c ameraId, CamcorderProfile . QUALITY_2160P ) ;
}
// Don't break.
case MAX_1080P :
if ( CamcorderProfile . hasProfile ( mC ameraId, CamcorderProfile . QUALITY_1080P ) ) {
return CamcorderProfile . get ( mC ameraId, CamcorderProfile . QUALITY_1080P ) ;
if ( CamcorderProfile . hasProfile ( c ameraId, CamcorderProfile . QUALITY_1080P ) ) {
return CamcorderProfile . get ( c ameraId, CamcorderProfile . QUALITY_1080P ) ;
}
// Don't break.
case MAX_720P :
if ( CamcorderProfile . hasProfile ( mC ameraId, CamcorderProfile . QUALITY_720P ) ) {
return CamcorderProfile . get ( mC ameraId, CamcorderProfile . QUALITY_720P ) ;
if ( CamcorderProfile . hasProfile ( c ameraId, CamcorderProfile . QUALITY_720P ) ) {
return CamcorderProfile . get ( c ameraId, CamcorderProfile . QUALITY_720P ) ;
}
// Don't break.
case MAX_480P :
if ( CamcorderProfile . hasProfile ( mC ameraId, CamcorderProfile . QUALITY_480P ) ) {
return CamcorderProfile . get ( mC ameraId, CamcorderProfile . QUALITY_480P ) ;
if ( CamcorderProfile . hasProfile ( c ameraId, CamcorderProfile . QUALITY_480P ) ) {
return CamcorderProfile . get ( c ameraId, CamcorderProfile . QUALITY_480P ) ;
}
// Don't break.
case MAX_QVGA :
if ( CamcorderProfile . hasProfile ( mC ameraId, CamcorderProfile . QUALITY_QVGA ) ) {
return CamcorderProfile . get ( mC ameraId, CamcorderProfile . QUALITY_QVGA ) ;
if ( CamcorderProfile . hasProfile ( c ameraId, CamcorderProfile . QUALITY_QVGA ) ) {
return CamcorderProfile . get ( c ameraId, CamcorderProfile . QUALITY_QVGA ) ;
}
// Don't break.
case LOWEST :
default :
// Fallback to lowest.
return CamcorderProfile . get ( mC ameraId, CamcorderProfile . QUALITY_LOW ) ;
return CamcorderProfile . get ( c ameraId, CamcorderProfile . QUALITY_LOW ) ;
}
}
@ -771,33 +827,48 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
@Override
boolean setZoom ( float zoom ) {
if ( ! isCameraAvailable ( ) ) return false ;
if ( ! mOptions . isZoomSupported ( ) ) return false ;
synchronized ( mLock ) {
void setZoom ( final float zoom , final PointF [ ] points , final boolean notify ) {
schedule ( mZoomTask , true , new Runnable ( ) {
@Override
public void run ( ) {
if ( ! mOptions . isZoomSupported ( ) ) return ;
mZoomValue = zoom ;
Camera . Parameters params = mCamera . getParameters ( ) ;
float max = params . getMaxZoom ( ) ;
params . setZoom ( ( int ) ( zoom * max ) ) ;
mCamera . setParameters ( params ) ;
if ( notify ) {
mCameraCallbacks . dispatchOnZoomChanged ( zoom , points ) ;
}
return true ;
}
} ) ;
}
@Override
boolean setExposureCorrection ( float EVvalue ) {
if ( ! isCameraAvailable ( ) ) return false ;
if ( ! mOptions . isExposureCorrectionSupported ( ) ) return false ;
void setExposureCorrection ( final float EVvalue , final float [ ] bounds ,
final PointF [ ] points , final boolean notify ) {
schedule ( mExposureCorrectionTask , true , new Runnable ( ) {
@Override
public void run ( ) {
if ( ! mOptions . isExposureCorrectionSupported ( ) ) return ;
float value = EVvalue ;
float max = mOptions . getExposureCorrectionMaxValue ( ) ;
float min = mOptions . getExposureCorrectionMinValue ( ) ;
EVvalue = EVvalue < min ? min : EVvalue > max ? max : EVvalue ; // cap
synchronized ( mLock ) {
value = value < min ? min : value > max ? max : value ; // cap
mExposureCorrectionValue = value ;
Camera . Parameters params = mCamera . getParameters ( ) ;
int indexValue = ( int ) ( EV value / params . getExposureCompensationStep ( ) ) ;
int indexValue = ( int ) ( value / params . getExposureCompensationStep ( ) ) ;
params . setExposureCompensation ( indexValue ) ;
mCamera . setParameters ( params ) ;
if ( notify ) {
mCameraCallbacks . dispatchOnExposureCorrectionChanged ( value , bounds , points ) ;
}
return true ;
}
} ) ;
}
// -----------------
@ -805,13 +876,25 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
@Override
boolean startAutoFocus ( @Nullable final Gesture gesture , PointF point ) {
if ( ! isCameraAvailable ( ) ) return false ;
if ( ! mOptions . isAutoFocusSupported ( ) ) return false ;
void startAutoFocus ( @Nullable final Gesture gesture , final PointF point ) {
// Must get width and height from the UI thread.
int viewWidth = 0 , viewHeight = 0 ;
if ( mPreview ! = null & & mPreview . isReady ( ) ) {
viewWidth = mPreview . getView ( ) . getWidth ( ) ;
viewHeight = mPreview . getView ( ) . getHeight ( ) ;
}
final int viewWidthF = viewWidth ;
final int viewHeightF = viewHeight ;
// Schedule.
schedule ( null , true , new Runnable ( ) {
@Override
public void run ( ) {
if ( ! mOptions . isAutoFocusSupported ( ) ) return ;
final PointF p = new PointF ( point . x , point . y ) ; // copy.
List < Camera . Area > meteringAreas2 = computeMeteringAreas ( p . x , p . y ) ;
List < Camera . Area > meteringAreas2 = computeMeteringAreas ( p . x , p . y ,
viewWidthF , viewHeightF , computeSensorToDisplayOffset ( ) ) ;
List < Camera . Area > meteringAreas1 = meteringAreas2 . subList ( 0 , 1 ) ;
synchronized ( mLock ) {
// At this point we are sure that camera supports auto focus... right? Look at CameraView.onTouchEvent().
Camera . Parameters params = mCamera . getParameters ( ) ;
int maxAF = params . getMaxNumFocusAreas ( ) ;
@ -832,18 +915,19 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
}
} ) ;
}
return true ;
} ) ;
}
private List < Camera . Area > computeMeteringAreas ( double viewClickX , double viewClickY ) {
@WorkerThread
private static List < Camera . Area > computeMeteringAreas ( double viewClickX , double viewClickY ,
int viewWidth , int viewHeight ,
int sensorToDisplay ) {
// Event came in view coordinates. We must rotate to sensor coordinates.
// First, rescale to the -1000 ... 1000 range.
int displayToSensor = - computeSensorToDisplayOffset ( ) ;
double viewWidth = mPreview . getView ( ) . getWidth ( ) ;
double viewHeight = mPreview . getView ( ) . getHeight ( ) ;
viewClickX = - 1000d + ( viewClickX / viewWidth ) * 2000d ;
viewClickY = - 1000d + ( viewClickY / viewHeight ) * 2000d ;
int displayToSensor = - sensorToDisplay ;
viewClickX = - 1000d + ( viewClickX / ( double ) viewWidth ) * 2000d ;
viewClickY = - 1000d + ( viewClickY / ( double ) viewHeight ) * 2000d ;
// Apply rotation to this point.
// https://academo.org/demos/rotation-about-point/
@ -866,7 +950,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
}
private Rect computeMeteringArea ( double centerX , double centerY , double size ) {
private static Rect computeMeteringArea ( double centerX , double centerY , double size ) {
double delta = size / 2d ;
int top = ( int ) Math . max ( centerY - delta , - 1000 ) ;
int bottom = ( int ) Math . min ( centerY + delta , 1000 ) ;
@ -924,23 +1008,15 @@ class Camera1 extends CameraController implements Camera.PreviewCallback {
LOG . i ( "size:" , "matchSize:" , "found consistent:" , consistent . size ( ) ) ;
LOG . i ( "size:" , "matchSize:" , "found big enough and consistent:" , bigEnoughAndConsistent . size ( ) ) ;
Size result ;
if ( biggestPossible ) {
if ( bigEnoughAndConsistent . size ( ) > 0 ) {
result = Collections . max ( bigEnoughAndConsistent ) ;
result = biggestPossible ?
Collections . max ( bigEnoughAndConsistent ) :
Collections . min ( bigEnoughAndConsistent ) ;
} else if ( consistent . size ( ) > 0 ) {
result = Collections . max ( consistent ) ;
} else {
result = Collections . max ( sizes ) ;
}
} else {
if ( bigEnoughAndConsistent . size ( ) > 0 ) {
result = Collections . min ( bigEnoughAndConsistent ) ;
} else if ( consistent . size ( ) > 0 ) {
result = Collections . max ( consistent ) ;
} else {
result = Collections . max ( sizes ) ;
}
}
LOG . i ( "size" , "matchSize:" , "returning result" , result ) ;
return result ;
}