java.lang.Object
↳androidx.camera.view.CameraController
Subclasses:
LifecycleCameraController
Gradle dependencies
compile group: 'androidx.camera', name: 'camera-view', version: '1.5.0-alpha01'
- groupId: androidx.camera
- artifactId: camera-view
- version: 1.5.0-alpha01
Artifact androidx.camera:camera-view:1.5.0-alpha01 it located at Google repository (https://maven.google.com/)
Overview
The abstract base camera controller class.
This a high level controller that provides most of the CameraX core features
in a single class. It handles camera initialization, creates and configures UseCases.
It also listens to device motion sensor and set the target rotation for the use cases.
The controller is required to be used with a PreviewView. PreviewView
provides the UI elements to display camera preview. The layout of the PreviewView is
used to set the crop rect so the output from other use cases matches the preview display in a
WYSIWYG way. The controller also listens to PreviewView's touch events to handle
tap-to-focus and pinch-to-zoom features.
This class provides features of 4 UseCases: Preview, ImageCapture,
ImageAnalysis and VideoCapture. Preview is required and always enabled.
ImageCapture and ImageAnalysis are enabled by default. The video capture is
disabled by default because it might affect other use cases, especially on lower end devices.
It might be necessary to disable ImageCapture and/or ImageAnalysis before the
video capture feature can be enabled. Disabling/enabling UseCases freezes the preview for
a short period of time. To avoid the glitch, the UseCases need to be enabled/disabled
before the controller is set on PreviewView.
Summary
Methods |
---|
public void | clearEffects()
Removes all effects. |
public void | clearImageAnalysisAnalyzer()
Removes a previously set analyzer. |
protected UseCaseGroup | createUseCaseGroup()
Creates UseCaseGroup from all the use cases. |
public <any> | enableTorch(boolean torchEnabled)
Enable the torch or disable the torch. |
public CameraControl | getCameraControl()
Gets the CameraControl of the currently attached camera. |
public CameraInfo | getCameraInfo()
Gets the CameraInfo of the currently attached camera. |
public CameraSelector | getCameraSelector()
Gets the CameraSelector. |
public java.util.concurrent.Executor | getImageAnalysisBackgroundExecutor()
Gets the default executor for ImageAnalysis background tasks. |
public int | getImageAnalysisBackpressureStrategy()
Returns the mode with which images are acquired. |
public int | getImageAnalysisImageQueueDepth()
Gets the image queue depth of ImageAnalysis. |
public int | getImageAnalysisOutputImageFormat()
Gets the output image format for ImageAnalysis. |
public ResolutionSelector | getImageAnalysisResolutionSelector()
Returns the ResolutionSelector for ImageAnalysis. |
public CameraController.OutputSize | getImageAnalysisTargetSize()
Returns the intended output size for ImageAnalysis set by
CameraController.setImageAnalysisTargetSize(CameraController.OutputSize), or null if not set. |
public int | getImageCaptureFlashMode()
Gets the flash mode for ImageCapture. |
public java.util.concurrent.Executor | getImageCaptureIoExecutor()
Gets the default executor for ImageCapture IO tasks. |
public int | getImageCaptureMode()
Returns the image capture mode. |
public ResolutionSelector | getImageCaptureResolutionSelector()
Returns the ResolutionSelector for ImageCapture. |
public CameraController.OutputSize | getImageCaptureTargetSize()
Returns the intended output size for ImageCapture set by
CameraController.setImageCaptureTargetSize(CameraController.OutputSize), or null if not set. |
public <any> | getInitializationFuture()
Gets a that completes when camera initialization completes. |
public DynamicRange | getPreviewDynamicRange()
Gets the DynamicRange for preview. |
public ResolutionSelector | getPreviewResolutionSelector()
Returns the ResolutionSelector for Preview. |
public CameraController.OutputSize | getPreviewTargetSize()
Returns the intended output size for Preview set by
CameraController.setPreviewTargetSize(CameraController.OutputSize), or null if not set. |
public ScreenFlashUiInfo | getScreenFlashUiInfoByPriority()
Returns a ScreenFlashUiInfo by prioritizing ScreenFlashView over
PreviewView. |
public LiveData<java.lang.Integer> | getTapToFocusState()
Returns a LiveData with the latest tap-to-focus state. |
public LiveData<java.lang.Integer> | getTorchState()
Returns a LiveData of current TorchState. |
public DynamicRange | getVideoCaptureDynamicRange()
Gets the DynamicRange for video capture. |
public int | getVideoCaptureMirrorMode()
Gets the mirror mode for video capture. |
public QualitySelector | getVideoCaptureQualitySelector()
Returns the QualitySelector for CameraController.VIDEO_CAPTURE. |
public <any> | getVideoCaptureTargetFrameRate()
Gets the target frame rate in frames per second for video capture. |
public LiveData<ZoomState> | getZoomState()
Returns a LiveData of ZoomState. |
public boolean | hasCamera(CameraSelector cameraSelector)
Checks if the given CameraSelector can be resolved to a camera. |
public boolean | isImageAnalysisEnabled()
Checks if ImageAnalysis is enabled. |
public boolean | isImageCaptureEnabled()
Checks if ImageCapture is enabled. |
public boolean | isPinchToZoomEnabled()
Returns whether pinch-to-zoom is enabled. |
public boolean | isRecording()
Returns whether there is an in-progress video recording. |
public boolean | isTapToFocusEnabled()
Returns whether tap-to-focus is enabled. |
public boolean | isVideoCaptureEnabled()
Checks if video capture is enabled. |
public void | setCameraSelector(CameraSelector cameraSelector)
Sets the CameraSelector. |
public void | setEffects(java.util.Set<CameraEffect> effects)
Sets CameraEffect. |
public void | setEnabledUseCases(int enabledUseCases)
Enables or disables use cases. |
public void | setImageAnalysisAnalyzer(java.util.concurrent.Executor executor, ImageAnalysis.Analyzer analyzer)
Sets an analyzer to receive and analyze images. |
public void | setImageAnalysisBackgroundExecutor(java.util.concurrent.Executor executor)
Sets the executor that will be used for ImageAnalysis background tasks. |
public void | setImageAnalysisBackpressureStrategy(int strategy)
Sets the backpressure strategy to apply to the image producer to deal with scenarios
where images may be produced faster than they can be analyzed. |
public void | setImageAnalysisImageQueueDepth(int depth)
Sets the image queue depth of ImageAnalysis. |
public void | setImageAnalysisOutputImageFormat(int imageAnalysisOutputImageFormat)
Sets the output image format for ImageAnalysis. |
public void | setImageAnalysisResolutionSelector(ResolutionSelector resolutionSelector)
Sets the ResolutionSelector for ImageAnalysis. |
public void | setImageAnalysisTargetSize(CameraController.OutputSize targetSize)
Sets the intended output size for ImageAnalysis. |
public void | setImageCaptureFlashMode(int flashMode)
Sets the flash mode for ImageCapture. |
public void | setImageCaptureIoExecutor(java.util.concurrent.Executor executor)
Sets the default executor that will be used for ImageCapture IO tasks. |
public void | setImageCaptureMode(int captureMode)
Sets the image capture mode. |
public void | setImageCaptureResolutionSelector(ResolutionSelector resolutionSelector)
Sets the ResolutionSelector for ImageCapture. |
public void | setImageCaptureTargetSize(CameraController.OutputSize targetSize)
Sets the intended image size for ImageCapture. |
public <any> | setLinearZoom(float linearZoom)
Sets current zoom by a linear zoom value ranging from 0f to 1.0f. |
public void | setPinchToZoomEnabled(boolean enabled)
Enables/disables pinch-to-zoom. |
public void | setPreviewDynamicRange(DynamicRange dynamicRange)
Sets the DynamicRange for preview. |
public void | setPreviewResolutionSelector(ResolutionSelector resolutionSelector)
Sets the ResolutionSelector for Preview. |
public void | setPreviewTargetSize(CameraController.OutputSize targetSize)
Sets the intended output size for Preview. |
public void | setScreenFlashUiInfo(ScreenFlashUiInfo screenFlashUiInfo)
Internal API used by PreviewView and ScreenFlashView to provide a
ImageCapture.ScreenFlash. |
public void | setTapToFocusEnabled(boolean enabled)
Enables/disables tap-to-focus. |
public void | setVideoCaptureDynamicRange(DynamicRange dynamicRange)
Sets the DynamicRange for video capture. |
public void | setVideoCaptureMirrorMode(int mirrorMode)
Sets the mirror mode for video capture. |
public void | setVideoCaptureQualitySelector(QualitySelector qualitySelector)
Sets the QualitySelector for CameraController.VIDEO_CAPTURE. |
public void | setVideoCaptureTargetFrameRate(<any> targetFrameRate)
Sets the target frame rate range in frames per second for video capture. |
public <any> | setZoomRatio(float zoomRatio)
Sets current zoom by ratio. |
public Recording | startRecording(FileDescriptorOutputOptions outputOptions, AudioConfig audioConfig, java.util.concurrent.Executor executor, Consumer<VideoRecordEvent> listener)
Takes a video to a given file descriptor. |
public Recording | startRecording(FileOutputOptions outputOptions, AudioConfig audioConfig, java.util.concurrent.Executor executor, Consumer<VideoRecordEvent> listener)
Takes a video to a given file. |
public Recording | startRecording(MediaStoreOutputOptions outputOptions, AudioConfig audioConfig, java.util.concurrent.Executor executor, Consumer<VideoRecordEvent> listener)
Takes a video to MediaStore. |
public void | takePicture(java.util.concurrent.Executor executor, ImageCapture.OnImageCapturedCallback callback)
Captures a new still image for in memory access. |
public void | takePicture(ImageCapture.OutputFileOptions outputFileOptions, java.util.concurrent.Executor executor, ImageCapture.OnImageSavedCallback imageSavedCallback)
Captures a new still image and saves to a file along with application specified metadata. |
public void | updateScreenFlashToImageCapture()
Internal API used by PreviewView and ScreenFlashView to update screen
flash mode to ImageCapture in case it's pending. |
from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Fields
public static final int
COORDINATE_SYSTEM_VIEW_REFERENCEDDeprecated: Use ImageAnalysis.COORDINATE_SYSTEM_VIEW_REFERENCED instead.
option for returning PreviewView coordinates.
When the is configured with this option, it will receive a
that will receive a value that represents the transformation from camera
sensor to the PreviewView, which can be used for highlighting detected result in
PreviewView. For example, laying over a bounding box on top of the detected face.
Note this option only works if the is set via
CameraController.setImageAnalysisAnalyzer(Executor, ImageAnalysis.Analyzer). It will not be effective when used with
camera-core directly.
See also:
public static final int
TAP_TO_FOCUS_NOT_STARTEDNo tap-to-focus action has been started by the end user.
public static final int
TAP_TO_FOCUS_STARTEDA tap-to-focus action has started but not completed. The app also gets notified with this
state if a new action happens before the previous one could finish.
public static final int
TAP_TO_FOCUS_FOCUSEDThe previous tap-to-focus action was completed successfully and the camera is focused.
public static final int
TAP_TO_FOCUS_NOT_FOCUSEDThe previous tap-to-focus action was completed successfully but the camera is still
unfocused, similar to the CaptureResult
state.
The end user might be able to get a better result by trying again with different camera
distances and/or lighting.
public static final int
TAP_TO_FOCUS_FAILEDThe previous tap-to-focus action was failed to complete. This is usually due to device
limitations.
public static final int
IMAGE_CAPTUREBitmask option to enable ImageCapture. In CameraController.setEnabledUseCases(int), if
(enabledUseCases & IMAGE_CAPTURE) != 0, then controller will enable image capture
features.
public static final int
IMAGE_ANALYSISBitmask option to enable ImageAnalysis. In CameraController.setEnabledUseCases(int), if
(enabledUseCases & IMAGE_ANALYSIS) != 0, then controller will enable image
analysis features.
public static final int
VIDEO_CAPTUREBitmask option to enable video capture use case. In CameraController.setEnabledUseCases(int), if
(enabledUseCases & VIDEO_CAPTURE) != 0, then controller will enable video capture
features.
Methods
public <any>
getInitializationFuture()
Gets a that completes when camera initialization completes.
This future may fail with an InitializationException and associated cause that
can be retrieved by getCause
. The cause will be a
CameraUnavailableException if it fails to access any camera during initialization.
In the rare case that the future fails with CameraUnavailableException, the
camera will become unusable. This could happen for various reasons, for example hardware
failure or the camera being held by another process. If the failure is temporary, killing
and restarting the app might fix the issue.
The initialization also try to bind use cases before completing the
. The will complete successfully
regardless of whether the use cases are ready to be bound, e.g. it will complete
successfully even if the controller is not set on a PreviewView. However the
will fail if the enabled use cases are not supported by the
current camera.
See also: ProcessCameraProvider.getInstance(Context)
public void
setEnabledUseCases(int enabledUseCases)
Enables or disables use cases.
Use cases need to be enabled before they can be used. By default, CameraController.IMAGE_CAPTURE
and CameraController.IMAGE_ANALYSIS are enabled, and CameraController.VIDEO_CAPTURE is disabled. This is
necessary because CameraController.VIDEO_CAPTURE may affect the available resolutions for other use
cases, especially on lower end devices.
To make sure getting the maximum resolution, only enable CameraController.VIDEO_CAPTURE when
shooting video. For example:
// By default, image capture is enabled. Taking picture works.
controller.takePicture(...);
// Switch to video capture to shoot video.
controller.setEnabledUseCases(VIDEO_CAPTURE);
controller.startRecording(...);
// Switch back to image capture and image analysis before taking another picture.
controller.setEnabledUseCases(IMAGE_CAPTURE|IMAGE_ANALYSIS);
controller.takePicture(...);
Parameters:
enabledUseCases: one or more of the following use cases, bitwise-OR-ed together:
CameraController.IMAGE_CAPTURE, CameraController.IMAGE_ANALYSIS and/or CameraController.VIDEO_CAPTURE.
See also: UseCase, ImageCapture, ImageAnalysis
Deprecated: Use CameraController.setPreviewResolutionSelector(ResolutionSelector) instead.
Sets the intended output size for Preview.
The value is used as a hint when determining the resolution and aspect ratio of the
preview. The actual output may differ from the requested value due to device constraints.
When set to null, the output will be based on the default config of
Preview.
Changing the value will reconfigure the camera which will cause additional latency. To
avoid this, set the value before controller is bound to lifecycle.
Parameters:
targetSize: the intended output size for Preview.
See also:
Deprecated: Use CameraController.getPreviewResolutionSelector() instead.
Returns the intended output size for Preview set by
CameraController.setPreviewTargetSize(CameraController.OutputSize), or null if not set.
Sets the ResolutionSelector for Preview.
CameraX uses this value as a hint to select the resolution for preview. The actual
output may differ from the requested value due to device constraints. When set to
null, CameraX will use the default config of Preview. By default, the
selected resolution will be limited by the PREVIEW size which is defined as the best
size match to the device's screen resolution, or to 1080p (1920x1080), whichever is smaller.
Changing the value will reconfigure the camera which will cause additional latency. To
avoid this, set the value before controller is bound to lifecycle.
See also:
Returns the ResolutionSelector for Preview.
This method returns the value set by
CameraController.setPreviewResolutionSelector(ResolutionSelector). It returns null if
the value has not been set.
public void
setPreviewDynamicRange(
DynamicRange dynamicRange)
Sets the DynamicRange for preview.
The dynamic range specifies how the range of colors, highlights and shadows that
are displayed on a display. Some dynamic ranges will allow the preview to make full use of
the extended range of brightness of a display.
The supported dynamic ranges of the camera can be queried using
CameraInfo.querySupportedDynamicRanges(Set).
It is possible to choose a high dynamic range (HDR) with unspecified encoding by providing
DynamicRange.HDR_UNSPECIFIED_10_BIT. If the dynamic range is not provided, the
default value is DynamicRange.SDR.
See also:
Gets the DynamicRange for preview.
TODO: make this public in the next release.
public boolean
isImageCaptureEnabled()
Checks if ImageCapture is enabled.
ImageCapture is enabled by default. It has to be enabled before
CameraController.takePicture(ImageCapture.OutputFileOptions, Executor, ImageCapture.OnImageSavedCallback) can be called.
See also: ImageCapture
public int
getImageCaptureFlashMode()
Gets the flash mode for ImageCapture.
Returns:
the flashMode. Value is ImageCapture.FLASH_MODE_AUTO,
ImageCapture.FLASH_MODE_ON, or ImageCapture.FLASH_MODE_OFF.
See also: ImageCapture
public void
setImageCaptureFlashMode(int flashMode)
Sets the flash mode for ImageCapture.
If not set, the flash mode will default to ImageCapture.FLASH_MODE_OFF.
If ImageCapture.FLASH_MODE_SCREEN is set, a valid
instance must be set to a PreviewView or ScreenFlashView which this
controller is set to. Trying to use ImageCapture.FLASH_MODE_SCREEN with a
non-front camera or without setting a non-null window will be no-op. While switching the
camera, it is the application's responsibility to change flash mode to the desired one if
it leads to a no-op case (e.g. switching to rear camera while FLASH_MODE_SCREEN is
still set). Otherwise, FLASH_MODE_OFF will be set.
Parameters:
flashMode: the flash mode for ImageCapture.
See also: PreviewView.setScreenFlashWindow(Window), ScreenFlashView.setScreenFlashWindow(Window)
Internal API used by PreviewView and ScreenFlashView to provide a
ImageCapture.ScreenFlash.
public void
updateScreenFlashToImageCapture()
Internal API used by PreviewView and ScreenFlashView to update screen
flash mode to ImageCapture in case it's pending.
Returns a ScreenFlashUiInfo by prioritizing ScreenFlashView over
PreviewView.
PreviewView always has a ScreenFlashView internally and does not know
if user is using another ScreenFlashView themselves. This API prioritizes user's
ScreenFlashView over the internal one in PreviewView and provides the
ScreenFlashUiInfo accordingly.
Captures a new still image and saves to a file along with application specified metadata.
The callback will be called only once for every invocation of this method.
By default, the saved image is mirrored to match the output of the preview if front
camera is used. To override this behavior, the app needs to explicitly set the flag to
false using and
.
The saved image is cropped to match the aspect ratio of the PreviewView. To
take a picture with the maximum available resolution, make sure that the
PreviewView's aspect ratio matches the max JPEG resolution supported by the camera.
Parameters:
outputFileOptions: Options to store the newly captured image.
executor: The executor in which the callback methods will be run.
imageSavedCallback: Callback to be called for the newly captured image.
See also: ImageCapture.takePicture(ImageCapture.OutputFileOptions, Executor, ImageCapture.OnImageSavedCallback)
Captures a new still image for in memory access.
The listener is responsible for calling ImageProxy.close() on the returned image.
Parameters:
executor: The executor in which the callback methods will be run.
callback: Callback to be invoked for the newly captured image
See also: ImageCapture.takePicture(Executor, ImageCapture.OnImageCapturedCallback)
public void
setImageCaptureMode(int captureMode)
Sets the image capture mode.
Valid capture modes are ,
which prioritizes latency over image quality, or
,
which prioritizes image quality over latency.
Changing the value will reconfigure the camera which will cause additional latency. To
avoid this, set the value before controller is bound to lifecycle.
Parameters:
captureMode: The requested image capture mode.
public int
getImageCaptureMode()
Returns the image capture mode.
See also: ImageCapture.getCaptureMode()
Deprecated: Use CameraController.setImageCaptureResolutionSelector(ResolutionSelector) instead.
Sets the intended image size for ImageCapture.
The value is used as a hint when determining the resolution and aspect ratio of the
captured image. The actual output may differ from the requested value due to device
constraints.
When set to null, the output will be based on the default config of
ImageCapture.
Changing the value will reconfigure the camera which will cause additional latency. To
avoid this, set the value before controller is bound to lifecycle.
Parameters:
targetSize: The intended image size for ImageCapture.
Deprecated: Use CameraController.getImageCaptureResolutionSelector() instead.
Returns the intended output size for ImageCapture set by
CameraController.setImageCaptureTargetSize(CameraController.OutputSize), or null if not set.
Sets the ResolutionSelector for ImageCapture.
CameraX uses this value as a hint to select the resolution for captured images. The actual
output may differ from the requested value due to device constraints. When set to
null, CameraX will use the default config of ImageCapture. The default
resolution strategy for ImageCapture is
ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY, which will select the largest
available resolution to use.
Changing the value will reconfigure the camera which will cause additional latency. To
avoid this, set the value before controller is bound to lifecycle.
See also:
Returns the ResolutionSelector for ImageCapture.
This method returns the value set by
CameraController.setImageCaptureResolutionSelector(ResolutionSelector) (ResolutionSelector)}. It returns null if
the value has not been set.
public void
setImageCaptureIoExecutor(java.util.concurrent.Executor executor)
Sets the default executor that will be used for ImageCapture IO tasks.
This executor will be used for any IO tasks specifically for ImageCapture,
such as CameraController.takePicture(ImageCapture.OutputFileOptions, Executor, ImageCapture.OnImageSavedCallback). If no executor is set, then a default Executor
specifically for IO will be used instead.
Changing the value will reconfigure the camera which will cause additional latency.
To avoid this, set the value before controller is bound to lifecycle.
Parameters:
executor: The executor which will be used for IO tasks.
public java.util.concurrent.Executor
getImageCaptureIoExecutor()
Gets the default executor for ImageCapture IO tasks.
public boolean
isImageAnalysisEnabled()
Checks if ImageAnalysis is enabled.
See also: ImageAnalysis
Sets an analyzer to receive and analyze images.
Applications can process or copy the image by implementing the
. The image needs to be closed by calling
ImageProxy.close() when the analyzing is done.
Setting an analyzer function replaces any previous analyzer. Only one analyzer can be
set at any time.
If the returns
ImageAnalysis.COORDINATE_SYSTEM_VIEW_REFERENCED, the analyzer will receive a
transformation via that converts
coordinates from the ImageAnalysis's coordinate system to the PreviewView's
coordinate system.
If the returns a non-null
value, calling this method will reconfigure the camera which might cause additional
latency. To avoid this, set the value before controller is bound to the lifecycle.
Parameters:
executor: The executor in which the
will be run.
analyzer: of the images.
See also: ImageAnalysis.setAnalyzer(Executor, ImageAnalysis.Analyzer)
public void
clearImageAnalysisAnalyzer()
Removes a previously set analyzer.
This will stop data from streaming to the ImageAnalysis.
If the current returns
non-null value, calling this method will reconfigure the camera which might cause additional
latency. To avoid this, call this method when the lifecycle is not active.
See also: ImageAnalysis.clearAnalyzer()
public int
getImageAnalysisBackpressureStrategy()
Returns the mode with which images are acquired.
If not set, it defaults to ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST.
Returns:
The backpressure strategy applied to the image producer.
See also:
public void
setImageAnalysisBackpressureStrategy(int strategy)
Sets the backpressure strategy to apply to the image producer to deal with scenarios
where images may be produced faster than they can be analyzed.
The available values are ImageAnalysis.STRATEGY_BLOCK_PRODUCER and
ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST. If not set, the backpressure strategy
will default to ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST.
Changing the value will reconfigure the camera which will cause additional latency. To
avoid this, set the value before controller is bound to lifecycle.
Parameters:
strategy: The strategy to use.
See also:
public void
setImageAnalysisImageQueueDepth(int depth)
Sets the image queue depth of ImageAnalysis.
This sets the number of images available in parallel to .
The value is only used if the backpressure strategy is
.
Changing the value will reconfigure the camera which will cause additional latency. To
avoid this, set the value before controller is bound to lifecycle.
Parameters:
depth: The total number of images available.
See also:
public int
getImageAnalysisImageQueueDepth()
Gets the image queue depth of ImageAnalysis.
See also: ImageAnalysis.getImageQueueDepth()
Deprecated: Use CameraController.setImageAnalysisResolutionSelector(ResolutionSelector) instead.
Sets the intended output size for ImageAnalysis.
The value is used as a hint when determining the resolution and aspect ratio of
the output buffer. The actual output may differ from the requested value due to device
constraints.
When set to null, the output will be based on the default config of
ImageAnalysis.
Changing the value will reconfigure the camera which will cause additional latency.
To avoid this, set the value before controller is bound to lifecycle.
Parameters:
targetSize: The intended output size for ImageAnalysis.
See also:
Deprecated: Use CameraController.getImageAnalysisResolutionSelector() instead.
Returns the intended output size for ImageAnalysis set by
CameraController.setImageAnalysisTargetSize(CameraController.OutputSize), or null if not set.
Sets the ResolutionSelector for ImageAnalysis.
CameraX uses this value as a hint to select the resolution for images. The actual
output may differ from the requested value due to device constraints. When set to
null, CameraX will use the default config of ImageAnalysis. ImageAnalysis has
a default ResolutionStrategy with bound size as 640x480 and fallback rule of
ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER.
Changing the value will reconfigure the camera which will cause additional latency. To
avoid this, set the value before controller is bound to lifecycle.
See also:
Returns the ResolutionSelector for ImageAnalysis.
This method returns the value set by
CameraController.setImageAnalysisResolutionSelector(ResolutionSelector). It returns null if
the value has not been set.
public void
setImageAnalysisBackgroundExecutor(java.util.concurrent.Executor executor)
Sets the executor that will be used for ImageAnalysis background tasks.
If not set, the background executor will default to an automatically generated
java.util.concurrent.Executor
.
Changing the value will reconfigure the camera, which will cause additional latency. To
avoid this, set the value before controller is bound to lifecycle.
Parameters:
executor: The executor for ImageAnalysis background tasks.
See also:
public java.util.concurrent.Executor
getImageAnalysisBackgroundExecutor()
Gets the default executor for ImageAnalysis background tasks.
See also:
public void
setImageAnalysisOutputImageFormat(int imageAnalysisOutputImageFormat)
Sets the output image format for ImageAnalysis.
The supported output image format
are and
.
If not set,
will be used.
Requesting
causes extra overhead because format conversion takes time.
Changing the value will reconfigure the camera, which may cause additional latency. To
avoid this, set the value before controller is bound to lifecycle. If the value is changed
when the camera is active, check the ImageProxy.getFormat() value to determine
when the new format takes effect.
See also: , ImageAnalysis.getOutputImageFormat()
public int
getImageAnalysisOutputImageFormat()
Gets the output image format for ImageAnalysis.
The returned image format can be either
ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888 or
ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888.
See also: , ImageAnalysis.getOutputImageFormat()
public boolean
isVideoCaptureEnabled()
Checks if video capture is enabled.
Video capture is disabled by default. It has to be enabled before CameraController.startRecording(FileOutputOptions, AudioConfig, Executor, Consumer)
can be called.
Takes a video to a given file.
Only a single recording can be active at a time, so if CameraController.isRecording() is
true, this will throw an java.lang.IllegalStateException
.
Upon successfully starting the recording, a event will
be the first event sent to the provided event listener.
If errors occur while starting the recording, a event
will be the first event sent to the provided listener, and information about the error can
be found in that event's method.
Recording with audio requires the
permission; without it, starting a recording will fail with a java.lang.SecurityException
.
Parameters:
outputOptions: The options to store the newly captured video.
audioConfig: The configuration of audio.
executor: The executor that the event listener will be run on.
listener: The event listener to handle video record events.
Returns:
a Recording that provides controls for new active recordings.
Takes a video to a given file descriptor.
Currently, file descriptors as output destinations are not supported on pre-Android O
(API 26) devices.
Only a single recording can be active at a time, so if CameraController.isRecording() is true,
this will throw an java.lang.IllegalStateException
.
Upon successfully starting the recording, a event will
be the first event sent to the provided event listener.
If errors occur while starting the recording, a event
will be the first event sent to the provided listener, and information about the error can
be found in that event's method.
Recording with audio requires the
permission; without it, starting a recording will fail with a java.lang.SecurityException
.
Parameters:
outputOptions: The options to store the newly captured video.
audioConfig: The configuration of audio.
executor: The executor that the event listener will be run on.
listener: The event listener to handle video record events.
Returns:
a Recording that provides controls for new active recordings.
Takes a video to MediaStore.
Only a single recording can be active at a time, so if CameraController.isRecording() is
true, this will throw an java.lang.IllegalStateException
.
Upon successfully starting the recording, a event will
be the first event sent to the provided event listener.
If errors occur while starting the recording, a event
will be the first event sent to the provided listener, and information about the error can
be found in that event's method.
Recording with audio requires the
permission; without it, starting a recording will fail with a java.lang.SecurityException
.
Parameters:
outputOptions: The options to store the newly captured video.
audioConfig: The configuration of audio.
executor: The executor that the event listener will be run on.
listener: The event listener to handle video record events.
Returns:
a Recording that provides controls for new active recordings.
public boolean
isRecording()
Returns whether there is an in-progress video recording.
public void
setVideoCaptureQualitySelector(
QualitySelector qualitySelector)
Sets the QualitySelector for CameraController.VIDEO_CAPTURE.
The provided quality selector is used to select the resolution of the recording
depending on the resolutions supported by the camera and codec capabilities.
If no quality selector is provided, the default is
Recorder.DEFAULT_QUALITY_SELECTOR.
Changing the value will reconfigure the camera which will cause video capture to stop. To
avoid this, set the value before controller is bound to lifecycle.
Parameters:
qualitySelector: The quality selector for CameraController.VIDEO_CAPTURE.
See also: QualitySelector
Returns the QualitySelector for CameraController.VIDEO_CAPTURE.
Returns:
the QualitySelector provided to
CameraController.setVideoCaptureQualitySelector(QualitySelector) or the default value of
Recorder.DEFAULT_QUALITY_SELECTOR if no quality selector was provided.
public void
setVideoCaptureMirrorMode(int mirrorMode)
Sets the mirror mode for video capture.
Valid values include: MirrorMode.MIRROR_MODE_OFF,
MirrorMode.MIRROR_MODE_ON and MirrorMode.MIRROR_MODE_ON_FRONT_ONLY.
If not set, it defaults to MirrorMode.MIRROR_MODE_OFF.
See also:
public int
getVideoCaptureMirrorMode()
Gets the mirror mode for video capture.
public void
setVideoCaptureDynamicRange(
DynamicRange dynamicRange)
Sets the DynamicRange for video capture.
The dynamic range specifies how the range of colors, highlights and shadows that
are captured by the video producer are displayed on a display. Some dynamic ranges will
allow the video to make full use of the extended range of brightness of a display when
the video is played back.
The supported dynamic ranges for video capture can be queried through the
VideoCapabilities returned by
Recorder.getVideoCapabilities(CameraInfo) via
VideoCapabilities.getSupportedDynamicRanges().
It is possible to choose a high dynamic range (HDR) with unspecified encoding by providing
DynamicRange.HDR_UNSPECIFIED_10_BIT.
If the dynamic range is not provided, the default value is DynamicRange.SDR.
See also:
Gets the DynamicRange for video capture.
public void
setVideoCaptureTargetFrameRate(<any> targetFrameRate)
Sets the target frame rate range in frames per second for video capture.
This target will be used as a part of the heuristics for the algorithm that determines
the final frame rate range and resolution of all concurrently bound use cases.
It is not guaranteed that this target frame rate will be the final range,
as other use cases as well as frame rate restrictions of the device may affect the
outcome of the algorithm that chooses the actual frame rate.
By default, the value is StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED. For supported
frame rates, see CameraInfo.getSupportedFrameRateRanges().
See also:
public <any>
getVideoCaptureTargetFrameRate()
Gets the target frame rate in frames per second for video capture.
Sets the CameraSelector.
Calling this method with a CameraSelector that resolves to a different camera
will change the camera being used by the controller. If camera initialization is complete,
the controller will immediately rebind use cases with the new CameraSelector;
otherwise, the new CameraSelector will be used when the camera becomes ready.
The default value is CameraSelector.DEFAULT_BACK_CAMERA.
See also: CameraSelector
Checks if the given CameraSelector can be resolved to a camera.
Use this method to check if the device has the given camera.
Only call this method after camera is initialized. e.g. after the
from CameraController.getInitializationFuture() is finished. Calling it prematurely throws
java.lang.IllegalStateException
. Example:
controller.getInitializationFuture().addListener(() -> {
if (controller.hasCamera(cameraSelector)) {
controller.setCameraSelector(cameraSelector);
} else {
// Update UI if the camera is not available.
}
// Attach PreviewView after we know the camera is available.
previewView.setController(controller);
}, ContextCompat.getMainExecutor(requireContext()));
Returns:
true if the CameraSelector can be resolved to a camera.
Gets the CameraSelector.
The default value isCameraSelector.DEFAULT_BACK_CAMERA.
See also: CameraSelector
public boolean
isPinchToZoomEnabled()
Returns whether pinch-to-zoom is enabled.
By default pinch-to-zoom is enabled.
Returns:
true if pinch-to-zoom is enabled.
public void
setPinchToZoomEnabled(boolean enabled)
Enables/disables pinch-to-zoom.
Once enabled, end user can pinch on the PreviewView to zoom in/out if the bound
camera supports zooming.
Parameters:
enabled: true to enable pinch-to-zoom.
public boolean
isTapToFocusEnabled()
Returns whether tap-to-focus is enabled.
By default tap-to-focus is enabled.
Returns:
true if tap-to-focus is enabled.
public void
setTapToFocusEnabled(boolean enabled)
Enables/disables tap-to-focus.
Once enabled, end user can tap on the PreviewView to set focus point.
Parameters:
enabled: true to enable tap-to-focus.
public
LiveData<java.lang.Integer>
getTapToFocusState()
Returns a LiveData with the latest tap-to-focus state.
When tap-to-focus feature is enabled, the LiveData will receive updates of
focusing states. This happens when the end user taps on PreviewView, and then again
when focusing is finished either successfully or unsuccessfully. The following table
displays the states the LiveData can be in, and the possible transitions between
them.
State |
Transition cause |
New State |
TAP_TO_FOCUS_NOT_STARTED |
User taps on PreviewView |
TAP_TO_FOCUS_STARTED |
TAP_TO_FOCUS_SUCCESSFUL |
User taps on PreviewView |
TAP_TO_FOCUS_STARTED |
TAP_TO_FOCUS_UNSUCCESSFUL |
User taps on PreviewView |
TAP_TO_FOCUS_STARTED |
TAP_TO_FOCUS_FAILED |
User taps on PreviewView |
TAP_TO_FOCUS_STARTED |
TAP_TO_FOCUS_STARTED |
Focusing succeeded |
TAP_TO_FOCUS_SUCCESSFUL |
Focusing failed due to lighting and/or camera distance |
TAP_TO_FOCUS_UNSUCCESSFUL |
Focusing failed due to device constraints |
TAP_TO_FOCUS_FAILED |
See also: CameraController.setTapToFocusEnabled(boolean), CameraControl.startFocusAndMetering(FocusMeteringAction)
public
LiveData<ZoomState>
getZoomState()
Returns a LiveData of ZoomState.
The LiveData will be updated whenever the set zoom state has been changed. This can
occur when the application updates the zoom via CameraController.setZoomRatio(float)
or CameraController.setLinearZoom(float). The zoom state can also change anytime a
camera starts up, for example when CameraController.setCameraSelector(CameraSelector) is called.
See also: CameraInfo.getZoomState()
Gets the CameraInfo of the currently attached camera.
For info available directly through CameraController as well as CameraInfo, it's
recommended to use the ones with CameraController, e.g. CameraController.getTorchState() v.s.
CameraInfo.getTorchState(). CameraInfo is a lower-layer API and may require
more steps to achieve the same effect, and will not maintain values when switching between
cameras.
Returns:
The CameraInfo of the current camera. Returns null if camera is not
ready.
See also: Camera.getCameraInfo()
Gets the CameraControl of the currently attached camera.
For controls available directly through CameraController as well as
CameraControl, it's recommended to use the ones with CameraController, e.g.
CameraController.setLinearZoom(float) v.s. CameraControl.setLinearZoom(float).
CameraControl is a lower-layer API and may require more steps to achieve the same effect,
and will not maintain control values when switching between cameras.
Returns:
The CameraControl of the current camera. Returns null if camera is
not ready.
See also: Camera.getCameraControl()
public <any>
setZoomRatio(float zoomRatio)
Sets current zoom by ratio.
Valid zoom values range from ZoomState.getMinZoomRatio() to
ZoomState.getMaxZoomRatio().
If the value is set before the camera is ready, CameraController waits for the
camera to be ready and then sets the zoom ratio.
Parameters:
zoomRatio: The requested zoom ratio.
Returns:
A which is finished when camera is set to the given ratio.
It fails with if there is newer value
being set or camera is closed. If the ratio is out of range, it fails with
java.lang.IllegalArgumentException
. Cancellation of this future is a no-op.
See also: CameraController.getZoomState(), CameraControl.setZoomRatio(float)
public <any>
setLinearZoom(float linearZoom)
Sets current zoom by a linear zoom value ranging from 0f to 1.0f.
LinearZoom 0f represents the minimum zoom while linearZoom 1.0f represents the maximum
zoom. The advantage of linearZoom is that it ensures the field of view (FOV) varies
linearly with the linearZoom value, for use with slider UI elements (while
CameraController.setZoomRatio(float) works well for pinch-zoom gestures).
If the value is set before the camera is ready, CameraController waits for the
camera to be ready and then sets the linear zoom.
Returns:
A which is finished when camera is set to the given ratio.
It fails with if there is newer value
being set or camera is closed. If the ratio is out of range, it fails with
java.lang.IllegalArgumentException
. Cancellation of this future is a no-op.
See also: CameraControl.setLinearZoom(float)
public
LiveData<java.lang.Integer>
getTorchState()
Returns a LiveData of current TorchState.
The torch can be turned on and off via CameraController.enableTorch(boolean) which will trigger
the change event to the returned LiveData.
Returns:
A LiveData containing current torch state.
See also: CameraInfo.getTorchState()
public <any>
enableTorch(boolean torchEnabled)
Enable the torch or disable the torch.
If the value is set before the camera is ready, CameraController waits for the
camera to be ready and then enables the torch.
Parameters:
torchEnabled: true to turn on the torch, false to turn it off.
Returns:
A which is successful when the torch was changed to the
value specified. It fails when it is unable to change the torch state. Cancellation of
this future is a no-op.
See also: CameraControl.enableTorch(boolean)
public void
setEffects(java.util.Set<CameraEffect> effects)
Sets CameraEffect.
Call this method to set a list of active effects. There is maximum one effect per
UseCase. Adding effects with duplicate or invalid targets throws
java.lang.IllegalArgumentException
. Once called, CameraX will rebind the UseCase
with the effects applied. Effects not in the list are automatically removed.
The method throws java.lang.IllegalArgumentException
if the effects combination is not
supported by CameraX. Please see the Javadoc of to
see the supported effects combinations.
Parameters:
effects: The effects applied to camera output.
See also:
public void
clearEffects()
Removes all effects.
Once called, CameraX will remove all the effects and rebind the UseCase.
Creates UseCaseGroup from all the use cases.
Preview is required. If it is null, then controller is not ready. Return
null and ignore other use cases.
Source
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.camera.view;
import static androidx.camera.core.impl.utils.Threads.checkMainThread;
import static androidx.camera.core.impl.utils.executor.CameraXExecutors.directExecutor;
import static androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor;
import static androidx.camera.core.impl.utils.futures.Futures.transform;
import static androidx.camera.view.internal.ScreenFlashUiInfo.ProviderType.PREVIEW_VIEW;
import static androidx.camera.view.internal.ScreenFlashUiInfo.ProviderType.SCREEN_FLASH_VIEW;
import static androidx.core.content.ContextCompat.getMainExecutor;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Matrix;
import android.hardware.camera2.CaptureResult;
import android.os.Build;
import android.util.Range;
import android.util.Rational;
import android.util.Size;
import android.view.Window;
import androidx.annotation.FloatRange;
import androidx.annotation.IntDef;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.annotation.RequiresApi;
import androidx.annotation.RequiresPermission;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.camera.core.AspectRatio;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraControl;
import androidx.camera.core.CameraEffect;
import androidx.camera.core.CameraInfo;
import androidx.camera.core.CameraInfoUnavailableException;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.CameraUnavailableException;
import androidx.camera.core.DynamicRange;
import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.FocusMeteringResult;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCapture.ScreenFlash;
import androidx.camera.core.ImageProxy;
import androidx.camera.core.InitializationException;
import androidx.camera.core.Logger;
import androidx.camera.core.MeteringPoint;
import androidx.camera.core.MeteringPointFactory;
import androidx.camera.core.MirrorMode;
import androidx.camera.core.Preview;
import androidx.camera.core.TorchState;
import androidx.camera.core.UseCase;
import androidx.camera.core.UseCaseGroup;
import androidx.camera.core.ViewPort;
import androidx.camera.core.ZoomState;
import androidx.camera.core.impl.ImageOutputConfig;
import androidx.camera.core.impl.StreamSpec;
import androidx.camera.core.impl.utils.CameraOrientationUtil;
import androidx.camera.core.impl.utils.ContextUtil;
import androidx.camera.core.impl.utils.Threads;
import androidx.camera.core.impl.utils.futures.FutureCallback;
import androidx.camera.core.impl.utils.futures.Futures;
import androidx.camera.core.resolutionselector.AspectRatioStrategy;
import androidx.camera.core.resolutionselector.ResolutionSelector;
import androidx.camera.core.resolutionselector.ResolutionStrategy;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.video.FileDescriptorOutputOptions;
import androidx.camera.video.FileOutputOptions;
import androidx.camera.video.MediaStoreOutputOptions;
import androidx.camera.video.OutputOptions;
import androidx.camera.video.PendingRecording;
import androidx.camera.video.QualitySelector;
import androidx.camera.video.Recorder;
import androidx.camera.video.Recording;
import androidx.camera.video.VideoCapture;
import androidx.camera.video.VideoRecordEvent;
import androidx.camera.view.internal.ScreenFlashUiInfo;
import androidx.camera.view.video.AudioConfig;
import androidx.core.content.PermissionChecker;
import androidx.core.util.Consumer;
import androidx.core.util.Preconditions;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.google.common.util.concurrent.ListenableFuture;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
/**
* The abstract base camera controller class.
*
* <p> This a high level controller that provides most of the CameraX core features
* in a single class. It handles camera initialization, creates and configures {@link UseCase}s.
* It also listens to device motion sensor and set the target rotation for the use cases.
*
* <p> The controller is required to be used with a {@link PreviewView}. {@link PreviewView}
* provides the UI elements to display camera preview. The layout of the {@link PreviewView} is
* used to set the crop rect so the output from other use cases matches the preview display in a
* WYSIWYG way. The controller also listens to {@link PreviewView}'s touch events to handle
* tap-to-focus and pinch-to-zoom features.
*
* <p> This class provides features of 4 {@link UseCase}s: {@link Preview}, {@link ImageCapture},
* {@link ImageAnalysis} and {@link VideoCapture}. {@link Preview} is required and always enabled.
* {@link ImageCapture} and {@link ImageAnalysis} are enabled by default. The video capture is
* disabled by default because it might affect other use cases, especially on lower end devices.
* It might be necessary to disable {@link ImageCapture} and/or {@link ImageAnalysis} before the
* video capture feature can be enabled. Disabling/enabling {@link UseCase}s freezes the preview for
* a short period of time. To avoid the glitch, the {@link UseCase}s need to be enabled/disabled
* before the controller is set on {@link PreviewView}.
*/
public abstract class CameraController {
private static final String TAG = "CameraController";
// Externally visible error messages.
private static final String CAMERA_NOT_INITIALIZED = "Camera not initialized.";
private static final String PREVIEW_VIEW_NOT_ATTACHED =
"PreviewView not attached to CameraController.";
private static final String CAMERA_NOT_ATTACHED = "Use cases not attached to camera.";
private static final String IMAGE_CAPTURE_DISABLED = "ImageCapture disabled.";
private static final String VIDEO_CAPTURE_DISABLED = "VideoCapture disabled.";
private static final String VIDEO_RECORDING_UNFINISHED = "Recording video. Only one recording"
+ " can be active at a time.";
// Auto focus is 1/6 of the area.
private static final float AF_SIZE = 1.0f / 6.0f;
private static final float AE_SIZE = AF_SIZE * 1.5f;
/**
* {@link ImageAnalysis.Analyzer} option for returning {@link PreviewView} coordinates.
*
* <p>When the {@link ImageAnalysis.Analyzer} is configured with this option, it will receive a
* {@link Matrix} that will receive a value that represents the transformation from camera
* sensor to the {@link PreviewView}, which can be used for highlighting detected result in
* {@link PreviewView}. For example, laying over a bounding box on top of the detected face.
*
* <p>Note this option only works if the {@link ImageAnalysis.Analyzer} is set via
* {@link CameraController#setImageAnalysisAnalyzer}. It will not be effective when used with
* camera-core directly.
*
* @see ImageAnalysis.Analyzer
* @deprecated Use {@link ImageAnalysis#COORDINATE_SYSTEM_VIEW_REFERENCED} instead.
*/
@Deprecated
public static final int COORDINATE_SYSTEM_VIEW_REFERENCED = 1;
/**
* No tap-to-focus action has been started by the end user.
*/
public static final int TAP_TO_FOCUS_NOT_STARTED = 0;
/**
* A tap-to-focus action has started but not completed. The app also gets notified with this
* state if a new action happens before the previous one could finish.
*/
public static final int TAP_TO_FOCUS_STARTED = 1;
/**
* The previous tap-to-focus action was completed successfully and the camera is focused.
*/
public static final int TAP_TO_FOCUS_FOCUSED = 2;
/**
* The previous tap-to-focus action was completed successfully but the camera is still
* unfocused, similar to the {@link CaptureResult#CONTROL_AF_STATE_NOT_FOCUSED_LOCKED} state.
* The end user might be able to get a better result by trying again with different camera
* distances and/or lighting.
*/
public static final int TAP_TO_FOCUS_NOT_FOCUSED = 3;
/**
* The previous tap-to-focus action was failed to complete. This is usually due to device
* limitations.
*/
public static final int TAP_TO_FOCUS_FAILED = 4;
/**
* Bitmask options to enable/disable use cases.
*/
@Retention(RetentionPolicy.SOURCE)
@RestrictTo(RestrictTo.Scope.LIBRARY)
@IntDef(flag = true, value = {IMAGE_CAPTURE, IMAGE_ANALYSIS, VIDEO_CAPTURE})
public @interface UseCases {
}
/**
* Bitmask option to enable {@link ImageCapture}. In {@link #setEnabledUseCases}, if
* {@code (enabledUseCases & IMAGE_CAPTURE) != 0}, then controller will enable image capture
* features.
*/
public static final int IMAGE_CAPTURE = 1;
/**
* Bitmask option to enable {@link ImageAnalysis}. In {@link #setEnabledUseCases}, if
* {@code (enabledUseCases & IMAGE_ANALYSIS) != 0}, then controller will enable image
* analysis features.
*/
public static final int IMAGE_ANALYSIS = 1 << 1;
/**
* Bitmask option to enable video capture use case. In {@link #setEnabledUseCases}, if
* {@code (enabledUseCases & VIDEO_CAPTURE) != 0}, then controller will enable video capture
* features.
*/
public static final int VIDEO_CAPTURE = 1 << 2;
private static final ScreenFlash NO_OP_SCREEN_FLASH =
new ScreenFlash() {
@Override
public void apply(long expirationTimeMillis,
@NonNull ImageCapture.ScreenFlashListener screenFlashListener) {
screenFlashListener.onCompleted();
}
@Override
public void clear() {
}
};
CameraSelector mCameraSelector = CameraSelector.DEFAULT_BACK_CAMERA;
// By default, ImageCapture and ImageAnalysis are enabled. VideoCapture is disabled.
private int mEnabledUseCases = IMAGE_CAPTURE | IMAGE_ANALYSIS;
// CameraController and PreviewView hold reference to each other. The 2-way link is managed
// by PreviewView.
// Synthetic access
@SuppressWarnings("WeakerAccess")
@NonNull
Preview mPreview;
@Nullable
OutputSize mPreviewTargetSize;
@Nullable
ResolutionSelector mPreviewResolutionSelector;
// Synthetic access
@SuppressWarnings("WeakerAccess")
@NonNull
ImageCapture mImageCapture;
@Nullable
OutputSize mImageCaptureTargetSize;
@Nullable
ResolutionSelector mImageCaptureResolutionSelector;
@Nullable
Executor mImageCaptureIoExecutor;
@Nullable
private Executor mAnalysisExecutor;
@Nullable
private Executor mAnalysisBackgroundExecutor;
@Nullable
private ImageAnalysis.Analyzer mAnalysisAnalyzer;
@NonNull
ImageAnalysis mImageAnalysis;
@Nullable
OutputSize mImageAnalysisTargetSize;
@Nullable
ResolutionSelector mImageAnalysisResolutionSelector;
@NonNull
VideoCapture<Recorder> mVideoCapture;
@Nullable
Recording mActiveRecording = null;
@NonNull
Map<Consumer<VideoRecordEvent>, Recording> mRecordingMap = new HashMap<>();
@NonNull
QualitySelector mVideoCaptureQualitySelector = Recorder.DEFAULT_QUALITY_SELECTOR;
@MirrorMode.Mirror
private int mVideoCaptureMirrorMode = MirrorMode.MIRROR_MODE_OFF;
@NonNull
private DynamicRange mVideoCaptureDynamicRange = DynamicRange.UNSPECIFIED;
@NonNull
private DynamicRange mPreviewDynamicRange = DynamicRange.UNSPECIFIED;
@NonNull
private Range<Integer> mVideoCaptureTargetFrameRate = StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED;
// The latest bound camera.
// Synthetic access
@SuppressWarnings("WeakerAccess")
@Nullable
Camera mCamera;
// Synthetic access
@SuppressWarnings("WeakerAccess")
@Nullable
ProcessCameraProviderWrapper mCameraProvider;
// Synthetic access
@SuppressWarnings("WeakerAccess")
@Nullable
ViewPort mViewPort;
// Synthetic access
@SuppressWarnings("WeakerAccess")
@Nullable
Preview.SurfaceProvider mSurfaceProvider;
private final RotationProvider mRotationProvider;
@VisibleForTesting
@NonNull
final RotationProvider.Listener mDeviceRotationListener;
private boolean mPinchToZoomEnabled = true;
private boolean mTapToFocusEnabled = true;
private final ForwardingLiveData<ZoomState> mZoomState = new ForwardingLiveData<>();
private final ForwardingLiveData<Integer> mTorchState = new ForwardingLiveData<>();
// Synthetic access
@SuppressWarnings("WeakerAccess")
final MutableLiveData<Integer> mTapToFocusState = new MutableLiveData<>(
TAP_TO_FOCUS_NOT_STARTED);
@NonNull
private final PendingValue<Boolean> mPendingEnableTorch = new PendingValue<>();
@NonNull
private final PendingValue<Float> mPendingLinearZoom = new PendingValue<>();
@NonNull
private final PendingValue<Float> mPendingZoomRatio = new PendingValue<>();
@NonNull
private final Set<CameraEffect> mEffects = new HashSet<>();
private final Context mAppContext;
@NonNull
private final ListenableFuture<Void> mInitializationFuture;
private final Map<ScreenFlashUiInfo.ProviderType, ScreenFlashUiInfo>
mScreenFlashUiInfoMap = new HashMap<>();
CameraController(@NonNull Context context) {
this(context, transform(ProcessCameraProvider.getInstance(context),
ProcessCameraProviderWrapperImpl::new, directExecutor()));
}
CameraController(@NonNull Context context,
@NonNull ListenableFuture<ProcessCameraProviderWrapper> cameraProviderFuture) {
mAppContext = ContextUtil.getApplicationContext(context);
mPreview = createPreview();
mImageCapture = createImageCapture(null);
mImageAnalysis = createImageAnalysis(null, null, null);
mVideoCapture = createVideoCapture();
// Wait for camera to be initialized before binding use cases.
mInitializationFuture = transform(
cameraProviderFuture,
provider -> {
mCameraProvider = provider;
unbindAllAndRecreate();
startCameraAndTrackStates();
return null;
}, mainThreadExecutor());
// Listen for device rotation changes and set target rotation for non-preview use cases.
// The output of non-preview use cases need to be corrected in fixed landscape/portrait
// mode.
mRotationProvider = new RotationProvider(mAppContext);
mDeviceRotationListener = rotation -> {
mImageAnalysis.setTargetRotation(rotation);
mImageCapture.setTargetRotation(rotation);
mVideoCapture.setTargetRotation(rotation);
};
}
/**
* Gets a {@link ListenableFuture} that completes when camera initialization completes.
*
* <p>This future may fail with an {@link InitializationException} and associated cause that
* can be retrieved by {@link Throwable#getCause()}. The cause will be a
* {@link CameraUnavailableException} if it fails to access any camera during initialization.
*
* <p>In the rare case that the future fails with {@link CameraUnavailableException}, the
* camera will become unusable. This could happen for various reasons, for example hardware
* failure or the camera being held by another process. If the failure is temporary, killing
* and restarting the app might fix the issue.
*
* <p>The initialization also try to bind use cases before completing the
* {@link ListenableFuture}. The {@link ListenableFuture} will complete successfully
* regardless of whether the use cases are ready to be bound, e.g. it will complete
* successfully even if the controller is not set on a {@link PreviewView}. However the
* {@link ListenableFuture} will fail if the enabled use cases are not supported by the
* current camera.
*
* @see ProcessCameraProvider#getInstance
*/
@NonNull
public ListenableFuture<Void> getInitializationFuture() {
return mInitializationFuture;
}
/**
* Implemented by children to refresh after {@link UseCase} is changed.
*
* @throws IllegalStateException For invalid {@link UseCase} combinations.
* @throws RuntimeException For invalid {@link CameraEffect} combinations.
*/
@Nullable
abstract Camera startCamera();
private boolean isCameraInitialized() {
return mCameraProvider != null;
}
private boolean isPreviewViewAttached() {
return mSurfaceProvider != null && mViewPort != null;
}
private boolean isCameraAttached() {
return mCamera != null;
}
/**
* Enables or disables use cases.
*
* <p>Use cases need to be enabled before they can be used. By default, {@link #IMAGE_CAPTURE}
* and {@link #IMAGE_ANALYSIS} are enabled, and {@link #VIDEO_CAPTURE} is disabled. This is
* necessary because {@link #VIDEO_CAPTURE} may affect the available resolutions for other use
* cases, especially on lower end devices.
*
* <p>To make sure getting the maximum resolution, only enable {@link #VIDEO_CAPTURE} when
* shooting video. For example:
*
* <pre><code>
* // By default, image capture is enabled. Taking picture works.
* controller.takePicture(...);
*
* // Switch to video capture to shoot video.
* controller.setEnabledUseCases(VIDEO_CAPTURE);
* controller.startRecording(...);
*
* // Switch back to image capture and image analysis before taking another picture.
* controller.setEnabledUseCases(IMAGE_CAPTURE|IMAGE_ANALYSIS);
* controller.takePicture(...);
*
* </code></pre>
*
* @param enabledUseCases one or more of the following use cases, bitwise-OR-ed together:
* {@link #IMAGE_CAPTURE}, {@link #IMAGE_ANALYSIS} and/or {@link #VIDEO_CAPTURE}.
* @throws IllegalStateException If the current camera selector is unable to resolve a camera
* to be used for the enabled use cases.
* @see UseCase
* @see ImageCapture
* @see ImageAnalysis
*/
@MainThread
public void setEnabledUseCases(@UseCases int enabledUseCases) {
checkMainThread();
if (enabledUseCases == mEnabledUseCases) {
return;
}
int oldEnabledUseCases = mEnabledUseCases;
mEnabledUseCases = enabledUseCases;
if (!isVideoCaptureEnabled() && isRecording()) {
stopRecording();
}
startCameraAndTrackStates(() -> mEnabledUseCases = oldEnabledUseCases);
}
/**
* Checks if the given use case mask is enabled.
*
* @param useCaseMask One of the {@link #IMAGE_CAPTURE}, {@link #IMAGE_ANALYSIS} or
* {@link #VIDEO_CAPTURE}.
* @return {@code true} if the use case is enabled.
*/
private boolean isUseCaseEnabled(int useCaseMask) {
return (mEnabledUseCases & useCaseMask) != 0;
}
/**
* Configures the resolution on the config.
*
* <p>If the {@link ResolutionSelector} and the {@link OutputSize} are set at the same time,
* the {@link ResolutionSelector} takes precedence.
*
* <p>If the given {@link ResolutionSelector} is {@code null} and {@link OutputSize}, the
* {@link AspectRatioStrategy} will be override to match the {@link ViewPort}.
*/
private void configureResolution(@NonNull ImageOutputConfig.Builder<?> builder,
@Nullable ResolutionSelector resolutionSelector, @Nullable OutputSize outputSize) {
if (resolutionSelector != null) {
builder.setResolutionSelector(resolutionSelector);
} else if (outputSize != null) {
setTargetOutputSize(builder, outputSize);
} else if (mViewPort != null) {
// Override the aspect ratio strategy if viewport is set and there's no resolution
// selector explicitly set by the user.
AspectRatioStrategy aspectRatioStrategy = getViewportAspectRatioStrategy(mViewPort);
if (aspectRatioStrategy != null) {
builder.setResolutionSelector(
new ResolutionSelector.Builder().setAspectRatioStrategy(
aspectRatioStrategy).build());
}
}
}
/**
* Sets the target aspect ratio or target resolution based on {@link OutputSize}.
*/
private void setTargetOutputSize(@NonNull ImageOutputConfig.Builder<?> builder,
@Nullable OutputSize outputSize) {
if (outputSize == null) {
return;
}
if (outputSize.getResolution() != null) {
builder.setTargetResolution(outputSize.getResolution());
} else if (outputSize.getAspectRatio() != OutputSize.UNASSIGNED_ASPECT_RATIO) {
builder.setTargetAspectRatio(outputSize.getAspectRatio());
} else {
Logger.e(TAG, "Invalid target surface size. " + outputSize);
}
}
/**
* Checks if two {@link OutputSize} are equal.
*/
private boolean isOutputSizeEqual(
@Nullable OutputSize currentSize,
@Nullable OutputSize newSize) {
if (currentSize == newSize) {
return true;
}
return currentSize != null && currentSize.equals(newSize);
}
// ------------------
// Preview use case.
// ------------------
/**
* Internal API used by {@link PreviewView} to notify changes.
*/
@SuppressLint({"MissingPermission", "WrongConstant"})
@MainThread
void attachPreviewSurface(@NonNull Preview.SurfaceProvider surfaceProvider,
@NonNull ViewPort viewPort) {
checkMainThread();
if (mSurfaceProvider != surfaceProvider) {
mSurfaceProvider = surfaceProvider;
mPreview.setSurfaceProvider(surfaceProvider);
}
boolean shouldUpdateAspectRatio = mViewPort == null || getViewportAspectRatioStrategy(
viewPort) != getViewportAspectRatioStrategy(mViewPort);
mViewPort = viewPort;
startListeningToRotationEvents();
if (shouldUpdateAspectRatio) {
unbindAllAndRecreate();
}
startCameraAndTrackStates();
}
/**
* Clears {@link PreviewView} to remove the UI reference.
*/
@MainThread
void clearPreviewSurface() {
checkMainThread();
if (mCameraProvider != null) {
// Preview is required. Unbind everything if Preview is down.
mCameraProvider.unbind(mPreview, mImageCapture, mImageAnalysis, mVideoCapture);
}
mPreview.setSurfaceProvider(null);
mCamera = null;
mSurfaceProvider = null;
mViewPort = null;
stopListeningToRotationEvents();
}
private void startListeningToRotationEvents() {
mRotationProvider.addListener(mainThreadExecutor(),
mDeviceRotationListener);
}
private void stopListeningToRotationEvents() {
mRotationProvider.removeListener(mDeviceRotationListener);
}
/**
* Sets the intended output size for {@link Preview}.
*
* <p>The value is used as a hint when determining the resolution and aspect ratio of the
* preview. The actual output may differ from the requested value due to device constraints.
*
* <p>When set to {@code null}, the output will be based on the default config of
* {@link Preview}.
*
* <p>Changing the value will reconfigure the camera which will cause additional latency. To
* avoid this, set the value before controller is bound to lifecycle.
*
* @param targetSize the intended output size for {@link Preview}.
* @see Preview.Builder#setTargetAspectRatio(int)
* @see Preview.Builder#setTargetResolution(Size)
* @deprecated Use {@link #setPreviewResolutionSelector(ResolutionSelector)} instead.
*/
@MainThread
@Deprecated
public void setPreviewTargetSize(@Nullable OutputSize targetSize) {
checkMainThread();
if (isOutputSizeEqual(mPreviewTargetSize, targetSize)) {
return;
}
mPreviewTargetSize = targetSize;
unbindPreviewAndRecreate();
startCameraAndTrackStates();
}
/**
* Returns the intended output size for {@link Preview} set by
* {@link #setPreviewTargetSize(OutputSize)}, or {@code null} if not set.
*
* @deprecated Use {@link #getPreviewResolutionSelector()} instead.
*/
@MainThread
@Deprecated
@Nullable
public OutputSize getPreviewTargetSize() {
checkMainThread();
return mPreviewTargetSize;
}
/**
* Sets the {@link ResolutionSelector} for {@link Preview}.
*
* <p>CameraX uses this value as a hint to select the resolution for preview. The actual
* output may differ from the requested value due to device constraints. When set to
* {@code null}, CameraX will use the default config of {@link Preview}. By default, the
* selected resolution will be limited by the {@code PREVIEW} size which is defined as the best
* size match to the device's screen resolution, or to 1080p (1920x1080), whichever is smaller.
*
* <p>Changing the value will reconfigure the camera which will cause additional latency. To
* avoid this, set the value before controller is bound to lifecycle.
*
* @see Preview.Builder#setResolutionSelector(ResolutionSelector)
*/
@MainThread
public void setPreviewResolutionSelector(@Nullable ResolutionSelector resolutionSelector) {
checkMainThread();
if (mPreviewResolutionSelector == resolutionSelector) {
return;
}
mPreviewResolutionSelector = resolutionSelector;
unbindPreviewAndRecreate();
startCameraAndTrackStates();
}
/**
* Returns the {@link ResolutionSelector} for {@link Preview}.
*
* <p>This method returns the value set by
* {@link #setPreviewResolutionSelector(ResolutionSelector)}. It returns {@code null} if
* the value has not been set.
*/
@Nullable
@MainThread
public ResolutionSelector getPreviewResolutionSelector() {
checkMainThread();
return mPreviewResolutionSelector;
}
/**
* Sets the {@link DynamicRange} for preview.
*
* <p>The dynamic range specifies how the range of colors, highlights and shadows that
* are displayed on a display. Some dynamic ranges will allow the preview to make full use of
* the extended range of brightness of a display.
*
* <p>The supported dynamic ranges of the camera can be queried using
* {@link CameraInfo#querySupportedDynamicRanges(Set)}.
*
* <p>It is possible to choose a high dynamic range (HDR) with unspecified encoding by providing
* {@link DynamicRange#HDR_UNSPECIFIED_10_BIT}. If the dynamic range is not provided, the
* default value is {@link DynamicRange#SDR}.
*
* @see Preview.Builder#setDynamicRange(DynamicRange)
*
* TODO: make this public in the next release.
*/
@MainThread
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public void setPreviewDynamicRange(@NonNull DynamicRange dynamicRange) {
checkMainThread();
mPreviewDynamicRange = dynamicRange;
unbindPreviewAndRecreate();
startCameraAndTrackStates();
}
/**
* Gets the {@link DynamicRange} for preview.
*
* TODO: make this public in the next release.
*/
@MainThread
@NonNull
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public DynamicRange getPreviewDynamicRange() {
checkMainThread();
return mPreviewDynamicRange;
}
/**
* Unbinds {@link Preview} and recreates with the latest parameters.
*/
@MainThread
private void unbindPreviewAndRecreate() {
if (isCameraInitialized()) {
mCameraProvider.unbind(mPreview);
}
mPreview = createPreview();
if (mSurfaceProvider != null) {
mPreview.setSurfaceProvider(mSurfaceProvider);
}
}
private Preview createPreview() {
Preview.Builder builder = new Preview.Builder();
configureResolution(builder, mPreviewResolutionSelector, mPreviewTargetSize);
builder.setDynamicRange(mPreviewDynamicRange);
return builder.build();
}
// ----------------------
// ImageCapture UseCase.
// ----------------------
/**
* Checks if {@link ImageCapture} is enabled.
*
* <p>{@link ImageCapture} is enabled by default. It has to be enabled before
* {@link #takePicture} can be called.
*
* @see ImageCapture
*/
@MainThread
public boolean isImageCaptureEnabled() {
checkMainThread();
return isUseCaseEnabled(IMAGE_CAPTURE);
}
/**
* Gets the flash mode for {@link ImageCapture}.
*
* @return the flashMode. Value is {@link ImageCapture#FLASH_MODE_AUTO},
* {@link ImageCapture#FLASH_MODE_ON}, or {@link ImageCapture#FLASH_MODE_OFF}.
* @see ImageCapture
*/
@MainThread
@ImageCapture.FlashMode
public int getImageCaptureFlashMode() {
checkMainThread();
return mImageCapture.getFlashMode();
}
/**
* Sets the flash mode for {@link ImageCapture}.
*
* <p>If not set, the flash mode will default to {@link ImageCapture#FLASH_MODE_OFF}.
*
* <p>If {@link ImageCapture#FLASH_MODE_SCREEN} is set, a valid {@link android.view.Window}
* instance must be set to a {@link PreviewView} or {@link ScreenFlashView} which this
* controller is set to. Trying to use {@link ImageCapture#FLASH_MODE_SCREEN} with a
* non-front camera or without setting a non-null window will be no-op. While switching the
* camera, it is the application's responsibility to change flash mode to the desired one if
* it leads to a no-op case (e.g. switching to rear camera while {@code FLASH_MODE_SCREEN} is
* still set). Otherwise, {@code FLASH_MODE_OFF} will be set.
*
* @param flashMode the flash mode for {@link ImageCapture}.
* @throws IllegalArgumentException If flash mode is invalid or
* {@link ImageCapture#FLASH_MODE_SCREEN} is used without a front camera.
* @see PreviewView#setScreenFlashWindow(Window)
* @see ScreenFlashView#setScreenFlashWindow(Window)
*/
@MainThread
public void setImageCaptureFlashMode(@ImageCapture.FlashMode int flashMode) {
checkMainThread();
if (flashMode == ImageCapture.FLASH_MODE_SCREEN) {
Integer lensFacing = mCameraSelector.getLensFacing();
if (lensFacing != null && lensFacing != CameraSelector.LENS_FACING_FRONT) {
throw new IllegalArgumentException(
"Not a front camera despite setting FLASH_MODE_SCREEN");
}
updateScreenFlashToImageCapture();
}
mImageCapture.setFlashMode(flashMode);
}
/**
* Internal API used by {@link PreviewView} and {@link ScreenFlashView} to provide a
* {@link ScreenFlash}.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public void setScreenFlashUiInfo(@NonNull ScreenFlashUiInfo screenFlashUiInfo) {
ScreenFlashUiInfo previousInfo = getScreenFlashUiInfoByPriority();
mScreenFlashUiInfoMap.put(screenFlashUiInfo.getProviderType(), screenFlashUiInfo);
ScreenFlashUiInfo prioritizedInfo = getScreenFlashUiInfoByPriority();
if (prioritizedInfo != null && !prioritizedInfo.equals(previousInfo)) {
updateScreenFlashToImageCapture();
}
}
/**
* Internal API used by {@link PreviewView} and {@link ScreenFlashView} to update screen
* flash mode to ImageCapture in case it's pending.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public void updateScreenFlashToImageCapture() {
ScreenFlashUiInfo screenFlashUiInfo = getScreenFlashUiInfoByPriority();
if (screenFlashUiInfo == null) {
// PreviewView/ScreenFlashView may have not been attached yet, so setting a NO-OP
// ScreenFlash until one of the views is attached
Logger.d(TAG, "No ScreenFlash instance set yet, need to wait for "
+ "controller to be set to either ScreenFlashView or PreviewView");
mImageCapture.setScreenFlash(NO_OP_SCREEN_FLASH);
return;
}
mImageCapture.setScreenFlash(screenFlashUiInfo.getScreenFlash());
Logger.d(TAG, "Set ScreenFlash instance to ImageCapture, provided by "
+ screenFlashUiInfo.getProviderType().name());
}
/**
* Returns a {@link ScreenFlashUiInfo} by prioritizing {@link ScreenFlashView} over
* {@link PreviewView}.
*
* <p>{@link PreviewView} always has a {@link ScreenFlashView} internally and does not know
* if user is using another {@link ScreenFlashView} themselves. This API prioritizes user's
* {@link ScreenFlashView} over the internal one in {@link PreviewView} and provides the
* {@link ScreenFlashUiInfo} accordingly.
*/
@Nullable
@RestrictTo(RestrictTo.Scope.LIBRARY)
public ScreenFlashUiInfo getScreenFlashUiInfoByPriority() {
if (mScreenFlashUiInfoMap.get(SCREEN_FLASH_VIEW) != null) {
return mScreenFlashUiInfoMap.get(SCREEN_FLASH_VIEW);
}
if (mScreenFlashUiInfoMap.get(PREVIEW_VIEW) != null) {
return mScreenFlashUiInfoMap.get(PREVIEW_VIEW);
}
return null;
}
/**
* Captures a new still image and saves to a file along with application specified metadata.
*
* <p>The callback will be called only once for every invocation of this method.
*
* <p>By default, the saved image is mirrored to match the output of the preview if front
* camera is used. To override this behavior, the app needs to explicitly set the flag to
* {@code false} using {@link ImageCapture.Metadata#setReversedHorizontal} and
* {@link ImageCapture.OutputFileOptions.Builder#setMetadata}.
*
* <p>The saved image is cropped to match the aspect ratio of the {@link PreviewView}. To
* take a picture with the maximum available resolution, make sure that the
* {@link PreviewView}'s aspect ratio matches the max JPEG resolution supported by the camera.
*
* @param outputFileOptions Options to store the newly captured image.
* @param executor The executor in which the callback methods will be run.
* @param imageSavedCallback Callback to be called for the newly captured image.
* @throws IllegalStateException If {@link ImageCapture#FLASH_MODE_SCREEN} is set to the
* {@link CameraController}, but a non-null {@link Window} instance has not been set with
* {@link PreviewView#setScreenFlashWindow}.
* @see ImageCapture#takePicture(
* ImageCapture.OutputFileOptions, Executor, ImageCapture.OnImageSavedCallback)
*/
@MainThread
public void takePicture(
@NonNull ImageCapture.OutputFileOptions outputFileOptions,
@NonNull Executor executor,
@NonNull ImageCapture.OnImageSavedCallback imageSavedCallback) {
checkMainThread();
Preconditions.checkState(isCameraInitialized(), CAMERA_NOT_INITIALIZED);
Preconditions.checkState(isImageCaptureEnabled(), IMAGE_CAPTURE_DISABLED);
throwExceptionForInvalidScreenFlashCapture();
updateMirroringFlagInOutputFileOptions(outputFileOptions);
mImageCapture.takePicture(outputFileOptions, executor, imageSavedCallback);
}
/**
* Updates {@link ImageCapture.OutputFileOptions} based on config.
*
* <p>Mirror the output image if front camera is used and if the flag is not set explicitly by
* the app.
*/
@VisibleForTesting
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
void updateMirroringFlagInOutputFileOptions(
@NonNull ImageCapture.OutputFileOptions outputFileOptions) {
if (mCameraSelector.getLensFacing() != null
&& !outputFileOptions.getMetadata().isReversedHorizontalSet()) {
outputFileOptions.getMetadata().setReversedHorizontal(
mCameraSelector.getLensFacing() == CameraSelector.LENS_FACING_FRONT);
}
}
/**
* Captures a new still image for in memory access.
*
* <p>The listener is responsible for calling {@link ImageProxy#close()} on the returned image.
*
* @param executor The executor in which the callback methods will be run.
* @param callback Callback to be invoked for the newly captured image
* @throws IllegalStateException If {@link ImageCapture#FLASH_MODE_SCREEN} is set to the
* {@link CameraController}, but a non-null {@link Window} instance has not been set with
* {@link PreviewView#setScreenFlashWindow}.
* @see ImageCapture#takePicture(Executor, ImageCapture.OnImageCapturedCallback)
*/
@MainThread
public void takePicture(
@NonNull Executor executor,
@NonNull ImageCapture.OnImageCapturedCallback callback) {
checkMainThread();
Preconditions.checkState(isCameraInitialized(), CAMERA_NOT_INITIALIZED);
Preconditions.checkState(isImageCaptureEnabled(), IMAGE_CAPTURE_DISABLED);
throwExceptionForInvalidScreenFlashCapture();
mImageCapture.takePicture(executor, callback);
}
private void throwExceptionForInvalidScreenFlashCapture() {
if (getImageCaptureFlashMode() == ImageCapture.FLASH_MODE_SCREEN && (
getScreenFlashUiInfoByPriority() == null
|| getScreenFlashUiInfoByPriority().getScreenFlash() == null)) {
// ScreenFlash instance won't be found at this point only if a non-null window was not
// set to PreviewView.
throw new IllegalStateException(
"No window set in PreviewView despite setting FLASH_MODE_SCREEN");
}
}
/**
* Sets the image capture mode.
*
* <p>Valid capture modes are {@link ImageCapture.CaptureMode#CAPTURE_MODE_MINIMIZE_LATENCY},
* which prioritizes latency over image quality, or
* {@link ImageCapture.CaptureMode#CAPTURE_MODE_MAXIMIZE_QUALITY},
* which prioritizes image quality over latency.
*
* <p>Changing the value will reconfigure the camera which will cause additional latency. To
* avoid this, set the value before controller is bound to lifecycle.
*
* @param captureMode The requested image capture mode.
*/
@MainThread
public void setImageCaptureMode(@ImageCapture.CaptureMode int captureMode) {
checkMainThread();
if (mImageCapture.getCaptureMode() == captureMode) {
return;
}
unbindImageCaptureAndRecreate(captureMode);
startCameraAndTrackStates();
}
/**
* Returns the image capture mode.
*
* @see ImageCapture#getCaptureMode()
*/
@MainThread
public int getImageCaptureMode() {
checkMainThread();
return mImageCapture.getCaptureMode();
}
/**
* Sets the intended image size for {@link ImageCapture}.
*
* <p>The value is used as a hint when determining the resolution and aspect ratio of the
* captured image. The actual output may differ from the requested value due to device
* constraints.
*
* <p>When set to {@code null}, the output will be based on the default config of
* {@link ImageCapture}.
*
* <p>Changing the value will reconfigure the camera which will cause additional latency. To
* avoid this, set the value before controller is bound to lifecycle.
*
* @param targetSize The intended image size for {@link ImageCapture}.
* @deprecated Use {@link #setImageCaptureResolutionSelector(ResolutionSelector)} instead.
*/
@MainThread
@Deprecated
public void setImageCaptureTargetSize(@Nullable OutputSize targetSize) {
checkMainThread();
if (isOutputSizeEqual(mImageCaptureTargetSize, targetSize)) {
return;
}
mImageCaptureTargetSize = targetSize;
unbindImageCaptureAndRecreate(getImageCaptureMode());
startCameraAndTrackStates();
}
/**
* Returns the intended output size for {@link ImageCapture} set by
* {@link #setImageCaptureTargetSize(OutputSize)}, or {@code null} if not set.
*
* @deprecated Use {@link #getImageCaptureResolutionSelector()} instead.
*/
@Deprecated
@MainThread
@Nullable
public OutputSize getImageCaptureTargetSize() {
checkMainThread();
return mImageCaptureTargetSize;
}
/**
* Sets the {@link ResolutionSelector} for {@link ImageCapture}.
*
* <p>CameraX uses this value as a hint to select the resolution for captured images. The actual
* output may differ from the requested value due to device constraints. When set to
* {@code null}, CameraX will use the default config of {@link ImageCapture}. The default
* resolution strategy for ImageCapture is
* {@link ResolutionStrategy#HIGHEST_AVAILABLE_STRATEGY}, which will select the largest
* available resolution to use.
*
* <p>Changing the value will reconfigure the camera which will cause additional latency. To
* avoid this, set the value before controller is bound to lifecycle.
*
* @see ImageCapture.Builder#setResolutionSelector(ResolutionSelector)
*/
@MainThread
public void setImageCaptureResolutionSelector(@Nullable ResolutionSelector resolutionSelector) {
checkMainThread();
if (mImageCaptureResolutionSelector == resolutionSelector) {
return;
}
mImageCaptureResolutionSelector = resolutionSelector;
unbindImageCaptureAndRecreate(getImageCaptureMode());
startCameraAndTrackStates();
}
/**
* Returns the {@link ResolutionSelector} for {@link ImageCapture}.
*
* <p>This method returns the value set by
* {@link #setImageCaptureResolutionSelector} (ResolutionSelector)}. It returns {@code null} if
* the value has not been set.
*/
@MainThread
@Nullable
public ResolutionSelector getImageCaptureResolutionSelector() {
checkMainThread();
return mImageCaptureResolutionSelector;
}
/**
* Sets the default executor that will be used for {@link ImageCapture} IO tasks.
*
* <p>This executor will be used for any IO tasks specifically for {@link ImageCapture},
* such as {@link #takePicture(ImageCapture.OutputFileOptions, Executor,
* ImageCapture.OnImageSavedCallback)}. If no executor is set, then a default Executor
* specifically for IO will be used instead.
*
* <p>Changing the value will reconfigure the camera which will cause additional latency.
* To avoid this, set the value before controller is bound to lifecycle.
*
* @param executor The executor which will be used for IO tasks.
*/
@MainThread
public void setImageCaptureIoExecutor(@Nullable Executor executor) {
checkMainThread();
if (mImageCaptureIoExecutor == executor) {
return;
}
mImageCaptureIoExecutor = executor;
unbindImageCaptureAndRecreate(getImageCaptureMode());
startCameraAndTrackStates();
}
/**
* Gets the default executor for {@link ImageCapture} IO tasks.
*/
@MainThread
@Nullable
public Executor getImageCaptureIoExecutor() {
checkMainThread();
return mImageCaptureIoExecutor;
}
/**
* Unbinds {@link ImageCapture} and recreates with the latest parameters.
*/
@MainThread
private void unbindImageCaptureAndRecreate(Integer imageCaptureMode) {
if (isCameraInitialized()) {
mCameraProvider.unbind(mImageCapture);
}
int flashMode = mImageCapture.getFlashMode();
mImageCapture = createImageCapture(imageCaptureMode);
setImageCaptureFlashMode(flashMode);
}
private ImageCapture createImageCapture(Integer imageCaptureMode) {
ImageCapture.Builder builder = new ImageCapture.Builder();
if (imageCaptureMode != null) {
builder.setCaptureMode(imageCaptureMode);
}
configureResolution(builder, mImageCaptureResolutionSelector, mImageCaptureTargetSize);
if (mImageCaptureIoExecutor != null) {
builder.setIoExecutor(mImageCaptureIoExecutor);
}
return builder.build();
}
// -----------------
// Image analysis
// -----------------
/**
* Checks if {@link ImageAnalysis} is enabled.
*
* @see ImageAnalysis
*/
@MainThread
public boolean isImageAnalysisEnabled() {
checkMainThread();
return isUseCaseEnabled(IMAGE_ANALYSIS);
}
/**
* Sets an analyzer to receive and analyze images.
*
* <p>Applications can process or copy the image by implementing the
* {@link ImageAnalysis.Analyzer}. The image needs to be closed by calling
* {@link ImageProxy#close()} when the analyzing is done.
*
* <p>Setting an analyzer function replaces any previous analyzer. Only one analyzer can be
* set at any time.
*
* <p>If the {@link ImageAnalysis.Analyzer#getTargetCoordinateSystem()} returns
* {@link ImageAnalysis#COORDINATE_SYSTEM_VIEW_REFERENCED}, the analyzer will receive a
* transformation via {@link ImageAnalysis.Analyzer#updateTransform} that converts
* coordinates from the {@link ImageAnalysis}'s coordinate system to the {@link PreviewView}'s
* coordinate system.
*
* <p>If the {@link ImageAnalysis.Analyzer#getDefaultTargetResolution()} returns a non-null
* value, calling this method will reconfigure the camera which might cause additional
* latency. To avoid this, set the value before controller is bound to the lifecycle.
*
* @param executor The executor in which the
* {@link ImageAnalysis.Analyzer#analyze(ImageProxy)} will be run.
* @param analyzer of the images.
* @see ImageAnalysis#setAnalyzer(Executor, ImageAnalysis.Analyzer)
*/
@MainThread
public void setImageAnalysisAnalyzer(@NonNull Executor executor,
@NonNull ImageAnalysis.Analyzer analyzer) {
checkMainThread();
if (mAnalysisAnalyzer == analyzer && mAnalysisExecutor == executor) {
return;
}
ImageAnalysis.Analyzer oldAnalyzer = mAnalysisAnalyzer;
mAnalysisExecutor = executor;
mAnalysisAnalyzer = analyzer;
mImageAnalysis.setAnalyzer(executor, analyzer);
restartCameraIfAnalyzerResolutionChanged(oldAnalyzer, analyzer);
}
/**
* Removes a previously set analyzer.
*
* <p>This will stop data from streaming to the {@link ImageAnalysis}.
*
* <p>If the current {@link ImageAnalysis.Analyzer#getDefaultTargetResolution()} returns
* non-null value, calling this method will reconfigure the camera which might cause additional
* latency. To avoid this, call this method when the lifecycle is not active.
*
* @see ImageAnalysis#clearAnalyzer().
*/
@MainThread
public void clearImageAnalysisAnalyzer() {
checkMainThread();
ImageAnalysis.Analyzer oldAnalyzer = mAnalysisAnalyzer;
mAnalysisExecutor = null;
mAnalysisAnalyzer = null;
mImageAnalysis.clearAnalyzer();
restartCameraIfAnalyzerResolutionChanged(oldAnalyzer, null);
}
private void restartCameraIfAnalyzerResolutionChanged(
@Nullable ImageAnalysis.Analyzer oldAnalyzer,
@Nullable ImageAnalysis.Analyzer newAnalyzer) {
Size oldResolution = oldAnalyzer == null ? null :
oldAnalyzer.getDefaultTargetResolution();
Size newResolution = newAnalyzer == null ? null :
newAnalyzer.getDefaultTargetResolution();
if (!Objects.equals(oldResolution, newResolution)) {
// Rebind ImageAnalysis to reconfigure target resolution.
unbindImageAnalysisAndRecreate(mImageAnalysis.getBackpressureStrategy(),
mImageAnalysis.getImageQueueDepth(), mImageAnalysis.getOutputImageFormat());
startCameraAndTrackStates();
}
}
/**
* Returns the mode with which images are acquired.
*
* <p>If not set, it defaults to {@link ImageAnalysis#STRATEGY_KEEP_ONLY_LATEST}.
*
* @return The backpressure strategy applied to the image producer.
* @see ImageAnalysis.Builder#getBackpressureStrategy()
*/
@MainThread
@ImageAnalysis.BackpressureStrategy
public int getImageAnalysisBackpressureStrategy() {
checkMainThread();
return mImageAnalysis.getBackpressureStrategy();
}
/**
* Sets the backpressure strategy to apply to the image producer to deal with scenarios
* where images may be produced faster than they can be analyzed.
*
* <p>The available values are {@link ImageAnalysis#STRATEGY_BLOCK_PRODUCER} and
* {@link ImageAnalysis#STRATEGY_KEEP_ONLY_LATEST}. If not set, the backpressure strategy
* will default to {@link ImageAnalysis#STRATEGY_KEEP_ONLY_LATEST}.
*
* <p>Changing the value will reconfigure the camera which will cause additional latency. To
* avoid this, set the value before controller is bound to lifecycle.
*
* @param strategy The strategy to use.
* @see ImageAnalysis.Builder#setBackpressureStrategy(int)
*/
@MainThread
public void setImageAnalysisBackpressureStrategy(
@ImageAnalysis.BackpressureStrategy int strategy) {
checkMainThread();
if (mImageAnalysis.getBackpressureStrategy() == strategy) {
return;
}
unbindImageAnalysisAndRecreate(strategy, mImageAnalysis.getImageQueueDepth(),
mImageAnalysis.getOutputImageFormat());
startCameraAndTrackStates();
}
/**
* Sets the image queue depth of {@link ImageAnalysis}.
*
* <p>This sets the number of images available in parallel to {@link ImageAnalysis.Analyzer}.
* The value is only used if the backpressure strategy is
* {@link ImageAnalysis.BackpressureStrategy#STRATEGY_BLOCK_PRODUCER}.
*
* <p>Changing the value will reconfigure the camera which will cause additional latency. To
* avoid this, set the value before controller is bound to lifecycle.
*
* @param depth The total number of images available.
* @see ImageAnalysis.Builder#setImageQueueDepth(int)
*/
@MainThread
public void setImageAnalysisImageQueueDepth(int depth) {
checkMainThread();
if (mImageAnalysis.getImageQueueDepth() == depth) {
return;
}
unbindImageAnalysisAndRecreate(mImageAnalysis.getBackpressureStrategy(), depth,
mImageAnalysis.getOutputImageFormat());
startCameraAndTrackStates();
}
/**
* Gets the image queue depth of {@link ImageAnalysis}.
*
* @see ImageAnalysis#getImageQueueDepth()
*/
@MainThread
public int getImageAnalysisImageQueueDepth() {
checkMainThread();
return mImageAnalysis.getImageQueueDepth();
}
/**
* Sets the intended output size for {@link ImageAnalysis}.
*
* <p>The value is used as a hint when determining the resolution and aspect ratio of
* the output buffer. The actual output may differ from the requested value due to device
* constraints.
*
* <p>When set to {@code null}, the output will be based on the default config of
* {@link ImageAnalysis}.
*
* <p>Changing the value will reconfigure the camera which will cause additional latency.
* To avoid this, set the value before controller is bound to lifecycle.
*
* @param targetSize The intended output size for {@link ImageAnalysis}.
* @see ImageAnalysis.Builder#setTargetAspectRatio(int)
* @see ImageAnalysis.Builder#setTargetResolution(Size)
* @deprecated Use {@link #setImageAnalysisResolutionSelector(ResolutionSelector)} instead.
*/
@MainThread
@Deprecated
public void setImageAnalysisTargetSize(@Nullable OutputSize targetSize) {
checkMainThread();
if (isOutputSizeEqual(mImageAnalysisTargetSize, targetSize)) {
return;
}
mImageAnalysisTargetSize = targetSize;
unbindImageAnalysisAndRecreate(
mImageAnalysis.getBackpressureStrategy(),
mImageAnalysis.getImageQueueDepth(),
mImageAnalysis.getOutputImageFormat());
startCameraAndTrackStates();
}
/**
* Returns the intended output size for {@link ImageAnalysis} set by
* {@link #setImageAnalysisTargetSize(OutputSize)}, or {@code null} if not set.
*
* @deprecated Use {@link #getImageAnalysisResolutionSelector()} instead.
*/
@MainThread
@Nullable
@Deprecated
public OutputSize getImageAnalysisTargetSize() {
checkMainThread();
return mImageAnalysisTargetSize;
}
/**
* Sets the {@link ResolutionSelector} for {@link ImageAnalysis}.
*
* <p>CameraX uses this value as a hint to select the resolution for images. The actual
* output may differ from the requested value due to device constraints. When set to
* {@code null}, CameraX will use the default config of {@link ImageAnalysis}. ImageAnalysis has
* a default {@link ResolutionStrategy} with bound size as 640x480 and fallback rule of
* {@link ResolutionStrategy#FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER}.
*
* <p>Changing the value will reconfigure the camera which will cause additional latency. To
* avoid this, set the value before controller is bound to lifecycle.
*
* @see ImageAnalysis.Builder#setResolutionSelector(ResolutionSelector)
*/
@MainThread
public void setImageAnalysisResolutionSelector(
@Nullable ResolutionSelector resolutionSelector) {
checkMainThread();
if (mImageAnalysisResolutionSelector == resolutionSelector) {
return;
}
mImageAnalysisResolutionSelector = resolutionSelector;
unbindImageAnalysisAndRecreate(
mImageAnalysis.getBackpressureStrategy(),
mImageAnalysis.getImageQueueDepth(),
mImageAnalysis.getOutputImageFormat());
startCameraAndTrackStates();
}
/**
* Returns the {@link ResolutionSelector} for {@link ImageAnalysis}.
*
* <p>This method returns the value set by
* {@link #setImageAnalysisResolutionSelector(ResolutionSelector)}. It returns {@code null} if
* the value has not been set.
*/
@MainThread
@Nullable
public ResolutionSelector getImageAnalysisResolutionSelector() {
checkMainThread();
return mImageAnalysisResolutionSelector;
}
/**
* Sets the executor that will be used for {@link ImageAnalysis} background tasks.
*
* <p>If not set, the background executor will default to an automatically generated
* {@link Executor}.
*
* <p>Changing the value will reconfigure the camera, which will cause additional latency. To
* avoid this, set the value before controller is bound to lifecycle.
*
* @param executor The executor for {@link ImageAnalysis} background tasks.
* @see ImageAnalysis.Builder#setBackgroundExecutor(Executor)
*/
@MainThread
public void setImageAnalysisBackgroundExecutor(@Nullable Executor executor) {
checkMainThread();
if (mAnalysisBackgroundExecutor == executor) {
return;
}
mAnalysisBackgroundExecutor = executor;
unbindImageAnalysisAndRecreate(mImageAnalysis.getBackpressureStrategy(),
mImageAnalysis.getImageQueueDepth(), mImageAnalysis.getOutputImageFormat());
startCameraAndTrackStates();
}
/**
* Gets the default executor for {@link ImageAnalysis} background tasks.
*
* @see ImageAnalysis.Builder#setBackgroundExecutor(Executor)
*/
@MainThread
@Nullable
public Executor getImageAnalysisBackgroundExecutor() {
checkMainThread();
return mAnalysisBackgroundExecutor;
}
/**
* Sets the output image format for {@link ImageAnalysis}.
*
* <p>The supported output image format
* are {@link ImageAnalysis.OutputImageFormat#OUTPUT_IMAGE_FORMAT_YUV_420_888} and
* {@link ImageAnalysis.OutputImageFormat#OUTPUT_IMAGE_FORMAT_RGBA_8888}.
*
* <p>If not set, {@link ImageAnalysis.OutputImageFormat#OUTPUT_IMAGE_FORMAT_YUV_420_888}
* will be used.
*
* <p>Requesting {@link ImageAnalysis.OutputImageFormat#OUTPUT_IMAGE_FORMAT_RGBA_8888}
* causes extra overhead because format conversion takes time.
*
* <p>Changing the value will reconfigure the camera, which may cause additional latency. To
* avoid this, set the value before controller is bound to lifecycle. If the value is changed
* when the camera is active, check the {@link ImageProxy#getFormat()} value to determine
* when the new format takes effect.
*
* @see ImageAnalysis.Builder#setOutputImageFormat(int)
* @see ImageAnalysis.Builder#getOutputImageFormat()
* @see ImageAnalysis#getOutputImageFormat()
*/
@MainThread
public void setImageAnalysisOutputImageFormat(
@ImageAnalysis.OutputImageFormat int imageAnalysisOutputImageFormat) {
checkMainThread();
if (imageAnalysisOutputImageFormat == mImageAnalysis.getOutputImageFormat()) {
// No-op if the value is not changed.
return;
}
unbindImageAnalysisAndRecreate(mImageAnalysis.getBackpressureStrategy(),
mImageAnalysis.getImageQueueDepth(), imageAnalysisOutputImageFormat);
}
/**
* Gets the output image format for {@link ImageAnalysis}.
*
* <p>The returned image format can be either
* {@link ImageAnalysis#OUTPUT_IMAGE_FORMAT_YUV_420_888} or
* {@link ImageAnalysis#OUTPUT_IMAGE_FORMAT_RGBA_8888}.
*
* @see ImageAnalysis.Builder#setOutputImageFormat(int)
* @see ImageAnalysis.Builder#getOutputImageFormat()
* @see ImageAnalysis#getOutputImageFormat()
*/
@MainThread
@ImageAnalysis.OutputImageFormat
public int getImageAnalysisOutputImageFormat() {
checkMainThread();
return mImageAnalysis.getOutputImageFormat();
}
/**
* Unbinds {@link ImageAnalysis} and recreates with the latest parameters.
*/
@MainThread
private void unbindImageAnalysisAndRecreate(Integer strategy, Integer imageQueueDepth,
Integer outputFormat) {
checkMainThread();
if (isCameraInitialized()) {
mCameraProvider.unbind(mImageAnalysis);
}
mImageAnalysis = createImageAnalysis(strategy, imageQueueDepth, outputFormat);
if (mAnalysisExecutor != null && mAnalysisAnalyzer != null) {
mImageAnalysis.setAnalyzer(mAnalysisExecutor, mAnalysisAnalyzer);
}
}
private ImageAnalysis createImageAnalysis(Integer strategy, Integer imageQueueDepth,
Integer outputFormat) {
ImageAnalysis.Builder builder = new ImageAnalysis.Builder();
if (strategy != null) {
builder.setBackpressureStrategy(strategy);
}
if (imageQueueDepth != null) {
builder.setImageQueueDepth(imageQueueDepth);
}
if (outputFormat != null) {
builder.setOutputImageFormat(outputFormat);
}
configureResolution(builder, mImageAnalysisResolutionSelector, mImageAnalysisTargetSize);
if (mAnalysisBackgroundExecutor != null) {
builder.setBackgroundExecutor(mAnalysisBackgroundExecutor);
}
return builder.build();
}
@OptIn(markerClass = {TransformExperimental.class})
@MainThread
void updatePreviewViewTransform(@Nullable Matrix matrix) {
checkMainThread();
if (mAnalysisAnalyzer == null) {
return;
}
if (mAnalysisAnalyzer.getTargetCoordinateSystem()
== ImageAnalysis.COORDINATE_SYSTEM_VIEW_REFERENCED) {
mAnalysisAnalyzer.updateTransform(matrix);
}
}
// -----------------
// Video capture
// -----------------
/**
* Checks if video capture is enabled.
*
* <p>Video capture is disabled by default. It has to be enabled before {@link #startRecording}
* can be called.
*/
@MainThread
public boolean isVideoCaptureEnabled() {
checkMainThread();
return isUseCaseEnabled(VIDEO_CAPTURE);
}
/**
* Takes a video to a given file.
*
* <p>Only a single recording can be active at a time, so if {@link #isRecording()} is
* {@code true}, this will throw an {@link IllegalStateException}.
*
* <p>Upon successfully starting the recording, a {@link VideoRecordEvent.Start} event will
* be the first event sent to the provided event listener.
*
* <p>If errors occur while starting the recording, a {@link VideoRecordEvent.Finalize} event
* will be the first event sent to the provided listener, and information about the error can
* be found in that event's {@link VideoRecordEvent.Finalize#getError()} method.
*
* <p>Recording with audio requires the {@link android.Manifest.permission#RECORD_AUDIO}
* permission; without it, starting a recording will fail with a {@link SecurityException}.
*
* @param outputOptions The options to store the newly captured video.
* @param audioConfig The configuration of audio.
* @param executor The executor that the event listener will be run on.
* @param listener The event listener to handle video record events.
* @return a {@link Recording} that provides controls for new active recordings.
* @throws IllegalStateException if there is an unfinished active recording.
* @throws SecurityException if the audio config specifies audio should be enabled but the
* {@link android.Manifest.permission#RECORD_AUDIO} permission is denied.
*/
@SuppressLint("MissingPermission")
@MainThread
@NonNull
public Recording startRecording(
@NonNull FileOutputOptions outputOptions,
@NonNull AudioConfig audioConfig,
@NonNull Executor executor,
@NonNull Consumer<VideoRecordEvent> listener) {
return startRecordingInternal(outputOptions, audioConfig, executor, listener);
}
/**
* Takes a video to a given file descriptor.
*
* <p>Currently, file descriptors as output destinations are not supported on pre-Android O
* (API 26) devices.
*
* <p>Only a single recording can be active at a time, so if {@link #isRecording()} is true,
* this will throw an {@link IllegalStateException}.
*
* <p>Upon successfully starting the recording, a {@link VideoRecordEvent.Start} event will
* be the first event sent to the provided event listener.
*
* <p>If errors occur while starting the recording, a {@link VideoRecordEvent.Finalize} event
* will be the first event sent to the provided listener, and information about the error can
* be found in that event's {@link VideoRecordEvent.Finalize#getError()} method.
*
* <p>Recording with audio requires the {@link android.Manifest.permission#RECORD_AUDIO}
* permission; without it, starting a recording will fail with a {@link SecurityException}.
*
* @param outputOptions The options to store the newly captured video.
* @param audioConfig The configuration of audio.
* @param executor The executor that the event listener will be run on.
* @param listener The event listener to handle video record events.
* @return a {@link Recording} that provides controls for new active recordings.
* @throws IllegalStateException if there is an unfinished active recording.
* @throws SecurityException if the audio config specifies audio should be enabled but the
* {@link android.Manifest.permission#RECORD_AUDIO} permission is denied.
*/
@SuppressLint("MissingPermission")
@RequiresApi(26)
@MainThread
@NonNull
public Recording startRecording(
@NonNull FileDescriptorOutputOptions outputOptions,
@NonNull AudioConfig audioConfig,
@NonNull Executor executor,
@NonNull Consumer<VideoRecordEvent> listener) {
return startRecordingInternal(outputOptions, audioConfig, executor, listener);
}
/**
* Takes a video to MediaStore.
*
* <p>Only a single recording can be active at a time, so if {@link #isRecording()} is
* {@code true}, this will throw an {@link IllegalStateException}.
*
* <p>Upon successfully starting the recording, a {@link VideoRecordEvent.Start} event will
* be the first event sent to the provided event listener.
*
* <p>If errors occur while starting the recording, a {@link VideoRecordEvent.Finalize} event
* will be the first event sent to the provided listener, and information about the error can
* be found in that event's {@link VideoRecordEvent.Finalize#getError()} method.
*
* <p>Recording with audio requires the {@link android.Manifest.permission#RECORD_AUDIO}
* permission; without it, starting a recording will fail with a {@link SecurityException}.
*
* @param outputOptions The options to store the newly captured video.
* @param audioConfig The configuration of audio.
* @param executor The executor that the event listener will be run on.
* @param listener The event listener to handle video record events.
* @return a {@link Recording} that provides controls for new active recordings.
* @throws IllegalStateException if there is an unfinished active recording.
* @throws SecurityException if the audio config specifies audio should be enabled but the
* {@link android.Manifest.permission#RECORD_AUDIO} permission is denied.
*/
@SuppressLint("MissingPermission")
@MainThread
@NonNull
public Recording startRecording(
@NonNull MediaStoreOutputOptions outputOptions,
@NonNull AudioConfig audioConfig,
@NonNull Executor executor,
@NonNull Consumer<VideoRecordEvent> listener) {
return startRecordingInternal(outputOptions, audioConfig, executor, listener);
}
@RequiresPermission(Manifest.permission.RECORD_AUDIO)
@MainThread
private Recording startRecordingInternal(
@NonNull OutputOptions outputOptions,
@NonNull AudioConfig audioConfig,
@NonNull Executor executor,
@NonNull Consumer<VideoRecordEvent> listener) {
checkMainThread();
Preconditions.checkState(isCameraInitialized(), CAMERA_NOT_INITIALIZED);
Preconditions.checkState(isVideoCaptureEnabled(), VIDEO_CAPTURE_DISABLED);
Preconditions.checkState(!isRecording(), VIDEO_RECORDING_UNFINISHED);
Consumer<VideoRecordEvent> wrappedListener =
wrapListenerToDeactivateRecordingOnFinalized(listener);
PendingRecording pendingRecording = prepareRecording(outputOptions);
boolean isAudioEnabled = audioConfig.getAudioEnabled();
if (isAudioEnabled) {
checkAudioPermissionGranted();
pendingRecording.withAudioEnabled();
}
Recording recording = pendingRecording.start(executor, wrappedListener);
setActiveRecording(recording, wrappedListener);
return recording;
}
private void checkAudioPermissionGranted() {
int permissionState = PermissionChecker.checkSelfPermission(mAppContext,
Manifest.permission.RECORD_AUDIO);
if (permissionState == PermissionChecker.PERMISSION_DENIED) {
throw new SecurityException("Attempted to start recording with audio, but "
+ "application does not have RECORD_AUDIO permission granted.");
}
}
/**
* Generates a {@link PendingRecording} instance for starting a recording.
*
* <p>This method handles {@code prepareRecording()} methods for different output formats,
* and makes {@link #startRecordingInternal(OutputOptions, AudioConfig, Executor, Consumer)}
* only handle the general flow.
*
* <p>This method uses the parent class {@link OutputOptions} as the parameter. On the other
* hand, the public {@code startRecording()} is overloaded with subclasses. The reason is to
* enforce compile-time check for API levels.
*/
@MainThread
private PendingRecording prepareRecording(@NonNull OutputOptions options) {
Recorder recorder = mVideoCapture.getOutput();
if (options instanceof FileOutputOptions) {
return recorder.prepareRecording(mAppContext, (FileOutputOptions) options);
} else if (options instanceof FileDescriptorOutputOptions) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
throw new UnsupportedOperationException(
"File descriptors are not supported on pre-Android O (API 26) devices."
);
}
return recorder.prepareRecording(mAppContext, (FileDescriptorOutputOptions) options);
} else if (options instanceof MediaStoreOutputOptions) {
return recorder.prepareRecording(mAppContext, (MediaStoreOutputOptions) options);
} else {
throw new IllegalArgumentException("Unsupported OutputOptions type.");
}
}
private Consumer<VideoRecordEvent> wrapListenerToDeactivateRecordingOnFinalized(
@NonNull final Consumer<VideoRecordEvent> listener) {
final Executor mainExecutor = getMainExecutor(mAppContext);
return new Consumer<VideoRecordEvent>() {
@Override
public void accept(VideoRecordEvent videoRecordEvent) {
if (videoRecordEvent instanceof VideoRecordEvent.Finalize) {
if (!Threads.isMainThread()) {
// Post on main thread to ensure thread safety.
mainExecutor.execute(() -> deactivateRecordingByListener(this));
} else {
deactivateRecordingByListener(this);
}
}
listener.accept(videoRecordEvent);
}
};
}
@MainThread
void deactivateRecordingByListener(@NonNull Consumer<VideoRecordEvent> listener) {
Recording recording = mRecordingMap.remove(listener);
if (recording != null) {
deactivateRecording(recording);
}
}
/**
* Clears the active video recording reference if the recording to be deactivated matches.
*/
@MainThread
private void deactivateRecording(@NonNull Recording recording) {
if (mActiveRecording == recording) {
mActiveRecording = null;
}
}
@MainThread
private void setActiveRecording(
@NonNull Recording recording,
@NonNull Consumer<VideoRecordEvent> listener) {
mRecordingMap.put(listener, recording);
mActiveRecording = recording;
}
/**
* Stops an in-progress video recording.
*
* <p>Once the current recording has been stopped, the next recording can be started.
*
* <p>If the recording completes successfully, a {@link VideoRecordEvent.Finalize} event with
* {@link VideoRecordEvent.Finalize#ERROR_NONE} will be sent to the provided listener.
*/
@MainThread
private void stopRecording() {
checkMainThread();
if (mActiveRecording != null) {
mActiveRecording.stop();
deactivateRecording(mActiveRecording);
}
}
/**
* Returns whether there is an in-progress video recording.
*/
@MainThread
public boolean isRecording() {
checkMainThread();
return mActiveRecording != null && !mActiveRecording.isClosed();
}
/**
* Sets the {@link QualitySelector} for {@link #VIDEO_CAPTURE}.
*
* <p>The provided quality selector is used to select the resolution of the recording
* depending on the resolutions supported by the camera and codec capabilities.
*
* <p>If no quality selector is provided, the default is
* {@link Recorder#DEFAULT_QUALITY_SELECTOR}.
*
* <p>Changing the value will reconfigure the camera which will cause video capture to stop. To
* avoid this, set the value before controller is bound to lifecycle.
*
* @param qualitySelector The quality selector for {@link #VIDEO_CAPTURE}.
* @see QualitySelector
*/
@MainThread
public void setVideoCaptureQualitySelector(@NonNull QualitySelector qualitySelector) {
checkMainThread();
mVideoCaptureQualitySelector = qualitySelector;
unbindVideoAndRecreate();
startCameraAndTrackStates();
}
/**
* Returns the {@link QualitySelector} for {@link #VIDEO_CAPTURE}.
*
* @return the {@link QualitySelector} provided to
* {@link #setVideoCaptureQualitySelector(QualitySelector)} or the default value of
* {@link Recorder#DEFAULT_QUALITY_SELECTOR} if no quality selector was provided.
*/
@MainThread
@NonNull
public QualitySelector getVideoCaptureQualitySelector() {
checkMainThread();
return mVideoCaptureQualitySelector;
}
/**
* Sets the mirror mode for video capture.
*
* <p>Valid values include: {@link MirrorMode#MIRROR_MODE_OFF},
* {@link MirrorMode#MIRROR_MODE_ON} and {@link MirrorMode#MIRROR_MODE_ON_FRONT_ONLY}.
* If not set, it defaults to {@link MirrorMode#MIRROR_MODE_OFF}.
*
* @see VideoCapture.Builder#setMirrorMode(int)
*/
@MainThread
public void setVideoCaptureMirrorMode(@MirrorMode.Mirror int mirrorMode) {
checkMainThread();
mVideoCaptureMirrorMode = mirrorMode;
unbindVideoAndRecreate();
startCameraAndTrackStates();
}
/**
* Gets the mirror mode for video capture.
*/
@MainThread
@MirrorMode.Mirror
public int getVideoCaptureMirrorMode() {
checkMainThread();
return mVideoCaptureMirrorMode;
}
/**
* Sets the {@link DynamicRange} for video capture.
*
* <p>The dynamic range specifies how the range of colors, highlights and shadows that
* are captured by the video producer are displayed on a display. Some dynamic ranges will
* allow the video to make full use of the extended range of brightness of a display when
* the video is played back.
*
* <p>The supported dynamic ranges for video capture can be queried through the
* {@link androidx.camera.video.VideoCapabilities} returned by
* {@link Recorder#getVideoCapabilities(CameraInfo)} via
* {@link androidx.camera.video.VideoCapabilities#getSupportedDynamicRanges()}.
*
* <p>It is possible to choose a high dynamic range (HDR) with unspecified encoding by providing
* {@link DynamicRange#HDR_UNSPECIFIED_10_BIT}.
*
* <p>If the dynamic range is not provided, the default value is {@link DynamicRange#SDR}.
*
* @see VideoCapture.Builder#setDynamicRange(DynamicRange)
*/
@MainThread
public void setVideoCaptureDynamicRange(@NonNull DynamicRange dynamicRange) {
checkMainThread();
mVideoCaptureDynamicRange = dynamicRange;
unbindVideoAndRecreate();
startCameraAndTrackStates();
}
/**
* Gets the {@link DynamicRange} for video capture.
*/
@MainThread
@NonNull
public DynamicRange getVideoCaptureDynamicRange() {
checkMainThread();
return mVideoCaptureDynamicRange;
}
/**
* Sets the target frame rate range in frames per second for video capture.
*
* <p>This target will be used as a part of the heuristics for the algorithm that determines
* the final frame rate range and resolution of all concurrently bound use cases.
*
* <p>It is not guaranteed that this target frame rate will be the final range,
* as other use cases as well as frame rate restrictions of the device may affect the
* outcome of the algorithm that chooses the actual frame rate.
*
* <p>By default, the value is {@link StreamSpec#FRAME_RATE_RANGE_UNSPECIFIED}. For supported
* frame rates, see {@link CameraInfo#getSupportedFrameRateRanges()}.
*
* @see VideoCapture.Builder#setTargetFrameRate(Range)
*/
@MainThread
public void setVideoCaptureTargetFrameRate(@NonNull Range<Integer> targetFrameRate) {
checkMainThread();
mVideoCaptureTargetFrameRate = targetFrameRate;
unbindVideoAndRecreate();
startCameraAndTrackStates();
}
/**
* Gets the target frame rate in frames per second for video capture.
*/
@MainThread
@NonNull
public Range<Integer> getVideoCaptureTargetFrameRate() {
checkMainThread();
return mVideoCaptureTargetFrameRate;
}
/**
* Unbinds VideoCapture and recreate with the latest parameters.
*/
@MainThread
private void unbindVideoAndRecreate() {
if (isCameraInitialized()) {
mCameraProvider.unbind(mVideoCapture);
}
mVideoCapture = createVideoCapture();
}
private VideoCapture<Recorder> createVideoCapture() {
Recorder.Builder videoRecorderBuilder = new Recorder.Builder().setQualitySelector(
mVideoCaptureQualitySelector);
if (mViewPort != null
&& mVideoCaptureQualitySelector == Recorder.DEFAULT_QUALITY_SELECTOR) {
int aspectRatioInt = getViewportAspectRatioInt(mViewPort);
if (aspectRatioInt != AspectRatio.RATIO_DEFAULT) {
videoRecorderBuilder.setAspectRatio(aspectRatioInt);
}
}
return new VideoCapture.Builder<>(videoRecorderBuilder.build())
.setTargetFrameRate(mVideoCaptureTargetFrameRate)
.setMirrorMode(mVideoCaptureMirrorMode)
.setDynamicRange(mVideoCaptureDynamicRange)
.build();
}
@Nullable
private AspectRatioStrategy getViewportAspectRatioStrategy(@NonNull ViewPort viewPort) {
int aspectRatioInt = getViewportAspectRatioInt(viewPort);
if (aspectRatioInt != AspectRatio.RATIO_DEFAULT) {
return new AspectRatioStrategy(aspectRatioInt, AspectRatioStrategy.FALLBACK_RULE_AUTO);
} else {
return null;
}
}
/**
* If the aspect ratio of the viewport is one of the {@link AspectRatio.Ratio}, returns it,
* otherwise returns {@link AspectRatio.Ratio#RATIO_DEFAULT}.
*/
@AspectRatio.Ratio
private int getViewportAspectRatioInt(@NonNull ViewPort viewPort) {
int surfaceRotationDegrees =
viewPort == null ? 0 : CameraOrientationUtil.surfaceRotationToDegrees(
viewPort.getRotation());
int sensorRotationDegrees =
mCameraProvider == null ? 0 : mCameraProvider.getCameraInfo(
mCameraSelector).getSensorRotationDegrees();
boolean isOppositeFacing =
mCameraProvider == null ? true : mCameraProvider.getCameraInfo(
mCameraSelector).getLensFacing() == CameraSelector.LENS_FACING_BACK;
int relativeRotation = CameraOrientationUtil.getRelativeImageRotation(
surfaceRotationDegrees, sensorRotationDegrees, isOppositeFacing);
Rational aspectRatio = viewPort.getAspectRatio();
if (relativeRotation == 90 || relativeRotation == 270) {
aspectRatio = new Rational(/* numerator= */ aspectRatio.getDenominator(),
/* denominator= */ aspectRatio.getNumerator());
}
if (aspectRatio.equals(new Rational(4, 3))) {
return AspectRatio.RATIO_4_3;
} else if (aspectRatio.equals(new Rational(16, 9))) {
return AspectRatio.RATIO_16_9;
} else {
return AspectRatio.RATIO_DEFAULT;
}
}
/**
* Unbinds all the use cases and recreate with the latest parameters.
*/
@MainThread
private void unbindAllAndRecreate() {
unbindPreviewAndRecreate();
unbindImageCaptureAndRecreate(getImageCaptureMode());
unbindImageAnalysisAndRecreate(mImageAnalysis.getBackpressureStrategy(),
mImageAnalysis.getImageQueueDepth(), mImageAnalysis.getOutputImageFormat());
unbindVideoAndRecreate();
}
// -----------------
// Camera control
// -----------------
/**
* Sets the {@link CameraSelector}.
*
* <p>Calling this method with a {@link CameraSelector} that resolves to a different camera
* will change the camera being used by the controller. If camera initialization is complete,
* the controller will immediately rebind use cases with the new {@link CameraSelector};
* otherwise, the new {@link CameraSelector} will be used when the camera becomes ready.
*
* <p>The default value is {@link CameraSelector#DEFAULT_BACK_CAMERA}.
*
* @throws IllegalStateException If the provided camera selector is unable to resolve a camera
* to be used for the enabled use cases.
* @see CameraSelector
*/
@MainThread
public void setCameraSelector(@NonNull CameraSelector cameraSelector) {
checkMainThread();
if (mCameraSelector == cameraSelector) {
return;
}
Integer lensFacing = cameraSelector.getLensFacing();
if (mImageCapture.getFlashMode() == ImageCapture.FLASH_MODE_SCREEN && lensFacing != null
&& lensFacing != CameraSelector.LENS_FACING_FRONT) {
throw new IllegalStateException("Not a front camera despite setting FLASH_MODE_SCREEN");
}
CameraSelector oldCameraSelector = mCameraSelector;
mCameraSelector = cameraSelector;
if (mCameraProvider == null) {
return;
}
mCameraProvider.unbind(mPreview, mImageCapture, mImageAnalysis, mVideoCapture);
startCameraAndTrackStates(() -> mCameraSelector = oldCameraSelector);
}
/**
* Checks if the given {@link CameraSelector} can be resolved to a camera.
*
* <p>Use this method to check if the device has the given camera.
*
* <p>Only call this method after camera is initialized. e.g. after the {@link ListenableFuture}
* from {@link #getInitializationFuture()} is finished. Calling it prematurely throws
* {@link IllegalStateException}. Example:
*
* <pre><code>
* controller.getInitializationFuture().addListener(() -> {
* if (controller.hasCamera(cameraSelector)) {
* controller.setCameraSelector(cameraSelector);
* } else {
* // Update UI if the camera is not available.
* }
* // Attach PreviewView after we know the camera is available.
* previewView.setController(controller);
* }, ContextCompat.getMainExecutor(requireContext()));
* </code></pre>
*
* @return {@code true} if the {@link CameraSelector} can be resolved to a camera.
* @throws IllegalStateException if the camera is not initialized.
*/
@MainThread
public boolean hasCamera(@NonNull CameraSelector cameraSelector) {
checkMainThread();
Preconditions.checkNotNull(cameraSelector);
if (mCameraProvider == null) {
throw new IllegalStateException("Camera not initialized. Please wait for "
+ "the initialization future to finish. See #getInitializationFuture().");
}
try {
return mCameraProvider.hasCamera(cameraSelector);
} catch (CameraInfoUnavailableException e) {
Logger.w(TAG, "Failed to check camera availability", e);
return false;
}
}
/**
* Gets the {@link CameraSelector}.
*
* <p>The default value is{@link CameraSelector#DEFAULT_BACK_CAMERA}.
*
* @see CameraSelector
*/
@NonNull
@MainThread
public CameraSelector getCameraSelector() {
checkMainThread();
return mCameraSelector;
}
/**
* Returns whether pinch-to-zoom is enabled.
*
* <p>By default pinch-to-zoom is enabled.
*
* @return {@code true} if pinch-to-zoom is enabled.
*/
@MainThread
public boolean isPinchToZoomEnabled() {
checkMainThread();
return mPinchToZoomEnabled;
}
/**
* Enables/disables pinch-to-zoom.
*
* <p>Once enabled, end user can pinch on the {@link PreviewView} to zoom in/out if the bound
* camera supports zooming.
*
* @param enabled {@code true} to enable pinch-to-zoom.
*/
@MainThread
public void setPinchToZoomEnabled(boolean enabled) {
checkMainThread();
mPinchToZoomEnabled = enabled;
}
/**
* Called by {@link PreviewView} for a pinch-to-zoom event.
*/
@SuppressWarnings("FutureReturnValueIgnored")
void onPinchToZoom(float pinchToZoomScale) {
if (!isCameraAttached()) {
Logger.w(TAG, CAMERA_NOT_ATTACHED);
return;
}
if (!mPinchToZoomEnabled) {
Logger.d(TAG, "Pinch to zoom disabled.");
return;
}
Logger.d(TAG, "Pinch to zoom with scale: " + pinchToZoomScale);
ZoomState zoomState = getZoomState().getValue();
if (zoomState == null) {
return;
}
float clampedRatio = zoomState.getZoomRatio() * speedUpZoomBy2X(pinchToZoomScale);
// Clamp the ratio with the zoom range.
clampedRatio = Math.min(Math.max(clampedRatio, zoomState.getMinZoomRatio()),
zoomState.getMaxZoomRatio());
setZoomRatio(clampedRatio);
}
private float speedUpZoomBy2X(float scaleFactor) {
if (scaleFactor > 1f) {
return 1.0f + (scaleFactor - 1.0f) * 2;
} else {
return 1.0f - (1.0f - scaleFactor) * 2;
}
}
/**
* Called by {@link PreviewView} for a tap-to-focus event.
*/
@SuppressWarnings("FutureReturnValueIgnored")
void onTapToFocus(MeteringPointFactory meteringPointFactory, float x, float y) {
if (!isCameraAttached()) {
Logger.w(TAG, CAMERA_NOT_ATTACHED);
return;
}
if (!mTapToFocusEnabled) {
Logger.d(TAG, "Tap to focus disabled. ");
return;
}
Logger.d(TAG, "Tap to focus started: " + x + ", " + y);
mTapToFocusState.postValue(TAP_TO_FOCUS_STARTED);
MeteringPoint afPoint = meteringPointFactory.createPoint(x, y, AF_SIZE);
MeteringPoint aePoint = meteringPointFactory.createPoint(x, y, AE_SIZE);
FocusMeteringAction focusMeteringAction = new FocusMeteringAction
.Builder(afPoint, FocusMeteringAction.FLAG_AF)
.addPoint(aePoint, FocusMeteringAction.FLAG_AE)
.build();
Futures.addCallback(mCamera.getCameraControl().startFocusAndMetering(focusMeteringAction),
new FutureCallback<FocusMeteringResult>() {
@Override
public void onSuccess(@Nullable FocusMeteringResult result) {
if (result == null) {
return;
}
Logger.d(TAG, "Tap to focus onSuccess: " + result.isFocusSuccessful());
mTapToFocusState.postValue(result.isFocusSuccessful()
? TAP_TO_FOCUS_FOCUSED : TAP_TO_FOCUS_NOT_FOCUSED);
}
@Override
public void onFailure(@NonNull Throwable t) {
if (t instanceof CameraControl.OperationCanceledException) {
Logger.d(TAG, "Tap-to-focus is canceled by new action.");
return;
}
Logger.d(TAG, "Tap to focus failed.", t);
mTapToFocusState.postValue(TAP_TO_FOCUS_FAILED);
}
}, directExecutor());
}
/**
* Returns whether tap-to-focus is enabled.
*
* <p>By default tap-to-focus is enabled.
*
* @return {@code true} if tap-to-focus is enabled.
*/
@MainThread
public boolean isTapToFocusEnabled() {
checkMainThread();
return mTapToFocusEnabled;
}
/**
* Enables/disables tap-to-focus.
*
* <p>Once enabled, end user can tap on the {@link PreviewView} to set focus point.
*
* @param enabled {@code true} to enable tap-to-focus.
*/
@MainThread
public void setTapToFocusEnabled(boolean enabled) {
checkMainThread();
mTapToFocusEnabled = enabled;
}
/**
* Returns a {@link LiveData} with the latest tap-to-focus state.
*
* <p>When tap-to-focus feature is enabled, the {@link LiveData} will receive updates of
* focusing states. This happens when the end user taps on {@link PreviewView}, and then again
* when focusing is finished either successfully or unsuccessfully. The following table
* displays the states the {@link LiveData} can be in, and the possible transitions between
* them.
*
* <table>
* <tr>
* <th>State</th>
* <th>Transition cause</th>
* <th>New State</th>
* </tr>
* <tr>
* <td>TAP_TO_FOCUS_NOT_STARTED</td>
* <td>User taps on {@link PreviewView}</td>
* <td>TAP_TO_FOCUS_STARTED</td>
* </tr>
* <tr>
* <td>TAP_TO_FOCUS_SUCCESSFUL</td>
* <td>User taps on {@link PreviewView}</td>
* <td>TAP_TO_FOCUS_STARTED</td>
* </tr>
* <tr>
* <td>TAP_TO_FOCUS_UNSUCCESSFUL</td>
* <td>User taps on {@link PreviewView}</td>
* <td>TAP_TO_FOCUS_STARTED</td>
* </tr>
* <tr>
* <td>TAP_TO_FOCUS_FAILED</td>
* <td>User taps on {@link PreviewView}</td>
* <td>TAP_TO_FOCUS_STARTED</td>
* </tr>
* <tr>
* <td rowspan="3">TAP_TO_FOCUS_STARTED</td>
* <td>Focusing succeeded</td>
* <td>TAP_TO_FOCUS_SUCCESSFUL</td>
* </tr>
* <tr>
* <td>Focusing failed due to lighting and/or camera distance</td>
* <td>TAP_TO_FOCUS_UNSUCCESSFUL</td>
* </tr>
* <tr>
* <td>Focusing failed due to device constraints</td>
* <td>TAP_TO_FOCUS_FAILED</td>
* </tr>
* </table>
*
* @see #setTapToFocusEnabled(boolean)
* @see CameraControl#startFocusAndMetering(FocusMeteringAction)
*/
@MainThread
@NonNull
public LiveData<Integer> getTapToFocusState() {
checkMainThread();
return mTapToFocusState;
}
/**
* Returns a {@link LiveData} of {@link ZoomState}.
*
* <p>The LiveData will be updated whenever the set zoom state has been changed. This can
* occur when the application updates the zoom via {@link #setZoomRatio(float)}
* or {@link #setLinearZoom(float)}. The zoom state can also change anytime a
* camera starts up, for example when {@link #setCameraSelector} is called.
*
* @see CameraInfo#getZoomState()
*/
@NonNull
@MainThread
public LiveData<ZoomState> getZoomState() {
checkMainThread();
return mZoomState;
}
/**
* Gets the {@link CameraInfo} of the currently attached camera.
*
* <p>For info available directly through CameraController as well as {@link CameraInfo}, it's
* recommended to use the ones with CameraController, e.g. {@link #getTorchState()} v.s.
* {@link CameraInfo#getTorchState()}. {@link CameraInfo} is a lower-layer API and may require
* more steps to achieve the same effect, and will not maintain values when switching between
* cameras.
*
* @return The {@link CameraInfo} of the current camera. Returns {@code null} if camera is not
* ready.
* @see Camera#getCameraInfo()
*/
@Nullable
@MainThread
public CameraInfo getCameraInfo() {
checkMainThread();
return mCamera == null ? null : mCamera.getCameraInfo();
}
/**
* Gets the {@link CameraControl} of the currently attached camera.
*
* <p>For controls available directly through CameraController as well as
* {@link CameraControl}, it's recommended to use the ones with CameraController, e.g.
* {@link #setLinearZoom(float)} v.s. {@link CameraControl#setLinearZoom(float)}.
* CameraControl is a lower-layer API and may require more steps to achieve the same effect,
* and will not maintain control values when switching between cameras.
*
* @return The {@link CameraControl} of the current camera. Returns {@code null} if camera is
* not ready.
* @see Camera#getCameraControl()
*/
@Nullable
@MainThread
public CameraControl getCameraControl() {
checkMainThread();
return mCamera == null ? null : mCamera.getCameraControl();
}
/**
* Sets current zoom by ratio.
*
* <p>Valid zoom values range from {@link ZoomState#getMinZoomRatio()} to
* {@link ZoomState#getMaxZoomRatio()}.
*
* <p>If the value is set before the camera is ready, {@link CameraController} waits for the
* camera to be ready and then sets the zoom ratio.
*
* @param zoomRatio The requested zoom ratio.
* @return A {@link ListenableFuture} which is finished when camera is set to the given ratio.
* It fails with {@link CameraControl.OperationCanceledException} if there is newer value
* being set or camera is closed. If the ratio is out of range, it fails with
* {@link IllegalArgumentException}. Cancellation of this future is a no-op.
* @see #getZoomState()
* @see CameraControl#setZoomRatio(float)
*/
@NonNull
@MainThread
public ListenableFuture<Void> setZoomRatio(float zoomRatio) {
checkMainThread();
if (!isCameraAttached()) {
return mPendingZoomRatio.setValue(zoomRatio);
}
return mCamera.getCameraControl().setZoomRatio(zoomRatio);
}
/**
* Sets current zoom by a linear zoom value ranging from 0f to 1.0f.
*
* <p>LinearZoom 0f represents the minimum zoom while linearZoom 1.0f represents the maximum
* zoom. The advantage of linearZoom is that it ensures the field of view (FOV) varies
* linearly with the linearZoom value, for use with slider UI elements (while
* {@link #setZoomRatio(float)} works well for pinch-zoom gestures).
*
* <p>If the value is set before the camera is ready, {@link CameraController} waits for the
* camera to be ready and then sets the linear zoom.
*
* @return A {@link ListenableFuture} which is finished when camera is set to the given ratio.
* It fails with {@link CameraControl.OperationCanceledException} if there is newer value
* being set or camera is closed. If the ratio is out of range, it fails with
* {@link IllegalArgumentException}. Cancellation of this future is a no-op.
* @see CameraControl#setLinearZoom(float)
*/
@NonNull
@MainThread
public ListenableFuture<Void> setLinearZoom(@FloatRange(from = 0f, to = 1f) float linearZoom) {
checkMainThread();
if (!isCameraAttached()) {
return mPendingLinearZoom.setValue(linearZoom);
}
return mCamera.getCameraControl().setLinearZoom(linearZoom);
}
/**
* Returns a {@link LiveData} of current {@link TorchState}.
*
* <p>The torch can be turned on and off via {@link #enableTorch(boolean)} which will trigger
* the change event to the returned {@link LiveData}.
*
* @return A {@link LiveData} containing current torch state.
* @see CameraInfo#getTorchState()
*/
@NonNull
@MainThread
public LiveData<Integer> getTorchState() {
checkMainThread();
return mTorchState;
}
/**
* Enable the torch or disable the torch.
*
* <p>If the value is set before the camera is ready, {@link CameraController} waits for the
* camera to be ready and then enables the torch.
*
* @param torchEnabled {@code true} to turn on the torch, {@code false} to turn it off.
* @return A {@link ListenableFuture} which is successful when the torch was changed to the
* value specified. It fails when it is unable to change the torch state. Cancellation of
* this future is a no-op.
* @see CameraControl#enableTorch(boolean)
*/
@NonNull
@MainThread
public ListenableFuture<Void> enableTorch(boolean torchEnabled) {
checkMainThread();
if (!isCameraAttached()) {
return mPendingEnableTorch.setValue(torchEnabled);
}
return mCamera.getCameraControl().enableTorch(torchEnabled);
}
// ------------------------
// Effects and extensions
// ------------------------
/**
* Sets {@link CameraEffect}.
*
* <p>Call this method to set a list of active effects. There is maximum one effect per
* {@link UseCase}. Adding effects with duplicate or invalid targets throws
* {@link IllegalArgumentException}. Once called, CameraX will rebind the {@link UseCase}
* with the effects applied. Effects not in the list are automatically removed.
*
* <p>The method throws {@link IllegalArgumentException} if the effects combination is not
* supported by CameraX. Please see the Javadoc of {@link UseCaseGroup.Builder#addEffect} to
* see the supported effects combinations.
*
* @param effects The effects applied to camera output.
* @throws IllegalArgumentException if the combination of effects is not supported by CameraX.
* @see UseCaseGroup.Builder#addEffect
*/
@MainThread
public void setEffects(@NonNull Set<CameraEffect> effects) {
checkMainThread();
if (Objects.equals(mEffects, effects)) {
// Same effect. No change needed.
return;
}
if (mCameraProvider != null) {
// Unbind to make sure the pipelines will be recreated.
mCameraProvider.unbindAll();
}
mEffects.clear();
mEffects.addAll(effects);
startCameraAndTrackStates();
}
/**
* Removes all effects.
*
* <p>Once called, CameraX will remove all the effects and rebind the {@link UseCase}.
*/
@MainThread
public void clearEffects() {
checkMainThread();
if (mCameraProvider != null) {
// Unbind to make sure the pipelines will be recreated.
mCameraProvider.unbindAll();
}
mEffects.clear();
startCameraAndTrackStates();
}
// ------------------------------
// Binding to lifecycle
// ------------------------------
/**
* Binds use cases, gets a new {@link Camera} instance and tracks the state of the camera.
*/
void startCameraAndTrackStates() {
startCameraAndTrackStates(null);
}
/**
* @param restoreStateRunnable {@link Runnable} to restore the controller to the previous good
* state if the binding fails.
* @throws IllegalStateException for invalid {@link UseCase} combinations.
* @throws RuntimeException for invalid {@link CameraEffect} combinations.
*/
void startCameraAndTrackStates(@Nullable Runnable restoreStateRunnable) {
try {
mCamera = startCamera();
} catch (RuntimeException exception) {
// Restore the previous state before re-throwing the exception.
if (restoreStateRunnable != null) {
restoreStateRunnable.run();
}
throw exception;
}
if (!isCameraAttached()) {
Logger.d(TAG, CAMERA_NOT_ATTACHED);
return;
}
mZoomState.setSource(mCamera.getCameraInfo().getZoomState());
mTorchState.setSource(mCamera.getCameraInfo().getTorchState());
mPendingEnableTorch.propagateIfHasValue(this::enableTorch);
mPendingLinearZoom.propagateIfHasValue(this::setLinearZoom);
mPendingZoomRatio.propagateIfHasValue(this::setZoomRatio);
}
/**
* Creates {@link UseCaseGroup} from all the use cases.
*
* <p>Preview is required. If it is {@code null}, then controller is not ready. Return
* {@code null} and ignore other use cases.
*/
@Nullable
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
protected UseCaseGroup createUseCaseGroup() {
if (!isCameraInitialized()) {
Logger.d(TAG, CAMERA_NOT_INITIALIZED);
return null;
}
if (!isPreviewViewAttached()) {
// Preview is required. Return early if preview Surface is not ready.
Logger.d(TAG, PREVIEW_VIEW_NOT_ATTACHED);
return null;
}
UseCaseGroup.Builder builder = new UseCaseGroup.Builder().addUseCase(mPreview);
if (isImageCaptureEnabled()) {
builder.addUseCase(mImageCapture);
} else {
mCameraProvider.unbind(mImageCapture);
}
if (isImageAnalysisEnabled()) {
builder.addUseCase(mImageAnalysis);
} else {
mCameraProvider.unbind(mImageAnalysis);
}
if (isVideoCaptureEnabled()) {
builder.addUseCase(mVideoCapture);
} else {
mCameraProvider.unbind(mVideoCapture);
}
builder.setViewPort(mViewPort);
for (CameraEffect effect : mEffects) {
builder.addEffect(effect);
}
return builder.build();
}
/**
* Represents the output size of a {@link UseCase}.
*
* <p>This class is a preferred output size to be used with {@link CameraController}. The
* preferred output size can be based on either resolution or aspect ratio, but not both.
*
* @see #setImageAnalysisTargetSize(OutputSize)
* @see #setPreviewTargetSize(OutputSize)
* @see #setImageCaptureTargetSize(OutputSize)
* @deprecated Use {@link ResolutionSelector} instead.
*/
@Deprecated
public static final class OutputSize {
/**
* A value that represents the aspect ratio is not assigned.
*/
public static final int UNASSIGNED_ASPECT_RATIO = -1;
/**
* Possible value for {@link #getAspectRatio()}
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {UNASSIGNED_ASPECT_RATIO, AspectRatio.RATIO_4_3, AspectRatio.RATIO_16_9})
public @interface OutputAspectRatio {
}
@OutputAspectRatio
private final int mAspectRatio;
@Nullable
private final Size mResolution;
/**
* Creates a {@link OutputSize} that is based on aspect ratio.
*
* @see Preview.Builder#setTargetAspectRatio(int)
* @see ImageAnalysis.Builder#setTargetAspectRatio(int)
*/
public OutputSize(@AspectRatio.Ratio int aspectRatio) {
Preconditions.checkArgument(aspectRatio != UNASSIGNED_ASPECT_RATIO);
mAspectRatio = aspectRatio;
mResolution = null;
}
/**
* Creates a {@link OutputSize} that is based on resolution.
*
* @see Preview.Builder#setTargetResolution(Size)
* @see ImageAnalysis.Builder#setTargetResolution(Size)
*/
public OutputSize(@NonNull Size resolution) {
Preconditions.checkNotNull(resolution);
mAspectRatio = UNASSIGNED_ASPECT_RATIO;
mResolution = resolution;
}
/**
* Gets the value of aspect ratio.
*
* @return {@link #UNASSIGNED_ASPECT_RATIO} if the size is not based on aspect ratio.
*/
@OutputAspectRatio
public int getAspectRatio() {
return mAspectRatio;
}
/**
* Gets the value of resolution.
*
* @return {@code null} if the size is not based on resolution.
*/
@Nullable
public Size getResolution() {
return mResolution;
}
@NonNull
@Override
public String toString() {
return "aspect ratio: " + mAspectRatio + " resolution: " + mResolution;
}
}
}