将 Camera2 API 与 ImageReader 一起使用
Using Camera2 API with ImageReader
我正在尝试使用 Galaxy S4 上的 Camera2 API 捕捉图像数据。 ImageReader 被用作表面提供者。所使用的图像格式已在 ImageFormat.YV12 和 ImageFormat 上进行了尝试。YUV_420_888 并产生了相同的结果。
设置看起来不错,我使用 ImageReader 从 ImageReader 获取图像。图像有 3 个平面。缓冲区是预期的大小,Y 平面的宽度*高度和其他两个平面的 (宽度*高度)/4。
问题是我无法通过两种方式正确获取数据。第一个问题是Y平面数据是镜像的。这可以解决,虽然很奇怪,所以我很好奇这是否是预期的。
更糟糕的是,其他两架飞机似乎根本无法正确传送数据。例如,图像大小为 640x480,导致 U 和 V 缓冲区大小为 76800 字节,只有缓冲区的前 320 个字节是非零值。这个数字会有所不同,并且似乎不遵循不同图像尺寸之间的固定比例,但在每种尺寸的图像之间似乎是一致的。
我想知道我在使用这个 API 时是否遗漏了什么。代码如下。
public class OnboardCamera {
private final String TAG = "OnboardCamera";
int mWidth = 1280;
int mHeight = 720;
int mYSize = mWidth*mHeight;
int mUVSize = mYSize/4;
int mFrameSize = mYSize+(mUVSize*2);
//handler for the camera
private HandlerThread mCameraHandlerThread;
private Handler mCameraHandler;
//the size of the ImageReader determines the output from the camera.
private ImageReader mImageReader = ImageReader.newInstance(mWidth, mHeight, ImageFormat.YV12, 30);
private Surface mCameraRecieverSurface = mImageReader.getSurface();
{
mImageReader.setOnImageAvailableListener(mImageAvailListener, mCameraHandler);
}
private byte[] tempYbuffer = new byte[mYSize];
private byte[] tempUbuffer = new byte[mUVSize];
private byte[] tempVbuffer = new byte[mUVSize];
ImageReader.OnImageAvailableListener mImageAvailListener = new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
//when a buffer is available from the camera
//get the image
Image image = reader.acquireNextImage();
Image.Plane[] planes = image.getPlanes();
//copy it into a byte[]
byte[] outFrame = new byte[mFrameSize];
int outFrameNextIndex = 0;
ByteBuffer sourceBuffer = planes[0].getBuffer();
sourceBuffer.get(tempYbuffer, 0, tempYbuffer.length);
ByteBuffer vByteBuf = planes[1].getBuffer();
vByteBuf.get(tempVbuffer);
ByteBuffer yByteBuf = planes[2].getBuffer();
yByteBuf.get(tempUbuffer);
//free the Image
image.close();
}
};
OnboardCamera() {
mCameraHandlerThread = new HandlerThread("mCameraHandlerThread");
mCameraHandlerThread.start();
mCameraHandler = new Handler(mCameraHandlerThread.getLooper());
}
@Override
public boolean startProducing() {
CameraManager cm = (CameraManager) Ten8Application.getAppContext().getSystemService(Context.CAMERA_SERVICE);
try {
String[] cameraList = cm.getCameraIdList();
for (String cd: cameraList) {
//get camera characteristics
CameraCharacteristics mCameraCharacteristics = cm.getCameraCharacteristics(cd);
//check if the camera is in the back - if not, continue to next
if (mCameraCharacteristics.get(CameraCharacteristics.LENS_FACING) != CameraCharacteristics.LENS_FACING_BACK) {
continue;
}
//get StreamConfigurationMap - supported image formats
StreamConfigurationMap scm = mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
android.util.Size[] sizes = scm.getOutputSizes(ImageFormat.YV12);
cm.openCamera(cd, mDeviceStateCallback, mCameraHandler);
}
} catch (CameraAccessException e) {
e.printStackTrace();
Log.e(TAG, "CameraAccessException detected", e);
}
return false;
}
private final CameraDevice.StateCallback mDeviceStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice camera) {
//make list of surfaces to give to camera
List<Surface> surfaceList = new ArrayList<>();
surfaceList.add(mCameraRecieverSurface);
try {
camera.createCaptureSession(surfaceList, mCaptureSessionStateCallback, mCameraHandler);
} catch (CameraAccessException e) {
Log.e(TAG, "createCaptureSession threw CameraAccessException.", e);
}
}
@Override
public void onDisconnected(CameraDevice camera) {
}
@Override
public void onError(CameraDevice camera, int error) {
}
};
private final CameraCaptureSession.StateCallback mCaptureSessionStateCallback = new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession session) {
try {
CaptureRequest.Builder requestBuilder = session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
requestBuilder.addTarget(mCameraRecieverSurface);
//set to null - image data will be produced but will not receive metadata
session.setRepeatingRequest(requestBuilder.build(), null, mCameraHandler);
} catch (CameraAccessException e) {
Log.e(TAG, "createCaptureSession threw CameraAccessException.", e);
}
}
@Override
public void onConfigureFailed(CameraCaptureSession session) {
}
};
}
您是否关注 Image.Plane 的 row- 和 pixelStride 参数?
由于硬件内存映射的限制,行步幅通常大于图像的宽度,图像中第y行的起始位置为(yrowStride)而不是(y width) 在给定平面的 ByteArray 中。
如果是这样,那么在 640x480 图像的前 320 个字节(1 行二次采样色度数据)之后,U 或 V 平面将在一段时间内为 0 就不足为奇了 - 应该有 (rowStride - width)字节的零或垃圾,然后下一行像素数据将开始。
请注意,如果pixelStride 不为1,那么您还必须在像素值之间跳过字节;当底层 YCbCr 缓冲区实际上是半平面而不是平面时,这最常使用。
我有同样的问题,我认为问题出在 Android API 21。我升级到 API 23 并且相同的代码工作正常。还在 API 22 上进行了测试,它也有效。
我正在尝试使用 Galaxy S4 上的 Camera2 API 捕捉图像数据。 ImageReader 被用作表面提供者。所使用的图像格式已在 ImageFormat.YV12 和 ImageFormat 上进行了尝试。YUV_420_888 并产生了相同的结果。
设置看起来不错,我使用 ImageReader 从 ImageReader 获取图像。图像有 3 个平面。缓冲区是预期的大小,Y 平面的宽度*高度和其他两个平面的 (宽度*高度)/4。
问题是我无法通过两种方式正确获取数据。第一个问题是Y平面数据是镜像的。这可以解决,虽然很奇怪,所以我很好奇这是否是预期的。
更糟糕的是,其他两架飞机似乎根本无法正确传送数据。例如,图像大小为 640x480,导致 U 和 V 缓冲区大小为 76800 字节,只有缓冲区的前 320 个字节是非零值。这个数字会有所不同,并且似乎不遵循不同图像尺寸之间的固定比例,但在每种尺寸的图像之间似乎是一致的。
我想知道我在使用这个 API 时是否遗漏了什么。代码如下。
public class OnboardCamera {
private final String TAG = "OnboardCamera";
int mWidth = 1280;
int mHeight = 720;
int mYSize = mWidth*mHeight;
int mUVSize = mYSize/4;
int mFrameSize = mYSize+(mUVSize*2);
//handler for the camera
private HandlerThread mCameraHandlerThread;
private Handler mCameraHandler;
//the size of the ImageReader determines the output from the camera.
private ImageReader mImageReader = ImageReader.newInstance(mWidth, mHeight, ImageFormat.YV12, 30);
private Surface mCameraRecieverSurface = mImageReader.getSurface();
{
mImageReader.setOnImageAvailableListener(mImageAvailListener, mCameraHandler);
}
private byte[] tempYbuffer = new byte[mYSize];
private byte[] tempUbuffer = new byte[mUVSize];
private byte[] tempVbuffer = new byte[mUVSize];
ImageReader.OnImageAvailableListener mImageAvailListener = new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
//when a buffer is available from the camera
//get the image
Image image = reader.acquireNextImage();
Image.Plane[] planes = image.getPlanes();
//copy it into a byte[]
byte[] outFrame = new byte[mFrameSize];
int outFrameNextIndex = 0;
ByteBuffer sourceBuffer = planes[0].getBuffer();
sourceBuffer.get(tempYbuffer, 0, tempYbuffer.length);
ByteBuffer vByteBuf = planes[1].getBuffer();
vByteBuf.get(tempVbuffer);
ByteBuffer yByteBuf = planes[2].getBuffer();
yByteBuf.get(tempUbuffer);
//free the Image
image.close();
}
};
OnboardCamera() {
mCameraHandlerThread = new HandlerThread("mCameraHandlerThread");
mCameraHandlerThread.start();
mCameraHandler = new Handler(mCameraHandlerThread.getLooper());
}
@Override
public boolean startProducing() {
CameraManager cm = (CameraManager) Ten8Application.getAppContext().getSystemService(Context.CAMERA_SERVICE);
try {
String[] cameraList = cm.getCameraIdList();
for (String cd: cameraList) {
//get camera characteristics
CameraCharacteristics mCameraCharacteristics = cm.getCameraCharacteristics(cd);
//check if the camera is in the back - if not, continue to next
if (mCameraCharacteristics.get(CameraCharacteristics.LENS_FACING) != CameraCharacteristics.LENS_FACING_BACK) {
continue;
}
//get StreamConfigurationMap - supported image formats
StreamConfigurationMap scm = mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
android.util.Size[] sizes = scm.getOutputSizes(ImageFormat.YV12);
cm.openCamera(cd, mDeviceStateCallback, mCameraHandler);
}
} catch (CameraAccessException e) {
e.printStackTrace();
Log.e(TAG, "CameraAccessException detected", e);
}
return false;
}
private final CameraDevice.StateCallback mDeviceStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice camera) {
//make list of surfaces to give to camera
List<Surface> surfaceList = new ArrayList<>();
surfaceList.add(mCameraRecieverSurface);
try {
camera.createCaptureSession(surfaceList, mCaptureSessionStateCallback, mCameraHandler);
} catch (CameraAccessException e) {
Log.e(TAG, "createCaptureSession threw CameraAccessException.", e);
}
}
@Override
public void onDisconnected(CameraDevice camera) {
}
@Override
public void onError(CameraDevice camera, int error) {
}
};
private final CameraCaptureSession.StateCallback mCaptureSessionStateCallback = new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession session) {
try {
CaptureRequest.Builder requestBuilder = session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
requestBuilder.addTarget(mCameraRecieverSurface);
//set to null - image data will be produced but will not receive metadata
session.setRepeatingRequest(requestBuilder.build(), null, mCameraHandler);
} catch (CameraAccessException e) {
Log.e(TAG, "createCaptureSession threw CameraAccessException.", e);
}
}
@Override
public void onConfigureFailed(CameraCaptureSession session) {
}
};
}
您是否关注 Image.Plane 的 row- 和 pixelStride 参数?
由于硬件内存映射的限制,行步幅通常大于图像的宽度,图像中第y行的起始位置为(yrowStride)而不是(y width) 在给定平面的 ByteArray 中。
如果是这样,那么在 640x480 图像的前 320 个字节(1 行二次采样色度数据)之后,U 或 V 平面将在一段时间内为 0 就不足为奇了 - 应该有 (rowStride - width)字节的零或垃圾,然后下一行像素数据将开始。
请注意,如果pixelStride 不为1,那么您还必须在像素值之间跳过字节;当底层 YCbCr 缓冲区实际上是半平面而不是平面时,这最常使用。
我有同样的问题,我认为问题出在 Android API 21。我升级到 API 23 并且相同的代码工作正常。还在 API 22 上进行了测试,它也有效。