企业网站个人备案吗,手机之家中关村,网站 建设 申请,安卓在线视频嗅探app预备知识
surface surfaceView SurfaceHolder surface 是什么#xff1f;
一句话来说#xff1a; surface是一块用于填充图像数据的内存。 surfaceView 是什么#xff1f;
它是一个显示surface 的View。 在app中仍在 ViewHierachy 中#xff0c;但在wms 中可以理解为…预备知识
surface surfaceView SurfaceHolder surface 是什么
一句话来说 surface是一块用于填充图像数据的内存。 surfaceView 是什么
它是一个显示surface 的View。 在app中仍在 ViewHierachy 中但在wms 中可以理解为Server端它与宿主窗口是分离的。它拥有独立的绘制表面 它不与其它宿主窗口共享一个绘制表面可以单独在一个线程进行绘制并不会占用主线程的资源这样绘制就会比较高效游戏、视频播放还有最近几年比较热门的直播都可以用surface surfaceHolder 是什么 它是一个接口给持有surface的对象使用。可以控制surface的大小和格式。编辑surface中的像素以及监听surface的变化这个接口通过surfaceView获取。 CameraX CameraX是Jetpack 支持库
特点
1、利用的是 camera2 的功能
2、它具有生命周期的感应使用更加简单代码量也减少了不少。可以灵活的录制视频和拍照。
3、抹平设备兼容问题。 使用
第一步
引入依赖
dependencies {def camerax_version 1.2.0-alpha04implementation androidx.camera:camera-core:${camerax_version}implementation androidx.camera:camera-camera2:${camerax_version}implementation androidx.camera:camera-lifecycle:${camerax_version}implementation androidx.camera:camera-video:${camerax_version}implementation androidx.camera:camera-view:${camerax_version}implementation androidx.camera:camera-extensions:${camerax_version}
}使用 ViewBinding因此请使用以下代码在 android{} 代码块末尾启用它
android {buildFeatures {viewBinding true}
}第二步
添加布局控件。 androidx.camera.view.PreViewView
?xml version1.0 encodingutf-8?
androidx.constraintlayout.widget.ConstraintLayout xmlns:androidhttp://schemas.android.com/apk/res/androidxmlns:apphttp://schemas.android.com/apk/res-autoxmlns:toolshttp://schemas.android.com/toolsandroid:layout_widthmatch_parentandroid:layout_heightmatch_parenttools:context.MainActivityandroidx.camera.view.PreviewViewandroid:idid/viewFinderandroid:layout_widthmatch_parentandroid:layout_heightmatch_parent /Buttonandroid:idid/image_capture_buttonandroid:layout_width110dpandroid:layout_height110dpandroid:layout_marginEnd50dpandroid:layout_marginBottom50dpandroid:elevation2dpandroid:textstring/take_photoapp:layout_constraintBottom_toBottomOfparentapp:layout_constraintEnd_toStartOfid/vertical_centerline /Buttonandroid:idid/video_capture_buttonandroid:layout_width110dpandroid:layout_height110dpandroid:layout_marginStart50dpandroid:layout_marginBottom50dpandroid:elevation2dpandroid:textstring/start_captureapp:layout_constraintBottom_toBottomOfparentapp:layout_constraintStart_toEndOfid/vertical_centerline /androidx.constraintlayout.widget.Guidelineandroid:idid/vertical_centerlineandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:orientationverticalapp:layout_constraintGuide_percent.50 //androidx.constraintlayout.widget.ConstraintLayout因为 build.gradle 中设置了 viewBinding true所以会为每个布局都生成对应的绑定类即 activity_main.xml 自动生成 ActivityMainBinding 类。 第三步
设置检查相机权限:
在 MainActivity.kt 中设置检查相机权限设置 Button 的响应事件: package com.bignerdranch.android.cameraxappimport android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.ImageCapture
import androidx.camera.video.Recorder
import androidx.camera.video.Recording
import androidx.camera.video.VideoCapture
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.bignerdranch.android.cameraxapp.databinding.ActivityMainBinding
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executorstypealias LumaListener (luma: Double) - Unitclass MainActivity : AppCompatActivity() {private lateinit var viewBinding: ActivityMainBindingprivate var imageCapture: ImageCapture? nullprivate var videoCapture: VideoCaptureRecorder? nullprivate var recording: Recording? nullprivate lateinit var cameraExecutor: ExecutorServiceoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)viewBinding ActivityMainBinding.inflate(layoutInflater)setContentView(viewBinding.root)// Request camera permissionsif (allPermissionsGranted()) {startCamera()} else {ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)}// Set up the listeners for take photo and video capture buttonsviewBinding.imageCaptureButton.setOnClickListener { takePhoto() }viewBinding.videoCaptureButton.setOnClickListener { captureVideo() }cameraExecutor Executors.newSingleThreadExecutor()}private fun takePhoto() {}private fun captureVideo() {}private fun startCamera() {}private fun allPermissionsGranted() REQUIRED_PERMISSIONS.all {ContextCompat.checkSelfPermission(baseContext, it) PackageManager.PERMISSION_GRANTED}override fun onDestroy() {super.onDestroy()cameraExecutor.shutdown()}companion object {private const val TAG CameraXAppprivate const val FILENAME_FORMAT yyyy-MM-dd-HH-mm-ss-SSSprivate const val REQUEST_CODE_PERMISSIONS 10private val REQUIRED_PERMISSIONS mutableListOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO).apply { }.toTypedArray()}
}在 AndroidManifest.xml 中申请摄像头权限其中 android.hardware.camera.any 可确保设备配有相机。指定 .any 表示它可以是前置摄像头也可以是后置摄像头。配置如下
uses-feature android:nameandroid.hardware.camera.any /
uses-permission android:nameandroid.permission.CAMERA /
uses-permission android:nameandroid.permission.RECORD_AUDIO /
uses-permission android:nameandroid.permission.WRITE_EXTERNAL_STORAGEandroid:maxSdkVersion28 /在 MainActivity 添加如下函数会根据用户批准的权限执行对应的回调函数: override fun onRequestPermissionsResult(requestCode: Int, permissions: Arrayout String, grantResults: IntArray) {super.onRequestPermissionsResult(requestCode, permissions, grantResults)if (requestCode REQUEST_CODE_PERMISSIONS) {if (allPermissionsGranted()) {startCamera()} else {Toast.makeText(this, Permissions not granted by the user, Toast.LENGTH_SHORT).show()finish()}}}第四步
预览 摄像头、 拍照、 图片分析
初始化cameraProviderFuture 并得到cameraProvider 。
得到cameraProvider 后初始化 PreView 、ImageCapture 、ImageAnalysis。 可以看到三个单独配置的。 所以完全解耦。 tips PreView 它是相机预览的数据流图像数据通过它来传输比如输出到surface中。 ImageCapture 用于拍照 这里的builder 都可以设置参数比如 大小、曝光度。 ImageAnalysis 用于图像分析如二维码识别、人脸等。 预览 摄像头
MainActivity 中实现 startCamera() 函数 private fun startCamera() {// 用于将相机的生命周期绑定到生命周期所有者(MainActivity)。 这消除了打开和关闭相机的任务因为 CameraX 具有生命周期感知能力。val cameraProviderFuture ProcessCameraProvider.getInstance(this)// 向 cameraProviderFuture 添加监听器。添加 Runnable 作为一个参数。我们会在稍后填写它。添加 ContextCompat.getMainExecutor() 作为第二个参数。这将返回一个在主线程上运行的 Executor。cameraProviderFuture.addListener({// 将相机的生命周期绑定到应用进程中的 LifecycleOwner。val cameraProvider: ProcessCameraProvider cameraProviderFuture.get()val preview Preview.Builder().build().also { it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider) } // preview 作为 usecaseval cameraSelector CameraSelector.DEFAULT_BACK_CAMERAtry {cameraProvider.unbindAll() // Unbind use cases before rebindingcameraProvider.bindToLifecycle(this, cameraSelector, preview) // Bind use cases to camera: 把 cameraSelector 和 preview 绑定} catch (exc: Exception) {Log.e(TAG, Use case binding failed, exc) // 有多种原因可能会导致此代码失败例如应用不再获得焦点。在此记录日志。}}, ContextCompat.getMainExecutor(this))}ImageCamera 拍照 MainActivity 中实现 takePhoto() 函数 private fun takePhoto() {// Get a stable reference of the modifiable image capture use caseval imageCapture imageCapture ?: return// 存图路径和参数时间、文件类型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)}// 我们希望将输出保存在 MediaStore 中以便其他应用可以显示它val outputOptions ImageCapture.OutputFileOptions.Builder(contentResolver, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues).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(outputFileResults: ImageCapture.OutputFileResults) {val msg Photo capture succeeded: ${outputFileResults.savedUri}Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()Log.d(TAG, msg)}})}在 MainActivity 的 startCamera() 函数中添加如下 imageCapture ImageCapture.Builder().build() 来初始化摄像头的 use case并绑定到 cameraProvider.bindToLifecycle() 中完整代码如下。 private fun startCamera() {// 用于将相机的生命周期绑定到生命周期所有者(MainActivity)。 这消除了打开和关闭相机的任务因为 CameraX 具有生命周期感知能力。val cameraProviderFuture ProcessCameraProvider.getInstance(this)// 向 cameraProviderFuture 添加监听器。添加 Runnable 作为一个参数。我们会在稍后填写它。添加 ContextCompat.getMainExecutor() 作为第二个参数。这将返回一个在主线程上运行的 Executor。cameraProviderFuture.addListener({// 将相机的生命周期绑定到应用进程中的 LifecycleOwner。val cameraProvider: ProcessCameraProvider cameraProviderFuture.get()val preview Preview.Builder().build().also { it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider) } // preview 作为 usecaseimageCapture ImageCapture.Builder().build()val cameraSelector CameraSelector.DEFAULT_BACK_CAMERAtry {cameraProvider.unbindAll() // Unbind use cases before rebindingcameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture) // Bind use cases to camera} catch (exc: Exception) {Log.e(TAG, Use case binding failed, exc) // 有多种原因可能会导致此代码失败例如应用不再获得焦点。在此记录日志。}}, ContextCompat.getMainExecutor(this))}用 ImageAnalysis 分析各帧
使用 ImageAnalysis 功能可让相机应用变得更加有趣。它允许定义实现 ImageAnalysis.Analyzer 接口的自定义类并使用传入的相机帧调用该类。我们无需管理相机会话状态甚至无需处理图像与其他生命周期感知型组件一样仅绑定到应用所需的生命周期就足够了。 private class LuminosityAnalyzer(private val listener: LumaListener) : ImageAnalysis.Analyzer {private fun ByteBuffer.toByteArray(): ByteArray {rewind() // Rewind the buffer to zeroval data ByteArray(remaining())get(data) // Copy the buffer into a byte arrayreturn data // Return the byte array}override fun analyze(image: ImageProxy) {val buffer image.planes[0].bufferval data buffer.toByteArray()val pixels data.map { it.toInt() and 0xFF }val luma pixels.average()listener(luma)image.close()}}然后在 startCamera() 函数中实例化 imageAnalyzer 对象通过 setAnalyzer() 设置其回调函数来打印 luma亮度并绑定到 cameraProvider.bindToLifecycle() 上代码如下 private fun startCamera() {// 用于将相机的生命周期绑定到生命周期所有者(MainActivity)。 这消除了打开和关闭相机的任务因为 CameraX 具有生命周期感知能力。val cameraProviderFuture ProcessCameraProvider.getInstance(this)// 向 cameraProviderFuture 添加监听器。添加 Runnable 作为一个参数。我们会在稍后填写它。添加 ContextCompat.getMainExecutor() 作为第二个参数。这将返回一个在主线程上运行的 Executor。cameraProviderFuture.addListener({// 将相机的生命周期绑定到应用进程中的 LifecycleOwner。val cameraProvider: ProcessCameraProvider cameraProviderFuture.get()val preview Preview.Builder().build().also { it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider) } // preview 作为 usecaseimageCapture ImageCapture.Builder().build()val imageAnalyzer ImageAnalysis.Builder().build().also {it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma -Log.d(TAG, Average luminosity: $luma)})}val cameraSelector CameraSelector.DEFAULT_BACK_CAMERAtry {cameraProvider.unbindAll() // Unbind use cases before rebindingcameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture, imageAnalyzer) // Bind use cases to camera} catch (exc: Exception) {Log.e(TAG, Use case binding failed, exc) // 有多种原因可能会导致此代码失败例如应用不再获得焦点。在此记录日志。}}, ContextCompat.getMainExecutor(this))}其实是通过 LuminosityAnalyzer.analyze() 函数内的 listener(luma) 将 luma 参数传给 listener() 函数然后我们通过 setAnalyzer() 自定义了 listener() 函数其接收亮度并通过 Logcat 打印。 用 VideoCapture 录像 MainActivity 中实现 captureVideo() private fun captureVideo() {val videoCapture this.videoCapture ?: returnviewBinding.videoCaptureButton.isEnabled falseval curRecording recordingif (curRecording ! null) {curRecording.stop()recording nullreturn}// create and start a new recording sessionval 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)}val mediaStoreOutputOptions MediaStoreOutputOptions.Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI).setContentValues(contentValues).build()recording videoCapture.output.prepareRecording(this, mediaStoreOutputOptions).apply {if (PermissionChecker.checkSelfPermission(thisMainActivity, Manifest.permission.RECORD_AUDIO) PermissionChecker.PERMISSION_GRANTED) {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 nullLog.e(TAG, Video capture ends with error: ${recordEvent.error})}viewBinding.videoCaptureButton.apply {text getString(R.string.start_capture)isEnabled true}}}}}然后在 MainActivity 的 startCamera() 函数中将 videoCapture 绑定到 cameraProvider.bindToLifecycle() 函数中因为camera 同时只能绑定3种use case所以本节在拍照、摄像、预览、分析中选择了前3种用途 private fun startCamera() {// 用于将相机的生命周期绑定到生命周期所有者(MainActivity)。 这消除了打开和关闭相机的任务因为 CameraX 具有生命周期感知能力。val cameraProviderFuture ProcessCameraProvider.getInstance(this)// 向 cameraProviderFuture 添加监听器。添加 Runnable 作为一个参数。我们会在稍后填写它。添加 ContextCompat.getMainExecutor() 作为第二个参数。这将返回一个在主线程上运行的 Executor。cameraProviderFuture.addListener({// 将相机的生命周期绑定到应用进程中的 LifecycleOwner。val cameraProvider: ProcessCameraProvider cameraProviderFuture.get()val preview Preview.Builder().build().also { it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider) } // preview 作为 use caseimageCapture ImageCapture.Builder().build()
// val imageAnalyzer ImageAnalysis.Builder().build().also {
// it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma -
// Log.d(TAG, Average luminosity: $luma)
// })
// }val recorder Recorder.Builder().setQualitySelector(QualitySelector.from(Quality.HIGHEST)).build()videoCapture VideoCapture.withOutput(recorder)val cameraSelector CameraSelector.DEFAULT_BACK_CAMERAtry {cameraProvider.unbindAll() // Unbind use cases before rebindingcameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture, videoCapture) // Bind use cases to camera} catch (exc: Exception) {Log.e(TAG, Use case binding failed, exc) // 有多种原因可能会导致此代码失败例如应用不再获得焦点。在此记录日志。}}, ContextCompat.getMainExecutor(this))}
结束。 参考
https://juejin.cn/post/7354922285092847668
https://juejin.cn/post/7354937037391986722
使用Preview预览和自定义SurfaceView或者TextureView实现CameraX预览功能_preview.setsurfaceprovider-CSDN博客
Android之SurfaceView和TextureView的分析_surfaceview和textview的区别-CSDN博客
在Android CameraX中PreviewView和SurfaceView的关系主要体现在它们都可以作为相机的预览显示视图但PreviewView提供了更多的功能和灵活性。
PreviewView和SurfaceView的区别和联系 功能差异 PreviewView这是一个专门为CameraX设计的视图支持裁剪、缩放和旋转确保预览的正确显示。它提供了更多的控制和灵活性例如可以设置不同的预览分辨率和帧率1。SurfaceView是一个更传统的视图用于展示相机预览图像。它不需要布局文件中的声明可以直接在代码中创建和使用。SurfaceView适合简单的预览需求但在CameraX中PreviewView是更推荐的选择12。 使用场景 PreviewView适用于需要高度自定义预览显示的场景如实时视频处理、预览画面的特殊效果等。它能够更好地与CameraX的其他组件集成提供更好的用户体验1。SurfaceView适用于简单的预览需求不需要复杂的预览处理。虽然SurfaceView在CameraX中不是首选但在一些旧的项目或特定的需求中仍然可以使用12。
如何在CameraX中使用PreviewView 添加PreviewView到布局文件 xmlCopy Code FrameLayout android:idid/container android:layout_widthmatch_parent android:layout_heightmatch_parent androidx.camera.view.PreviewView android:idid/previewView android:layout_widthmatch_parent android:layout_heightmatch_parent/ /FrameLayout 初始化ProcessCameraProvider并绑定PreviewView kotlinCopy Code override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (savedInstanceState null) { Android OpenGL ES 学习(一) -- 基本概念-CSDN博客 Android 音视频编解码(一) -- MediaCodec 初探_安卓音视频解码-CSDN博客