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

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
# NDK Camera [![Build Status](https://travis-ci.org/luncliff/NdkCamera.svg?branch=master)](https://travis-ci.org/luncliff/NdkCamera) > If there is an issue with this library, please mail to luncliff@gmail.com Simplified [Android Camera 2 API](https://www.youtube.com/watch?v=Bi4QjMfSOE0). Available for both Java/JNI. - API level: 24+ - NDK ### Reference - Personal Template Project: https://github.com/luncliff/Muffin - [API Reference](https://developer.android.com/ndk/reference/group/camera) - [Android Camera Overview](https://source.android.com/devices/camera) - Camera HAL3: https://source.android.com/devices/camera/camera3 - HAL Subsystem: https://source.android.com/devices/camera/camera3_requests_hal - Multi-Camera Support: https://source.android.com/devices/camera/multi-camera - Version Support: https://source.android.com/devices/camera/versioning - Android Media - https://source.android.com/devices/media/framework-hardening ## How to ### Build For **Windows** environment, latest [Android Studio](https://developer.android.com/studio/) is recommended. For **Linux/MacOS**, [Gradle 4.10.2](https://gradle.org/) will be enough. ```console $ git clone https://github.com/luncliff/NdkCamera $ cd ./NdkCamera $ gradle assemble # Build: libndk_camera.so & NdkCamera.aar ``` ### Test Connect your device and run the test with Gradle. Please reference the [test codes](./android/test/ndcam/). ```console $ gradle connectedAndroidTest # Run test ``` ### Use The following code shows working with `SurfaceView` class. ```java package dev.example; // ... import dev.ndcam.*; // Expect we already have a camera permission public class SurfaceDisplay implements SurfaceHolder.Callback { SurfaceView surfaceView; Surface surface; ndcam.Device camera; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // ... // Load/Init library ndcam.CameraModel.Init(); surfaceView = findViewById(R.id.surfaceView); SurfaceHolder holder = surfaceView.getHolder(); holder.setFixedSize(1920, 1080); holder.setFormat(ImageFormat.YUV_420_888); holder.addCallback(this); } @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { // Get all devices in array form for(ndcam.Device device : ndcam.CameraModel.GetDevices()) if(device.facing() == CameraCharacteristics.LENS_FACING_BACK) camera = device; } @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) { // Make a repeating caputre request with surface surface = surfaceHolder.getSurface(); camera.repeat(surface); } @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { // No more capture if(camera != null) camera.stopRepeat(); } } ```
Android NDK 是 Android 开发中一个非常重要的组成部分,它允许开发者使用 C/C++ 语言编写高效的、稳定的、高性能的应用程序和库。如果你想在 Android 应用中实现数据库管理,可以使用 Android NDK 编写 C/C++ 代码来实现。 1. 首先,你需要在你的 Android 项目中添加 NDK 支持。在 Android Studio 中,可以通过 File -> New -> New Module -> Import .JAR/.AAR Package 添加 NDK 库。 2. 然后,你需要编写 C/C++ 代码来实现数据库管理。你可以使用 SQLite 数据库引擎,在 C/C++ 中使用 SQLite API 来实现数据的存储、查询等操作。SQLite 是一种轻量级的关系型数据库,非常适用于移动设备上的数据管理。 3. 在 C/C++ 代码中,你需要使用 JNI 接口来与 Java 层进行通信。JNI 接口允许你在 C/C++ 代码中调用 Java 方法和访问 Java 对象。 4. 最后,你需要在 Android 应用中调用 C/C++ 代码。你可以通过在 Java 层创建一个包含 JNI 方法的类来实现。在 JNI 方法中,你可以调用 C/C++ 代码并将结果返回给 Java 层。 下面是一个简单的例子,演示了如何使用 Android NDK 实现简单的数据库管理: 1. 在 Android Studio 中创建一个新的 Android 项目,并添加 NDK 支持。 2. 在 C/C++ 代码中引入 SQLite 库,并使用 SQLite API 实现数据库的创建和查询操作。以下是一个示例代码: ``` #include <sqlite3.h> sqlite3 *db; int open_database(const char *name) { return sqlite3_open(name, &db); } int close_database() { return sqlite3_close(db); } int execute_query(const char *sql) { return sqlite3_exec(db, sql, NULL, NULL, NULL); } int get_data(const char *sql, char ***result, int *rows, int *cols) { char *errmsg; int rc = sqlite3_get_table(db, sql, result, rows, cols, &errmsg); if (rc != SQLITE_OK) { sqlite3_free(errmsg); } return rc; } ``` 3. 在 C/C++ 代码中使用 JNI 接口与 Java 层进行通信。以下是一个示例代码: ``` #include <jni.h> JNIEXPORT jint JNICALL Java_com_example_myapplication_DatabaseHelper_openDatabase(JNIEnv *env, jobject obj, jstring name) { const char *dbname = env->GetStringUTFChars(name, NULL); int rc = open_database(dbname); env->ReleaseStringUTFChars(name, dbname); return rc; } JNIEXPORT jint JNICALL Java_com_example_myapplication_DatabaseHelper_closeDatabase(JNIEnv *env, jobject obj) { return close_database(); } JNIEXPORT jint JNICALL Java_com_example_myapplication_DatabaseHelper_executeQuery(JNIEnv *env, jobject obj, jstring sql) { const char *query = env->GetStringUTFChars(sql, NULL); int rc = execute_query(query); env->ReleaseStringUTFChars(sql, query); return rc; } JNIEXPORT jint JNICALL Java_com_example_myapplication_DatabaseHelper_getData(JNIEnv *env, jobject obj, jstring sql, jobjectArray result) { char **rows; int rows_num, cols_num, i, j; const char *query = env->GetStringUTFChars(sql, NULL); int rc = get_data(query, &rows, &rows_num, &cols_num); env->ReleaseStringUTFChars(sql, query); if (rc == SQLITE_OK) { for (i = 0; i < rows_num; i++) { jobjectArray row = (jobjectArray) env->GetObjectArrayElement(result, i); for (j = 0; j < cols_num; j++) { env->SetObjectArrayElement(row, j, env->NewStringUTF(rows[i * cols_num + j])); } } sqlite3_free_table(rows); } return rc; } ``` 4. 在 Java 层创建一个包含 JNI 方法的类,并调用 C/C++ 代码来实现数据库管理。以下是一个示例代码: ``` public class DatabaseHelper { static { System.loadLibrary("database"); } public native int openDatabase(String name); public native int closeDatabase(); public native int executeQuery(String sql); public native int getData(String sql, String[][] result); private SQLiteDatabase db; public DatabaseHelper(Context context, String name) { db = context.openOrCreateDatabase(name, Context.MODE_PRIVATE, null); } public void close() { db.close(); closeDatabase(); } public void execute(String sql) { db.execSQL(sql); executeQuery(sql); } public String[][] getData(String sql) { String[][] result = new String[0][0]; int rows = 0, cols = 0, i, j; getData(sql, result); Cursor cursor = db.rawQuery(sql, null); if (cursor.moveToFirst()) { rows = cursor.getCount(); cols = cursor.getColumnCount(); result = new String[rows][cols]; for (i = 0; i < rows; i++) { for (j = 0; j < cols; j++) { result[i][j] = cursor.getString(j); } cursor.moveToNext(); } } cursor.close(); return result; } } ``` 使用以上方法,你可以轻松地在 Android 应用中实现数据库管理。当然,在实际应用中,你可能需要更加复杂的逻辑和功能。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值