Merge "Camera2: Add jpeg encoding support for all camera extensions" into sc-dev

This commit is contained in:
TreeHugger Robot
2021-02-23 21:48:35 +00:00
committed by Android (Google) Code Review
10 changed files with 1104 additions and 41 deletions

View File

@@ -378,9 +378,10 @@ public abstract class CameraDevice implements AutoCloseable {
* released, continuous repeating requests stopped and any pending
* multi-frame capture requests flushed.</p>
*
* <p>Note that the CameraExtensionSession currently supports at most two
* multi frame capture surface formats: ImageFormat.YUV_420_888 and
* ImageFormat.JPEG. Clients must query the multi-frame capture format support using
* <p>Note that the CameraExtensionSession currently supports at most wo
* multi frame capture surface formats: ImageFormat.JPEG will be supported
* by all extensions and ImageFormat.YUV_420_888 may or may not be supported.
* Clients must query the multi-frame capture format support using
* {@link CameraExtensionCharacteristics#getExtensionSupportedSizes(int, int)}.
* For repeating requests CameraExtensionSession supports only
* {@link android.graphics.SurfaceTexture} as output. Clients can query the supported resolution

View File

@@ -35,6 +35,7 @@ import android.util.Log;
import android.util.Pair;
import android.util.Size;
import java.util.HashSet;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -153,12 +154,8 @@ public final class CameraExtensionCharacteristics {
mChars = chars;
}
private static List<Size> generateSupportedSizes(List<SizeList> sizesList,
Integer format,
StreamConfigurationMap streamMap) {
// Per API contract it is assumed that the extension is able to support all
// camera advertised sizes for a given format in case it doesn't return
// a valid non-empty size list.
private static ArrayList<Size> getSupportedSizes(List<SizeList> sizesList,
Integer format) {
ArrayList<Size> ret = new ArrayList<>();
if ((sizesList != null) && (!sizesList.isEmpty())) {
for (SizeList entry : sizesList) {
@@ -170,13 +167,36 @@ public final class CameraExtensionCharacteristics {
}
}
}
return ret;
}
private static List<Size> generateSupportedSizes(List<SizeList> sizesList,
Integer format,
StreamConfigurationMap streamMap) {
// Per API contract it is assumed that the extension is able to support all
// camera advertised sizes for a given format in case it doesn't return
// a valid non-empty size list.
ArrayList<Size> ret = getSupportedSizes(sizesList, format);
Size[] supportedSizes = streamMap.getOutputSizes(format);
if (supportedSizes != null) {
if ((ret.isEmpty()) && (supportedSizes != null)) {
ret.addAll(Arrays.asList(supportedSizes));
}
return ret;
}
private static List<Size> generateJpegSupportedSizes(List<SizeList> sizesList,
StreamConfigurationMap streamMap) {
ArrayList<Size> extensionSizes = getSupportedSizes(sizesList, ImageFormat.YUV_420_888);
HashSet<Size> supportedSizes = extensionSizes.isEmpty() ? new HashSet<>(Arrays.asList(
streamMap.getOutputSizes(ImageFormat.YUV_420_888))) : new HashSet<>(extensionSizes);
HashSet<Size> supportedJpegSizes = new HashSet<>(Arrays.asList(streamMap.getOutputSizes(
ImageFormat.JPEG)));
supportedSizes.retainAll(supportedJpegSizes);
return new ArrayList<>(supportedSizes);
}
/**
* A per-process global camera extension manager instance, to track and
* initialize/release extensions depending on client activity.
@@ -488,8 +508,8 @@ public final class CameraExtensionCharacteristics {
* {@link StreamConfigurationMap#getOutputSizes}.</p>
*
* <p>Device-specific extensions currently support at most two
* multi-frame capture surface formats, ImageFormat.YUV_420_888 or
* ImageFormat.JPEG.</p>
* multi-frame capture surface formats. ImageFormat.JPEG will be supported by all
* extensions and ImageFormat.YUV_420_888 may or may not be supported.</p>
*
* @param extension the extension type
* @param format device-specific extension output format
@@ -526,14 +546,17 @@ public final class CameraExtensionCharacteristics {
format, streamMap);
} else if (format == ImageFormat.JPEG) {
extenders.second.init(mCameraId, mChars.getNativeMetadata());
if (extenders.second.getCaptureProcessor() == null) {
if (extenders.second.getCaptureProcessor() != null) {
// The framework will perform the additional encoding pass on the
// processed YUV_420 buffers.
return generateJpegSupportedSizes(
extenders.second.getSupportedResolutions(), streamMap);
} else {
return generateSupportedSizes(null, format, streamMap);
}
return new ArrayList<>();
} else {
throw new IllegalArgumentException("Unsupported format: " + format);
}
throw new IllegalArgumentException("Unsupported format: " + format);
} finally {
unregisterClient(clientId);
}

View File

@@ -238,8 +238,10 @@ public abstract class CameraExtensionSession implements AutoCloseable {
* from the camera device, to produce a single high-quality output result.
*
* <p>Note that single capture requests currently do not support
* client parameters. Settings included in the request will
* be entirely overridden by the device-specific extension. </p>
* client parameters except for {@link CaptureRequest#JPEG_ORIENTATION orientation} and
* {@link CaptureRequest#JPEG_QUALITY quality} in case of ImageFormat.JPEG output target.
* The rest of the settings included in the request will be entirely overridden by
* the device-specific extension. </p>
*
* <p>The {@link CaptureRequest.Builder#addTarget} supports only one
* ImageFormat.YUV_420_888 or ImageFormat.JPEG target surface. {@link CaptureRequest}

View File

@@ -0,0 +1,312 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.hardware.camera2.impl;
import android.annotation.NonNull;
import android.graphics.ImageFormat;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.extension.CaptureBundle;
import android.hardware.camera2.extension.ICaptureProcessorImpl;
import android.media.Image;
import android.media.Image.Plane;
import android.media.ImageReader;
import android.media.ImageWriter;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.Surface;
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
// Jpeg compress input YUV and queue back in the client target surface.
public class CameraExtensionJpegProcessor implements ICaptureProcessorImpl {
public final static String TAG = "CameraExtensionJpeg";
private final static int JPEG_QUEUE_SIZE = 1;
private final static int JPEG_DEFAULT_QUALITY = 100;
private final static int JPEG_DEFAULT_ROTATION = 0;
private final Handler mHandler;
private final HandlerThread mHandlerThread;
private final ICaptureProcessorImpl mProcessor;
private ImageReader mYuvReader = null;
private android.hardware.camera2.extension.Size mResolution = null;
private int mFormat = -1;
private Surface mOutputSurface = null;
private ImageWriter mOutputWriter = null;
private static final class JpegParameters {
public HashSet<Long> mTimeStamps = new HashSet<>();
public int mRotation = JPEG_DEFAULT_ROTATION; // CCW multiple of 90 degrees
public int mQuality = JPEG_DEFAULT_QUALITY; // [0..100]
}
private ConcurrentLinkedQueue<JpegParameters> mJpegParameters = new ConcurrentLinkedQueue<>();
public CameraExtensionJpegProcessor(@NonNull ICaptureProcessorImpl processor) {
mProcessor = processor;
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
}
public void close() {
mHandlerThread.quitSafely();
if (mOutputWriter != null) {
mOutputWriter.close();
mOutputWriter = null;
}
if (mYuvReader != null) {
mYuvReader.close();
mYuvReader = null;
}
}
private static JpegParameters getJpegParameters(List<CaptureBundle> captureBundles) {
JpegParameters ret = new JpegParameters();
if (!captureBundles.isEmpty()) {
// The quality and orientation settings must be equal for requests in a burst
Byte jpegQuality = captureBundles.get(0).captureResult.get(CaptureResult.JPEG_QUALITY);
if (jpegQuality != null) {
ret.mQuality = jpegQuality;
} else {
Log.w(TAG, "No jpeg quality set, using default: " + JPEG_DEFAULT_QUALITY);
}
Integer orientation = captureBundles.get(0).captureResult.get(
CaptureResult.JPEG_ORIENTATION);
if (orientation != null) {
ret.mRotation = orientation / 90;
} else {
Log.w(TAG, "No jpeg rotation set, using default: " + JPEG_DEFAULT_ROTATION);
}
for (CaptureBundle bundle : captureBundles) {
Long timeStamp = bundle.captureResult.get(CaptureResult.SENSOR_TIMESTAMP);
if (timeStamp != null) {
ret.mTimeStamps.add(timeStamp);
} else {
Log.e(TAG, "Capture bundle without valid sensor timestamp!");
}
}
}
return ret;
}
/**
* Compresses a YCbCr image to jpeg, applying a crop and rotation.
* <p>
* The input is defined as a set of 3 planes of 8-bit samples, one plane for
* each channel of Y, Cb, Cr.<br>
* The Y plane is assumed to have the same width and height of the entire
* image.<br>
* The Cb and Cr planes are assumed to be downsampled by a factor of 2, to
* have dimensions (floor(width / 2), floor(height / 2)).<br>
* Each plane is specified by a direct java.nio.ByteBuffer, a pixel-stride,
* and a row-stride. So, the sample at coordinate (x, y) can be retrieved
* from byteBuffer[x * pixel_stride + y * row_stride].
* <p>
* The pre-compression transformation is applied as follows:
* <ol>
* <li>The image is cropped to the rectangle from (cropLeft, cropTop) to
* (cropRight - 1, cropBottom - 1). So, a cropping-rectangle of (0, 0) -
* (width, height) is a no-op.</li>
* <li>The rotation is applied counter-clockwise relative to the coordinate
* space of the image, so a CCW rotation will appear CW when the image is
* rendered in scanline order. Only rotations which are multiples of
* 90-degrees are suppored, so the parameter 'rot90' specifies which
* multiple of 90 to rotate the image.</li>
* </ol>
*
* @param width the width of the image to compress
* @param height the height of the image to compress
* @param yBuf the buffer containing the Y component of the image
* @param yPStride the stride between adjacent pixels in the same row in
* yBuf
* @param yRStride the stride between adjacent rows in yBuf
* @param cbBuf the buffer containing the Cb component of the image
* @param cbPStride the stride between adjacent pixels in the same row in
* cbBuf
* @param cbRStride the stride between adjacent rows in cbBuf
* @param crBuf the buffer containing the Cr component of the image
* @param crPStride the stride between adjacent pixels in the same row in
* crBuf
* @param crRStride the stride between adjacent rows in crBuf
* @param outBuf a direct java.nio.ByteBuffer to hold the compressed jpeg.
* This must have enough capacity to store the result, or an
* error code will be returned.
* @param outBufCapacity the capacity of outBuf
* @param quality the jpeg-quality (1-100) to use
* @param cropLeft left-edge of the bounds of the image to crop to before
* rotation
* @param cropTop top-edge of the bounds of the image to crop to before
* rotation
* @param cropRight right-edge of the bounds of the image to crop to before
* rotation
* @param cropBottom bottom-edge of the bounds of the image to crop to
* before rotation
* @param rot90 the multiple of 90 to rotate the image CCW (after cropping)
*/
private static native int compressJpegFromYUV420pNative(
int width, int height,
ByteBuffer yBuf, int yPStride, int yRStride,
ByteBuffer cbBuf, int cbPStride, int cbRStride,
ByteBuffer crBuf, int crPStride, int crRStride,
ByteBuffer outBuf, int outBufCapacity,
int quality,
int cropLeft, int cropTop, int cropRight, int cropBottom,
int rot90);
public void process(List<CaptureBundle> captureBundle) throws RemoteException {
JpegParameters jpegParams = getJpegParameters(captureBundle);
try {
mJpegParameters.add(jpegParams);
mProcessor.process(captureBundle);
} catch (Exception e) {
mJpegParameters.remove(jpegParams);
throw e;
}
}
public void onOutputSurface(Surface surface, int format) throws RemoteException {
if (format != ImageFormat.JPEG) {
Log.e(TAG, "Unsupported output format: " + format);
return;
}
mOutputSurface = surface;
initializePipeline();
}
@Override
public void onResolutionUpdate(android.hardware.camera2.extension.Size size)
throws RemoteException {
mResolution = size;
initializePipeline();
}
public void onImageFormatUpdate(int format) throws RemoteException {
if (format != ImageFormat.YUV_420_888) {
Log.e(TAG, "Unsupported input format: " + format);
return;
}
mFormat = format;
initializePipeline();
}
private void initializePipeline() throws RemoteException {
if ((mFormat != -1) && (mOutputSurface != null) && (mResolution != null) &&
(mYuvReader == null)) {
// Jpeg/blobs are expected to be configured with (w*h)x1
mOutputWriter = ImageWriter.newInstance(mOutputSurface, 1 /*maxImages*/,
ImageFormat.JPEG, mResolution.width * mResolution.height, 1);
mYuvReader = ImageReader.newInstance(mResolution.width, mResolution.height, mFormat,
JPEG_QUEUE_SIZE);
mYuvReader.setOnImageAvailableListener(new YuvCallback(), mHandler);
mProcessor.onOutputSurface(mYuvReader.getSurface(), mFormat);
mProcessor.onResolutionUpdate(mResolution);
mProcessor.onImageFormatUpdate(mFormat);
}
}
@Override
public IBinder asBinder() {
throw new UnsupportedOperationException("Binder IPC not supported!");
}
private class YuvCallback implements ImageReader.OnImageAvailableListener {
@Override
public void onImageAvailable(ImageReader reader) {
Image yuvImage = null;
Image jpegImage = null;
try {
yuvImage = mYuvReader.acquireNextImage();
jpegImage = mOutputWriter.dequeueInputImage();
} catch (IllegalStateException e) {
if (yuvImage != null) {
yuvImage.close();
}
if (jpegImage != null) {
jpegImage.close();
}
Log.e(TAG, "Failed to acquire processed yuv image or jpeg image!");
return;
}
ByteBuffer jpegBuffer = jpegImage.getPlanes()[0].getBuffer();
jpegBuffer.clear();
// Jpeg/blobs are expected to be configured with (w*h)x1
int jpegCapacity = jpegImage.getWidth();
Plane lumaPlane = yuvImage.getPlanes()[0];
Plane crPlane = yuvImage.getPlanes()[1];
Plane cbPlane = yuvImage.getPlanes()[2];
Iterator<JpegParameters> jpegIter = mJpegParameters.iterator();
JpegParameters jpegParams = null;
while(jpegIter.hasNext()) {
JpegParameters currentParams = jpegIter.next();
if (currentParams.mTimeStamps.contains(yuvImage.getTimestamp())) {
jpegParams = currentParams;
jpegIter.remove();
break;
}
}
if (jpegParams == null) {
if (mJpegParameters.isEmpty()) {
Log.w(TAG, "Empty jpeg settings queue! Using default jpeg orientation"
+ " and quality!");
jpegParams = new JpegParameters();
jpegParams.mRotation = JPEG_DEFAULT_ROTATION;
jpegParams.mQuality = JPEG_DEFAULT_QUALITY;
} else {
Log.w(TAG, "No jpeg settings found with matching timestamp for current"
+ " processed input!");
Log.w(TAG, "Using values from the top of the queue!");
jpegParams = mJpegParameters.poll();
}
}
compressJpegFromYUV420pNative(
yuvImage.getWidth(), yuvImage.getHeight(),
lumaPlane.getBuffer(), lumaPlane.getPixelStride(), lumaPlane.getRowStride(),
crPlane.getBuffer(), crPlane.getPixelStride(), crPlane.getRowStride(),
cbPlane.getBuffer(), cbPlane.getPixelStride(), cbPlane.getRowStride(),
jpegBuffer, jpegCapacity, jpegParams.mQuality,
0, 0, yuvImage.getWidth(), yuvImage.getHeight(),
jpegParams.mRotation);
yuvImage.close();
try {
mOutputWriter.queueInputImage(jpegImage);
} catch (IllegalStateException e) {
Log.e(TAG, "Failed to queue encoded result!");
} finally {
jpegImage.close();
}
}
}
}

View File

@@ -91,6 +91,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
private ImageReader mStubCaptureImageReader = null;
private ImageWriter mRepeatingRequestImageWriter = null;
private CameraExtensionJpegProcessor mImageJpegProcessor = null;
private ICaptureProcessorImpl mImageProcessor = null;
private CameraExtensionForwardProcessor mPreviewImageProcessor = null;
private IRequestUpdateProcessorImpl mPreviewRequestUpdateProcessor = null;
@@ -413,6 +414,10 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
if (mImageProcessor != null) {
if (mClientCaptureSurface != null) {
SurfaceInfo surfaceInfo = querySurface(mClientCaptureSurface);
if (surfaceInfo.mFormat == ImageFormat.JPEG) {
mImageJpegProcessor = new CameraExtensionJpegProcessor(mImageProcessor);
mImageProcessor = mImageJpegProcessor;
}
mBurstCaptureImageReader = ImageReader.newInstance(surfaceInfo.mWidth,
surfaceInfo.mHeight, CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT,
mImageExtender.getMaxCaptureStage());
@@ -570,14 +575,16 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
return null;
}
// Set user supported jpeg quality and rotation parameters
// This will override the extension capture stage jpeg parameters with the user set
// jpeg quality and rotation. This will guarantee that client configured jpeg
// parameters always have highest priority.
Integer jpegRotation = clientRequest.get(CaptureRequest.JPEG_ORIENTATION);
if (jpegRotation != null) {
requestBuilder.set(CaptureRequest.JPEG_ORIENTATION, jpegRotation);
captureStage.parameters.set(CaptureRequest.JPEG_ORIENTATION, jpegRotation);
}
Byte jpegQuality = clientRequest.get(CaptureRequest.JPEG_QUALITY);
if (jpegQuality != null) {
requestBuilder.set(CaptureRequest.JPEG_QUALITY, jpegQuality);
captureStage.parameters.set(CaptureRequest.JPEG_QUALITY, jpegQuality);
}
requestBuilder.addTarget(target);
@@ -753,6 +760,11 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
mPreviewImageProcessor = null;
}
if (mImageJpegProcessor != null) {
mImageJpegProcessor.close();
mImageJpegProcessor = null;
}
mCaptureSession = null;
mImageProcessor = null;
mCameraRepeatingSurface = mClientRepeatingRequestSurface = null;
@@ -1014,7 +1026,10 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
mCaptureRequestMap.clear();
mCapturePendingMap.clear();
boolean processStatus = true;
List<CaptureBundle> captureList = initializeParcelable(mCaptureStageMap);
Byte jpegQuality = mClientRequest.get(CaptureRequest.JPEG_QUALITY);
Integer jpegOrientation = mClientRequest.get(CaptureRequest.JPEG_ORIENTATION);
List<CaptureBundle> captureList = initializeParcelable(mCaptureStageMap,
jpegOrientation, jpegQuality);
try {
mImageProcessor.process(captureList);
} catch (RemoteException e) {
@@ -1444,10 +1459,8 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
}
for (int i = idx; i >= 0; i--) {
if (previewMap.valueAt(i).first != null) {
Log.w(TAG, "Discard pending buffer with timestamp: " + previewMap.keyAt(i));
previewMap.valueAt(i).first.close();
} else {
Log.w(TAG, "Discard pending result with timestamp: " + previewMap.keyAt(i));
if (mClientNotificationsEnabled && ((i != idx) || notifyCurrentIndex)) {
Log.w(TAG, "Preview frame drop with timestamp: " + previewMap.keyAt(i));
final long ident = Binder.clearCallingIdentity();
@@ -1639,7 +1652,8 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
}
private static List<CaptureBundle> initializeParcelable(
HashMap<Integer, Pair<Image, TotalCaptureResult>> captureMap) {
HashMap<Integer, Pair<Image, TotalCaptureResult>> captureMap, Integer jpegOrientation,
Byte jpegQuality) {
ArrayList<CaptureBundle> ret = new ArrayList<>();
for (Integer stagetId : captureMap.keySet()) {
Pair<Image, TotalCaptureResult> entry = captureMap.get(stagetId);
@@ -1648,6 +1662,12 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
bundle.captureImage = initializeParcelImage(entry.first);
bundle.sequenceId = entry.second.getSequenceId();
bundle.captureResult = entry.second.getNativeMetadata();
if (jpegOrientation != null) {
bundle.captureResult.set(CaptureResult.JPEG_ORIENTATION, jpegOrientation);
}
if (jpegQuality != null) {
bundle.captureResult.set(CaptureResult.JPEG_QUALITY, jpegQuality);
}
ret.add(bundle);
}

View File

@@ -175,6 +175,7 @@ cc_library_shared {
"android_hardware_Camera.cpp",
"android_hardware_camera2_CameraMetadata.cpp",
"android_hardware_camera2_DngCreator.cpp",
"android_hardware_camera2_impl_CameraExtensionJpegProcessor.cpp",
"android_hardware_camera2_utils_SurfaceUtils.cpp",
"android_hardware_display_DisplayManagerGlobal.cpp",
"android_hardware_display_DisplayViewport.cpp",
@@ -231,6 +232,7 @@ cc_library_shared {
"audioclient-types-aidl-cpp",
"audioflinger-aidl-cpp",
"av-types-aidl-cpp",
"android.hardware.camera.device@3.2",
"libandroidicu",
"libbpf_android",
"libnetdbpf",
@@ -260,6 +262,7 @@ cc_library_shared {
"libdataloader",
"libvulkan",
"libETC1",
"libjpeg",
"libhardware",
"libhardware_legacy",
"libselinux",

View File

@@ -74,6 +74,7 @@ extern int register_android_opengl_jni_GLES32(JNIEnv* env);
extern int register_android_hardware_Camera(JNIEnv *env);
extern int register_android_hardware_camera2_CameraMetadata(JNIEnv *env);
extern int register_android_hardware_camera2_DngCreator(JNIEnv *env);
extern int register_android_hardware_camera2_impl_CameraExtensionJpegProcessor(JNIEnv* env);
extern int register_android_hardware_camera2_utils_SurfaceUtils(JNIEnv* env);
extern int register_android_hardware_display_DisplayManagerGlobal(JNIEnv* env);
extern int register_android_hardware_HardwareBuffer(JNIEnv *env);
@@ -1533,6 +1534,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_hardware_Camera),
REG_JNI(register_android_hardware_camera2_CameraMetadata),
REG_JNI(register_android_hardware_camera2_DngCreator),
REG_JNI(register_android_hardware_camera2_impl_CameraExtensionJpegProcessor),
REG_JNI(register_android_hardware_camera2_utils_SurfaceUtils),
REG_JNI(register_android_hardware_display_DisplayManagerGlobal),
REG_JNI(register_android_hardware_HardwareBuffer),

View File

@@ -0,0 +1,629 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <array>
#include <cstring>
#include <cstdio>
#include <inttypes.h>
#include <memory.h>
#include <vector>
#include <setjmp.h>
#include <android/hardware/camera/device/3.2/types.h>
#include "core_jni_helpers.h"
#include "jni.h"
#include <nativehelper/JNIHelp.h>
#define CAMERA_PROCESSOR_CLASS_NAME "android/hardware/camera2/impl/CameraExtensionJpegProcessor"
extern "C" {
#include "jpeglib.h"
}
using namespace std;
using namespace android;
using android::hardware::camera::device::V3_2::CameraBlob;
using android::hardware::camera::device::V3_2::CameraBlobId;
class Transform;
struct Plane;
inline int sgn(int val) { return (0 < val) - (val < 0); }
inline int min(int a, int b) { return a < b ? a : b; }
inline int max(int a, int b) { return a > b ? a : b; }
/**
* Represents a combined cropping and rotation transformation.
*
* The transformation maps the coordinates (mOrigX, mOrigY) and (mOneX, mOneY)
* in the input image to the origin and (mOutputWidth, mOutputHeight)
* respectively.
*/
class Transform {
public:
Transform(int origX, int origY, int oneX, int oneY);
static Transform forCropFollowedByRotation(int cropLeft, int cropTop,
int cropRight, int cropBottom, int rot90);
inline int getOutputWidth() const { return mOutputWidth; }
inline int getOutputHeight() const { return mOutputHeight; }
bool operator==(const Transform& other) const;
/**
* Transforms the input coordinates. Coordinates outside the cropped region
* are clamped to valid values.
*/
void map(int x, int y, int* outX, int* outY) const;
private:
int mOutputWidth;
int mOutputHeight;
// The coordinates of the point to map the origin to.
const int mOrigX, mOrigY;
// The coordinates of the point to map the point (getOutputWidth(),
// getOutputHeight()) to.
const int mOneX, mOneY;
// A matrix for the rotational component.
int mMat00, mMat01;
int mMat10, mMat11;
};
/**
* Represents a model for accessing pixel data for a single plane of an image.
* Note that the actual data is not owned by this class, and the underlying
* data does not need to be stored in separate planes.
*/
struct Plane {
// The dimensions of this plane of the image
int width;
int height;
// A pointer to raw pixel data
const unsigned char* data;
// The difference in address between consecutive pixels in the same row
int pixelStride;
// The difference in address between the start of consecutive rows
int rowStride;
};
/**
* Provides an interface for simultaneously reading a certain number of rows of
* an image plane as contiguous arrays, suitable for use with libjpeg.
*/
template <unsigned int ROWS>
class RowIterator {
public:
/**
* Creates a new RowIterator which will crop and rotate with the given
* transform.
*
* @param plane the plane to iterate over
* @param transform the transformation to map output values into the
* coordinate space of the plane
* @param rowLength the length of the rows returned via LoadAt(). If this is
* longer than the width of the output (after applying the transform), then
* the right-most value is repeated.
*/
inline RowIterator(Plane plane, Transform transform, int rowLength);
/**
* Returns an array of pointers into consecutive rows of contiguous image
* data starting at y. That is, samples within each row are contiguous.
* However, the individual arrays pointed-to may be separate.
* When the end of the image is reached, the last row of the image is
* repeated.
* The returned pointers are valid until the next call to loadAt().
*/
inline const std::array<unsigned char*, ROWS> loadAt(int baseY);
private:
Plane mPlane;
Transform mTransform;
// The length of a row, with padding to the next multiple of 64.
int mPaddedRowLength;
std::vector<unsigned char> mBuffer;
};
template <unsigned int ROWS>
RowIterator<ROWS>::RowIterator(Plane plane, Transform transform,
int rowLength)
: mPlane(plane), mTransform(transform) {
mPaddedRowLength = rowLength;
mBuffer = std::vector<unsigned char>(rowLength * ROWS);
}
template <unsigned int ROWS>
const std::array<unsigned char*, ROWS> RowIterator<ROWS>::loadAt(int baseY) {
std::array<unsigned char*, ROWS> bufPtrs;
for (unsigned int i = 0; i < ROWS; i++) {
bufPtrs[i] = &mBuffer[mPaddedRowLength * i];
}
if (mPlane.width == 0 || mPlane.height == 0) {
return bufPtrs;
}
for (unsigned int i = 0; i < ROWS; i++) {
int y = i + baseY;
y = min(y, mTransform.getOutputHeight() - 1);
int output_width = mPaddedRowLength;
output_width = min(output_width, mTransform.getOutputWidth());
output_width = min(output_width, mPlane.width);
// Each row in the output image will be copied into buf_ by gathering pixels
// along an axis-aligned line in the plane.
// The line is defined by (startX, startY) -> (endX, endY), computed via the
// current Transform.
int startX;
int startY;
mTransform.map(0, y, &startX, &startY);
int endX;
int endY;
mTransform.map(output_width - 1, y, &endX, &endY);
// Clamp (startX, startY) and (endX, endY) to the valid bounds of the plane.
startX = min(startX, mPlane.width - 1);
startY = min(startY, mPlane.height - 1);
endX = min(endX, mPlane.width - 1);
endY = min(endY, mPlane.height - 1);
startX = max(startX, 0);
startY = max(startY, 0);
endX = max(endX, 0);
endY = max(endY, 0);
// To reduce work inside the copy-loop, precompute the start, end, and
// stride relating the values to be gathered from mPlane into buf
// for this particular scan-line.
int dx = sgn(endX - startX);
int dy = sgn(endY - startY);
if (!(dx == 0 || dy == 0)) {
ALOGE("%s: Unexpected bounds: %dx%d %dx%d!", __FUNCTION__, startX, endX, startY, endY);
return bufPtrs;
}
// The index into mPlane.data of (startX, startY)
int plane_start = startX * mPlane.pixelStride + startY * mPlane.rowStride;
// The index into mPlane.data of (endX, endY)
int plane_end = endX * mPlane.pixelStride + endY * mPlane.rowStride;
// The stride, in terms of indices in plane_data, required to enumerate the
// samples between the start and end points.
int stride = dx * mPlane.pixelStride + dy * mPlane.rowStride;
// In the degenerate-case of a 1x1 plane, startX and endX are equal, so
// stride would be 0, resulting in an infinite-loop. To avoid this case,
// use a stride of at-least 1.
if (stride == 0) {
stride = 1;
}
int outX = 0;
for (int idx = plane_start; idx >= min(plane_start, plane_end) &&
idx <= max(plane_start, plane_end); idx += stride) {
bufPtrs[i][outX] = mPlane.data[idx];
outX++;
}
// Fill the remaining right-edge of the buffer by extending the last
// value.
unsigned char right_padding_value = bufPtrs[i][outX - 1];
for (; outX < mPaddedRowLength; outX++) {
bufPtrs[i][outX] = right_padding_value;
}
}
return bufPtrs;
}
template <typename T>
void safeDelete(T& t) {
delete t;
t = nullptr;
}
template <typename T>
void safeDeleteArray(T& t) {
delete[] t;
t = nullptr;
}
Transform::Transform(int origX, int origY, int oneX, int oneY)
: mOrigX(origX), mOrigY(origY), mOneX(oneX), mOneY(oneY) {
if (origX == oneX || origY == oneY) {
// Handle the degenerate case of cropping to a 0x0 rectangle.
mMat00 = 0;
mMat01 = 0;
mMat10 = 0;
mMat11 = 0;
return;
}
if (oneX > origX && oneY > origY) {
// 0-degree rotation
mMat00 = 1;
mMat01 = 0;
mMat10 = 0;
mMat11 = 1;
mOutputWidth = abs(oneX - origX);
mOutputHeight = abs(oneY - origY);
} else if (oneX < origX && oneY > origY) {
// 90-degree CCW rotation
mMat00 = 0;
mMat01 = -1;
mMat10 = 1;
mMat11 = 0;
mOutputWidth = abs(oneY - origY);
mOutputHeight = abs(oneX - origX);
} else if (oneX > origX && oneY < origY) {
// 270-degree CCW rotation
mMat00 = 0;
mMat01 = 1;
mMat10 = -1;
mMat11 = 0;
mOutputWidth = abs(oneY - origY);
mOutputHeight = abs(oneX - origX);;
} else if (oneX < origX && oneY < origY) {
// 180-degree CCW rotation
mMat00 = -1;
mMat01 = 0;
mMat10 = 0;
mMat11 = -1;
mOutputWidth = abs(oneX - origX);
mOutputHeight = abs(oneY - origY);
}
}
Transform Transform::forCropFollowedByRotation(int cropLeft, int cropTop, int cropRight,
int cropBottom, int rot90) {
// The input crop-region excludes cropRight and cropBottom, so transform the
// crop rect such that it defines the entire valid region of pixels
// inclusively.
cropRight -= 1;
cropBottom -= 1;
int cropXLow = min(cropLeft, cropRight);
int cropYLow = min(cropTop, cropBottom);
int cropXHigh = max(cropLeft, cropRight);
int cropYHigh = max(cropTop, cropBottom);
rot90 %= 4;
if (rot90 == 0) {
return Transform(cropXLow, cropYLow, cropXHigh + 1, cropYHigh + 1);
} else if (rot90 == 1) {
return Transform(cropXHigh, cropYLow, cropXLow - 1, cropYHigh + 1);
} else if (rot90 == 2) {
return Transform(cropXHigh, cropYHigh, cropXLow - 1, cropYLow - 1);
} else if (rot90 == 3) {
return Transform(cropXLow, cropYHigh, cropXHigh + 1, cropYLow - 1);
}
// Impossible case.
return Transform(cropXLow, cropYLow, cropXHigh + 1, cropYHigh + 1);
}
bool Transform::operator==(const Transform& other) const {
return other.mOrigX == mOrigX && //
other.mOrigY == mOrigY && //
other.mOneX == mOneX && //
other.mOneY == mOneY;
}
/**
* Transforms the input coordinates. Coordinates outside the cropped region
* are clamped to valid values.
*/
void Transform::map(int x, int y, int* outX, int* outY) const {
x = max(x, 0);
y = max(y, 0);
x = min(x, getOutputWidth() - 1);
y = min(y, getOutputHeight() - 1);
*outX = x * mMat00 + y * mMat01 + mOrigX;
*outY = x * mMat10 + y * mMat11 + mOrigY;
}
int compress(int img_width, int img_height, RowIterator<16>& y_row_generator,
RowIterator<8>& cb_row_generator, RowIterator<8>& cr_row_generator,
unsigned char* out_buf, size_t out_buf_capacity, std::function<void(size_t)> flush,
int quality) {
// libjpeg requires the use of setjmp/longjmp to recover from errors. Since
// this doesn't play well with RAII, we must use pointers and manually call
// delete. See POSIX documentation for longjmp() for details on why the
// volatile keyword is necessary.
volatile jpeg_compress_struct cinfov;
jpeg_compress_struct& cinfo =
*const_cast<struct jpeg_compress_struct*>(&cinfov);
JSAMPROW* volatile yArr = nullptr;
JSAMPROW* volatile cbArr = nullptr;
JSAMPROW* volatile crArr = nullptr;
JSAMPARRAY imgArr[3];
// Error handling
struct my_error_mgr {
struct jpeg_error_mgr pub;
jmp_buf setjmp_buffer;
} err;
cinfo.err = jpeg_std_error(&err.pub);
// Default error_exit will call exit(), so override
// to return control via setjmp/longjmp.
err.pub.error_exit = [](j_common_ptr cinfo) {
my_error_mgr* myerr = reinterpret_cast<my_error_mgr*>(cinfo->err);
(*cinfo->err->output_message)(cinfo);
// Return control to the setjmp point (see call to setjmp()).
longjmp(myerr->setjmp_buffer, 1);
};
cinfo.err = (struct jpeg_error_mgr*)&err;
// Set the setjmp point to return to in case of error.
if (setjmp(err.setjmp_buffer)) {
// If libjpeg hits an error, control will jump to this point (see call to
// longjmp()).
jpeg_destroy_compress(&cinfo);
safeDeleteArray(yArr);
safeDeleteArray(cbArr);
safeDeleteArray(crArr);
return -1;
}
// Create jpeg compression context
jpeg_create_compress(&cinfo);
// Stores data needed by our c-style callbacks into libjpeg
struct ClientData {
unsigned char* out_buf;
size_t out_buf_capacity;
std::function<void(size_t)> flush;
int totalOutputBytes;
} clientData{out_buf, out_buf_capacity, flush, 0};
cinfo.client_data = &clientData;
// Initialize destination manager
jpeg_destination_mgr dest;
dest.init_destination = [](j_compress_ptr cinfo) {
ClientData& cdata = *reinterpret_cast<ClientData*>(cinfo->client_data);
cinfo->dest->next_output_byte = cdata.out_buf;
cinfo->dest->free_in_buffer = cdata.out_buf_capacity;
};
dest.empty_output_buffer = [](j_compress_ptr cinfo) -> boolean {
ClientData& cdata = *reinterpret_cast<ClientData*>(cinfo->client_data);
size_t numBytesInBuffer = cdata.out_buf_capacity;
cdata.flush(numBytesInBuffer);
cdata.totalOutputBytes += numBytesInBuffer;
// Reset the buffer
cinfo->dest->next_output_byte = cdata.out_buf;
cinfo->dest->free_in_buffer = cdata.out_buf_capacity;
return true;
};
dest.term_destination = [](j_compress_ptr cinfo __unused) {
// do nothing to terminate the output buffer
};
cinfo.dest = &dest;
// Set jpeg parameters
cinfo.image_width = img_width;
cinfo.image_height = img_height;
cinfo.input_components = 3;
// Set defaults based on the above values
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, quality, true);
cinfo.dct_method = JDCT_IFAST;
cinfo.raw_data_in = true;
jpeg_set_colorspace(&cinfo, JCS_YCbCr);
cinfo.comp_info[0].h_samp_factor = 2;
cinfo.comp_info[0].v_samp_factor = 2;
cinfo.comp_info[1].h_samp_factor = 1;
cinfo.comp_info[1].v_samp_factor = 1;
cinfo.comp_info[2].h_samp_factor = 1;
cinfo.comp_info[2].v_samp_factor = 1;
jpeg_start_compress(&cinfo, true);
yArr = new JSAMPROW[cinfo.comp_info[0].v_samp_factor * DCTSIZE];
cbArr = new JSAMPROW[cinfo.comp_info[1].v_samp_factor * DCTSIZE];
crArr = new JSAMPROW[cinfo.comp_info[2].v_samp_factor * DCTSIZE];
imgArr[0] = const_cast<JSAMPARRAY>(yArr);
imgArr[1] = const_cast<JSAMPARRAY>(cbArr);
imgArr[2] = const_cast<JSAMPARRAY>(crArr);
for (int y = 0; y < img_height; y += DCTSIZE * 2) {
std::array<unsigned char*, 16> yData = y_row_generator.loadAt(y);
std::array<unsigned char*, 8> cbData = cb_row_generator.loadAt(y / 2);
std::array<unsigned char*, 8> crData = cr_row_generator.loadAt(y / 2);
for (int row = 0; row < DCTSIZE * 2; row++) {
yArr[row] = yData[row];
}
for (int row = 0; row < DCTSIZE; row++) {
cbArr[row] = cbData[row];
crArr[row] = crData[row];
}
jpeg_write_raw_data(&cinfo, imgArr, DCTSIZE * 2);
}
jpeg_finish_compress(&cinfo);
int numBytesInBuffer = cinfo.dest->next_output_byte - out_buf;
flush(numBytesInBuffer);
clientData.totalOutputBytes += numBytesInBuffer;
safeDeleteArray(yArr);
safeDeleteArray(cbArr);
safeDeleteArray(crArr);
jpeg_destroy_compress(&cinfo);
return clientData.totalOutputBytes;
}
int compress(
/** Input image dimensions */
int width, int height,
/** Y Plane */
unsigned char* yBuf, int yPStride, int yRStride,
/** Cb Plane */
unsigned char* cbBuf, int cbPStride, int cbRStride,
/** Cr Plane */
unsigned char* crBuf, int crPStride, int crRStride,
/** Output */
unsigned char* outBuf, size_t outBufCapacity,
/** Jpeg compression parameters */
int quality,
/** Crop */
int cropLeft, int cropTop, int cropRight, int cropBottom,
/** Rotation (multiple of 90). For example, rot90 = 1 implies a 90 degree
* rotation. */
int rot90) {
int finalWidth;
int finalHeight;
finalWidth = cropRight - cropLeft;
finalHeight = cropBottom - cropTop;
rot90 %= 4;
// for 90 and 270-degree rotations, flip the final width and height
if (rot90 == 1) {
finalWidth = cropBottom - cropTop;
finalHeight = cropRight - cropLeft;
} else if (rot90 == 3) {
finalWidth = cropBottom - cropTop;
finalHeight = cropRight - cropLeft;
}
const Plane yP = {width, height, yBuf, yPStride, yRStride};
const Plane cbP = {width / 2, height / 2, cbBuf, cbPStride, cbRStride};
const Plane crP = {width / 2, height / 2, crBuf, crPStride, crRStride};
auto flush = [](size_t numBytes __unused) {
// do nothing
};
// Round up to the nearest multiple of 64.
int y_row_length = (finalWidth + 16 + 63) & ~63;
int cb_row_length = (finalWidth / 2 + 16 + 63) & ~63;
int cr_row_length = (finalWidth / 2 + 16 + 63) & ~63;
Transform yTrans = Transform::forCropFollowedByRotation(
cropLeft, cropTop, cropRight, cropBottom, rot90);
Transform chromaTrans = Transform::forCropFollowedByRotation(
cropLeft / 2, cropTop / 2, cropRight / 2, cropBottom / 2, rot90);
RowIterator<16> yIter(yP, yTrans, y_row_length);
RowIterator<8> cbIter(cbP, chromaTrans, cb_row_length);
RowIterator<8> crIter(crP, chromaTrans, cr_row_length);
return compress(finalWidth, finalHeight, yIter, cbIter, crIter, outBuf, outBufCapacity, flush,
quality);
}
extern "C" {
static jint CameraExtensionJpegProcessor_compressJpegFromYUV420p(
JNIEnv* env, jclass clazz __unused,
/** Input image dimensions */
jint width, jint height,
/** Y Plane */
jobject yBuf, jint yPStride, jint yRStride,
/** Cb Plane */
jobject cbBuf, jint cbPStride, jint cbRStride,
/** Cr Plane */
jobject crBuf, jint crPStride, jint crRStride,
/** Output */
jobject outBuf, jint outBufCapacity,
/** Jpeg compression parameters */
jint quality,
/** Crop */
jint cropLeft, jint cropTop, jint cropRight, jint cropBottom,
/** Rotation (multiple of 90). For example, rot90 = 1 implies a 90 degree
* rotation. */
jint rot90) {
jbyte* y = (jbyte*)env->GetDirectBufferAddress(yBuf);
jbyte* cb = (jbyte*)env->GetDirectBufferAddress(cbBuf);
jbyte* cr = (jbyte*)env->GetDirectBufferAddress(crBuf);
jbyte* out = (jbyte*)env->GetDirectBufferAddress(outBuf);
size_t actualJpegSize = compress(width, height,
(unsigned char*)y, yPStride, yRStride,
(unsigned char*)cb, cbPStride, cbRStride,
(unsigned char*)cr, crPStride, crRStride,
(unsigned char*)out, (size_t)outBufCapacity,
quality, cropLeft, cropTop, cropRight, cropBottom, rot90);
size_t finalJpegSize = actualJpegSize + sizeof(CameraBlob);
if (finalJpegSize > outBufCapacity) {
ALOGE("%s: Final jpeg buffer %zu not large enough for the jpeg blob header with "\
"capacity %d", __FUNCTION__, finalJpegSize, outBufCapacity);
return actualJpegSize;
}
int8_t* header = static_cast<int8_t *> (out) +
(outBufCapacity - sizeof(CameraBlob));
CameraBlob *blob = reinterpret_cast<CameraBlob *> (header);
blob->blobId = CameraBlobId::JPEG;
blob->blobSize = actualJpegSize;
return actualJpegSize;
}
} // extern "C"
static const JNINativeMethod gCameraExtensionJpegProcessorMethods[] = {
{"compressJpegFromYUV420pNative",
"(IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IIIIIII)I",
(void*)CameraExtensionJpegProcessor_compressJpegFromYUV420p}};
// Get all the required offsets in java class and register native functions
int register_android_hardware_camera2_impl_CameraExtensionJpegProcessor(JNIEnv* env) {
// Register native functions
return RegisterMethodsOrDie(env, CAMERA_PROCESSOR_CLASS_NAME,
gCameraExtensionJpegProcessorMethods, NELEM(gCameraExtensionJpegProcessorMethods));
}

View File

@@ -131,7 +131,59 @@ public class ImageWriter implements AutoCloseable {
*/
public static @NonNull ImageWriter newInstance(@NonNull Surface surface,
@IntRange(from = 1) int maxImages) {
return new ImageWriter(surface, maxImages, ImageFormat.UNKNOWN);
return new ImageWriter(surface, maxImages, ImageFormat.UNKNOWN, -1 /*width*/,
-1 /*height*/);
}
/**
* <p>
* Create a new ImageWriter with given number of max Images, format and producer dimension.
* </p>
* <p>
* The {@code maxImages} parameter determines the maximum number of
* {@link Image} objects that can be be dequeued from the
* {@code ImageWriter} simultaneously. Requesting more buffers will use up
* more memory, so it is important to use only the minimum number necessary.
* </p>
* <p>
* The format specifies the image format of this ImageWriter. The format
* from the {@code surface} will be overridden with this format. For example,
* if the surface is obtained from a {@link android.graphics.SurfaceTexture}, the default
* format may be {@link PixelFormat#RGBA_8888}. If the application creates an ImageWriter
* with this surface and {@link ImageFormat#PRIVATE}, this ImageWriter will be able to operate
* with {@link ImageFormat#PRIVATE} Images.
* </p>
* <p>
* Note that the consumer end-point may or may not be able to support Images with different
* format, for such case, the application should only use this method if the consumer is able
* to consume such images.
* </p>
* <p> The input Image size can also be set by the client. </p>
*
* @param surface The destination Surface this writer produces Image data
* into.
* @param maxImages The maximum number of Images the user will want to
* access simultaneously for producing Image data. This should be
* as small as possible to limit memory use. Once maxImages
* Images are dequeued by the user, one of them has to be queued
* back before a new Image can be dequeued for access via
* {@link #dequeueInputImage()}.
* @param format The format of this ImageWriter. It can be any valid format specified by
* {@link ImageFormat} or {@link PixelFormat}.
*
* @param width Input size width.
* @param height Input size height.
*
* @return a new ImageWriter instance.
*
* @hide
*/
public static @NonNull ImageWriter newInstance(@NonNull Surface surface,
@IntRange(from = 1) int maxImages, @Format int format, int width, int height) {
if (!ImageFormat.isPublicFormat(format) && !PixelFormat.isPublicFormat(format)) {
throw new IllegalArgumentException("Invalid format is specified: " + format);
}
return new ImageWriter(surface, maxImages, format, width, height);
}
/**
@@ -180,13 +232,13 @@ public class ImageWriter implements AutoCloseable {
if (!ImageFormat.isPublicFormat(format) && !PixelFormat.isPublicFormat(format)) {
throw new IllegalArgumentException("Invalid format is specified: " + format);
}
return new ImageWriter(surface, maxImages, format);
return new ImageWriter(surface, maxImages, format, -1 /*width*/, -1 /*height*/);
}
/**
* @hide
*/
protected ImageWriter(Surface surface, int maxImages, int format) {
protected ImageWriter(Surface surface, int maxImages, int format, int width, int height) {
if (surface == null || maxImages < 1) {
throw new IllegalArgumentException("Illegal input argument: surface " + surface
+ ", maxImages: " + maxImages);
@@ -196,7 +248,8 @@ public class ImageWriter implements AutoCloseable {
// Note that the underlying BufferQueue is working in synchronous mode
// to avoid dropping any buffers.
mNativeContext = nativeInit(new WeakReference<>(this), surface, maxImages, format);
mNativeContext = nativeInit(new WeakReference<>(this), surface, maxImages, format, width,
height);
// nativeInit internally overrides UNKNOWN format. So does surface format query after
// nativeInit and before getEstimatedNativeAllocBytes().
@@ -919,7 +972,7 @@ public class ImageWriter implements AutoCloseable {
// Native implemented ImageWriter methods.
private synchronized native long nativeInit(Object weakSelf, Surface surface, int maxImgs,
int format);
int format, int width, int height);
private synchronized native void nativeClose(long nativeCtx);

View File

@@ -364,7 +364,7 @@ static void ImageWriter_classInit(JNIEnv* env, jclass clazz) {
}
static jlong ImageWriter_init(JNIEnv* env, jobject thiz, jobject weakThiz, jobject jsurface,
jint maxImages, jint userFormat) {
jint maxImages, jint userFormat, jint userWidth, jint userHeight) {
status_t res;
ALOGV("%s: maxImages:%d", __FUNCTION__, maxImages);
@@ -405,20 +405,38 @@ static jlong ImageWriter_init(JNIEnv* env, jobject thiz, jobject weakThiz, jobje
// Get the dimension and format of the producer.
sp<ANativeWindow> anw = producer;
int32_t width, height, surfaceFormat;
if ((res = anw->query(anw.get(), NATIVE_WINDOW_WIDTH, &width)) != OK) {
ALOGE("%s: Query Surface width failed: %s (%d)", __FUNCTION__, strerror(-res), res);
jniThrowRuntimeException(env, "Failed to query Surface width");
return 0;
if (userWidth < 0) {
if ((res = anw->query(anw.get(), NATIVE_WINDOW_WIDTH, &width)) != OK) {
ALOGE("%s: Query Surface width failed: %s (%d)", __FUNCTION__, strerror(-res), res);
jniThrowRuntimeException(env, "Failed to query Surface width");
return 0;
}
} else {
width = userWidth;
}
ctx->setBufferWidth(width);
if ((res = anw->query(anw.get(), NATIVE_WINDOW_HEIGHT, &height)) != OK) {
ALOGE("%s: Query Surface height failed: %s (%d)", __FUNCTION__, strerror(-res), res);
jniThrowRuntimeException(env, "Failed to query Surface height");
return 0;
if (userHeight < 0) {
if ((res = anw->query(anw.get(), NATIVE_WINDOW_HEIGHT, &height)) != OK) {
ALOGE("%s: Query Surface height failed: %s (%d)", __FUNCTION__, strerror(-res), res);
jniThrowRuntimeException(env, "Failed to query Surface height");
return 0;
}
} else {
height = userHeight;
}
ctx->setBufferHeight(height);
if ((userWidth > 0) && (userHeight > 0)) {
res = native_window_set_buffers_user_dimensions(anw.get(), userWidth, userHeight);
if (res != OK) {
ALOGE("%s: Set buffer dimensions failed: %s (%d)", __FUNCTION__, strerror(-res), res);
jniThrowRuntimeException(env, "Set buffer dimensions failed");
return 0;
}
}
// Query surface format if no valid user format is specified, otherwise, override surface format
// with user format.
if (userFormat == IMAGE_FORMAT_UNKNOWN) {
@@ -1045,7 +1063,7 @@ static jobjectArray Image_createSurfacePlanes(JNIEnv* env, jobject thiz,
static JNINativeMethod gImageWriterMethods[] = {
{"nativeClassInit", "()V", (void*)ImageWriter_classInit },
{"nativeInit", "(Ljava/lang/Object;Landroid/view/Surface;II)J",
{"nativeInit", "(Ljava/lang/Object;Landroid/view/Surface;IIII)J",
(void*)ImageWriter_init },
{"nativeClose", "(J)V", (void*)ImageWriter_close },
{"nativeAttachAndQueueImage", "(JJIJIIIIII)I", (void*)ImageWriter_attachAndQueueImage },