前言
通过调用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);
}
}
至此,相机就可以开始预览了,但有始即有终,最终不能忘记关闭相机会话