从this article的印象是,在一个会话中,人们可以将一张镜头中的数据转换成多个流,每个流将这些数据转换为不同的格式。因此,我尝试将两个ANativeWindow
中的两个AImageReader
(一个用于RAW16,另一个用于JPEG)添加到我的ACaptureSessionOutputContainer
中,并执行一个捕获请求,希望两个读者都能可以以不同的格式获得相同的照片。
我确实获得了具有预期格式的两幅图像,它们具有大致相同的场景,但是仔细检查后发现,这些场景之间的差异大约为70毫秒。这是我的发现方式:我运行了bash循环
for ((i=0;;++i)); do echo -n "$i "; sleep 0.01; done
并拍摄了其实时输出的照片。这是两张照片的作物,首先是原始照片,然后是JPEG:
这是我的代码(从Kotlin代码调用的本机C ++库;目录eu.sisik.cam
是this tutorial的后代,该代码最初基于该目录):
#include <jni.h>
#include <string>
#include <fstream>
#include <camera/NdkCameraManager.h>
#include <camera/NdkCameraMetadata.h>
#include <camera/NdkCameraDevice.h>
#include <media/NdkImageReader.h>
#include <android/log.h>
#define S(x) #x
#define S_(x) S(x)
#define S__LINE__ S_(__LINE__)
#define LOGD(...) (__android_log_print(ANDROID_LOG_DEBUG, "LOGD: " __FILE__ " | " S__LINE__, __VA_ARGS__))
#define LOGE(...) (__android_log_print(ANDROID_LOG_ERROR, "LOGE: " __FILE__ " | " S__LINE__, __VA_ARGS__))
#define CHECK_MEDIA_CALL(call, HANDLER_STATEMENT) \
if(const media_status_t status=call; status!=AMEDIA_OK) \
{ \
LOGE("Call %s failed. Error code %d", #call, int(status)); \
HANDLER_STATEMENT; \
}
#define CHECK_CAM_CALL(call, HANDLER_STATEMENT) \
if(const camera_status_t status=call; status!=ACAMERA_OK) \
{ \
LOGE("Call %s failed. Error code %d", #call, int(status)); \
HANDLER_STATEMENT; \
}
ACameraManager* cameraManager;
ACameraDevice* cameraDevice;
ACaptureRequest* captureRequest;
ANativeWindow* rawImageWindow;
ANativeWindow* jpegImageWindow;
ACameraOutputTarget* rawImageTarget;
ACameraOutputTarget* jpegImageTarget;
AImageReader* rawImageReader;
AImageReader* jpegImageReader;
ACaptureSessionOutput* rawImageOutput;
ACaptureSessionOutput* jpegImageOutput;
ACaptureSessionOutputContainer* outputs;
ACameraCaptureSession* captureSession;
int numberOfTimesCaptured=0;
ACameraCaptureSession_captureCallbacks captureCallbacks
{
.onCaptureCompleted = [](void*, ACameraCaptureSession*, ACaptureRequest*,
const ACameraMetadata* metadata)
{
LOGD("Capture completed");
if(numberOfTimesCaptured++) return;
// Retry because first frame will most likely be blurred
CHECK_CAM_CALL(ACameraCaptureSession_capture(captureSession, &captureCallbacks,
1, &captureRequest, nullptr),);
},
.onCaptureFailed = [](auto, auto, auto, auto)
{
LOGE("***************** Capture failed! **********************");
},
};
AImageReader* createImageReader(AIMAGE_FORMATS desiredFormat,
unsigned width, unsigned height)
{
AImageReader* reader = nullptr;
CHECK_MEDIA_CALL(AImageReader_new(width, height, desiredFormat, 1, &reader),
return nullptr);
static const auto imageCallback=[](void* context, AImageReader* reader)
{
const int format=reinterpret_cast<uintptr_t>(context);
LOGD("Format %d: imageCallback()", format);
AImage *image = nullptr;
CHECK_MEDIA_CALL(AImageReader_acquireNextImage(reader, &image), return);
const bool isRaw = format==AIMAGE_FORMAT_RAW16;
const auto filename=std::string("/data/data/eu.sisik.cam/IMG_TEST")+
(isRaw?".raw":".jpg");
uint8_t *data = nullptr;
int len = 0;
AImage_getPlaneData(image, 0, &data, &len);
int32_t width, height;
AImage_getWidth(image, &width);
AImage_getHeight(image, &height);
LOGD("Format %d: Plane data len: %d", format, len);
std::ofstream file(filename);
if(isRaw)
{
file.write(reinterpret_cast<const char*>(&width ), sizeof width);
file.write(reinterpret_cast<const char*>(&height), sizeof height);
}
file.write(reinterpret_cast<const char*>(data), len);
AImage_delete(image);
LOGD("Format %d: returning from imageCallback", format);
};
AImageReader_ImageListener listener
{
.context=reinterpret_cast<void*>(desiredFormat),
.onImageAvailable = imageCallback
};
AImageReader_setImageListener(reader, &listener);
return reader;
}
static void initCam()
{
cameraManager = ACameraManager_create();
ACameraDevice_stateCallbacks cameraDeviceCallbacks = {
.onError=[](void*, ACameraDevice*, int error) { LOGD("error %d", error); }};
ACameraManager_openCamera(cameraManager, "0", &cameraDeviceCallbacks,
&cameraDevice);
ACameraDevice_createCaptureRequest(cameraDevice, TEMPLATE_STILL_CAPTURE,
&captureRequest);
ACaptureSessionOutputContainer_create(&outputs);
rawImageReader = createImageReader(AIMAGE_FORMAT_RAW16, 4144, 3106);
AImageReader_getWindow(rawImageReader, &rawImageWindow);
ANativeWindow_acquire(rawImageWindow);
CHECK_CAM_CALL(ACameraOutputTarget_create(rawImageWindow, &rawImageTarget),);
CHECK_CAM_CALL(ACaptureRequest_addTarget(captureRequest, rawImageTarget),);
CHECK_CAM_CALL(ACaptureSessionOutput_create(rawImageWindow, &rawImageOutput),);
CHECK_CAM_CALL(ACaptureSessionOutputContainer_add(outputs, rawImageOutput),);
jpegImageReader = createImageReader(AIMAGE_FORMAT_JPEG, 4128, 2322);
AImageReader_getWindow(jpegImageReader, &jpegImageWindow);
ANativeWindow_acquire(jpegImageWindow);
CHECK_CAM_CALL(ACameraOutputTarget_create(jpegImageWindow, &jpegImageTarget),);
CHECK_CAM_CALL(ACaptureRequest_addTarget(captureRequest, jpegImageTarget),);
CHECK_CAM_CALL(ACaptureSessionOutput_create(jpegImageWindow, &jpegImageOutput),);
CHECK_CAM_CALL(ACaptureSessionOutputContainer_add(outputs, jpegImageOutput),);
static const ACameraCaptureSession_stateCallbacks sessionStateCallbacks{};
CHECK_CAM_CALL(ACameraDevice_createCaptureSession(cameraDevice, outputs,
&sessionStateCallbacks,
&captureSession),);
CHECK_CAM_CALL(ACameraCaptureSession_capture(captureSession, &captureCallbacks,
1, &captureRequest, nullptr),);
}
static void exitCam()
{
numberOfTimesCaptured = 0;
if(!cameraManager) return;
ACaptureSessionOutputContainer_free(outputs);
ACaptureSessionOutput_free(rawImageOutput);
ACaptureSessionOutput_free(jpegImageOutput);
ACameraDevice_close(cameraDevice);
ACameraManager_delete(cameraManager);
cameraManager = nullptr;
AImageReader_delete(rawImageReader);
AImageReader_delete(jpegImageReader);
ACaptureRequest_free(captureRequest);
ACameraOutputTarget_free(rawImageTarget);
ACameraOutputTarget_free(jpegImageTarget);
}
extern "C"
{
JNIEXPORT void JNICALL
Java_eu_sisik_cam_MainActivity_initCam(JNIEnv *env, jobject)
{
initCam();
}
JNIEXPORT void JNICALL
Java_eu_sisik_cam_MainActivity_exitCam(JNIEnv *env, jobject)
{
exitCam();
}
}
现在我的问题是:我真的可以从多种格式的一张镜头中获取数据吗?如果是,那么如何正确执行?