添加依赖
implementation 'androidx.camera:camera-core:1.2.2'
implementation 'androidx.camera:camera-camera2:1.2.2'
implementation 'androidx.camera:camera-lifecycle:1.2.2'
implementation 'androidx.camera:camera-view:1.2.2'
implementation 'androidx.camera:camera-extensions:1.2.2'
implementation 'com.google.accompanist:accompanist-permissions:0.24.7-alpha'
权限申请
清单中添加
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-permission android:name="android.permission.CAMERA" />
动态申请权限
val permissionState =
rememberPermissionState(android.Manifest.permission.CAMERA)
if (permissionState.status.isGranted) {
val cameraState = rememberCameraState()
CameraPreviewCard(modifier = Modifier.fillMaxSize(), cameraState)
} else {
Button(onClick = {
permissionState.launchPermissionRequest()
}) {
Text(text = "请求授权")
}
}
相机预览
@androidx.camera.core.ExperimentalGetImage
@Composable
fun CameraPreviewCard(
modifier: Modifier = Modifier,
cameraState: CameraState,
) {
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
var preview by remember { mutableStateOf<Preview?>(null) }
AndroidView(modifier = modifier, factory = { ctx ->
PreviewView(ctx).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
)
controller = cameraState.controller.apply {
bindToLifecycle(lifecycleOwner)
}
previewStreamState.observe(lifecycleOwner) { state ->
cameraState.isStreaming = state == PreviewView.StreamState.STREAMING
}
}
}, update = { previewView ->
val cameraSelector: CameraSelector =
CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
val cameraProviderFuture: ListenableFuture<ProcessCameraProvider> =
ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener(
{
val rotate = Surface.ROTATION_180
preview = Preview.Builder().setTargetRotation(rotate).build().also {
it.setSurfaceProvider(previewView.surfaceProvider)
}
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(lifecycleOwner,cameraSelector,preview)
} catch (e: Exception) {
Log.d("CameraPreview", "CameraPreview: ${e.localizedMessage}")
}
}, ContextCompat.getMainExecutor(context)
)
})
}
class CameraState internal constructor(context: Context) {
/**
* Check if camera is streaming or not.
* */
var isStreaming: Boolean by mutableStateOf(false)
internal set
/**
* Main controller from CameraX. useful in cases that haven't been release some feature yet.
* */
internal val controller: LifecycleCameraController = LifecycleCameraController(context)
}
这样在主页就可以对相机进行预览了。
图像分析
如果要对camera的图像数据进行分析处理,可添加ImageAnalysis.Analyzer,
class FaceAnalyser :
ImageAnalysis.Analyzer {
override fun analyze(image: ImageProxy) {
try {
if (image.format == ImageFormat.YUV_420_888) {
val yuV420toNV21 = YUV420toNV21(image.image!!) //这里只做简单的图像转换然后存储
var rotateNv21: ByteArray? = yuV420toNV21
Nv21Data.data = rotateNv21
Nv21Data.width = image.width
Nv21Data.height = image.height
}
image.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
fun saveYuv(byteArray: ByteArray, width: Int, height: Int) {
val yuvImage = YuvImage(byteArray, ImageFormat.NV21, width, height, null)
val yFile = File(
Environment.getExternalStorageDirectory().path + "/test/",
"y_" + System.currentTimeMillis() + ".jpg"
)
Log.d("linlian", "${yFile.absolutePath}")
saveYuvToFile(yFile, width, height, yuvImage)
}
fun saveYuvToFile(file: File, wid: Int, height: Int, yuvImage: YuvImage) {
try {
val c = file.createNewFile()
Log.d("test", "$file created: $c")
val fos = FileOutputStream(file)
yuvImage.compressToJpeg(Rect(0, 0, wid, height), 100, fos)
fos.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
fun YUV420toNV21(image: Image): ByteArray? {
val crop = image.cropRect
val format = image.format
val width = crop.width()
val height = crop.height()
val planes = image.planes
val data = ByteArray(width * height * ImageFormat.getBitsPerPixel(format) / 8)
val rowData = ByteArray(planes[0].rowStride)
var channelOffset = 0
var outputStride = 1
for (i in planes.indices) {
when (i) {
0 -> {
channelOffset = 0
outputStride = 1
}
1 -> {
channelOffset = width * height + 1
outputStride = 2
}
2 -> {
channelOffset = width * height
outputStride = 2
}
}
val buffer = planes[i].buffer
val rowStride = planes[i].rowStride
val pixelStride = planes[i].pixelStride
val shift = if (i == 0) 0 else 1
val w = width shr shift
val h = height shr shift
buffer.position(rowStride * (crop.top shr shift) + pixelStride * (crop.left shr shift))
for (row in 0 until h) {
var length: Int
if (pixelStride == 1 && outputStride == 1) {
length = w
buffer[data, channelOffset, length]
channelOffset += length
} else {
length = (w - 1) * pixelStride + 1
buffer[rowData, 0, length]
for (col in 0 until w) {
data[channelOffset] = rowData[col * pixelStride]
channelOffset += outputStride
}
}
if (row < h - 1) {
buffer.position(buffer.position() + rowStride - length)
}
}
}
return data
}
object Nv21Data {
var data: ByteArray? = null
var width = 0
var height = 0
}
camera中添加分析器
Camera preview中添加
val cameraProviderFuture: ListenableFuture<ProcessCameraProvider> =
ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener(
{
val rotate = Surface.ROTATION_180
preview = Preview.Builder().setTargetRotation(rotate).build().also {
it.setSurfaceProvider(previewView.surfaceProvider)
}
val cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor() //分析数据的线程
//图像分析
val faceAnalyser = FaceAnalyser()
val imageAnalysis: ImageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)//default OUTPUT_IMAGE_FORMAT_YUV_420_888
.build().also {
it.setAnalyzer(cameraExecutor, faceAnalyser)
}
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(lifecycleOwner,cameraSelector,preview,imageAnalysis)//绑定
} catch (e: Exception) {
Log.d("CameraPreview", "CameraPreview: ${e.localizedMessage}")
}
}, ContextCompat.getMainExecutor(context)
)
添加保存图片的按钮
Button(onClick = {
Nv21Data.data?.let {
saveYuv(it, Nv21Data.width, Nv21Data.height)
}
}) {
Text(text = "save")
}
点击 save就可以在sdcard上得到当前图像的文件,可以观察数据格式,图像方向是否正确。
保存文件,记得需要提前申请文件存储权限