学习笔记 -- 从零开始学习Android Camera2 -- (1)

这篇具有很好参考价值的文章主要介绍了学习笔记 -- 从零开始学习Android Camera2 -- (1)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一.仿写流程

学习一个框架,第一步学习肯定是照着代码看文档。 既然要看代码,就要看最权威的,这里我是代码是参照https://github.com/android/camera-samples android给的官方示例,结合官方文档https://developer.android.com/reference/android/hardware/camera2/package-summary来看,所以首先要先看一遍文档,然后重写一遍它里面最基础的Camera2Basic。

文档里面写了啥

第一: android.hardware.camera2是在API level 21加上的,21以下的设备不能用,Camera2是为了替换deprecated的Camera类

第二:使用Camera2的步骤
1. 首先要获取一个CameraManager对象,这个是所有操作的前提。我们可以通过这个对象来进行枚举Camera设备的操作,也可以通过CameraManager的getCameraCharacteristics(String)方法获取每个摄像机设备的信息,这些信息存放在CameraCharacteristics类中。
2. 然后我们还可以通过CameraManager获取每一个摄像头对应的CameraDevices对象,获取的方法是调用CameraManager的openCamera方法。每个CameraDevices都对应一个Andorid的Camera设备。
3. 下一步需要创建一个session,session中包含了一系列已经设置好宽高和格式的Surface,摄像机会通过这些宽高和格式来设置摄像头的输出。Surface可以从SurfaceView, SurfaceTexture , MediaCodec, MediaRecorder, Allocation, 和 ImageReader中来。 除此之外,创建session还需要构建一个包含各种摄像头参数和输出Surface的CaptureRequest。 其实每次调用拍照都会把CaptureRequest送到相机设备,而相机设备也是根据这个CaptureRequest来进行拍照,你可以在 CaptureRequest中设置白平衡,光圈大小,曝光时间等参数。
4. 最后根据需要,看是需要拍照(capture),还是摄像(repeating),拍照API的优先级比摄像的优先级高。如果调用多次,就把CaptureRequest送到相机设备中多次,调用capture就送一次。

文档不如代码看起来舒服,还是直接上代码。
代码结构如下

├── app
│   ├── build.gradle
│   └── src
│       ├── main
│       │   ├── AndroidManifest.xml
│       │   ├── java
│       │   │   └── com
│       │   │       └── example
│       │   │           └── android
│       │   │               └── camera2
│       │   │                   └── basic
│       │   │                       ├── CameraActivity.kt
│       │   │                       └── fragments
│       │   │                           ├── CameraFragment.kt
│       │   │                           ├── ImageViewerFragment.kt
│       │   │                           ├── PermissionsFragment.kt
│       │   │                           └── SelectorFragment.kt
│       │   └── res
│       │       ├── layout
│       │       │   ├── activity_camera.xml
│       │       │   └── fragment_camera.xml
│       │       ├── navigation
│       │       │   └── nav_graph.xml
└── utils
    └── src
        └── main
            ├── AndroidManifest.xml
            ├── java
            │   └── com
            │       └── example
            │           └── android
            │               └── camera
            │                   └── utils
            │                       ├── AutoFitSurfaceView.kt
            │                       ├── CameraSizes.kt
            │                       ├── ExifUtils.kt
            │                       ├── GenericListAdapter.kt
            │                       ├── OrientationLiveData.kt
            │                       ├── Yuv.kt
            │                       └── YuvToRgbConverter.kt

CameraActivity用的是nav_graph.xml来进行导航,CameraActivity本身并不含任何的逻辑和UI,基本上所有的逻辑代码都是在各个Fragment中写的。

首先是PermissionFramgment,这个Fragment没有什么,就是请求Camera权限,请求完权限后就会导航到SelectorFragment。

SelectorFragment


 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        view as RecyclerView
        view.apply {
            layoutManager = LinearLayoutManager(requireContext())

			//这里就是文档中提到的第一步,先获取CameraManager对象
            val cameraManager =
                    requireContext().getSystemService(Context.CAMERA_SERVICE) as CameraManager

			// 调用enumerateCameras方法,获取一个List<FormatItem>
            val cameraList = enumerateCameras(cameraManager)

			// 下面就是初始化RecyclerView中的一部分
            val layoutId = android.R.layout.simple_list_item_1
            adapter = GenericListAdapter(cameraList, itemLayoutId = layoutId) { view, item, _ ->
                view.findViewById<TextView>(android.R.id.text1).text = item.title
                view.setOnClickListener {
                    Navigation.findNavController(requireActivity(), R.id.fragment_container)
                    		//每个item点击之后,都会导航到CameraFragment,参数是cameraId和格式format。
                            .navigate(SelectorFragmentDirections.actionSelectorToCamera(
                                    item.cameraId, item.format))
                }
            }
        }

    }

上面来看,最重要的就是调用enumerateCameras来获取List,那么就看看enumerateCameras方法做了什么。

private fun enumerateCameras(cameraManager: CameraManager): List<FormatItem> {
            val availableCameras: MutableList<FormatItem> = mutableListOf()

            // Get list of all compatible cameras
            // 筛选符合要求的cameraId
            val cameraIds = cameraManager.cameraIdList.filter {
            	// 获取每个Camera的设备信息
                val characteristics = cameraManager.getCameraCharacteristics(it)
                // 看看这个设备是否满足Camera的最小功能集(深度摄像机不一定包含在里面)
                val capabilities = characteristics.get(
                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)
                capabilities?.contains(
                        CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) ?: false
            }


            // Iterate over the list of cameras and return all the compatible ones
            // 下面就是把每个camera和其支持的format都分别加入到List<FormatItem>中,这里判断支持的格式就只有
            cameraIds.forEach { id ->
                val characteristics = cameraManager.getCameraCharacteristics(id)
                val orientation = lensOrientationString(
                        characteristics.get(CameraCharacteristics.LENS_FACING)!!)

                // Query the available capabilities and output formats
                val capabilities = characteristics.get(
                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)!!
                val outputFormats = characteristics.get(
                        CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!.outputFormats

                // All cameras *must* support JPEG output so we don't need to check characteristics
                // 每个摄像机都支持JPEG输出(压缩后的格式)
                availableCameras.add(FormatItem(
                        "$orientation JPEG ($id)", id, ImageFormat.JPEG))

                // Return cameras that support RAW capability
                // 是否支持RAW(原始画质), 使用RAW_SENSOR的话,每种相机都不同
                if (capabilities.contains(
                                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW) &&
                        outputFormats.contains(ImageFormat.RAW_SENSOR)) {
                    availableCameras.add(FormatItem(
                            "$orientation RAW ($id)", id, ImageFormat.RAW_SENSOR))
                }

                // Return cameras that support JPEG DEPTH capability
                // 深度相机,基本用不到
                if (capabilities.contains(
                            CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT) &&
                        outputFormats.contains(ImageFormat.DEPTH_JPEG)) {
                    availableCameras.add(FormatItem(
                            "$orientation DEPTH ($id)", id, ImageFormat.DEPTH_JPEG))
                }
            }

            return availableCameras
        }

所以SelectorFragment中基本就是获取cameraId和format,然后交给CameraFragment来使用,那么看来,最重要的代码还是在CameraFragment中实现的,所以接下来看看CameraFragment是怎么实现的。

 fragmentCameraBinding.viewFinder.holder.addCallback(object : SurfaceHolder.Callback {
            override fun surfaceDestroyed(holder: SurfaceHolder) = Unit

            override fun surfaceChanged(
                    holder: SurfaceHolder,
                    format: Int,
                    width: Int,
                    height: Int) = Unit

			// 这个时候Surface已经创建了
            override fun surfaceCreated(holder: SurfaceHolder) {
                // Selects appropriate preview size and configures view finder
                
                //获取最优的预览大小,小于1080p和屏幕尺寸下最大的摄像头拍摄分辨率
                //这里就是从characteristics.getCameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputSizes(SurfaceHolder::class.java)中获取Size数组,这个数据就是摄像头的输出分辨率
                val previewSize = getPreviewOutputSize(
                    fragmentCameraBinding.viewFinder.display,
                    characteristics,
                    SurfaceHolder::class.java
                )
                Log.d(TAG, "View finder size: ${fragmentCameraBinding.viewFinder.width} x ${fragmentCameraBinding.viewFinder.height}")
                Log.d(TAG, "Selected preview size: $previewSize")
                // 根据要得到的摄像头分辨率设置 surface分辨率
                // 这个surface要用来创建session,就是上面的第三步,然后摄像机就可以根据这个surface大小来选择拍照的分辨率。
                // 这样就能保证预览的宽高比,与摄像头一致,就可以保证预览没有进行拉伸
                fragmentCameraBinding.viewFinder.setAspectRatio(
                    previewSize.width,
                    previewSize.height
                )

                // To ensure that size is set, initialize camera in the view's thread
                view.post { initializeCamera() }
            }
        })

其实上面最关键的就是计算出想要的摄像头分辨率,然后把对应的宽高比传给View,View在进行调整,调整到和摄像头输出的宽高比一致,这样就不会造成预览画面弯曲。计算的过程需要得到对应CameraDevice的OutputSize数组。
然后通过View调用initializeCamera,初始化摄像头,这样就能保证Surface的宽高比已经改变了,摄像头也会根据这个Surface来初始化摄像头的分辨率。

initializeCamera方法是整个Demo中最关键的部分

private fun initializeCamera() = lifecycleScope.launch(Dispatchers.Main) {
        // Open the selected camera
        // 这里面实现也简单,就是我们在文档第二步做所,用cameraManager根据cameraId来创建CameraDevice
        camera = openCamera(cameraManager, args.cameraId, cameraHandler)

        // Initialize an image reader which will be used to capture still photos
        //设置输出的格式
        val size = characteristics.get(
                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
                .getOutputSizes(args.pixelFormat).maxByOrNull { it.height * it.width }!!
        imageReader = ImageReader.newInstance(
                size.width, size.height, args.pixelFormat, IMAGE_BUFFER_SIZE)

        // Creates list of Surfaces where the camera will output frames
        // 设置到Request中输出的Surface,第三步中所说
        val targets = listOf(fragmentCameraBinding.viewFinder.holder.surface, imageReader.surface)

        // Start a capture session using our open camera and list of Surfaces where frames will go
        // 创建Session,第三步中所说
        // createCaptureSession中也简单,就是调用CameraDevice对应的createCaptureSession方法
        session = createCaptureSession(camera, targets, cameraHandler)

        val captureRequest = camera.createCaptureRequest(
                CameraDevice.TEMPLATE_PREVIEW).apply { addTarget(fragmentCameraBinding.viewFinder.holder.surface) }

        // This will keep sending the capture request as frequently as possible until the
        // session is torn down or session.stopRepeating() is called
        // 这里就是第四步,调用setRepeatingRequest获取一个持续的图片流
        session.setRepeatingRequest(captureRequest.build(), null, cameraHandler)

        // Listen to the capture button
        fragmentCameraBinding.captureButton.setOnClickListener {

            // Disable click listener to prevent multiple requests simultaneously in flight
            it.isEnabled = false

            // Perform I/O heavy operations in a different scope
            lifecycleScope.launch(Dispatchers.IO) {
                // takePhoto方法是单独拍照
                takePhoto().use { result ->
                    Log.d(TAG, "Result received: $result")

                    // Save the result to disk
                    val output = saveResult(result)
                    Log.d(TAG, "Image saved: ${output.absolutePath}")

                    // If the result is a JPEG file, update EXIF metadata with orientation info
                    if (output.extension == "jpg") {
                        val exif = ExifInterface(output.absolutePath)
                        exif.setAttribute(
                                ExifInterface.TAG_ORIENTATION, result.orientation.toString())
                        exif.saveAttributes()
                        Log.d(TAG, "EXIF metadata saved: ${output.absolutePath}")
                    }

                    // Display the photo taken to user
                    lifecycleScope.launch(Dispatchers.Main) {
                        navController.navigate(CameraFragmentDirections
                                .actionCameraToJpegViewer(output.absolutePath)
                                .setOrientation(result.orientation)
                                .setDepth(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
                                        result.format == ImageFormat.DEPTH_JPEG))
                    }
                }

                // Re-enable click listener after photo is taken
                it.post { it.isEnabled = true }
            }
        }
    }

上面的流程就走完了调用一个摄像头的全部流程,主要就是先根据CameraId,调用CameraManager中的方法创建CameraDevice对象,然后根据这个CameraDevice对象,创建一个CameraCaptureSession对象,用这个对象调用setRepeatingRequest方法。
除此之外,还有一个takePhoto方法,这个方法就是在预览摄像头的同时进行拍照用的,看一下这个方法的具体实现。

    private suspend fun takePhoto():
            CombinedCaptureResult = suspendCoroutine { cont ->

        // Flush any images left in the image reader
        @Suppress("ControlFlowWithEmptyBody")
        while (imageReader.acquireNextImage() != null) {
        }

        // Start a new image queue
        val imageQueue = ArrayBlockingQueue<Image>(IMAGE_BUFFER_SIZE)
        imageReader.setOnImageAvailableListener({ reader ->
            val image = reader.acquireNextImage()
            Log.d(TAG, "Image available in queue: ${image.timestamp}")
            imageQueue.add(image)
        }, imageReaderHandler)

		// TEMPLATE_STILL_CAPTURE会优先保证拍出来的画面质量,而不是帧数
		// 这个时候预览的Surface会有一个白色动画,所以其实也没必要保证帧数
        val captureRequest = session.device.createCaptureRequest(
                CameraDevice.TEMPLATE_STILL_CAPTURE).apply { addTarget(imageReader.surface) }
        session.capture(captureRequest.build(), object : CameraCaptureSession.CaptureCallback() {

            override fun onCaptureStarted(
                    session: CameraCaptureSession,
                    request: CaptureRequest,
                    timestamp: Long,
                    frameNumber: Long) {
                super.onCaptureStarted(session, request, timestamp, frameNumber)
                fragmentCameraBinding.viewFinder.post(animationTask)
            }

            override fun onCaptureCompleted(
                    session: CameraCaptureSession,
                    request: CaptureRequest,
                    result: TotalCaptureResult) {
                super.onCaptureCompleted(session, request, result)
                val resultTimestamp = result.get(CaptureResult.SENSOR_TIMESTAMP)
                Log.d(TAG, "Capture result received: $resultTimestamp")

                // Set a timeout in case image captured is dropped from the pipeline
                val exc = TimeoutException("Image dequeuing took too long")
                val timeoutRunnable = Runnable { cont.resumeWithException(exc) }
                imageReaderHandler.postDelayed(timeoutRunnable, IMAGE_CAPTURE_TIMEOUT_MILLIS)

                // Loop in the coroutine's context until an image with matching timestamp comes
                // We need to launch the coroutine context again because the callback is done in
                //  the handler provided to the `capture` method, not in our coroutine context
                @Suppress("BlockingMethodInNonBlockingContext")
                lifecycleScope.launch(cont.context) {
                    while (true) {

                        // Dequeue images while timestamps don't match
                        val image = imageQueue.take()
                        // TODO(owahltinez): b/142011420
                        // if (image.timestamp != resultTimestamp) continue
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
                                image.format != ImageFormat.DEPTH_JPEG &&
                                image.timestamp != resultTimestamp) continue
                        Log.d(TAG, "Matching image dequeued: ${image.timestamp}")

                        // Unset the image reader listener
                        imageReaderHandler.removeCallbacks(timeoutRunnable)
                        imageReader.setOnImageAvailableListener(null, null)

                        // Clear the queue of images, if there are left
                        while (imageQueue.size > 0) {
                            imageQueue.take().close()
                        }

                        // Compute EXIF orientation metadata
                        val rotation = relativeOrientation.value ?: 0
                        val mirrored = characteristics.get(CameraCharacteristics.LENS_FACING) ==
                                CameraCharacteristics.LENS_FACING_FRONT
                        val exifOrientation = computeExifOrientation(rotation, mirrored)

                        // Build the result and resume progress
                        cont.resume(CombinedCaptureResult(
                                image, result, exifOrientation, imageReader.imageFormat))

                        // There is no need to break out of the loop, this coroutine will suspend
                    }
                }
            }
        }, cameraHandler)
    }

其他的就不是很关键,从这个库我们能学到摄像机的基本应用流程。

根据上面这个库,自己搞一下整个摄像机的流程,如下

private void initCameraParam() {
		//检测相机权限
        if (!checkPermission())
            return;
        //获取CameraManager对象
        mCameraManager = (CameraManager) (getSystemService(Context.CAMERA_SERVICE));
        if (mCameraManager == null)
            return;
        try {
            mData = new ArrayList<>();
            //这里我们直接使用了第一个摄像机设备ID
            CameraCharacteristics cc = mCameraManager.getCameraCharacteristics(mCameraManager.getCameraIdList()[0]);

			// 这个View继承SurfaceView,可以根据传入的分辨率自己调整View的大小,除此之外和SurfaceView一样
            AutoFitSurfaceView autoView = findViewById(R.id.camera_surface);
            
            autoView.getHolder().addCallback(new SurfaceHolder.Callback() {
            	//需要等到Surface创建后加入到
                @Override
                public void surfaceCreated(@NonNull SurfaceHolder holder) {
					//下面这一段就是根据View的初步大小和Camera设备能输出的大小进行匹配,找到一个最合适的
                    Display display = autoView.getDisplay();
                    Point point = new Point();
                    display.getRealSize(point);

                    SmartSize renderSize = new SmartSize(point.x, point.y);
                    SmartSize maxSize = new SmartSize(1920, 1080);

                    if (renderSize.getLongSize() <= maxSize.getLongSize() && renderSize.getShortSize() <= maxSize.getShortSize())
                        maxSize = renderSize;

                    StreamConfigurationMap config = cc.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                    Size[] sizes = config.getOutputSizes(SurfaceHolder.class);
                    SmartSize resultSize = new SmartSize(0, 0);
                    for (Size size: sizes) {
                        int longSize = Math.max(size.getWidth(), size.getHeight());
                        int shortSize = Math.min(size.getWidth(), size.getHeight());
                        if (resultSize.getLongSize() <= longSize && longSize <= maxSize.getLongSize() &&
                                resultSize.getShortSize() <= shortSize && shortSize <= maxSize.getShortSize()) {
                            resultSize = new SmartSize(longSize, shortSize);
                        }
                    }
                    if (resultSize.getShortSize() != 0 && resultSize.getLongSize() != 0)
                        autoView.setAspectRatio(resultSize.getLongSize(), resultSize.getShortSize());


					// 通过view.post 保证Surface的大小已经确定修改
                    autoView.post(() -> {
                        try {
                            if (!checkPermission())
                                return;
                            //打开摄像机 还是默认使用第一个设备ID
                            mCameraManager.openCamera(mCameraManager.getCameraIdList()[0], new CameraDevice.StateCallback() {
                                @Override
                                public void onOpened(@NonNull CameraDevice camera) {
                                    mCameraDevice = camera;
									//打开之后创建Session和Request
                                    OutputConfiguration outputConfiguration = new OutputConfiguration(autoView.getHolder().getSurface());
                                    CameraCaptureSession.StateCallback callback = new CameraCaptureSession.StateCallback() {
                                        @Override
                                        public void onConfigured(@NonNull CameraCaptureSession session) {
                                           //Session创建成功的回调,在这个回调中创建Request,然后调用setRepeatingRequest获取图像流
                                            mCameraCaptureSession = session;
                                            try {
                                                CaptureRequest.Builder builder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                                                builder.addTarget(autoView.getHolder().getSurface());
                                                mCameraCaptureSession.setRepeatingRequest(builder.build(), new CameraCaptureSession.CaptureCallback() {
                                                    @Override
                                                    public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) {
                                                        super.onCaptureStarted(session, request, timestamp, frameNumber);
                                                    }

                                                    @Override
                                                    public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) {
                                                        super.onCaptureProgressed(session, request, partialResult);
                                                    }

                                                    @Override
                                                    public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                                                        super.onCaptureCompleted(session, request, result);
                                                    }

                                                    @Override
                                                    public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) {
                                                        super.onCaptureFailed(session, request, failure);
                                                    }

                                                    @Override
                                                    public void onCaptureSequenceCompleted(@NonNull CameraCaptureSession session, int sequenceId, long frameNumber) {
                                                        super.onCaptureSequenceCompleted(session, sequenceId, frameNumber);
                                                    }

                                                    @Override
                                                    public void onCaptureSequenceAborted(@NonNull CameraCaptureSession session, int sequenceId) {
                                                        super.onCaptureSequenceAborted(session, sequenceId);
                                                    }

                                                    @Override
                                                    public void onCaptureBufferLost(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull Surface target, long frameNumber) {
                                                        super.onCaptureBufferLost(session, request, target, frameNumber);
                                                    }
                                                }, mCameraHandler);
                                            } catch (Exception e) {
                                                e.printStackTrace();
                                            }
                                        }

                                        @Override
                                        public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                                            Log.e(TAG, "onConfigureFailed: ");
                                        }
                                    };
                                    //这里是主动创建Session的地方
                                    try {
                                        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
                                            SessionConfiguration configuration = new SessionConfiguration(SessionConfiguration.SESSION_REGULAR,
                                                    Collections.singletonList(outputConfiguration),
                                                    new HandlerExecutor(mCameraHandler),
                                                    callback);
                                            mCameraDevice.createCaptureSession(configuration);
                                        } else {
                                            mCameraDevice.createCaptureSession(Collections.singletonList(autoView.getHolder().getSurface()),
                                                    callback, mCameraHandler);
                                        }
                                    } catch (Exception e) {
                                        e.printStackTrace();
                                    }

                                }

                                @Override
                                public void onDisconnected(@NonNull CameraDevice camera) {
                                    Log.e(TAG, "onDisconnected: ");
                                }

                                @Override
                                public void onError(@NonNull CameraDevice camera, int error) {
                                    String msg;
                                    switch (error) {
                                        case ERROR_CAMERA_DEVICE:
                                            msg = "Fatal (device)";
                                            break;
                                        case ERROR_CAMERA_DISABLED:
                                            msg = "Device policy";
                                            break;
                                        case ERROR_CAMERA_IN_USE:
                                            msg = "Camera in use";
                                            break;
                                        case ERROR_CAMERA_SERVICE:
                                            msg = "Fatal (service)";
                                            break;
                                        case ERROR_MAX_CAMERAS_IN_USE:
                                            msg = "Maximum cameras in use";
                                            break;
                                        default:
                                            msg = "Unknown";
                                            break;
                                    }
                                    Log.e(TAG, "onError: " + msg);

                                }
                            }, mCameraHandler);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    });
                }

                @Override
                public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {


                }

                @Override
                public void surfaceDestroyed(@NonNull SurfaceHolder holder) {

                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

以上,就基本根据一个Demo写了一个摄像头从创建到预览的全部过程,复写代码是为了更好的掌握结构,所以只是注重了流程,接下来会详细学习摄像头的一些参数。文章来源地址https://www.toymoban.com/news/detail-437971.html

到了这里,关于学习笔记 -- 从零开始学习Android Camera2 -- (1)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android 高通Camera2 Camera Device Close

     1、很多人看到这个日志第一感觉可能觉得哪里没有合理释放,于是带着这个思路去进行百度探索 2、一开始我去寻找 ImageReader.OnImageAvailableListener 这个问题 3、后面网上去寻找因为  Camera2最大连拍限制是 2 网上很多数包括Google相机源码 需要单独开个线程去处理图片的逻辑

    2023年04月09日
    浏览(44)
  • 原生Camera2的对焦原理和框架,以及代码实现流程

    在Android中,Camera2 API提供了对相机硬件的底层访问,包括对焦功能。以下是Camera2对焦原理和框架的简要概述,以及代码实现流程: 对焦原理和框架: 预览: 在开始对焦之前,通常需要先启动相机的预览。预览不仅允许用户看到实时视频流,还可以提供关于相机状态的信息,

    2024年01月16日
    浏览(56)
  • NDK Android平台camera2采集视频

    《Android平台使用camera2采集视频 代码实现 》链接: https://edu.csdn.net/learn/38258/606148?spm=1003.2001.3001.4157 在Android平台开发实时音视频项目,摄像头的采集是一个必不可少的流程;通常在Android平台上采集摄像头数据可以使用Camera1接口、Camera2接口或者CameraX接口。Camera1接口只支持

    2024年02月11日
    浏览(64)
  • Android Camera2 —CameraManager API详解

    一、CameraManager类概述 CameraManager是用于检测、表征和连接到 CameraDevices 的系统服务管理器。 CameraManager 是一个负责查询和建立相机连接的系统服务,它的功能不多,这里列出几个 CameraManager 的关键功能: 1)、将相机信息封装到 Camera Characteristics 中,并提获取 CameraCharacterist

    2024年02月12日
    浏览(56)
  • 十分钟实现 Android Camera2 视频录制

    因为工作中要使用 Android Camera2 API ,但因为 Camera2 比较复杂,网上资料也比较乱,有一定入门门槛,所以花了几天时间系统研究了下,并在 CSDN 上记录了下,希望能帮助到更多的小伙伴。 上两篇文章使用 Camera2 实现了相机预览和拍照的功能,这篇文章我们接着上文,来实现

    2024年02月11日
    浏览(53)
  • 十分钟实现 Android Camera2 相机拍照

    因为工作中要使用 Android Camera2 API ,但因为 Camera2 比较复杂,网上资料也比较乱,有一定入门门槛,所以花了几天时间系统研究了下,并在 CSDN 上记录了下,希望能帮助到更多的小伙伴。 上篇文章 我们使用 Camera2 实现了相机预览的功能,这篇文章我们接着上文,来实现 Cam

    2024年02月11日
    浏览(68)
  • 十分钟实现 Android Camera2 相机预览

    因为工作中要使用 Android Camera2 API ,但因为 Camera2 比较复杂,网上资料也比较乱,有一定入门门槛,所以花了几天时间系统研究了下,并在 CSDN 上记录了下,希望能帮助到更多的小伙伴。 Camera2 API 的包名是 android.hardware.camera2 ,是 Android 5.0 后推出的一套调用摄像头设备的接口

    2024年02月13日
    浏览(74)
  • android camera系列(Camera1、Camera2、CameraX)的使用以及输出的图像格式

    1.1.1、布局 1.1.2、实现预览 Camera.open() 打开摄像头 setPreviewDisplay 设置预览展示的控件 startPreview 开始预览 发现预览是横着的,需要使用 setDisplayOrientation 调整预览图像的方向 1.1.3、获取摄像头的原始数据 setPreviewCallback 设置预览数据的回调 2560*1440 默认返回图像的分辨率 Image

    2024年02月21日
    浏览(53)
  • Android 使用Camera2 API 和 GLSurfaceView实现相机预览

    GLSurfaceView 和 SurfaceView 是 Android 中用于显示图像的两个视图类,它们在实现方式和使用场景上有一些区别。 实现方式:GLSurfaceView 基于 OpenGL ES 技术实现,可以通过 OpenGL ES 渲染图像。而 SurfaceView 则是通过基于线程的绘制方式,可以在独立的线程中进行绘制操作。 性能:由于

    2024年02月09日
    浏览(46)
  • Android studio APK切换多个摄像头(Camera2)

    1.先设置camera的权限 2.布局 3.主界面代码 这部分代码是用来授权AndroidManifest.xml里面权限的第三方sdk代码 效果:

    2024年02月11日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包