If your tiện ích uses the original Camera
class ("Camera1"), which has been deprecated since
Android 5.0 (API level 21),
we highly recommend updating vĩ đại a modern Android camera API. Android offers
CameraX (a standardized, robust Jetpack camera
API) and Camera2 (a low-level, framework API). For the
vast majority of cases, we recommend migrating your tiện ích vĩ đại CameraX. Here's why:
- Ease of use: CameraX handles the low-level details, sánh that you can focus less on building a camera experience from scratch and more on differentiating your tiện ích.
- CameraX handles fragmentation for you: CameraX reduces long-term maintenance costs and device-specific code, bringing higher quality experiences to users. For more on this, kiểm tra out our Better Device Compatibility with CameraX blog post.
- Advanced capabilities: CameraX is carefully designed vĩ đại make advanced functionality simple vĩ đại incorporate into your tiện ích. For example, you can easily apply Bokeh, Face Retouch, HDR (High Dynamic Range), and low-light-brightening Night capture mode vĩ đại your photos with CameraX Extensions.
- Updatability: Android releases new capabilities and bug fixes vĩ đại CameraX throughout the year. By migrating vĩ đại CameraX, your tiện ích gets the latest Android camera technology with each CameraX release, not just on the annual Android version releases.
In this guide, you'll find common scenarios for camera applications. Each scenario includes a Camera1 implementation and a CameraX implementation for comparison.
Bạn đang xem: camera1.xyz
When it comes vĩ đại migration, sometimes you need extra flexibility vĩ đại integrate
with an existing codebase. All CameraX code in this guide has a
CameraController
implementation—great if you want the simplest way vĩ đại use CameraX—and also a
CameraProvider
implementation—great if you need more flexibility. To help you decide which is
the right one for you, here are the benefits of each:
CameraController |
CameraProvider |
Requires little setup code | Allows for more control |
Allowing CameraX vĩ đại handle more of the setup process means functionality lượt thích tap-to-focus and pinch-to-zoom work automatically |
Since the tiện ích developer handles setup, there are more opportunities
vĩ đại customize the configuration, lượt thích enabling output image rotation
or setting the output image format in ImageAnalysis
|
Requiring PreviewView for the camera preview allows
CameraX vĩ đại offer seamless end-to-end integration, as in our ML Kit
integration which can map the ML model result coordinates (like face
bounding boxes) directly onto the preview coordinates
|
The ability vĩ đại use a custom `Surface` for camera preview allows for more flexibility, such as using your existing `Surface` code which could be an input vĩ đại other parts of your app |
If you get stuck trying vĩ đại migrate, reach out vĩ đại us on the CameraX Discussion Group.
Before you migrate
Compare CameraX vĩ đại Camera1 usage
While the code may look different, the underlying concepts in Camera1 and
CameraX are very similar. CameraX
abstracts common camera functionality into use cases,
and as a result, many tasks that were left vĩ đại the developer in Camera1 are
handled automatically by CameraX. There are four
UseCase
s in CameraX, which you can
use for a variety of camera tasks: Preview
,
ImageCapture
,
VideoCapture
, and
ImageAnalysis
.
One example of CameraX handling low-level details for developers is the
ViewPort
that is shared among
active UseCase
s. This ensures all UseCase
s see exactly the same pixels.
In Camera1, you have vĩ đại manage these details yourself, and given the variability
in aspect ratios across devices' camera sensors and screens, it can be tricky to
ensure your preview matches captured photos and videos.
As another example, CameraX handles Lifecycle
callbacks automatically on the
Lifecycle
instance you pass it. This means CameraX handles your app's
connection vĩ đại the camera during the entire
Android activity lifecycle,
including the following cases: closing the camera when your tiện ích goes into the
background; removing the camera preview when the screen no longer requires
displaying it; and pausing the camera preview when another activity takes
foreground precedence, lượt thích an incoming Clip Hotline.
Finally, CameraX handles rotation and scaling without any additional code needed
on your part. In the case of an Activity
with an unlocked orientation, the
UseCase
setup is done every time the device is rotated, as the system destroys
and recreates the Activity
on orientation changes. This results in the
UseCases
setting their target rotation vĩ đại match the display's orientation by
default each time.
Read more about rotations in CameraX.
Before jumping into the details, here's a high-level look at CameraX's
UseCase
s and how a Camera1 tiện ích would relate. (CameraX concepts are in
blue and Camera1
concepts are in
green.)
CameraX |
|||
CameraController / CameraProvider Configuration | |||
↓ | ↓ | ↓ | ↓ |
Preview | ImageCapture | VideoCapture | ImageAnalysis |
⁞ | ⁞ | ⁞ | ⁞ |
Manage preview Surface and mix it on Camera | Set PictureCallback and Hotline takePicture() on Camera | Manage Camera and MediaRecorder configuration in specific order | Custom analysis code built on top of preview Surface |
↑ | ↑ | ↑ | ↑ |
Device-specific Code | |||
↑ | |||
Device Rotation and Scaling Management | |||
↑ | |||
Camera Session Management (Camera Selection, Lifecycle Management) | |||
Camera1 |
Compatibility and performance in CameraX
CameraX supports devices running Android 5.0 (API level 21) and higher. This represents over 98% of existing Android devices. CameraX is built vĩ đại handle differences between devices automatically, reducing the need for device-specific code in your tiện ích. Furthermore, we test over 150 physical devices on all Android versions since 5.0 in our CameraX Test Lab. You can review the full list of devices currently in the Test Lab.
CameraX uses an Executor
to
drive the camera stack. You can
set your own executor on CameraX
if your tiện ích has specific threading requirements. If not mix, CameraX creates
and uses an optimized mặc định internal Executor
. Many of the platform APIs on
which CameraX is built require blocking interprocess communication (IPC) with
hardware that can sometimes take hundreds of milliseconds vĩ đại respond. For this
reason, CameraX only calls these APIs from background threads, which ensures the
main thread is not blocked and that the UI remains fluid.
Read more about threads.
If the target market for your tiện ích includes low-end devices, CameraX provides a
way vĩ đại reduce setup time with a
camera limiter. Since the
process of connecting vĩ đại hardware components can take a non-trivial amount of
time, especially on low-end devices, you can specify the mix of cameras your app
needs. CameraX only connects vĩ đại these cameras during setup. For example, if
the application uses only back-facing cameras, it can mix this configuration
with DEFAULT_BACK_CAMERA
and then CameraX avoids initializing front-facing
cameras vĩ đại reduce the latency.
Android development concepts
This guide assumes a general familiarity with Android development. Beyond the basics, here are a couple of concepts that are helpful vĩ đại understand before jumping into the code below:
- View Binding generates a binding class for
your XML layout files, allowing you vĩ đại easily
reference your views in Activities,
as is done in several code snippets below. There are some
differences between view binding and
findViewById()
(the prior way vĩ đại reference views), but in the code below you should be able to replace the view binding lines with a similarfindViewById()
Hotline. - Asynchronous Coroutines are a concurrency design
pattern added in Kotlin 1.3 that can be used vĩ đại handle CameraX methods that
return a
ListenableFuture
. This is made easier with the Jetpack Concurrent library as of version 1.1.0. To add an asynchronous coroutine vĩ đại your app:- Add
implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
to your Gradle tệp tin. - Put any CameraX code that returns a
ListenableFuture
in alaunch
block or suspending function. - Add an
await()
call vĩ đại the function Hotline that returns aListenableFuture
. - For a deeper understanding of how coroutines work, see the Start a coroutine guide.
- Add
Migrate common scenarios
This section explains how vĩ đại migrate common scenarios from Camera1 vĩ đại CameraX.
Each scenario covers a Camera1 implementation, a CameraX CameraProvider
implementation, and a CameraX CameraController
implementation.
Selecting a camera
In your camera application, one of the first things you may want vĩ đại offer is a way vĩ đại select different cameras.
Camera1
In Camera1, you can either call
Camera.open()
with no parameters
to open the first back-facing camera, or you can pass in an integer ID for the
camera you want vĩ đại open. Here's an example of how that might look:
// Camera1: select a camera from id. // Note: opening the camera is a non-trivial task, and it shouldn't be // called from the main thread, unlike CameraX calls, which can be // on the main thread since CameraX kicks off background threads // internally as needed. private fun safeCameraOpen(id: Int): Boolean { return try { releaseCameraAndPreview() camera = Camera.open(id) true } catch (e: Exception) { Log.e(TAG, "failed vĩ đại open camera", e) false } } private fun releaseCameraAndPreview() { preview?.setCamera(null) camera?.release() camera = null }
CameraX: CameraController
In CameraX, camera selection is handled by the CameraSelector
class. CameraX
makes the common case of using the mặc định camera easy. You can specify whether
you want the mặc định front camera or the mặc định back camera. Furthermore,
CameraX's CameraControl
object lets you easily
set the zoom level for your tiện ích, sánh if
your tiện ích is running on a device that supports
logical cameras, then it will switch
to the proper lens.
Here's the CameraX code for using the mặc định back camera with a
CameraController
:
// CameraX: select a camera with CameraController var cameraController = LifecycleCameraController(baseContext) val selector = CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_BACK).build() cameraController.cameraSelector = selector
CameraX: CameraProvider
Here's an example of selecting the mặc định front camera with CameraProvider
(either the front or back camera can be used with either CameraController
or
CameraProvider
):
// CameraX: select a camera with CameraProvider. // Use await() within a suspend function vĩ đại get CameraProvider instance. // For more details on await(), see the "Android development concepts" // section above. private suspend fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this).await() // Set up UseCases (more on UseCases in later scenarios) var useCases:Array= ... // Set the cameraSelector vĩ đại use the mặc định front-facing (selfie) // camera. val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA try { // Unbind UseCases before rebinding. cameraProvider.unbindAll() // Bind UseCases vĩ đại camera. This function returns a camera // object which can be used vĩ đại perform operations lượt thích zoom, // flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, useCases) } catch(exc: Exception) { Log.e(TAG, "UseCase binding failed", exc) } }) ... // Call startCamera in the setup flow of your tiện ích, such as in onViewCreated. override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) ... lifecycleScope.launch { startCamera() } }
If you want control over which camera is selected, this is also possible in
CameraX if you use CameraProvider
by calling
getAvailableCameraInfos()
,
which gives you a CameraInfo
object for checking certain camera properties like
isFocusMeteringSupported()
.
You can then convert it vĩ đại a CameraSelector
vĩ đại be used lượt thích in the above
examples with the CameraInfo.getCameraSelector()
method.
You can get more details about each camera by using the
Camera2CameraInfo
class. Call
getCameraCharacteristic()
with a key for the camera data you want. Check the
CameraCharacteristics
class for a list of all the keys you can query for.
Here's an example using a custom checkFocalLength()
function that you could
define yourself:
// CameraX: get a cameraSelector for first camera that matches the criteria // defined in checkFocalLength(). val cameraInfo = cameraProvider.getAvailableCameraInfos() .first { cameraInfo -> val focalLengths = Camera2CameraInfo.from(cameraInfo) .getCameraCharacteristic( CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS ) return checkFocalLength(focalLengths) } val cameraSelector = cameraInfo.getCameraSelector()
Showing a preview
A majority of camera applications need vĩ đại show the camera feed on-screen at some point. With Camera1, you need vĩ đại manage the lifecycle callbacks correctly, and you also need vĩ đại determine the rotation and scaling for your preview.
Additionally, in Camera1 you need vĩ đại decide whether vĩ đại use a
TextureView
or a
SurfaceView
as your preview surface.
Both options come with tradeoffs, and in either case, Camera1 requires you to
handle rotation and scaling correctly. CameraX's PreviewView
, on the other
hand, has underlying implementations for both TextureView
and SurfaceView
.
CameraX decides which implementation is best depending on factors such as
the type of device and the Android version your tiện ích is running on. If either
implementation is compatible, you can declare your preference with
PreviewView.ImplementationMode
.
The COMPATIBLE
option uses a TextureView
for the preview, and the
PERFORMANCE
value uses a SurfaceView
(when possible).
Camera1
To show a preview, you need vĩ đại write your own Preview
class with an
implementation of the
android.view.SurfaceHolder.Callback
interface, which is used vĩ đại pass image data from the camera hardware vĩ đại the
application. Then, before you can start the live image preview, the Preview
class must be passed vĩ đại the Camera
object.
// Camera1: mix up a camera preview. class Preview( context: Context, private val camera: Camera ) : SurfaceView(context), SurfaceHolder.Callback { private val holder: SurfaceHolder = holder.apply { addCallback(this@Preview) setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS) } override fun surfaceCreated(holder: SurfaceHolder) { // The Surface has been created, now tell the camera // where vĩ đại draw the preview. camera.apply { try { setPreviewDisplay(holder) startPreview() } catch (e: IOException) { Log.d(TAG, "error setting camera preview", e) } } } override fun surfaceDestroyed(holder: SurfaceHolder) { // Take care of releasing the Camera preview in your activity. } override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) { // If your preview can change or rotate, take care of those // events here. Make sure vĩ đại stop the preview before resizing // or reformatting it. if (holder.surface == null) { return // The preview surface does not exist. } // Stop preview before making changes. try { camera.stopPreview() } catch (e: Exception) { // Tried vĩ đại stop a non-existent preview; nothing vĩ đại vì thế. } // Set preview size and make any resize, rotate or // reformatting changes here. // Start preview with new settings. camera.apply { try { setPreviewDisplay(holder) startPreview() } catch (e: Exception) { Log.d(TAG, "error starting camera preview", e) } } } } class CameraActivity : AppCompatActivity() { private lateinit var viewBinding: ActivityMainBinding private var camera: Camera? = null private var preview: Preview? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewBinding = ActivityMainBinding.inflate(layoutInflater) setContentView(viewBinding.root) // Create an instance of Camera. camera = getCameraInstance() preview = camera?.let { // Create the Preview view. Preview(this, it) } // Set the Preview view as the nội dung of the activity. val cameraPreview: FrameLayout = viewBinding.cameraPreview cameraPreview.addView(preview) } }
CameraX: CameraController
In CameraX, there's a lot less for you, the developer, vĩ đại manage. If you use a
CameraController
, then you must also use PreviewView
. This means the
Preview
UseCase
is implied, making the setup much less work:
// CameraX: mix up a camera preview with a CameraController. class MainActivity : AppCompatActivity() { private lateinit var viewBinding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewBinding = ActivityMainBinding.inflate(layoutInflater) setContentView(viewBinding.root) // Create the CameraController and mix it on the previewView. var cameraController = LifecycleCameraController(baseContext) cameraController.bindToLifecycle(this) val previewView: PreviewView = viewBinding.cameraPreview previewView.controller = cameraController } }
CameraX: CameraProvider
With CameraX's CameraProvider
, you vì thế not have vĩ đại use PreviewView
, but it
still greatly simplifies the preview setup over Camera1. For demonstration
purposes, this example uses a PreviewView
, but you can write a custom
SurfaceProvider
vĩ đại pass into setSurfaceProvider()
if you have more complex
needs.
Here, the Preview
UseCase
is not implied lượt thích it is with CameraController
,
so you need vĩ đại mix it up:
// CameraX: mix up a camera preview with a CameraProvider. // Use await() within a suspend function vĩ đại get CameraProvider instance. // For more details on await(), see the "Android development concepts" // section above. private suspend fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this).await() // Create Preview UseCase. val preview = Preview.Builder() .build() .also { it.setSurfaceProvider( viewBinding.viewFinder.surfaceProvider ) } // Select mặc định back camera. val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA try { // Unbind UseCases before rebinding. cameraProvider.unbindAll() // Bind UseCases vĩ đại camera. This function returns a camera // object which can be used vĩ đại perform operations lượt thích zoom, // flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, useCases) } catch(exc: Exception) { Log.e(TAG, "UseCase binding failed", exc) } }) ... // Call startCamera() in the setup flow of your tiện ích, such as in onViewCreated. override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) ... lifecycleScope.launch { startCamera() } }
Tap-to-focus
When your camera preview is on screen, a common control is vĩ đại mix the focus point when the user taps on the preview.
Camera1
To implement tap-to-focus in Camera1, you must calculate the optimal focus
Area
vĩ đại indicate where the Camera
should attempt vĩ đại focus. This Area
is
passed into setFocusAreas()
. Also, you must mix a compatible focus mode on the
Camera
. Focus area only has effect if the current focus mode is
FOCUS_MODE_AUTO
, FOCUS_MODE_MACRO
, FOCUS_MODE_CONTINUOUS_VIDEO
, or
FOCUS_MODE_CONTINUOUS_PICTURE
.
Each Area
is a rectangle with specified weight. The weight is a value between
1 and 1000, and it's used vĩ đại prioritize focus Areas
if multiple are mix. This
example only uses one Area
, sánh the weight value doesn't matter. Coordinates of
the rectangle range from -1000 vĩ đại 1000. The upper left point is (-1000, -1000).
The lower right point is (1000, 1000). The direction is relative vĩ đại the sensor
orientation, that is, what the sensor sees. The direction is not affected by the
rotation or mirroring of Camera.setDisplayOrientation()
, sánh you need to
convert the touch sự kiện coordinates vĩ đại the sensor coordinates.
Xem thêm: thế thân thành ánh trăng sáng
// Camera1: implement tap-to-focus. class TapToFocusHandler : Camera.AutoFocusCallback { private fun handleFocus(event: MotionEvent) { val camera = camera ?: return val parameters = try { camera.getParameters() } catch (e: RuntimeException) { return } // Cancel previous auto-focus function, if one was in progress. camera.cancelAutoFocus() // Create focus Area. val rect = calculateFocusAreaCoordinates(event.x, sự kiện.y) val weight = 1 // This value's not important since there's only 1 Area. val focusArea = Camera.Area(rect, weight) // Set the focus parameters. parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO) parameters.setFocusAreas(listOf(focusArea)) // Set the parameters back on the camera and initiate auto-focus. camera.setParameters(parameters) camera.autoFocus(this) } private fun calculateFocusAreaCoordinates(x: Int, y: Int) { // Define the size of the Area vĩ đại be returned. This value // should be optimized for your tiện ích. val focusAreaSize = 100 // You must define functions vĩ đại rotate and scale the x and nó values to // be values between 0 and 1, where (0, 0) is the upper left-hand side // of the preview, and (1, 1) is the lower right-hand side. val normalizedX = (rotateAndScaleX(x) - 0.5) * 2000 val normalizedY = (rotateAndScaleY(y) - 0.5) * 2000 // Calculate the values for left, top, right, and bottom of the Rect to // be returned. If the Rect would extend beyond the allowed values of // (-1000, -1000, 1000, 1000), then crop the values vĩ đại fit inside of // that boundary. val left = max(normalizedX - (focusAreaSize / 2), -1000) val top = max(normalizedY - (focusAreaSize / 2), -1000) val right = min(left + focusAreaSize, 1000) val bottom = min(top + focusAreaSize, 1000) return Rect(left, top, left + focusAreaSize, top + focusAreaSize) } override fun onAutoFocus(focused: Boolean, camera: Camera) { if (!focused) { Log.d(TAG, "tap-to-focus failed") } } }
CameraX: CameraController
CameraController
listens vĩ đại PreviewView
's touch events vĩ đại handle
tap-to-focus automatically. You can enable and disable tap-to-focus with
setTapToFocusEnabled()
,
and kiểm tra the value with the corresponding getter
isTapToFocusEnabled()
.
The
getTapToFocusState()
method returns a LiveData
object
for tracking changes vĩ đại the focus state on the CameraController
.
// CameraX: track the state of tap-to-focus over the Lifecycle of a PreviewView, // with handlers you can define for focused, not focused, and failed states. val tapToFocusStateObserver = Observer{ state -> when (state) { CameraController.TAP_TO_FOCUS_NOT_STARTED -> Log.d(TAG, "tap-to-focus init") CameraController.TAP_TO_FOCUS_STARTED -> Log.d(TAG, "tap-to-focus started") CameraController.TAP_TO_FOCUS_FOCUSED -> Log.d(TAG, "tap-to-focus finished (focus successful)") CameraController.TAP_TO_FOCUS_NOT_FOCUSED -> Log.d(TAG, "tap-to-focus finished (focused unsuccessful)") CameraController.TAP_TO_FOCUS_FAILED -> Log.d(TAG, "tap-to-focus failed") } } cameraController.getTapToFocusState().observe(this, tapToFocusStateObserver)
CameraX: CameraProvider
When using a CameraProvider
, there is some setup required vĩ đại get tap-to-focus
working. This example assumes you're using PreviewView
. If not, you need to
adapt the logic vĩ đại apply vĩ đại your custom Surface
.
Here are the steps when using PreviewView
:
- Set up a gesture detector vĩ đại handle tap events.
- With the tap sự kiện, create a
MeteringPoint
usingMeteringPointFactory.createPoint()
. - With the
MeteringPoint
, create aFocusMeteringAction
. - With the
CameraControl
object on yourCamera
(returned frombindToLifecycle()
), HotlinestartFocusAndMetering()
, passing in theFocusMeteringAction
. - (Optional) Respond vĩ đại the
FocusMeteringResult
. - Set your gesture detector vĩ đại respond vĩ đại touch events in
PreviewView.setOnTouchListener()
.
// CameraX: implement tap-to-focus with CameraProvider. // Define a gesture detector vĩ đại respond vĩ đại tap events and call // startFocusAndMetering on CameraControl. If you want vĩ đại use a // coroutine with await() vĩ đại kiểm tra the result of focusing, see the // "Android development concepts" section above. val gestureDetector = GestureDetectorCompat(context, object : SimpleOnGestureListener() { override fun onSingleTapUp(e: MotionEvent): Boolean { val previewView = previewView ?: return val camera = camera ?: return val meteringPointFactory = previewView.meteringPointFactory val focusPoint = meteringPointFactory.createPoint(e.x, e.y) val meteringAction = FocusMeteringAction .Builder(meteringPoint).build() lifecycleScope.launch { val focusResult = camera.cameraControl .startFocusAndMetering(meteringAction).await() if (!result.isFocusSuccessful()) { Log.d(TAG, "tap-to-focus failed") } } } } ) ... // Set the gestureDetector in a touch listener on the PreviewView. previewView.setOnTouchListener { _, sự kiện -> // See pinch-to-zooom scenario for scaleGestureDetector definition. var didConsume = scaleGestureDetector.onTouchEvent(event) if (!scaleGestureDetector.isInProgress) { didConsume = gestureDetector.onTouchEvent(event) } didConsume }
Pinch-to-zoom
Zooming in and out of a preview is another common direct manipulation of the camera preview. With the increasing number of cameras on devices, users also expect the lens with the best focal length vĩ đại be automatically selected as the result of zooming.
Camera1
There are two ways vĩ đại zoom using Camera1. The Camera.startSmoothZoom()
method
animates from the current zoom level vĩ đại the zoom level you pass in. The
Camera.Parameters.setZoom()
method jumps directly vĩ đại the zoom level you pass
in. Before using either one, Hotline isSmoothZoomSupported()
or
isZoomSupported()
, respectively, vĩ đại ensure the related zoom methods you need
are available on your Camera.
To implement pinch-to-zoom, this example uses setZoom()
because the touch
listener on the preview surface continually fires events as the pinch
gesture happens, sánh it updates the zoom level immediately each time. The
ZoomTouchListener
class is defined below, and it should be mix as a callback
to your preview surface's touch listener.
// Camera1: implement pinch-to-zoom. // Define a scale gesture detector vĩ đại respond vĩ đại pinch events and call // setZoom on Camera.Parameters. val scaleGestureDetector = ScaleGestureDetector(context, object : ScaleGestureDetector.OnScaleGestureListener { override fun onScale(detector: ScaleGestureDetector): Boolean { val camera = camera ?: return false val parameters = try { camera.parameters } catch (e: RuntimeException) { return false } // In case there is any focus happening, stop it. camera.cancelAutoFocus() // Set the zoom level on the Camera.Parameters, and set // the Parameters back onto the Camera. val currentZoom = parameters.zoom parameters.setZoom(detector.scaleFactor * currentZoom) camera.setParameters(parameters) return true } } ) // Define a View.OnTouchListener vĩ đại attach vĩ đại your preview view. class ZoomTouchListener : View.OnTouchListener { override fun onTouch(v: View, event: MotionEvent): Boolean = scaleGestureDetector.onTouchEvent(event) } // Set a ZoomTouchListener vĩ đại handle touch events on your preview view // if zoom is supported by the current camera. if (camera.getParameters().isZoomSupported()) { view.setOnTouchListener(ZoomTouchListener()) }
CameraX: CameraController
Similar vĩ đại tap-to-focus, CameraController
listens vĩ đại PreviewView's touch
events vĩ đại handle pinch-to-zoom automatically. You can enable and disable
pinch-to-zoom with
setPinchToZoomEnabled()
,
and kiểm tra the value with the corresponding getter
isPinchToZoomEnabled()
.
The
getZoomState()
method returns a LiveData
object for tracking changes vĩ đại the
ZoomState
on the
CameraController
.
// CameraX: track the state of pinch-to-zoom over the Lifecycle of // a PreviewView, logging the linear zoom ratio. val pinchToZoomStateObserver = Observer{ state -> val zoomRatio = state.getZoomRatio() Log.d(TAG, "ptz-zoom-ratio $zoomRatio") } cameraController.getZoomState().observe(this, pinchToZoomStateObserver)
CameraX: CameraProvider
To get pinch-to-zoom working with CameraProvider
, some setup is required. If
you're not using PreviewView
, you need vĩ đại adapt the logic vĩ đại apply vĩ đại your
custom Surface
.
Here are the steps when using PreviewView
:
- Set up a scale gesture detector vĩ đại handle pinch events.
- Get the
ZoomState
from theCamera.CameraInfo
object, where theCamera
instance is returned when you callbindToLifecycle()
. - If the
ZoomState
has azoomRatio
value, save that as the current zoom ratio. If there is nozoomRatio
onZoomState
, then use the camera's default zoom rate (1.0). - Take the product of the current zoom ratio with the
scaleFactor
to determine the new zoom ratio, and pass that intoCameraControl.setZoomRatio()
. - Set your gesture detector vĩ đại respond vĩ đại touch events in
PreviewView.setOnTouchListener()
.
// CameraX: implement pinch-to-zoom with CameraProvider. // Define a scale gesture detector vĩ đại respond vĩ đại pinch events and call // setZoomRatio on CameraControl. val scaleGestureDetector = ScaleGestureDetector(context, object : SimpleOnGestureListener() { override fun onScale(detector: ScaleGestureDetector): Boolean { val camera = camera ?: return val zoomState = camera.cameraInfo.zoomState val currentZoomRatio: Float = zoomState.value?.zoomRatio ?: 1f camera.cameraControl.setZoomRatio( detector.scaleFactor * currentZoomRatio ) } } ) ... // Set the scaleGestureDetector in a touch listener on the PreviewView. previewView.setOnTouchListener { _, sự kiện -> var didConsume = scaleGestureDetector.onTouchEvent(event) if (!scaleGestureDetector.isInProgress) { // See pinch-to-zooom scenario for gestureDetector definition. didConsume = gestureDetector.onTouchEvent(event) } didConsume }
Taking a photo
This section shows how vĩ đại trigger photo capture, whether you need vĩ đại vì thế it on a shutter button press, after a timer has elapsed, or on any other sự kiện of your choosing.
Camera1
In Camera1, you first define a
Camera.PictureCallback
to manage the picture data when it's requested. Here's a simple example of
PictureCallback
for handling JPEG image data:
// Camera1: define a Camera.PictureCallback vĩ đại handle JPEG data. private val picture = Camera.PictureCallback { data, _ -> val pictureFile: File = getOutputMediaFile(MEDIA_TYPE_IMAGE) ?: lập cập { Log.d(TAG, "error creating truyền thông tệp tin, kiểm tra storage permissions") return@PictureCallback } try { val fos = FileOutputStream(pictureFile) fos.write(data) fos.close() } catch (e: FileNotFoundException) { Log.d(TAG, "file not found", e) } catch (e: IOException) { Log.d(TAG, "error accessing file", e) } }
Then, whenever you want vĩ đại take a picture, you Hotline the takePicture()
method
on your Camera
instance. This takePicture()
method has three different
parameters for different data types. The first parameter is for a
ShutterCallback
(which isn't defined in this example). The second parameter is
for a PictureCallback
vĩ đại handle the raw (uncompressed) camera data. The third
parameter is the one this example uses, since it's a PictureCallback
vĩ đại handle
JPEG image data.
// Camera1: Hotline takePicture on Camera instance, passing our PictureCallback. camera?.takePicture(null, null, picture)
CameraX: CameraController
CameraX's CameraController
maintains the simplicity of Camera1 for image
capture by implementing a takePicture()
method of its own. Here, define a
function vĩ đại configure a MediaStore
entry and take a photo vĩ đại be saved there.
// CameraX: define a function that uses CameraController vĩ đại take a photo. private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" private fun takePhoto() { // Create time stamped name and MediaStore entry. val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) .format(System.currentTimeMillis()) val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, name) put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image") } } // Create output options object which contains tệp tin + metadata. val outputOptions = ImageCapture.OutputFileOptions .Builder(context.getContentResolver(), MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) .build() // Set up image capture listener, which is triggered after photo has // been taken. cameraController.takePicture( outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback { override fun onError(e: ImageCaptureException) { Log.e(TAG, "photo capture failed", e) } override fun onImageSaved( output: ImageCapture.OutputFileResults ) { val msg = "Photo capture succeeded: ${output.savedUri}" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.d(TAG, msg) } } ) }
CameraX: CameraProvider
Taking a photo with CameraProvider
works almost the exact same way as with
CameraController
, but you first need vĩ đại create and bind an ImageCapture
UseCase
vĩ đại have an object vĩ đại Hotline takePicture()
on:
// CameraX: create and bind an ImageCapture UseCase. // Make a reference vĩ đại the ImageCapture UseCase at a scope that can be accessed // throughout the camera logic in your tiện ích. private var imageCapture: ImageCapture? = null ... // Create an ImageCapture instance (can be added with other // UseCase definitions). imageCapture = ImageCapture.Builder().build() ... // Bind UseCases vĩ đại camera (adding imageCapture along with preview here, but // preview is not required vĩ đại use imageCapture). This function returns a camera // object which can be used vĩ đại perform operations lượt thích zoom, flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, preview, imageCapture)
Then, whenever you want vĩ đại capture a photo, you can call
ImageCapture.takePicture()
. See the CameraController
code in this section
for a full example of the takePhoto()
function.
// CameraX: define a function that uses CameraController vĩ đại take a photo. private fun takePhoto() { // Get a stable reference of the modifiable ImageCapture UseCase. val imageCapture = imageCapture ?: return ... // Call takePicture on imageCapture instance. imageCapture.takePicture( ... ) }
Recording a video
Recording a Clip is considerably more complicated phàn nàn the scenarios looked at so far. Each part of the process must be mix up properly, usually in a particular order. Also, you might need vĩ đại verify that the Clip and audio are in sync or giảm giá khuyến mãi with additional device inconsistencies.
As you'll see, CameraX again handles a lot of this complexity for you.
Camera1
Video capture using Camera1 requires careful management of the Camera
and
MediaRecorder
, and the methods must
be called in a particular order. You must follow this order for
your application vĩ đại work properly:
- Open the camera.
- Prepare and start a preview (if your tiện ích shows the Clip being recorded, which is usually the case).
- Unlock the camera for use by
MediaRecorder
by callingCamera.unlock()
. - Configure the recording by calling these methods on
MediaRecorder
:- Connect your
Camera
instance withsetCamera(camera)
. - Call
setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
. - Call
setVideoSource(MediaRecorder.VideoSource.CAMERA)
. - Call
setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P))
to mix the quality. SeeCamcorderProfile
for all of the quality options. - Call
setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())
. - If your tiện ích has a preview of the Clip, call
setPreviewDisplay(preview?.holder?.surface)
. - Call
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
. - Call
setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
. - Call
setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)
. - Call
prepare()
vĩ đại finalize the configuration of yourMediaRecorder
.
- Connect your
- To start recording, Hotline
MediaRecorder.start()
. - To stop recording, Hotline these methods. Again, follow this exact order:
- Call
MediaRecorder.stop()
. - Optionally, remove the current
MediaRecorder
configuration by callingMediaRecorder.reset()
. - Call
MediaRecorder.release()
. - Lock the camera sánh that future
MediaRecorder
sessions can use it by callingCamera.lock()
.
- Call
- To stop the preview, Hotline
Camera.stopPreview()
. - Finally, vĩ đại release the
Camera
sánh other processes can use it, callCamera.release()
.
Here are all of those steps combined:
// Camera1: mix up a MediaRecorder and a function vĩ đại start and stop video // recording. // Make a reference vĩ đại the MediaRecorder at a scope that can be accessed // throughout the camera logic in your tiện ích. private var mediaRecorder: MediaRecorder? = null private var isRecording = false ... private fun prepareMediaRecorder(): Boolean { mediaRecorder = MediaRecorder() // Unlock and mix camera vĩ đại MediaRecorder. camera?.unlock() mediaRecorder?.lập cập { setCamera(camera) // Set the audio and Clip sources. setAudioSource(MediaRecorder.AudioSource.CAMCORDER) setVideoSource(MediaRecorder.VideoSource.CAMERA) // Set a CamcorderProfile (requires API Level 8 or higher). setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH)) // Set the output tệp tin. setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString()) // Set the preview output. setPreviewDisplay(preview?.holder?.surface) setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT) setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT) // Prepare configured MediaRecorder. return try { prepare() true } catch (e: IllegalStateException) { Log.d(TAG, "preparing MediaRecorder failed", e) releaseMediaRecorder() false } catch (e: IOException) { Log.d(TAG, "setting MediaRecorder tệp tin failed", e) releaseMediaRecorder() false } } return false } private fun releaseMediaRecorder() { mediaRecorder?.reset() mediaRecorder?.release() mediaRecorder = null camera?.lock() } private fun startStopVideo() { if (isRecording) { // Stop recording and release camera. mediaRecorder?.stop() releaseMediaRecorder() camera?.lock() isRecording = false // This is a good place vĩ đại inform user that Clip recording has stopped. } else { // Initialize Clip camera. if (prepareVideoRecorder()) { // Camera is available and unlocked, MediaRecorder is prepared, now // you can start recording. mediaRecorder?.start() isRecording = true // This is a good place vĩ đại inform the user that recording has // started. } else { // Prepare didn't work, release the camera. releaseMediaRecorder() // Inform user here. } } }
CameraX: CameraController
With CameraX's CameraController
, you can toggle the ImageCapture
,
VideoCapture
, and ImageAnalysis
UseCase
s independently,
as long as the list of UseCases can be used concurrently.
The ImageCapture
and ImageAnalysis
UseCase
s are enabled by mặc định, which
is why you didn't need vĩ đại Hotline setEnabledUseCases()
for taking a photo.
To use a CameraController
for Clip recording, you first need vĩ đại use
setEnabledUseCases()
vĩ đại allow the VideoCapture
UseCase
.
// CameraX: Enable VideoCapture UseCase on CameraController. cameraController.setEnabledUseCases(VIDEO_CAPTURE);
When you want vĩ đại start recording Clip, you can Hotline the
CameraController.startRecording()
function. This function can save the recorded Clip vĩ đại a File
, as you can see
in the example below. Additionally, you need vĩ đại pass an Executor
and a class
that implements
OnVideoSavedCallback
to handle success and error callbacks. When the recording should over, call
CameraController.stopRecording()
.
Note: If you're using CameraX 1.3.0-alpha02 or later, there is an additional
AudioConfig
parameter
that allows you vĩ đại enable or disable audio recording on your Clip. To enable
audio recording, you need vĩ đại ensure you have microphone permissions.
Additionally, the stopRecording()
method is removed in 1.3.0-alpha02, and
startRecording()
returns a Recording
object that can be used for pausing,
resuming, and stopping the Clip recording.
// CameraX: implement Clip capture with CameraController. private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" // Define a VideoSaveCallback class for handling success and error states. class VideoSaveCallback : OnVideoSavedCallback { override fun onVideoSaved(outputFileResults: OutputFileResults) { val msg = "Video capture succeeded: ${outputFileResults.savedUri}" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.d(TAG, msg) } override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) { Log.d(TAG, "error saving video: $message", cause) } } private fun startStopVideo() { if (cameraController.isRecording()) { // Stop the current recording session. cameraController.stopRecording() return } // Define the File options for saving the Clip. val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) .format(System.currentTimeMillis()) val outputFileOptions = OutputFileOptions .Builder(File(this.filesDir, name)) .build() // Call startRecording on the CameraController. cameraController.startRecording( outputFileOptions, ContextCompat.getMainExecutor(this), VideoSaveCallback() ) }
CameraX: CameraProvider
If you're using a CameraProvider
, you need vĩ đại create a VideoCapture
UseCase
and pass in a Recorder
object. On the Recorder.Builder
, you can
set the Clip quality and, optionally, a
FallbackStrategy
, which
handles cases when a device can't meet your desired quality specifications. Then
bind the VideoCapture
instance vĩ đại the CameraProvider
with your other
UseCase
s.
// CameraX: create and bind a VideoCapture UseCase with CameraProvider. // Make a reference vĩ đại the VideoCapture UseCase and Recording at a // scope that can be accessed throughout the camera logic in your tiện ích. private lateinit var videoCapture: VideoCaptureprivate var recording: Recording? = null ... // Create a Recorder instance vĩ đại mix on a VideoCapture instance (can be // added with other UseCase definitions). val recorder = Recorder.Builder() .setQualitySelector(QualitySelector.from(Quality.FHD)) .build() videoCapture = VideoCapture.withOutput(recorder) ... // Bind UseCases vĩ đại camera (adding videoCapture along with preview here, but // preview is not required vĩ đại use videoCapture). This function returns a camera // object which can be used vĩ đại perform operations lượt thích zoom, flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, preview, videoCapture)
At this point, the Recorder
can be accessed on the videoCapture.output
property. The Recorder
can start Clip recordings that are saved vĩ đại a File
,
ParcelFileDescriptor
, or MediaStore
. This example uses MediaStore
.
Xem thêm: kỳ thật cây lim có thể dựa
On the Recorder
, there are several methods vĩ đại Hotline vĩ đại prepare it. Call
prepareRecording()
vĩ đại mix the MediaStore
output options. If your tiện ích has
permission vĩ đại use the device's microphone, Hotline withAudioEnabled()
as well.
Then, Hotline start()
vĩ đại begin recording, passing in a context and a
Consumer<VideoRecordEvent>
sự kiện listener vĩ đại handle Clip record events. If
successful, the returned Recording
can be used vĩ đại pause, resume, or stop the
recording.
// CameraX: implement Clip capture with CameraProvider. private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" private fun startStopVideo() { val videoCapture = this.videoCapture ?: return if (recording != null) { // Stop the current recording session. recording.stop() recording = null return } // Create and start a new recording session. val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) .format(System.currentTimeMillis()) val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, name) put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4") if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX-Video") } } val mediaStoreOutputOptions = MediaStoreOutputOptions .Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI) .setContentValues(contentValues) .build() recording = videoCapture.output .prepareRecording(this, mediaStoreOutputOptions) .withAudioEnabled() .start(ContextCompat.getMainExecutor(this)) { recordEvent -> when(recordEvent) { is VideoRecordEvent.Start -> { viewBinding.videoCaptureButton.apply { text = getString(R.string.stop_capture) isEnabled = true } } is VideoRecordEvent.Finalize -> { if (!recordEvent.hasError()) { val msg = "Video capture succeeded: " + "${recordEvent.outputResults.outputUri}" Toast.makeText( baseContext, msg, Toast.LENGTH_SHORT ).show() Log.d(TAG, msg) } else { recording?.close() recording = null Log.e(TAG, "video capture ends with error", recordEvent.error) } viewBinding.videoCaptureButton.apply { text = getString(R.string.start_capture) isEnabled = true } } } } }
Additional resources
We have several complete CameraX apps in our Camera Samples GitHub Repository. These samples show you how the scenarios in this guide fit into a fully-fledged Android tiện ích.
If you'd lượt thích additional tư vấn in migrating vĩ đại CameraX or have questions regarding the suite of Android Camera APIs, please reach out vĩ đại us on the CameraX Discussion Group.
Bình luận