You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过JNI函数控制Android相机闪光灯的开关状态?

刚好我之前做过类似的JNI闪光灯控制需求,Android NDK的ACamera系列API确实没有像Java层CameraManager.setTorchMode()那样直接的开关接口,但我们可以通过配置相机的捕获请求来实现完全等价的功能。下面给你详细的实现方案:

使用Android NDK Camera API实现JNI闪光灯控制

核心实现思路

要在Native层控制闪光灯,我们需要完成这几个关键步骤:

  • 获取相机管理器实例,枚举所有可用相机,筛选出支持闪光灯的后置相机
  • 打开目标相机设备,创建捕获会话(即使不需要预览画面,也需要一个有效的输出目标来满足API要求)
  • 通过修改捕获请求的曝光模式和闪光灯模式参数,实现闪光灯的开关切换

完整JNI代码实现

#include <jni.h>
#include <android/log.h>
#include <camera/NdkCameraManager.h>
#include <camera/NdkCameraDevice.h>
#include <camera/NdkCameraCaptureSession.h>
#include <camera/NdkCameraMetadata.h>
#include <android/native_window.h>

#define LOG_TAG "FlashlightJNI"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

// 全局变量保存相机相关资源
static ACameraManager* gCameraManager = nullptr;
static ACameraDevice* gCameraDevice = nullptr;
static ACameraCaptureSession* gCaptureSession = nullptr;
static ACameraOutputTarget* gOutputTarget = nullptr;
static ACaptureRequest* gCaptureRequest = nullptr;
static bool gIsFlashOn = false;
static ANativeWindow* gDummyWindow = nullptr;

// 相机设备状态回调(处理相机连接/断开事件)
static void cameraDeviceCallback(void* context, ACameraDevice* device, ACameraDeviceStatus status) {
    if (status == ACAMERA_DEVICE_STATUS_DISCONNECTED) {
        LOGD("Camera device disconnected");
        // 这里可以添加资源清理逻辑
    }
}

// 捕获会话状态回调
static void captureSessionCallback(void* context, ACameraCaptureSession* session, ACameraCaptureSessionStatus status) {
    if (status == ACAMERA_CAPTURE_SESSION_STATUS_CLOSED) {
        LOGD("Capture session closed");
    }
}

// 查找支持闪光灯的后置相机ID
const char* findFlashSupportedCamera(ACameraManager* manager) {
    ACameraIdList* cameraIdList = nullptr;
    if (ACameraManager_getCameraIdList(manager, &cameraIdList) != ACAMERA_OK) {
        LOGE("Failed to get camera ID list");
        return nullptr;
    }

    for (int i = 0; i < cameraIdList->numCameras; i++) {
        const char* cameraId = cameraIdList->cameraIds[i];
        ACameraMetadata* metadata = nullptr;
        if (ACameraManager_getCameraCharacteristics(manager, cameraId, &metadata) != ACAMERA_OK) {
            continue;
        }

        // 检查是否支持闪光灯
        uint8_t flashAvailable = 0;
        if (ACameraMetadata_getConstEntry(metadata, ACAMERA_FLASH_INFO_AVAILABLE, &flashAvailable) == ACAMERA_OK 
            && flashAvailable == 1) {
            // 检查是否为后置相机
            int32_t lensFacing = 0;
            if (ACameraMetadata_getConstEntry(metadata, ACAMERA_LENS_FACING, &lensFacing) == ACAMERA_OK 
                && lensFacing == ACAMERA_LENS_FACING_BACK) {
                ACameraMetadata_free(metadata);
                ACameraManager_deleteCameraIdList(cameraIdList);
                return cameraId;
            }
        }
        ACameraMetadata_free(metadata);
    }

    ACameraManager_deleteCameraIdList(cameraIdList);
    LOGE("No back camera with flash found");
    return nullptr;
}

// 创建虚拟输出目标(满足捕获会话的要求)
bool createDummyOutputTarget() {
    // 创建一个最小尺寸的离屏窗口作为虚拟输出
    gDummyWindow = ANativeWindow_create();
    if (!gDummyWindow) {
        LOGE("Failed to create dummy window");
        return false;
    }
    ANativeWindow_setBuffersGeometry(gDummyWindow, 1, 1, WINDOW_FORMAT_RGBA_8888);
    gOutputTarget = ACameraOutputTarget_create(gDummyWindow);
    return gOutputTarget != nullptr;
}

// 切换闪光灯状态
void toggleFlashState() {
    if (!gCaptureRequest) return;

    gIsFlashOn = !gIsFlashOn;
    if (gIsFlashOn) {
        // 设置闪光灯常亮模式
        ACaptureRequest_setEntry_i32(gCaptureRequest, ACAMERA_CONTROL_AE_MODE, ACAMERA_CONTROL_AE_MODE_ON_ALWAYS_FLASH);
        ACaptureRequest_setEntry_i32(gCaptureRequest, ACAMERA_FLASH_MODE, ACAMERA_FLASH_MODE_TORCH);
    } else {
        // 关闭闪光灯,恢复自动曝光
        ACaptureRequest_setEntry_i32(gCaptureRequest, ACAMERA_CONTROL_AE_MODE, ACAMERA_CONTROL_AE_MODE_ON);
        ACaptureRequest_setEntry_i32(gCaptureRequest, ACAMERA_FLASH_MODE, ACAMERA_FLASH_MODE_OFF);
    }

    // 更新捕获请求到会话
    if (gCaptureSession) {
        ACameraCaptureSession_setRepeatingRequest(gCaptureSession, nullptr, 1, &gCaptureRequest, nullptr);
    }
}

// 初始化相机并创建捕获会话
bool initCamera(const char* cameraId) {
    // 打开相机设备
    if (ACameraManager_openCamera(gCameraManager, cameraId, cameraDeviceCallback, nullptr, &gCameraDevice) != ACAMERA_OK) {
        LOGE("Failed to open camera");
        return false;
    }

    // 创建虚拟输出目标
    if (!createDummyOutputTarget()) {
        ACameraDevice_close(gCameraDevice);
        gCameraDevice = nullptr;
        return false;
    }

    // 创建捕获会话
    if (ACameraDevice_createCaptureSession(gCameraDevice, 1, &gOutputTarget, captureSessionCallback, nullptr, &gCaptureSession) != ACAMERA_OK) {
        LOGE("Failed to create capture session");
        ACameraOutputTarget_free(gOutputTarget);
        ACameraDevice_close(gCameraDevice);
        gCameraDevice = nullptr;
        return false;
    }

    // 创建捕获请求并初始化闪光灯状态
    if (ACameraDevice_createCaptureRequest(gCameraDevice, TEMPLATE_PREVIEW, &gCaptureRequest) != ACAMERA_OK) {
        LOGE("Failed to create capture request");
        ACameraCaptureSession_close(gCaptureSession);
        ACameraOutputTarget_free(gOutputTarget);
        ACameraDevice_close(gCameraDevice);
        gCameraDevice = nullptr;
        return false;
    }

    // 默认关闭闪光灯
    ACaptureRequest_setEntry_i32(gCaptureRequest, ACAMERA_CONTROL_AE_MODE, ACAMERA_CONTROL_AE_MODE_ON);
    ACaptureRequest_setEntry_i32(gCaptureRequest, ACAMERA_FLASH_MODE, ACAMERA_FLASH_MODE_OFF);
    ACameraCaptureSession_setRepeatingRequest(gCaptureSession, nullptr, 1, &gCaptureRequest, nullptr);
    return true;
}

JNIEXPORT void JNICALL Java_com_android_rxjava_flashlightflicker_MainActivity_flasher(JNIEnv *env, jobject instance) {
    // 初始化相机管理器
    if (!gCameraManager) {
        gCameraManager = ACameraManager_create();
        if (!gCameraManager) {
            LOGE("Failed to create camera manager");
            return;
        }
    }

    // 相机未初始化时,先初始化相机
    if (!gCameraDevice) {
        const char* cameraId = findFlashSupportedCamera(gCameraManager);
        if (!cameraId) return;
        if (!initCamera(cameraId)) return;
    }

    // 切换闪光灯状态
    toggleFlashState();
}

// 释放所有相机资源的JNI方法,必须在Activity销毁时调用
JNIEXPORT void JNICALL Java_com_android_rxjava_flashlightflicker_MainActivity_releaseFlashResources(JNIEnv *env, jobject instance) {
    if (gCaptureSession) {
        ACameraCaptureSession_stopRepeating(gCaptureSession);
        ACameraCaptureSession_close(gCaptureSession);
        gCaptureSession = nullptr;
    }
    if (gOutputTarget) {
        ACameraOutputTarget_free(gOutputTarget);
        gOutputTarget = nullptr;
    }
    if (gCaptureRequest) {
        ACaptureRequest_free(gCaptureRequest);
        gCaptureRequest = nullptr;
    }
    if (gCameraDevice) {
        ACameraDevice_close(gCameraDevice);
        gCameraDevice = nullptr;
    }
    if (gDummyWindow) {
        ANativeWindow_destroy(gDummyWindow);
        gDummyWindow = nullptr;
    }
    if (gCameraManager) {
        ACameraManager_delete(gCameraManager);
        gCameraManager = nullptr;
    }
}

关键注意事项

  • 权限要求:必须在AndroidManifest.xml中声明CAMERA权限,Android 6.0及以上还需要动态申请权限,否则相机无法打开。
  • 虚拟输出目标:Native Camera API要求捕获会话必须绑定有效的输出目标,这里我们创建了一个最小尺寸的虚拟窗口来满足要求,避免了必须显示预览画面的限制。
  • 资源释放:一定要在Activity销毁时调用releaseFlashResources()方法,否则会导致相机资源泄漏,影响其他应用使用相机。
  • 设备兼容性:部分设备的相机特性可能有差异,建议在初始化前检查相机的闪光灯支持状态,避免崩溃。

Java层配合代码示例

public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("flashlight-jni");
    }

    public native void flasher();
    public native void releaseFlashResources();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 动态申请相机权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 1001);
        }
    }

    // 点击按钮切换闪光灯
    public void onToggleFlashClick(View view) {
        flasher();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 释放Native层资源
        releaseFlashResources();
    }
}

内容的提问来源于stack exchange,提问作者Alex2452

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 03:14:10