Table of Contents
Introduction
Capturing Photos and Videos in Android, With the evolution of mobile technology, camera capabilities have become one of the most critical features of smartphones. This has opened up a plethora of opportunities for developers to create rich multimedia applications. The Camera API in Android allows developers to harness these capabilities and build apps that can capture photos and videos seamlessly. In this blog, we will delve into the Camera API using Kotlin, providing extensive examples to guide you through capturing photos and videos.
Understanding Camera API
The Camera API in Android provides a set of features that allow developers to manage camera operations. The primary classes and components involved are:
- CameraX: A Jetpack support library that makes integrating camera features easier.
- Camera2: The advanced Camera API introduced in Android 5.0 (Lollipop) for more granular control.
- Intents: A simpler way to use the camera for basic tasks.
Setting Up CameraX
CameraX is the preferred choice for most developers due to its ease of use and compatibility with a wide range of devices. To get started with CameraX, you need to add the necessary dependencies in your build.gradle
file:
dependencies {
def camerax_version = "1.0.1"
implementation "androidx.camera:camera-core:$camerax_version"
implementation "androidx.camera:camera-camera2:$camerax_version"
implementation "androidx.camera:camera-lifecycle:$camerax_version"
implementation "androidx.camera:camera-view:$camerax_version"
implementation "androidx.camera:camera-extensions:$camerax_version"
}
Permissions
To use the camera, your app must request the necessary permissions in the AndroidManifest.xml
file:
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
Don’t forget to check and request permissions at runtime if your app targets Android 6.0 (Marshmallow) or higher.
Capturing Photos with CameraX
Let’s dive into capturing photos with CameraX. Here’s a step-by-step guide:
- Layout Setup
First, create a layout file (activity_main.xml
) with a PreviewView
to display the camera preview and a button to capture the photo.
<androidx.camera.view.PreviewView
android:id="@+id/viewFinder"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<Button
android:id="@+id/captureButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Capture"
android:layout_gravity="center"/>
- Initializing CameraX
In your MainActivity.kt
, initialize CameraX and set up the camera preview:
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import android.widget.Button
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import java.io.File
class MainActivity : AppCompatActivity() {
private lateinit var viewFinder: PreviewView
private lateinit var imageCapture: ImageCapture
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewFinder = findViewById(R.id.viewFinder)
val captureButton: Button = findViewById(R.id.captureButton)
// Request camera permissions
if (allPermissionsGranted()) {
startCamera()
} else {
ActivityCompat.requestPermissions(
this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
}
captureButton.setOnClickListener { takePhoto() }
}
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build().also {
it.setSurfaceProvider(viewFinder.surfaceProvider)
}
imageCapture = ImageCapture.Builder().build()
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture)
} catch(exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(this))
}
private fun takePhoto() {
val photoFile = File(
outputDirectory,
SimpleDateFormat(FILENAME_FORMAT, Locale.US
).format(System.currentTimeMillis()) + ".jpg")
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
imageCapture.takePicture(
outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback {
override fun onError(exc: ImageCaptureException) {
Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
}
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val savedUri = Uri.fromFile(photoFile)
val msg = "Photo capture succeeded: $savedUri"
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
Log.d(TAG, msg)
}
})
}
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(
baseContext, it) == PackageManager.PERMISSION_GRANTED
}
companion object {
private const val TAG = "CameraXBasic"
private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
private const val REQUEST_CODE_PERMISSIONS = 10
private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
}
}
Capturing Videos with CameraX
Capturing videos is similar to capturing photos but requires the use of VideoCapture
. Here’s how you can set it up:
- Add
VideoCapture
Use Case
Modify the startCamera
function to include VideoCapture
.
private lateinit var videoCapture: VideoCapture
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build().also {
it.setSurfaceProvider(viewFinder.surfaceProvider)
}
imageCapture = ImageCapture.Builder().build()
videoCapture = VideoCapture.Builder().build()
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture, videoCapture)
} catch(exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(this))
}
- Capture Video
Add a button to start and stop video recording. Update MainActivity.kt
with the logic to handle video capture:
private lateinit var captureVideoButton: Button
private var isRecording = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewFinder = findViewById(R.id.viewFinder)
captureVideoButton = findViewById(R.id.captureVideoButton)
if (allPermissionsGranted()) {
startCamera()
} else {
ActivityCompat.requestPermissions(
this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
}
captureVideoButton.setOnClickListener {
if (isRecording) {
videoCapture.stopRecording()
captureVideoButton.text = "Start Recording"
} else {
startRecording()
captureVideoButton.text = "Stop Recording"
}
isRecording = !isRecording
}
}
private fun startRecording() {
val videoFile = File(
outputDirectory,
SimpleDateFormat(FILENAME_FORMAT, Locale.US
).format(System.currentTimeMillis()) + ".mp4")
val outputOptions = VideoCapture.OutputFileOptions.Builder(videoFile).build()
videoCapture.startRecording(
outputOptions, ContextCompat.getMainExecutor(this), object : VideoCapture.OnVideoSavedCallback {
override fun onVideoSaved(output: VideoCapture.OutputFileResults) {
val savedUri = Uri.fromFile(videoFile)
val msg = "Video capture succeeded: $savedUri"
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
Log.d(TAG, msg)
}
override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) {
Log.e(TAG, "Video capture failed: $message", cause)
}
})
}
Handling Permissions
Since Android 6.0 (Marshmallow), you must request permissions at runtime. Use the following method to handle permission requests:
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (allPermissionsGranted()) {
startCamera()
} else {
Toast.makeText(this,
"Permissions not granted by the user.",
Toast.LENGTH_SHORT).show()
finish()
}
}
}
Conclusion
The Camera API in Android, particularly through CameraX, provides a powerful and flexible way to capture photos and videos in your applications.