NDK实现相机预览

前言

通过调用Android SDK实现相机预览是一件非常简单的事情,但这终归是通过JAVA调用native接口,那么如何直接通过native的方式来进行相机预览呢,当然,这就需要借助到NDK

然而,会发现,ndkcamera2中提供的头文件都有这么一行

#if __ANDROID_API__ >= 24

也就是说,minSdkVersion必须要大于等于24,,也就是说,其支持的安卓设备最低版本为7.0

Camera2

要使用ndkcamera2,就必须先判断设备是否支持Camera2

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private fun isCamera2Device(): Boolean {
        val cameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager
        try {
            val cameraIds = cameraManager.cameraIdList
            for (id in cameraIds) {
                val characteristics = cameraManager.getCameraCharacteristics(id)
                val deviceLevel = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
                val facing = characteristics.get(CameraCharacteristics.LENS_FACING)
                if (deviceLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY && facing == CameraCharacteristics.LENS_FACING_BACK) {
                    return false
                }
            }
        } catch (e: CameraAccessException) {
            return false
        } catch (e: NullPointerException) {
            return false
        }
        return true
    }

申请相机权限

这个自不必多说

    private fun requestCamera() {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(
                    this,
                    arrayOf(Manifest.permission.CAMERA),
                    PERMISSION_REQUEST_CODE_CAMERA
            )
            return
        }
        createTextureView()
    }

就绪TextureView

当然,相机预览是在TextureView上执行的

    private fun createTextureView() {
        texturePreview.surfaceTextureListener = this
        if (texturePreview.isAvailable) onSurfaceTextureAvailable(texturePreview.surfaceTexture, texturePreview.width, texturePreview.height)
    }

开启相机

创建连接native层相机的接口

    private external fun createCamera(width: Int, height: Int): Long

native层开启相机

NDKCamera::NDKCamera() : cameraOrientation(0),
                         cameraMgr(nullptr),
                         activeCameraId(""),
                         outputContainer(nullptr) {
    valid_ = false;
    requests.resize(CAPTURE_REQUEST_COUNT);
    cameras.clear();
    cameraMgr = ACameraManager_create();
    ASSERT(cameraMgr, "Failed to create cameraManager");
    EnumerateCamera();
    ASSERT(activeCameraId.size(), "Unknown ActiveCameraIdx");

    ACameraManager_openCamera(cameraMgr, activeCameraId.c_str(), GetDeviceListener(),
                   &cameras[activeCameraId].device);
    ACameraManager_registerAvailabilityCallback(cameraMgr, GetManagerListener());
}

匹配合适的相机原始预览尺寸

    private external fun getMinimumCompatiblePreviewSize(ndkCamera: Long): Size
bool
NDKCamera::MatchCaptureSizeRequest(int32_t requestWidth, int32_t requestHeight, ImageFormat *view,
                                   ImageFormat *capture) {
    DisplayDimension disp(requestWidth, requestHeight);
    if (cameraOrientation == 90 || cameraOrientation == 270) {// 如果是竖屏,则进行翻转
        disp.Flip();
    }
    ACameraMetadata *metadata;
    ACameraManager_getCameraCharacteristics(cameraMgr, activeCameraId.c_str(), &metadata);
    ACameraMetadata_const_entry entry;
    ACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &entry);
    bool foundIt = false;
    DisplayDimension foundRes(4000, 4000);
    DisplayDimension maxJPG(0, 0);
    for (int i = 0; i < entry.count; i += 4) {
        int32_t input = entry.data.i32[i + 3];
        int32_t format = entry.data.i32[i + 0];
        if (input)continue;
        if (format == AIMAGE_FORMAT_YUV_420_888 || format == AIMAGE_FORMAT_JPEG) {
            DisplayDimension res(entry.data.i32[i + 1], entry.data.i32[i + 2]);
            if (!disp.IsSameRatio(res))continue;
            if (format == AIMAGE_FORMAT_YUV_420_888 && foundRes > res) {
                foundIt = true;
                foundRes = res;
            } else if (format == AIMAGE_FORMAT_JPEG && res > maxJPG) {
                maxJPG = res;
            }
        }
    }
    if (foundIt) {
        view->width = foundRes.GetOrginalWidth();
        view->height = foundRes.GetOrginalHeight();
        if (capture) {// 判断是否为非空指针
            capture->width = maxJPG.GetOrginalWidth();
            capture->height = maxJPG.GetOrginalHeight();
        }
    } else {
        LOGW("Did not find any compatible camera resolution, taking 640x480");
        if (disp.IsPortrait()) {
            view->width = 480;
            view->height = 640;
        } else {
            view->width = 640;
            view->height = 480;
        }
        if (capture)*capture = *view;
    }
    view->format = AIMAGE_FORMAT_YUV_420_888;
    if (capture)capture->format = AIMAGE_FORMAT_JPEG;
    return foundIt;
}
    private fun createNativeCamera() {
        val display = windowManager.defaultDisplay
        ndkCamera = createCamera(display.mode.physicalWidth, display.mode.physicalHeight)
        cameraPreviewSize = getMinimumCompatiblePreviewSize(ndkCamera)
    }

调整TextureView

根据匹配到的相机预览尺寸,等比例调整TextureView的宽高和画布属性

    private fun resizeTextureView(textureWidth: Int) {
        val rotation = windowManager.defaultDisplay.rotation
        cameraPreviewSize ?: return
        var newHeight = textureWidth * cameraPreviewSize!!.width / cameraPreviewSize!!.height
        when (rotation) {
            Surface.ROTATION_90, Surface.ROTATION_270 -> newHeight = textureWidth * cameraPreviewSize!!.height / cameraPreviewSize!!.width
        }
        texturePreview.layoutParams = ConstraintLayout.LayoutParams(textureWidth, newHeight)
        configureTransform(textureWidth, newHeight)
    }

    private fun configureTransform(width: Int, height: Int) {
        val matrix = Matrix()
        when (windowManager.defaultDisplay.rotation) {
            Surface.ROTATION_90 ->
                matrix.setPolyToPoly(
                        floatArrayOf(0f, 0f, // top left
                                width.toFloat(), 0f, // top right
                                0f, height.toFloat(), // bottom left
                                width.toFloat(), height.toFloat())// bottom right
                        , 0,
                        floatArrayOf(0f, height.toFloat(), // top left
                                0f, 0f, // top right
                                width.toFloat(), height.toFloat(), // bottom left
                                width.toFloat(), 0f)// bottom right
                        , 0, 4
                )
            Surface.ROTATION_270 ->
                matrix.setPolyToPoly(
                        floatArrayOf(0f, 0f, // top left
                                width.toFloat(), 0f, // top right
                                0f, height.toFloat(), // bottom left
                                width.toFloat(), height.toFloat())// bottom right
                        , 0,
                        floatArrayOf(width.toFloat(), 0f, // top left
                                width.toFloat(), height.toFloat(), // top right
                                0f, 0f, // bottom left
                                0f, height.toFloat())// bottom right
                        , 0, 4
                )
            Surface.ROTATION_180 -> matrix.postRotate(180f, width.toFloat() / 2, height.toFloat() / 2)
        }
        texturePreview.setTransform(matrix)
    }

创建相机会话

    private external fun onPreviewSurfaceCreated(ndkCamera: Long, surface: Surface?)
void
NDKCamera::CreateSession(ANativeWindow *previewWindow, ANativeWindow *jpgWindow, bool manaulPreview,
                         int32_t imageRotation) {
    // Create output from this app's ANativeWindow, and add into output container
    requests[PREVIEW_REQUEST_IDX].outNativeWindow = previewWindow;
    requests[PREVIEW_REQUEST_IDX].deviceTemplate = TEMPLATE_PREVIEW;
    requests[JPG_CAPTURE_REQUEST_IDX].outNativeWindow = jpgWindow;
    requests[JPG_CAPTURE_REQUEST_IDX].deviceTemplate = TEMPLATE_STILL_CAPTURE;

    ACaptureSessionOutputContainer_create(&outputContainer);

    for (auto &req:requests) {
        if (!req.outNativeWindow)continue;
        ANativeWindow_acquire(req.outNativeWindow);
        ACaptureSessionOutput_create(req.outNativeWindow, &req.sessionOutput);
        ACaptureSessionOutputContainer_add(outputContainer, req.sessionOutput);
        ACameraOutputTarget_create(req.outNativeWindow, &req.target);
        ACameraDevice_createCaptureRequest(cameras[activeCameraId].device, req.deviceTemplate,
                                           &req.request);
        ACaptureRequest_addTarget(req.request, req.target);
    }

    captureSessionState = CaptureSessionState::READY;
    ACameraDevice_createCaptureSession(cameras[activeCameraId].device, outputContainer,
                                       GetSessionListener(), &captureSession);
    if (jpgWindow) {
        ACaptureRequest_setEntry_i32(requests[JPG_CAPTURE_REQUEST_IDX].request,
                                     ACAMERA_JPEG_ORIENTATION, 1, &imageRotation);
    }
    if (!manaulPreview)return;
    uint8_t aeModeOff = ACAMERA_CONTROL_AE_MODE_OFF;
    ACaptureRequest_setEntry_u8(requests[JPG_CAPTURE_REQUEST_IDX].request, ACAMERA_CONTROL_AE_MODE,
                                1, &aeModeOff);
    ACaptureRequest_setEntry_i32(requests[JPG_CAPTURE_REQUEST_IDX].request,
                                 ACAMERA_SENSOR_SENSITIVITY, 1, &sensitivity);
    ACaptureRequest_setEntry_i64(requests[JPG_CAPTURE_REQUEST_IDX].request,
                                 ACAMERA_SENSOR_EXPOSURE_TIME, 1, &exposureTime);
}

相机预览

通过相机会话,进行不间断地相机预览请求

void NDKCamera::StartPreview(bool start) {
    if (start) {
        ACameraCaptureSession_setRepeatingRequest(captureSession, nullptr, 1,
                                                  &requests[PREVIEW_REQUEST_IDX].request,
                                                  nullptr);
    } else if (!start && captureSessionState == CaptureSessionState::ACTIVE) {
        ACameraCaptureSession_stopRepeating(captureSession);
    }
}

至此,相机就可以开始预览了,但有始即有终,最终不能忘记关闭相机会话

参考

NdkCamera Sample

你可能感兴趣的:(随记)