camera2: Add HAL1 compatibility shim skeleton.
This adds basic support for running the Camera2 API on a device running a camera HAL version lower than CAMERA_MODULE_API_VERSION_2_0. This CL includes support for: - N-way preview output streams - N-way jpeg output streams - CameraDevice emulation at the binder interface - Basic camera metadata querying in the CameraManager Bug: 15117269 Bug: 15116722 Change-Id: I8322955034c91f34bb348d4b28c2b774dbef38f6
This commit is contained in:
@@ -169,6 +169,10 @@ public class Camera {
|
||||
private boolean mFaceDetectionRunning = false;
|
||||
private Object mAutoFocusCallbackLock = new Object();
|
||||
|
||||
private static final int NO_ERROR = 0;
|
||||
private static final int EACCESS = -13;
|
||||
private static final int ENODEV = -19;
|
||||
|
||||
/**
|
||||
* Broadcast Action: A new picture is taken by the camera, and the entry of
|
||||
* the picture has been added to the media store.
|
||||
@@ -328,6 +332,24 @@ public class Camera {
|
||||
}
|
||||
|
||||
Camera(int cameraId) {
|
||||
int err = cameraInit(cameraId);
|
||||
if (checkInitErrors(err)) {
|
||||
switch(err) {
|
||||
case EACCESS:
|
||||
throw new RuntimeException("Fail to connect to camera service");
|
||||
case ENODEV:
|
||||
throw new RuntimeException("Camera initialization failed");
|
||||
default:
|
||||
// Should never hit this.
|
||||
throw new RuntimeException("Unknown camera error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public int cameraInit(int cameraId) {
|
||||
mShutterCallback = null;
|
||||
mRawImageCallback = null;
|
||||
mJpegCallback = null;
|
||||
@@ -347,7 +369,21 @@ public class Camera {
|
||||
|
||||
String packageName = ActivityThread.currentPackageName();
|
||||
|
||||
native_setup(new WeakReference<Camera>(this), cameraId, packageName);
|
||||
return native_setup(new WeakReference<Camera>(this), cameraId, packageName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public static boolean checkInitErrors(int err) {
|
||||
return err != NO_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public static Camera openUninitialized() {
|
||||
return new Camera();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -360,7 +396,7 @@ public class Camera {
|
||||
release();
|
||||
}
|
||||
|
||||
private native final void native_setup(Object camera_this, int cameraId,
|
||||
private native final int native_setup(Object camera_this, int cameraId,
|
||||
String packageName);
|
||||
|
||||
private native final void native_release();
|
||||
@@ -458,13 +494,16 @@ public class Camera {
|
||||
*/
|
||||
public final void setPreviewDisplay(SurfaceHolder holder) throws IOException {
|
||||
if (holder != null) {
|
||||
setPreviewDisplay(holder.getSurface());
|
||||
setPreviewSurface(holder.getSurface());
|
||||
} else {
|
||||
setPreviewDisplay((Surface)null);
|
||||
setPreviewSurface((Surface)null);
|
||||
}
|
||||
}
|
||||
|
||||
private native final void setPreviewDisplay(Surface surface) throws IOException;
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public native final void setPreviewSurface(Surface surface) throws IOException;
|
||||
|
||||
/**
|
||||
* Sets the {@link SurfaceTexture} to be used for live preview.
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.content.Context;
|
||||
import android.hardware.ICameraService;
|
||||
import android.hardware.ICameraServiceListener;
|
||||
import android.hardware.camera2.impl.CameraMetadataNative;
|
||||
import android.hardware.camera2.legacy.CameraDeviceUserShim;
|
||||
import android.hardware.camera2.utils.CameraBinderDecorator;
|
||||
import android.hardware.camera2.utils.CameraRuntimeException;
|
||||
import android.hardware.camera2.utils.BinderHolder;
|
||||
@@ -194,7 +195,6 @@ public final class CameraManager {
|
||||
// impossible
|
||||
return null;
|
||||
}
|
||||
|
||||
return new CameraCharacteristics(info);
|
||||
}
|
||||
|
||||
@@ -236,10 +236,23 @@ public final class CameraManager {
|
||||
handler);
|
||||
|
||||
BinderHolder holder = new BinderHolder();
|
||||
mCameraService.connectDevice(device.getCallbacks(),
|
||||
Integer.parseInt(cameraId),
|
||||
mContext.getPackageName(), USE_CALLING_UID, holder);
|
||||
cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder());
|
||||
|
||||
ICameraDeviceCallbacks callbacks = device.getCallbacks();
|
||||
int id = Integer.parseInt(cameraId);
|
||||
try {
|
||||
mCameraService.connectDevice(callbacks, id, mContext.getPackageName(),
|
||||
USE_CALLING_UID, holder);
|
||||
cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder());
|
||||
} catch (CameraRuntimeException e) {
|
||||
if (e.getReason() == CameraAccessException.CAMERA_DEPRECATED_HAL) {
|
||||
// Use legacy camera implementation for HAL1 devices
|
||||
Log.i(TAG, "Using legacy camera HAL.");
|
||||
cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id);
|
||||
} else {
|
||||
// Rethrow otherwise
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: factor out listener to be non-nested, then move setter to constructor
|
||||
// For now, calling setRemoteDevice will fire initial
|
||||
|
||||
@@ -22,6 +22,8 @@ import android.os.Parcelable;
|
||||
import android.util.Rational;
|
||||
import android.view.Surface;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -198,6 +200,20 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
|
||||
dest.writeParcelableArray(mSurfaceSet.toArray(new Surface[mSurfaceSet.size()]), flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public boolean containsTarget(Surface surface) {
|
||||
return mSurfaceSet.contains(surface);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public Collection<Surface> getTargets() {
|
||||
return Collections.unmodifiableCollection(mSurfaceSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for capture requests.
|
||||
*
|
||||
|
||||
@@ -26,7 +26,8 @@ import android.hardware.camera2.utils.LongParcelable;
|
||||
interface ICameraDeviceUser
|
||||
{
|
||||
/**
|
||||
* Keep up-to-date with frameworks/av/include/camera/camera2/ICameraDeviceUser.h
|
||||
* Keep up-to-date with frameworks/av/include/camera/camera2/ICameraDeviceUser.h and
|
||||
* frameworks/base/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
|
||||
*/
|
||||
void disconnect();
|
||||
|
||||
@@ -41,6 +42,27 @@ interface ICameraDeviceUser
|
||||
|
||||
int cancelRequest(int requestId, out LongParcelable lastFrameNumber);
|
||||
|
||||
/**
|
||||
* Begin the device configuration.
|
||||
*
|
||||
* <p>
|
||||
* beginConfigure must be called before any call to deleteStream, createStream,
|
||||
* or endConfigure. It is not valid to call this when the device is not idle.
|
||||
* <p>
|
||||
*/
|
||||
int beginConfigure();
|
||||
|
||||
/**
|
||||
* End the device configuration.
|
||||
*
|
||||
* <p>
|
||||
* endConfigure must be called after stream configuration is complete (i.e. after
|
||||
* a call to beginConfigure and subsequent createStream/deleteStream calls). This
|
||||
* must be called before any requests can be submitted.
|
||||
* <p>
|
||||
*/
|
||||
int endConfigure();
|
||||
|
||||
int deleteStream(int streamId);
|
||||
|
||||
// non-negative value is the stream ID. negative value is status_t
|
||||
|
||||
@@ -216,7 +216,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
|
||||
try {
|
||||
waitUntilIdle();
|
||||
|
||||
// TODO: mRemoteDevice.beginConfigure
|
||||
mRemoteDevice.beginConfigure();
|
||||
// Delete all streams first (to free up HW resources)
|
||||
for (Integer streamId : deleteList) {
|
||||
mRemoteDevice.deleteStream(streamId);
|
||||
@@ -231,7 +231,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
|
||||
mConfiguredOutputs.put(streamId, s);
|
||||
}
|
||||
|
||||
// TODO: mRemoteDevice.endConfigure
|
||||
mRemoteDevice.endConfigure();
|
||||
} catch (CameraRuntimeException e) {
|
||||
if (e.getReason() == CAMERA_IN_USE) {
|
||||
throw new IllegalStateException("The camera is currently busy." +
|
||||
|
||||
@@ -63,7 +63,7 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
|
||||
private static final String TAG = "CameraMetadataJV";
|
||||
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
|
||||
// this should be in sync with HAL_PIXEL_FORMAT_BLOB defined in graphics.h
|
||||
private static final int NATIVE_JPEG_FORMAT = 0x21;
|
||||
public static final int NATIVE_JPEG_FORMAT = 0x21;
|
||||
|
||||
public CameraMetadataNative() {
|
||||
super();
|
||||
|
||||
@@ -45,6 +45,15 @@ public class CaptureResultExtras implements Parcelable {
|
||||
readFromParcel(in);
|
||||
}
|
||||
|
||||
public CaptureResultExtras(int requestId, int subsequenceId, int afTriggerId,
|
||||
int precaptureTriggerId, long frameNumber) {
|
||||
this.requestId = requestId;
|
||||
this.subsequenceId = subsequenceId;
|
||||
this.afTriggerId = afTriggerId;
|
||||
this.precaptureTriggerId = precaptureTriggerId;
|
||||
this.frameNumber = frameNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
|
||||
82
core/java/android/hardware/camera2/legacy/BurstHolder.java
Normal file
82
core/java/android/hardware/camera2/legacy/BurstHolder.java
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.legacy;
|
||||
|
||||
import android.hardware.camera2.CaptureRequest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Immutable container for a burst of capture results.
|
||||
*/
|
||||
public class BurstHolder {
|
||||
|
||||
private final ArrayList<CaptureRequest> mRequests;
|
||||
private final boolean mRepeating;
|
||||
private final int mRequestId;
|
||||
|
||||
/**
|
||||
* Immutable container for a burst of capture results.
|
||||
*
|
||||
* @param requestId id of the burst request.
|
||||
* @param repeating true if this burst is repeating.
|
||||
* @param requests a {@link java.util.List} of {@link CaptureRequest}s in this burst.
|
||||
*/
|
||||
public BurstHolder(int requestId, boolean repeating, List<CaptureRequest> requests) {
|
||||
mRequests = new ArrayList<CaptureRequest>(requests);
|
||||
mRepeating = repeating;
|
||||
mRequestId = requestId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of this request.
|
||||
*/
|
||||
public int getRequestId() {
|
||||
return mRequestId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this repeating.
|
||||
*/
|
||||
public boolean isRepeating() {
|
||||
return mRepeating;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of requests in this burst sequence.
|
||||
*/
|
||||
public int getNumberOfRequests() {
|
||||
return mRequests.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a list of {@link RequestHolder} objects encapsulating the requests in this burst.
|
||||
*
|
||||
* @param frameNumber the starting framenumber for this burst.
|
||||
* @return the list of {@link RequestHolder} objects.
|
||||
*/
|
||||
public List<RequestHolder> produceRequestHolders(long frameNumber) {
|
||||
ArrayList<RequestHolder> holders = new ArrayList<RequestHolder>();
|
||||
int i = 0;
|
||||
for (CaptureRequest r : mRequests) {
|
||||
holders.add(new RequestHolder(mRequestId, i, r, mRepeating, frameNumber + i));
|
||||
++i;
|
||||
}
|
||||
return holders;
|
||||
}
|
||||
}
|
||||
259
core/java/android/hardware/camera2/legacy/CameraDeviceState.java
Normal file
259
core/java/android/hardware/camera2/legacy/CameraDeviceState.java
Normal file
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.legacy;
|
||||
|
||||
import android.hardware.camera2.impl.CameraMetadataNative;
|
||||
import android.hardware.camera2.utils.CameraBinderDecorator;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Emulates a the state of a single Camera2 device.
|
||||
*
|
||||
* <p>
|
||||
* This class acts as the state machine for a camera device. Valid state transitions are given
|
||||
* in the table below:
|
||||
* </p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@code UNCONFIGURED -> CONFIGURING}</li>
|
||||
* <li>{@code CONFIGURING -> IDLE}</li>
|
||||
* <li>{@code IDLE -> CONFIGURING}</li>
|
||||
* <li>{@code IDLE -> CAPTURING}</li>
|
||||
* <li>{@code CAPTURING -> IDLE}</li>
|
||||
* <li>{@code ANY -> ERROR}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class CameraDeviceState {
|
||||
private static final String TAG = "CameraDeviceState";
|
||||
private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
|
||||
|
||||
private static final int STATE_ERROR = 0;
|
||||
private static final int STATE_UNCONFIGURED = 1;
|
||||
private static final int STATE_CONFIGURING = 2;
|
||||
private static final int STATE_IDLE = 3;
|
||||
private static final int STATE_CAPTURING = 4;
|
||||
|
||||
private int mCurrentState = STATE_UNCONFIGURED;
|
||||
private int mCurrentError = CameraBinderDecorator.NO_ERROR;
|
||||
|
||||
private RequestHolder mCurrentRequest = null;
|
||||
|
||||
private Handler mCurrentHandler = null;
|
||||
private CameraDeviceStateListener mCurrentListener = null;
|
||||
|
||||
|
||||
/**
|
||||
* CameraDeviceStateListener callbacks to be called after state transitions.
|
||||
*/
|
||||
public interface CameraDeviceStateListener {
|
||||
void onError(int errorCode, RequestHolder holder);
|
||||
void onConfiguring();
|
||||
void onIdle();
|
||||
void onCaptureStarted(RequestHolder holder);
|
||||
void onCaptureResult(CameraMetadataNative result, RequestHolder holder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition to the {@code ERROR} state.
|
||||
*
|
||||
* <p>
|
||||
* The device cannot exit the {@code ERROR} state. If the device was not already in the
|
||||
* {@code ERROR} state, {@link CameraDeviceStateListener#onError(int, RequestHolder)} will be
|
||||
* called.
|
||||
* </p>
|
||||
*
|
||||
* @param error the error to set. Should be one of the error codes defined in
|
||||
* {@link android.hardware.camera2.utils.CameraBinderDecorator}.
|
||||
*/
|
||||
public synchronized void setError(int error) {
|
||||
mCurrentError = error;
|
||||
doStateTransition(STATE_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition to the {@code CONFIGURING} state, or {@code ERROR} if in an invalid state.
|
||||
*
|
||||
* <p>
|
||||
* If the device was not already in the {@code CONFIGURING} state,
|
||||
* {@link CameraDeviceStateListener#onConfiguring()} will be called.
|
||||
* </p>
|
||||
*
|
||||
* @returns {@link CameraBinderDecorator#NO_ERROR}, or an error if one has occurred.
|
||||
*/
|
||||
public synchronized int setConfiguring() {
|
||||
doStateTransition(STATE_CONFIGURING);
|
||||
return mCurrentError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition to the {@code IDLE} state, or {@code ERROR} if in an invalid state.
|
||||
*
|
||||
* <p>
|
||||
* If the device was not already in the {@code IDLE} state,
|
||||
* {@link CameraDeviceStateListener#onIdle()} will be called.
|
||||
* </p>
|
||||
*
|
||||
* @returns {@link CameraBinderDecorator#NO_ERROR}, or an error if one has occurred.
|
||||
*/
|
||||
public synchronized int setIdle() {
|
||||
doStateTransition(STATE_IDLE);
|
||||
return mCurrentError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition to the {@code CAPTURING} state, or {@code ERROR} if in an invalid state.
|
||||
*
|
||||
* <p>
|
||||
* If the device was not already in the {@code CAPTURING} state,
|
||||
* {@link CameraDeviceStateListener#onCaptureStarted(RequestHolder)} will be called.
|
||||
* </p>
|
||||
*
|
||||
* @param request A {@link RequestHolder} containing the request for the current capture.
|
||||
* @returns {@link CameraBinderDecorator#NO_ERROR}, or an error if one has occurred.
|
||||
*/
|
||||
public synchronized int setCaptureStart(final RequestHolder request) {
|
||||
mCurrentRequest = request;
|
||||
doStateTransition(STATE_CAPTURING);
|
||||
return mCurrentError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the result for a capture.
|
||||
*
|
||||
* <p>
|
||||
* If the device was in the {@code CAPTURING} state,
|
||||
* {@link CameraDeviceStateListener#onCaptureResult(CameraMetadataNative, RequestHolder)} will
|
||||
* be called with the given result, otherwise this will result in the device transitioning to
|
||||
* the {@code ERROR} state,
|
||||
* </p>
|
||||
*
|
||||
* @param request the {@link RequestHolder} request that created this result.
|
||||
* @param result the {@link CameraMetadataNative} result to set.
|
||||
* @returns {@link CameraBinderDecorator#NO_ERROR}, or an error if one has occurred.
|
||||
*/
|
||||
public synchronized int setCaptureResult(final RequestHolder request,
|
||||
final CameraMetadataNative result) {
|
||||
if (mCurrentState != STATE_CAPTURING) {
|
||||
Log.e(TAG, "Cannot receive result while in state: " + mCurrentState);
|
||||
mCurrentError = CameraBinderDecorator.INVALID_OPERATION;
|
||||
doStateTransition(STATE_ERROR);
|
||||
return mCurrentError;
|
||||
}
|
||||
|
||||
if (mCurrentHandler != null && mCurrentListener != null) {
|
||||
mCurrentHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mCurrentListener.onCaptureResult(result, request);
|
||||
}
|
||||
});
|
||||
}
|
||||
return mCurrentError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the listener for state transition callbacks.
|
||||
*
|
||||
* @param handler handler on which to call the callbacks.
|
||||
* @param listener the {@link CameraDeviceStateListener} callbacks to call.
|
||||
*/
|
||||
public synchronized void setCameraDeviceCallbacks(Handler handler,
|
||||
CameraDeviceStateListener listener) {
|
||||
mCurrentHandler = handler;
|
||||
mCurrentListener = listener;
|
||||
}
|
||||
|
||||
private void doStateTransition(int newState) {
|
||||
if (DEBUG) {
|
||||
if (newState != mCurrentState) {
|
||||
Log.d(TAG, "Transitioning to state " + newState);
|
||||
}
|
||||
}
|
||||
switch(newState) {
|
||||
case STATE_ERROR:
|
||||
if (mCurrentState != STATE_ERROR && mCurrentHandler != null &&
|
||||
mCurrentListener != null) {
|
||||
mCurrentHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mCurrentListener.onError(mCurrentError, mCurrentRequest);
|
||||
}
|
||||
});
|
||||
}
|
||||
mCurrentState = STATE_ERROR;
|
||||
break;
|
||||
case STATE_CONFIGURING:
|
||||
if (mCurrentState != STATE_UNCONFIGURED && mCurrentState != STATE_IDLE) {
|
||||
Log.e(TAG, "Cannot call configure while in state: " + mCurrentState);
|
||||
mCurrentError = CameraBinderDecorator.INVALID_OPERATION;
|
||||
doStateTransition(STATE_ERROR);
|
||||
break;
|
||||
}
|
||||
if (mCurrentState != STATE_CONFIGURING && mCurrentHandler != null &&
|
||||
mCurrentListener != null) {
|
||||
mCurrentHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mCurrentListener.onConfiguring();
|
||||
}
|
||||
});
|
||||
}
|
||||
mCurrentState = STATE_CONFIGURING;
|
||||
break;
|
||||
case STATE_IDLE:
|
||||
if (mCurrentState != STATE_CONFIGURING && mCurrentState != STATE_CAPTURING) {
|
||||
Log.e(TAG, "Cannot call idle while in state: " + mCurrentState);
|
||||
mCurrentError = CameraBinderDecorator.INVALID_OPERATION;
|
||||
doStateTransition(STATE_ERROR);
|
||||
break;
|
||||
}
|
||||
if (mCurrentState != STATE_IDLE && mCurrentHandler != null &&
|
||||
mCurrentListener != null) {
|
||||
mCurrentHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mCurrentListener.onIdle();
|
||||
}
|
||||
});
|
||||
}
|
||||
mCurrentState = STATE_IDLE;
|
||||
break;
|
||||
case STATE_CAPTURING:
|
||||
if (mCurrentState != STATE_IDLE && mCurrentState != STATE_CAPTURING) {
|
||||
Log.e(TAG, "Cannot call capture while in state: " + mCurrentState);
|
||||
mCurrentError = CameraBinderDecorator.INVALID_OPERATION;
|
||||
doStateTransition(STATE_ERROR);
|
||||
break;
|
||||
}
|
||||
if (mCurrentHandler != null && mCurrentListener != null) {
|
||||
mCurrentHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mCurrentListener.onCaptureStarted(mCurrentRequest);
|
||||
}
|
||||
});
|
||||
}
|
||||
mCurrentState = STATE_CAPTURING;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Transition to unknown state: " + newState);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.legacy;
|
||||
|
||||
import android.hardware.Camera;
|
||||
import android.hardware.camera2.CameraAccessException;
|
||||
import android.hardware.camera2.CaptureRequest;
|
||||
import android.hardware.camera2.ICameraDeviceCallbacks;
|
||||
import android.hardware.camera2.ICameraDeviceUser;
|
||||
import android.hardware.camera2.utils.LongParcelable;
|
||||
import android.hardware.camera2.impl.CameraMetadataNative;
|
||||
import android.hardware.camera2.utils.CameraBinderDecorator;
|
||||
import android.hardware.camera2.utils.CameraRuntimeException;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.Surface;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Compatibility implementation of the Camera2 API binder interface.
|
||||
*
|
||||
* <p>
|
||||
* This is intended to be called from the same process as client
|
||||
* {@link android.hardware.camera2.CameraDevice}, and wraps a
|
||||
* {@link android.hardware.camera2.legacy.LegacyCameraDevice} that emulates Camera2 service using
|
||||
* the Camera1 API.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Keep up to date with ICameraDeviceUser.aidl.
|
||||
* </p>
|
||||
*/
|
||||
public class CameraDeviceUserShim implements ICameraDeviceUser {
|
||||
private static final String TAG = "CameraDeviceUserShim";
|
||||
|
||||
private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
|
||||
|
||||
private final LegacyCameraDevice mLegacyDevice;
|
||||
|
||||
private final Object mConfigureLock = new Object();
|
||||
private int mSurfaceIdCounter;
|
||||
private boolean mConfiguring;
|
||||
private final SparseArray<Surface> mSurfaces;
|
||||
|
||||
protected CameraDeviceUserShim(int cameraId, LegacyCameraDevice legacyCamera) {
|
||||
mLegacyDevice = legacyCamera;
|
||||
mConfiguring = false;
|
||||
mSurfaces = new SparseArray<Surface>();
|
||||
|
||||
mSurfaceIdCounter = 0;
|
||||
}
|
||||
|
||||
public static CameraDeviceUserShim connectBinderShim(ICameraDeviceCallbacks callbacks,
|
||||
int cameraId) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Opening shim Camera device");
|
||||
}
|
||||
// TODO: Move open/init into LegacyCameraDevice thread when API is switched to async.
|
||||
Camera legacyCamera = Camera.openUninitialized();
|
||||
int initErrors = legacyCamera.cameraInit(cameraId);
|
||||
// Check errors old HAL initialization
|
||||
if (Camera.checkInitErrors(initErrors)) {
|
||||
// TODO: Map over old camera error codes. This likely involves improving the error
|
||||
// reporting in the HAL1 connect path.
|
||||
throw new CameraRuntimeException(CameraAccessException.CAMERA_DISCONNECTED);
|
||||
}
|
||||
LegacyCameraDevice device = new LegacyCameraDevice(cameraId, legacyCamera, callbacks);
|
||||
return new CameraDeviceUserShim(cameraId, device);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "disconnect called.");
|
||||
}
|
||||
mLegacyDevice.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int submitRequest(CaptureRequest request, boolean streaming,
|
||||
/*out*/LongParcelable lastFrameNumber) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "submitRequest called.");
|
||||
}
|
||||
synchronized(mConfigureLock) {
|
||||
if (mConfiguring) {
|
||||
Log.e(TAG, "Cannot submit request, configuration change in progress.");
|
||||
return CameraBinderDecorator.INVALID_OPERATION;
|
||||
}
|
||||
}
|
||||
return mLegacyDevice.submitRequest(request, streaming, lastFrameNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int submitRequestList(List<CaptureRequest> request, boolean streaming,
|
||||
/*out*/LongParcelable lastFrameNumber) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "submitRequestList called.");
|
||||
}
|
||||
synchronized(mConfigureLock) {
|
||||
if (mConfiguring) {
|
||||
Log.e(TAG, "Cannot submit request, configuration change in progress.");
|
||||
return CameraBinderDecorator.INVALID_OPERATION;
|
||||
}
|
||||
}
|
||||
return mLegacyDevice.submitRequestList(request, streaming, lastFrameNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int cancelRequest(int requestId, /*out*/LongParcelable lastFrameNumber) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "cancelRequest called.");
|
||||
}
|
||||
synchronized(mConfigureLock) {
|
||||
if (mConfiguring) {
|
||||
Log.e(TAG, "Cannot cancel request, configuration change in progress.");
|
||||
return CameraBinderDecorator.INVALID_OPERATION;
|
||||
}
|
||||
}
|
||||
long lastFrame = mLegacyDevice.cancelRequest(requestId);
|
||||
lastFrameNumber.setNumber(lastFrame);
|
||||
return CameraBinderDecorator.NO_ERROR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int beginConfigure() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "beginConfigure called.");
|
||||
}
|
||||
synchronized(mConfigureLock) {
|
||||
if (mConfiguring) {
|
||||
Log.e(TAG, "Cannot begin configure, configuration change already in progress.");
|
||||
return CameraBinderDecorator.INVALID_OPERATION;
|
||||
}
|
||||
mConfiguring = true;
|
||||
}
|
||||
return CameraBinderDecorator.NO_ERROR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int endConfigure() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "endConfigure called.");
|
||||
}
|
||||
ArrayList<Surface> surfaces = null;
|
||||
synchronized(mConfigureLock) {
|
||||
if (!mConfiguring) {
|
||||
Log.e(TAG, "Cannot end configure, no configuration change in progress.");
|
||||
return CameraBinderDecorator.INVALID_OPERATION;
|
||||
}
|
||||
int numSurfaces = mSurfaces.size();
|
||||
if (numSurfaces > 0) {
|
||||
surfaces = new ArrayList<Surface>();
|
||||
for (int i = 0; i < numSurfaces; ++i) {
|
||||
surfaces.add(mSurfaces.valueAt(i));
|
||||
}
|
||||
}
|
||||
mConfiguring = false;
|
||||
}
|
||||
return mLegacyDevice.configureOutputs(surfaces);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteStream(int streamId) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "deleteStream called.");
|
||||
}
|
||||
synchronized(mConfigureLock) {
|
||||
if (!mConfiguring) {
|
||||
Log.e(TAG, "Cannot delete stream, beginConfigure hasn't been called yet.");
|
||||
return CameraBinderDecorator.INVALID_OPERATION;
|
||||
}
|
||||
int index = mSurfaces.indexOfKey(streamId);
|
||||
if (index < 0) {
|
||||
Log.e(TAG, "Cannot delete stream, stream id " + streamId + " doesn't exist.");
|
||||
return CameraBinderDecorator.BAD_VALUE;
|
||||
}
|
||||
mSurfaces.removeAt(index);
|
||||
}
|
||||
return CameraBinderDecorator.NO_ERROR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int createStream(int width, int height, int format, Surface surface) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "createStream called.");
|
||||
}
|
||||
synchronized(mConfigureLock) {
|
||||
if (!mConfiguring) {
|
||||
Log.e(TAG, "Cannot create stream, beginConfigure hasn't been called yet.");
|
||||
return CameraBinderDecorator.INVALID_OPERATION;
|
||||
}
|
||||
int id = ++mSurfaceIdCounter;
|
||||
mSurfaces.put(id, surface);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int createDefaultRequest(int templateId, /*out*/CameraMetadataNative request) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "createDefaultRequest called.");
|
||||
}
|
||||
// TODO: implement createDefaultRequest.
|
||||
Log.e(TAG, "createDefaultRequest unimplemented.");
|
||||
return CameraBinderDecorator.NO_ERROR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCameraInfo(/*out*/CameraMetadataNative info) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "getCameraInfo called.");
|
||||
}
|
||||
// TODO: implement getCameraInfo.
|
||||
Log.e(TAG, "getCameraInfo unimplemented.");
|
||||
return CameraBinderDecorator.NO_ERROR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int waitUntilIdle() throws RemoteException {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "waitUntilIdle called.");
|
||||
}
|
||||
synchronized(mConfigureLock) {
|
||||
if (mConfiguring) {
|
||||
Log.e(TAG, "Cannot wait until idle, configuration change in progress.");
|
||||
return CameraBinderDecorator.INVALID_OPERATION;
|
||||
}
|
||||
}
|
||||
mLegacyDevice.waitUntilIdle();
|
||||
return CameraBinderDecorator.NO_ERROR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int flush(/*out*/LongParcelable lastFrameNumber) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "flush called.");
|
||||
}
|
||||
synchronized(mConfigureLock) {
|
||||
if (mConfiguring) {
|
||||
Log.e(TAG, "Cannot flush, configuration change in progress.");
|
||||
return CameraBinderDecorator.INVALID_OPERATION;
|
||||
}
|
||||
}
|
||||
// TODO: implement flush.
|
||||
return CameraBinderDecorator.NO_ERROR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder asBinder() {
|
||||
// This is solely intended to be used for in-process binding.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
234
core/java/android/hardware/camera2/legacy/GLThreadManager.java
Normal file
234
core/java/android/hardware/camera2/legacy/GLThreadManager.java
Normal file
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.legacy;
|
||||
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.os.ConditionVariable;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.view.Surface;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* GLThreadManager handles the thread used for rendering into the configured output surfaces.
|
||||
*/
|
||||
public class GLThreadManager {
|
||||
private final String TAG;
|
||||
private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
|
||||
|
||||
private static final int MSG_NEW_CONFIGURATION = 1;
|
||||
private static final int MSG_NEW_FRAME = 2;
|
||||
private static final int MSG_CLEANUP = 3;
|
||||
private static final int MSG_DROP_FRAMES = 4;
|
||||
private static final int MSG_ALLOW_FRAMES = 5;
|
||||
|
||||
private final SurfaceTextureRenderer mTextureRenderer;
|
||||
|
||||
private final RequestHandlerThread mGLHandlerThread;
|
||||
|
||||
private final RequestThreadManager.FpsCounter mPrevCounter =
|
||||
new RequestThreadManager.FpsCounter("GL Preview Producer");
|
||||
|
||||
/**
|
||||
* Container object for Configure messages.
|
||||
*/
|
||||
private static class ConfigureHolder {
|
||||
public final ConditionVariable condition;
|
||||
public final Collection<Surface> surfaces;
|
||||
|
||||
public ConfigureHolder(ConditionVariable condition, Collection<Surface> surfaces) {
|
||||
this.condition = condition;
|
||||
this.surfaces = surfaces;
|
||||
}
|
||||
}
|
||||
|
||||
private final Handler.Callback mGLHandlerCb = new Handler.Callback() {
|
||||
private boolean mCleanup = false;
|
||||
private boolean mConfigured = false;
|
||||
private boolean mDroppingFrames = false;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
if (mCleanup) {
|
||||
return true;
|
||||
}
|
||||
switch (msg.what) {
|
||||
case MSG_NEW_CONFIGURATION:
|
||||
ConfigureHolder configure = (ConfigureHolder) msg.obj;
|
||||
mTextureRenderer.cleanupEGLContext();
|
||||
mTextureRenderer.configureSurfaces(configure.surfaces);
|
||||
configure.condition.open();
|
||||
mConfigured = true;
|
||||
break;
|
||||
case MSG_NEW_FRAME:
|
||||
if (mDroppingFrames) {
|
||||
Log.w(TAG, "Ignoring frame.");
|
||||
break;
|
||||
}
|
||||
if (DEBUG) {
|
||||
mPrevCounter.countAndLog();
|
||||
}
|
||||
if (!mConfigured) {
|
||||
Log.e(TAG, "Dropping frame, EGL context not configured!");
|
||||
}
|
||||
mTextureRenderer.drawIntoSurfaces((Collection<Surface>) msg.obj);
|
||||
break;
|
||||
case MSG_CLEANUP:
|
||||
mTextureRenderer.cleanupEGLContext();
|
||||
mCleanup = true;
|
||||
mConfigured = false;
|
||||
break;
|
||||
case MSG_DROP_FRAMES:
|
||||
mDroppingFrames = true;
|
||||
break;
|
||||
case MSG_ALLOW_FRAMES:
|
||||
mDroppingFrames = false;
|
||||
default:
|
||||
Log.e(TAG, "Unhandled message " + msg.what + " on GLThread.");
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new GL thread and renderer.
|
||||
*
|
||||
* @param cameraId the camera id for this thread.
|
||||
*/
|
||||
public GLThreadManager(int cameraId) {
|
||||
mTextureRenderer = new SurfaceTextureRenderer();
|
||||
TAG = String.format("CameraDeviceGLThread-%d", cameraId);
|
||||
mGLHandlerThread = new RequestHandlerThread(TAG, mGLHandlerCb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the thread.
|
||||
*
|
||||
* <p>
|
||||
* This must be called before queueing new frames.
|
||||
* </p>
|
||||
*/
|
||||
public void start() {
|
||||
mGLHandlerThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until the thread has started.
|
||||
*/
|
||||
public void waitUntilStarted() {
|
||||
mGLHandlerThread.waitUntilStarted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Quit the thread.
|
||||
*
|
||||
* <p>
|
||||
* No further methods can be called after this.
|
||||
* </p>
|
||||
*/
|
||||
public void quit() {
|
||||
Handler handler = mGLHandlerThread.getHandler();
|
||||
handler.sendMessageAtFrontOfQueue(handler.obtainMessage(MSG_CLEANUP));
|
||||
mGLHandlerThread.quitSafely();
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a new call to draw into a given set of surfaces.
|
||||
*
|
||||
* <p>
|
||||
* The set of surfaces passed here must be a subset of the set of surfaces passed in
|
||||
* the last call to {@link #setConfigurationAndWait}.
|
||||
* </p>
|
||||
*
|
||||
* @param targets a collection of {@link android.view.Surface}s to draw into.
|
||||
*/
|
||||
public void queueNewFrame(Collection<Surface> targets) {
|
||||
Handler handler = mGLHandlerThread.getHandler();
|
||||
|
||||
/**
|
||||
* Avoid queuing more than one new frame. If we are not consuming faster than frames
|
||||
* are produced, drop frames rather than allowing the queue to back up.
|
||||
*/
|
||||
if (!handler.hasMessages(MSG_NEW_FRAME)) {
|
||||
handler.sendMessage(handler.obtainMessage(MSG_NEW_FRAME, targets));
|
||||
} else {
|
||||
Log.e(TAG, "GLThread dropping frame. Not consuming frames quickly enough!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the GL renderer for the given set of output surfaces, and block until
|
||||
* this configuration has been applied.
|
||||
*
|
||||
* @param surfaces a collection of {@link android.view.Surface}s to configure.
|
||||
*/
|
||||
public void setConfigurationAndWait(Collection<Surface> surfaces) {
|
||||
Handler handler = mGLHandlerThread.getHandler();
|
||||
|
||||
final ConditionVariable condition = new ConditionVariable(/*closed*/false);
|
||||
ConfigureHolder configure = new ConfigureHolder(condition, surfaces);
|
||||
|
||||
Message m = handler.obtainMessage(MSG_NEW_CONFIGURATION, /*arg1*/0, /*arg2*/0, configure);
|
||||
handler.sendMessage(m);
|
||||
|
||||
// Block until configuration applied.
|
||||
condition.block();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying surface to produce frames from.
|
||||
*
|
||||
* <p>
|
||||
* This returns the surface that is drawn into the set of surfaces passed in for each frame.
|
||||
* This method should only be called after a call to
|
||||
* {@link #setConfigurationAndWait(java.util.Collection)}. Calling this before the first call
|
||||
* to {@link #setConfigurationAndWait(java.util.Collection)}, after {@link #quit()}, or
|
||||
* concurrently to one of these calls may result in an invalid
|
||||
* {@link android.graphics.SurfaceTexture} being returned.
|
||||
* </p>
|
||||
*
|
||||
* @return an {@link android.graphics.SurfaceTexture} to draw to.
|
||||
*/
|
||||
public SurfaceTexture getCurrentSurfaceTexture() {
|
||||
return mTextureRenderer.getSurfaceTexture();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore any subsequent calls to {@link #queueNewFrame(java.util.Collection)}.
|
||||
*/
|
||||
public void ignoreNewFrames() {
|
||||
mGLHandlerThread.getHandler().sendEmptyMessage(MSG_DROP_FRAMES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until no messages are queued.
|
||||
*/
|
||||
public void waitUntilIdle() {
|
||||
mGLHandlerThread.waitUntilIdle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-enable drawing new frames after a call to {@link #ignoreNewFrames()}.
|
||||
*/
|
||||
public void allowNewFrames() {
|
||||
mGLHandlerThread.getHandler().sendEmptyMessage(MSG_ALLOW_FRAMES);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.legacy;
|
||||
|
||||
import android.graphics.ImageFormat;
|
||||
import android.hardware.Camera;
|
||||
import android.hardware.camera2.CaptureRequest;
|
||||
import android.hardware.camera2.impl.CaptureResultExtras;
|
||||
import android.hardware.camera2.ICameraDeviceCallbacks;
|
||||
import android.hardware.camera2.utils.LongParcelable;
|
||||
import android.hardware.camera2.impl.CameraMetadataNative;
|
||||
import android.hardware.camera2.utils.CameraBinderDecorator;
|
||||
import android.hardware.camera2.utils.CameraRuntimeException;
|
||||
import android.os.ConditionVariable;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.view.Surface;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* This class emulates the functionality of a Camera2 device using a the old Camera class.
|
||||
*
|
||||
* <p>
|
||||
* There are two main components that are used to implement this:
|
||||
* - A state machine containing valid Camera2 device states ({@link CameraDeviceState}).
|
||||
* - A message-queue based pipeline that manages an old Camera class, and executes capture and
|
||||
* configuration requests.
|
||||
* </p>
|
||||
*/
|
||||
public class LegacyCameraDevice implements AutoCloseable {
|
||||
public static final String DEBUG_PROP = "HAL1ShimLogging";
|
||||
|
||||
private final String TAG;
|
||||
|
||||
private final int mCameraId;
|
||||
private final ICameraDeviceCallbacks mDeviceCallbacks;
|
||||
private final CameraDeviceState mDeviceState = new CameraDeviceState();
|
||||
|
||||
private final ConditionVariable mIdle = new ConditionVariable(/*open*/true);
|
||||
private final AtomicInteger mRequestIdCounter = new AtomicInteger(0);
|
||||
|
||||
private final HandlerThread mCallbackHandlerThread = new HandlerThread("ResultThread");
|
||||
private final Handler mCallbackHandler;
|
||||
private static final int ILLEGAL_VALUE = -1;
|
||||
|
||||
private CaptureResultExtras getExtrasFromRequest(RequestHolder holder) {
|
||||
if (holder == null) {
|
||||
return new CaptureResultExtras(ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE,
|
||||
ILLEGAL_VALUE, ILLEGAL_VALUE);
|
||||
}
|
||||
return new CaptureResultExtras(holder.getRequestId(), holder.getSubsequeceId(),
|
||||
/*afTriggerId*/0, /*precaptureTriggerId*/0, holder.getFrameNumber());
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for the camera device state machine. Calls the appropriate
|
||||
* {@link ICameraDeviceCallbacks} for each state transition.
|
||||
*/
|
||||
private final CameraDeviceState.CameraDeviceStateListener mStateListener =
|
||||
new CameraDeviceState.CameraDeviceStateListener() {
|
||||
@Override
|
||||
public void onError(final int errorCode, RequestHolder holder) {
|
||||
mIdle.open();
|
||||
final CaptureResultExtras extras = getExtrasFromRequest(holder);
|
||||
try {
|
||||
mDeviceCallbacks.onCameraError(errorCode, extras);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Received remote exception during onCameraError callback: ", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfiguring() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIdle() {
|
||||
mIdle.open();
|
||||
|
||||
try {
|
||||
mDeviceCallbacks.onCameraIdle();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Received remote exception during onCameraIdle callback: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCaptureStarted(RequestHolder holder) {
|
||||
final CaptureResultExtras extras = getExtrasFromRequest(holder);
|
||||
|
||||
try {
|
||||
// TODO: Don't fake timestamp
|
||||
mDeviceCallbacks.onCaptureStarted(extras, System.nanoTime());
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Received remote exception during onCameraError callback: ", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCaptureResult(CameraMetadataNative result, RequestHolder holder) {
|
||||
final CaptureResultExtras extras = getExtrasFromRequest(holder);
|
||||
|
||||
try {
|
||||
// TODO: Don't fake metadata
|
||||
mDeviceCallbacks.onResultReceived(result, extras);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Received remote exception during onCameraError callback: ", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final RequestThreadManager mRequestThreadManager;
|
||||
|
||||
/**
|
||||
* Check if a given surface uses {@link ImageFormat#YUV_420_888} format.
|
||||
*
|
||||
* @param s the surface to check.
|
||||
* @return {@code true} if the surfaces uses {@link ImageFormat#YUV_420_888}.
|
||||
*/
|
||||
static boolean needsConversion(Surface s) {
|
||||
return LegacyCameraDevice.nativeDetectSurfaceType(s) == ImageFormat.YUV_420_888;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new emulated camera device from a given Camera 1 API camera.
|
||||
*
|
||||
* <p>
|
||||
* The {@link Camera} provided to this constructor must already have been successfully opened,
|
||||
* and ownership of the provided camera is passed to this object. No further calls to the
|
||||
* camera methods should be made following this constructor.
|
||||
* </p>
|
||||
*
|
||||
* @param cameraId the id of the camera.
|
||||
* @param camera an open {@link Camera} device.
|
||||
* @param callbacks {@link ICameraDeviceCallbacks} callbacks to call for Camera2 API operations.
|
||||
*/
|
||||
public LegacyCameraDevice(int cameraId, Camera camera, ICameraDeviceCallbacks callbacks) {
|
||||
mCameraId = cameraId;
|
||||
mDeviceCallbacks = callbacks;
|
||||
TAG = String.format("CameraDevice-%d-LE", mCameraId);
|
||||
|
||||
mCallbackHandlerThread.start();
|
||||
mCallbackHandler = new Handler(mCallbackHandlerThread.getLooper());
|
||||
mDeviceState.setCameraDeviceCallbacks(mCallbackHandler, mStateListener);
|
||||
mRequestThreadManager =
|
||||
new RequestThreadManager(cameraId, camera, mDeviceState);
|
||||
mRequestThreadManager.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the device with a set of output surfaces.
|
||||
*
|
||||
* @param outputs a list of surfaces to set.
|
||||
* @return an error code for this binder operation, or {@link CameraBinderDecorator.NO_ERROR}
|
||||
* on success.
|
||||
*/
|
||||
public int configureOutputs(List<Surface> outputs) {
|
||||
int error = mDeviceState.setConfiguring();
|
||||
if (error == CameraBinderDecorator.NO_ERROR) {
|
||||
mRequestThreadManager.configure(outputs);
|
||||
error = mDeviceState.setIdle();
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit a burst of capture requests.
|
||||
*
|
||||
* @param requestList a list of capture requests to execute.
|
||||
* @param repeating {@code true} if this burst is repeating.
|
||||
* @param frameNumber an output argument that contains either the frame number of the last frame
|
||||
* that will be returned for this request, or the frame number of the last
|
||||
* frame that will be returned for the current repeating request if this
|
||||
* burst is set to be repeating.
|
||||
* @return the request id.
|
||||
*/
|
||||
public int submitRequestList(List<CaptureRequest> requestList, boolean repeating,
|
||||
/*out*/LongParcelable frameNumber) {
|
||||
// TODO: validate request here
|
||||
mIdle.close();
|
||||
return mRequestThreadManager.submitCaptureRequests(requestList, repeating,
|
||||
frameNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit a single capture request.
|
||||
*
|
||||
* @param request the capture request to execute.
|
||||
* @param repeating {@code true} if this request is repeating.
|
||||
* @param frameNumber an output argument that contains either the frame number of the last frame
|
||||
* that will be returned for this request, or the frame number of the last
|
||||
* frame that will be returned for the current repeating request if this
|
||||
* request is set to be repeating.
|
||||
* @return the request id.
|
||||
*/
|
||||
public int submitRequest(CaptureRequest request, boolean repeating,
|
||||
/*out*/LongParcelable frameNumber) {
|
||||
ArrayList<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
|
||||
requestList.add(request);
|
||||
return submitRequestList(requestList, repeating, frameNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the repeating request with the given request id.
|
||||
*
|
||||
* @param requestId the request id of the request to cancel.
|
||||
* @return the last frame number to be returned from the HAL for the given repeating request, or
|
||||
* {@code INVALID_FRAME} if none exists.
|
||||
*/
|
||||
public long cancelRequest(int requestId) {
|
||||
return mRequestThreadManager.cancelRepeating(requestId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Block until the {@link ICameraDeviceCallbacks#onCameraIdle()} callback is received.
|
||||
*/
|
||||
public void waitUntilIdle() {
|
||||
mIdle.block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
mRequestThreadManager.quit();
|
||||
mCallbackHandlerThread.quitSafely();
|
||||
// TODO: throw IllegalStateException in every method after close has been called
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
close();
|
||||
} catch (CameraRuntimeException e) {
|
||||
Log.e(TAG, "Got error while trying to finalize, ignoring: " + e.getMessage());
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
protected static native int nativeDetectSurfaceType(Surface surface);
|
||||
|
||||
protected static native void nativeDetectSurfaceDimens(Surface surface, int[] dimens);
|
||||
|
||||
protected static native void nativeConfigureSurface(Surface surface, int width, int height,
|
||||
int pixelFormat);
|
||||
|
||||
protected static native void nativeProduceFrame(Surface surface, byte[] pixelBuffer, int width,
|
||||
int height, int pixelFormat);
|
||||
|
||||
protected static native void nativeSetSurfaceFormat(Surface surface, int pixelFormat);
|
||||
|
||||
protected static native void nativeSetSurfaceDimens(Surface surface, int width, int height);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.legacy;
|
||||
|
||||
import android.os.ConditionVariable;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import android.os.MessageQueue;
|
||||
|
||||
public class RequestHandlerThread extends HandlerThread {
|
||||
private final ConditionVariable mStarted = new ConditionVariable(false);
|
||||
private final ConditionVariable mIdle = new ConditionVariable(true);
|
||||
private Handler.Callback mCallback;
|
||||
private volatile Handler mHandler;
|
||||
|
||||
public RequestHandlerThread(String name, Handler.Callback callback) {
|
||||
super(name, Thread.MAX_PRIORITY);
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLooperPrepared() {
|
||||
mHandler = new Handler(getLooper(), mCallback);
|
||||
mStarted.open();
|
||||
}
|
||||
|
||||
// Blocks until thread has started
|
||||
public void waitUntilStarted() {
|
||||
mStarted.block();
|
||||
}
|
||||
|
||||
// May return null if the handler is not set up yet.
|
||||
public Handler getHandler() {
|
||||
return mHandler;
|
||||
}
|
||||
|
||||
// Blocks until thread has started
|
||||
public Handler waitAndGetHandler() {
|
||||
waitUntilStarted();
|
||||
return getHandler();
|
||||
}
|
||||
|
||||
// Atomic multi-type message existence check
|
||||
public boolean hasAnyMessages(int[] what) {
|
||||
synchronized (mHandler.getLooper().getQueue()) {
|
||||
for (int i : what) {
|
||||
if (mHandler.hasMessages(i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Atomic multi-type message remove
|
||||
public void removeMessages(int[] what) {
|
||||
synchronized (mHandler.getLooper().getQueue()) {
|
||||
for (int i : what) {
|
||||
mHandler.removeMessages(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final MessageQueue.IdleHandler mIdleHandler = new MessageQueue.IdleHandler() {
|
||||
@Override
|
||||
public boolean queueIdle() {
|
||||
mIdle.open();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Blocks until thread is idling
|
||||
public void waitUntilIdle() {
|
||||
Looper looper = waitAndGetHandler().getLooper();
|
||||
if (looper.isIdling()) {
|
||||
return;
|
||||
}
|
||||
mIdle.close();
|
||||
looper.getQueue().addIdleHandler(mIdleHandler);
|
||||
if (looper.isIdling()) {
|
||||
return;
|
||||
}
|
||||
mIdle.block();
|
||||
}
|
||||
|
||||
}
|
||||
159
core/java/android/hardware/camera2/legacy/RequestHolder.java
Normal file
159
core/java/android/hardware/camera2/legacy/RequestHolder.java
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.legacy;
|
||||
|
||||
import android.hardware.camera2.CaptureRequest;
|
||||
import android.hardware.camera2.impl.CameraMetadataNative;
|
||||
import android.view.Surface;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Immutable container for a single capture request and associated information.
|
||||
*/
|
||||
public class RequestHolder {
|
||||
|
||||
private final boolean mRepeating;
|
||||
private final CaptureRequest mRequest;
|
||||
private final int mRequestId;
|
||||
private final int mSubsequeceId;
|
||||
private final long mFrameNumber;
|
||||
|
||||
RequestHolder(int requestId, int subsequenceId, CaptureRequest request, boolean repeating,
|
||||
long frameNumber) {
|
||||
mRepeating = repeating;
|
||||
mRequest = request;
|
||||
mRequestId = requestId;
|
||||
mSubsequeceId = subsequenceId;
|
||||
mFrameNumber = frameNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the request id for the contained {@link CaptureRequest}.
|
||||
*/
|
||||
public int getRequestId() {
|
||||
return mRequestId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the contained request is repeating.
|
||||
*/
|
||||
public boolean isRepeating() {
|
||||
return mRepeating;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the subsequence id for this request.
|
||||
*/
|
||||
public int getSubsequeceId() {
|
||||
return mSubsequeceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the frame number for this request.
|
||||
*/
|
||||
public long getFrameNumber() {
|
||||
return mFrameNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contained request.
|
||||
*/
|
||||
public CaptureRequest getRequest() {
|
||||
return mRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a read-only collection of the surfaces targeted by the contained request.
|
||||
*/
|
||||
public Collection<Surface> getHolderTargets() {
|
||||
return getRequest().getTargets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any of the surfaces targeted by the contained request require jpeg buffers.
|
||||
*/
|
||||
public boolean hasJpegTargets() {
|
||||
for (Surface s : getHolderTargets()) {
|
||||
if (jpegType(s)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any of the surfaces targeted by the contained request require a
|
||||
* non-jpeg buffer type.
|
||||
*/
|
||||
public boolean hasPreviewTargets() {
|
||||
for (Surface s : getHolderTargets()) {
|
||||
if (previewType(s)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first surface targeted by the contained request that requires a
|
||||
* non-jpeg buffer type.
|
||||
*/
|
||||
public Surface getFirstPreviewTarget() {
|
||||
for (Surface s : getHolderTargets()) {
|
||||
if (previewType(s)) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given surface requires jpeg buffers.
|
||||
*
|
||||
* @param s a {@link Surface} to check.
|
||||
* @return true if the surface requires a jpeg buffer.
|
||||
*/
|
||||
public static boolean jpegType(Surface s) {
|
||||
if (LegacyCameraDevice.nativeDetectSurfaceType(s) ==
|
||||
CameraMetadataNative.NATIVE_JPEG_FORMAT) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given surface requires non-jpeg buffer types.
|
||||
*
|
||||
* <p>
|
||||
* "Jpeg buffer" refers to the buffers returned in the jpeg
|
||||
* {@link android.hardware.Camera.PictureCallback}. Non-jpeg buffers are created using a tee
|
||||
* of the preview stream drawn to the surface
|
||||
* set via {@link android.hardware.Camera#setPreviewDisplay(android.view.SurfaceHolder)} or
|
||||
* equivalent methods.
|
||||
* </p>
|
||||
* @param s a {@link Surface} to check.
|
||||
* @return true if the surface requires a non-jpeg buffer type.
|
||||
*/
|
||||
public static boolean previewType(Surface s) {
|
||||
if (LegacyCameraDevice.nativeDetectSurfaceType(s) !=
|
||||
CameraMetadataNative.NATIVE_JPEG_FORMAT) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
132
core/java/android/hardware/camera2/legacy/RequestQueue.java
Normal file
132
core/java/android/hardware/camera2/legacy/RequestQueue.java
Normal file
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.legacy;
|
||||
|
||||
import android.hardware.camera2.CaptureRequest;
|
||||
import android.hardware.camera2.utils.LongParcelable;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A queue of bursts of requests.
|
||||
*
|
||||
* <p>This queue maintains the count of frames that have been produced, and is thread safe.</p>
|
||||
*/
|
||||
public class RequestQueue {
|
||||
private static final String TAG = "RequestQueue";
|
||||
|
||||
private static final long INVALID_FRAME = -1;
|
||||
|
||||
private BurstHolder mRepeatingRequest = null;
|
||||
private final ArrayDeque<BurstHolder> mRequestQueue = new ArrayDeque<BurstHolder>();
|
||||
|
||||
private long mCurrentFrameNumber = 0;
|
||||
private long mCurrentRepeatingFrameNumber = INVALID_FRAME;
|
||||
private int mCurrentRequestId = 0;
|
||||
|
||||
public RequestQueue() {}
|
||||
|
||||
/**
|
||||
* Return and remove the next burst on the queue.
|
||||
*
|
||||
* <p>If a repeating burst is returned, it will not be removed.</p>
|
||||
*
|
||||
* @return a pair containing the next burst and the current frame number, or null if none exist.
|
||||
*/
|
||||
public synchronized Pair<BurstHolder, Long> getNext() {
|
||||
BurstHolder next = mRequestQueue.poll();
|
||||
if (next == null && mRepeatingRequest != null) {
|
||||
next = mRepeatingRequest;
|
||||
mCurrentRepeatingFrameNumber = mCurrentFrameNumber +
|
||||
next.getNumberOfRequests();
|
||||
}
|
||||
|
||||
if (next == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Pair<BurstHolder, Long> ret = new Pair<BurstHolder, Long>(next, mCurrentFrameNumber);
|
||||
mCurrentFrameNumber += next.getNumberOfRequests();
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a repeating request.
|
||||
*
|
||||
* @param requestId the id of the repeating request to cancel.
|
||||
* @return the last frame to be returned from the HAL for the given repeating request, or
|
||||
* {@code INVALID_FRAME} if none exists.
|
||||
*/
|
||||
public synchronized long stopRepeating(int requestId) {
|
||||
long ret = INVALID_FRAME;
|
||||
if (mRepeatingRequest != null && mRepeatingRequest.getRequestId() == requestId) {
|
||||
mRepeatingRequest = null;
|
||||
ret = mCurrentRepeatingFrameNumber;
|
||||
mCurrentRepeatingFrameNumber = INVALID_FRAME;
|
||||
} else {
|
||||
Log.e(TAG, "cancel failed: no repeating request exists for request id: " + requestId);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a the given burst to the queue.
|
||||
*
|
||||
* <p>If the burst is repeating, replace the current repeating burst.</p>
|
||||
*
|
||||
* @param requests the burst of requests to add to the queue.
|
||||
* @param repeating true if the burst is repeating.
|
||||
* @param frameNumber an output argument that contains either the frame number of the last frame
|
||||
* that will be returned for this request, or the frame number of the last
|
||||
* frame that will be returned for the current repeating request if this
|
||||
* burst is set to be repeating.
|
||||
* @return the request id.
|
||||
*/
|
||||
public synchronized int submit(List<CaptureRequest> requests, boolean repeating,
|
||||
/*out*/LongParcelable frameNumber) {
|
||||
int requestId = mCurrentRequestId++;
|
||||
BurstHolder burst = new BurstHolder(requestId, repeating, requests);
|
||||
long ret = INVALID_FRAME;
|
||||
if (burst.isRepeating()) {
|
||||
if (mRepeatingRequest != null) {
|
||||
ret = mCurrentRepeatingFrameNumber;
|
||||
}
|
||||
mCurrentRepeatingFrameNumber = INVALID_FRAME;
|
||||
mRepeatingRequest = burst;
|
||||
} else {
|
||||
mRequestQueue.offer(burst);
|
||||
ret = calculateLastFrame(burst.getRequestId());
|
||||
}
|
||||
frameNumber.setNumber(ret);
|
||||
return requestId;
|
||||
}
|
||||
|
||||
private long calculateLastFrame(int requestId) {
|
||||
long total = mCurrentFrameNumber;
|
||||
for (BurstHolder b : mRequestQueue) {
|
||||
total += b.getNumberOfRequests();
|
||||
if (b.getRequestId() == requestId) {
|
||||
return total;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"At least one request must be in the queue to calculate frame number");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,491 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.legacy;
|
||||
|
||||
import android.graphics.ImageFormat;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.hardware.Camera;
|
||||
import android.hardware.camera2.CaptureRequest;
|
||||
import android.hardware.camera2.utils.LongParcelable;
|
||||
import android.hardware.camera2.impl.CameraMetadataNative;
|
||||
import android.os.ConditionVariable;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.Surface;
|
||||
|
||||
import java.io.IOError;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class executes requests to the {@link Camera}.
|
||||
*
|
||||
* <p>
|
||||
* The main components of this class are:
|
||||
* - A message queue of requests to the {@link Camera}.
|
||||
* - A thread that consumes requests to the {@link Camera} and executes them.
|
||||
* - A {@link GLThreadManager} that draws to the configured output {@link Surface}s.
|
||||
* - An {@link CameraDeviceState} state machine that manages the callbacks for various operations.
|
||||
* </p>
|
||||
*/
|
||||
public class RequestThreadManager {
|
||||
private final String TAG;
|
||||
private final int mCameraId;
|
||||
private final RequestHandlerThread mRequestThread;
|
||||
|
||||
private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
|
||||
private final Camera mCamera;
|
||||
|
||||
private final CameraDeviceState mDeviceState;
|
||||
|
||||
private static final int MSG_CONFIGURE_OUTPUTS = 1;
|
||||
private static final int MSG_SUBMIT_CAPTURE_REQUEST = 2;
|
||||
private static final int MSG_CLEANUP = 3;
|
||||
|
||||
private static final int PREVIEW_FRAME_TIMEOUT = 300; // ms
|
||||
private static final int JPEG_FRAME_TIMEOUT = 1000; // ms
|
||||
|
||||
private boolean mPreviewRunning = false;
|
||||
|
||||
private volatile RequestHolder mInFlightPreview;
|
||||
private volatile RequestHolder mInFlightJpeg;
|
||||
|
||||
private List<Surface> mPreviewOutputs = new ArrayList<Surface>();
|
||||
private List<Surface> mCallbackOutputs = new ArrayList<Surface>();
|
||||
private GLThreadManager mGLThreadManager;
|
||||
private SurfaceTexture mPreviewTexture;
|
||||
|
||||
private final RequestQueue mRequestQueue = new RequestQueue();
|
||||
private SurfaceTexture mDummyTexture;
|
||||
private Surface mDummySurface;
|
||||
|
||||
private final FpsCounter mPrevCounter = new FpsCounter("Incoming Preview");
|
||||
|
||||
/**
|
||||
* Container object for Configure messages.
|
||||
*/
|
||||
private static class ConfigureHolder {
|
||||
public final ConditionVariable condition;
|
||||
public final Collection<Surface> surfaces;
|
||||
|
||||
public ConfigureHolder(ConditionVariable condition, Collection<Surface> surfaces) {
|
||||
this.condition = condition;
|
||||
this.surfaces = surfaces;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Counter class used to calculate and log the current FPS of frame production.
|
||||
*/
|
||||
public static class FpsCounter {
|
||||
//TODO: Hook this up to SystTrace?
|
||||
private static final String TAG = "FpsCounter";
|
||||
private int mFrameCount = 0;
|
||||
private long mLastTime = 0;
|
||||
private long mLastPrintTime = 0;
|
||||
private double mLastFps = 0;
|
||||
private final String mStreamType;
|
||||
private static final long NANO_PER_SECOND = 1000000000; //ns
|
||||
|
||||
public FpsCounter(String streamType) {
|
||||
mStreamType = streamType;
|
||||
}
|
||||
|
||||
public synchronized void countFrame() {
|
||||
mFrameCount++;
|
||||
long nextTime = SystemClock.elapsedRealtimeNanos();
|
||||
if (mLastTime == 0) {
|
||||
mLastTime = nextTime;
|
||||
}
|
||||
if (nextTime > mLastTime + NANO_PER_SECOND) {
|
||||
long elapsed = nextTime - mLastTime;
|
||||
mLastFps = mFrameCount * (NANO_PER_SECOND / (double) elapsed);
|
||||
mFrameCount = 0;
|
||||
mLastTime = nextTime;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized double checkFps() {
|
||||
return mLastFps;
|
||||
}
|
||||
|
||||
public synchronized void staggeredLog() {
|
||||
if (mLastTime > mLastPrintTime + 5 * NANO_PER_SECOND) {
|
||||
mLastPrintTime = mLastTime;
|
||||
Log.d(TAG, "FPS for " + mStreamType + " stream: " + mLastFps );
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void countAndLog() {
|
||||
countFrame();
|
||||
staggeredLog();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Fake preview for jpeg captures when there is no active preview
|
||||
*/
|
||||
private void createDummySurface() {
|
||||
if (mDummyTexture == null || mDummySurface == null) {
|
||||
mDummyTexture = new SurfaceTexture(/*ignored*/0);
|
||||
// TODO: use smallest default sizes
|
||||
mDummyTexture.setDefaultBufferSize(640, 480);
|
||||
mDummySurface = new Surface(mDummyTexture);
|
||||
}
|
||||
}
|
||||
|
||||
private final ConditionVariable mReceivedJpeg = new ConditionVariable(false);
|
||||
private final ConditionVariable mReceivedPreview = new ConditionVariable(false);
|
||||
|
||||
private final Camera.PictureCallback mJpegCallback = new Camera.PictureCallback() {
|
||||
@Override
|
||||
public void onPictureTaken(byte[] data, Camera camera) {
|
||||
Log.i(TAG, "Received jpeg.");
|
||||
RequestHolder holder = mInFlightJpeg;
|
||||
if (holder == null) {
|
||||
Log.w(TAG, "Dropping jpeg frame.");
|
||||
mInFlightJpeg = null;
|
||||
return;
|
||||
}
|
||||
for (Surface s : holder.getHolderTargets()) {
|
||||
if (RequestHolder.jpegType(s)) {
|
||||
Log.i(TAG, "Producing jpeg buffer...");
|
||||
LegacyCameraDevice.nativeSetSurfaceDimens(s, data.length, /*height*/1);
|
||||
LegacyCameraDevice.nativeProduceFrame(s, data, data.length, /*height*/1,
|
||||
CameraMetadataNative.NATIVE_JPEG_FORMAT);
|
||||
}
|
||||
}
|
||||
mReceivedJpeg.open();
|
||||
}
|
||||
};
|
||||
|
||||
private final SurfaceTexture.OnFrameAvailableListener mPreviewCallback =
|
||||
new SurfaceTexture.OnFrameAvailableListener() {
|
||||
@Override
|
||||
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
|
||||
if (DEBUG) {
|
||||
mPrevCounter.countAndLog();
|
||||
}
|
||||
RequestHolder holder = mInFlightPreview;
|
||||
if (holder == null) {
|
||||
Log.w(TAG, "Dropping preview frame.");
|
||||
mInFlightPreview = null;
|
||||
return;
|
||||
}
|
||||
if (holder.hasPreviewTargets()) {
|
||||
mGLThreadManager.queueNewFrame(holder.getHolderTargets());
|
||||
}
|
||||
|
||||
mReceivedPreview.open();
|
||||
}
|
||||
};
|
||||
|
||||
private void stopPreview() {
|
||||
if (mPreviewRunning) {
|
||||
mCamera.stopPreview();
|
||||
mPreviewRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void startPreview() {
|
||||
if (!mPreviewRunning) {
|
||||
mCamera.startPreview();
|
||||
mPreviewRunning = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void doJpegCapture(RequestHolder request) throws IOException {
|
||||
if (!mPreviewRunning) {
|
||||
createDummySurface();
|
||||
mCamera.setPreviewTexture(mDummyTexture);
|
||||
startPreview();
|
||||
}
|
||||
mInFlightJpeg = request;
|
||||
// TODO: Hook up shutter callback to CameraDeviceStateListener#onCaptureStarted
|
||||
mCamera.takePicture(/*shutter*/null, /*raw*/null, mJpegCallback);
|
||||
mPreviewRunning = false;
|
||||
}
|
||||
|
||||
private void doPreviewCapture(RequestHolder request) throws IOException {
|
||||
mInFlightPreview = request;
|
||||
if (mPreviewRunning) {
|
||||
return; // Already running
|
||||
}
|
||||
|
||||
mPreviewTexture.setDefaultBufferSize(640, 480); // TODO: size selection based on request
|
||||
mCamera.setPreviewTexture(mPreviewTexture);
|
||||
Camera.Parameters params = mCamera.getParameters();
|
||||
List<int[]> supportedFpsRanges = params.getSupportedPreviewFpsRange();
|
||||
int[] bestRange = getPhotoPreviewFpsRange(supportedFpsRanges);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "doPreviewCapture - Selected range [" +
|
||||
bestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] + "," +
|
||||
bestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] + "]");
|
||||
}
|
||||
params.setPreviewFpsRange(bestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
|
||||
bestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
|
||||
params.setRecordingHint(true);
|
||||
mCamera.setParameters(params);
|
||||
|
||||
startPreview();
|
||||
}
|
||||
|
||||
private void configureOutputs(Collection<Surface> outputs) throws IOException {
|
||||
stopPreview();
|
||||
if (mGLThreadManager != null) {
|
||||
mGLThreadManager.waitUntilStarted();
|
||||
mGLThreadManager.ignoreNewFrames();
|
||||
mGLThreadManager.waitUntilIdle();
|
||||
}
|
||||
mPreviewOutputs.clear();
|
||||
mCallbackOutputs.clear();
|
||||
mPreviewTexture = null;
|
||||
mInFlightPreview = null;
|
||||
mInFlightJpeg = null;
|
||||
|
||||
for (Surface s : outputs) {
|
||||
int format = LegacyCameraDevice.nativeDetectSurfaceType(s);
|
||||
switch (format) {
|
||||
case CameraMetadataNative.NATIVE_JPEG_FORMAT:
|
||||
mCallbackOutputs.add(s);
|
||||
break;
|
||||
default:
|
||||
mPreviewOutputs.add(s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Detect and optimize single-output paths here to skip stream teeing.
|
||||
if (mGLThreadManager == null) {
|
||||
mGLThreadManager = new GLThreadManager(mCameraId);
|
||||
mGLThreadManager.start();
|
||||
}
|
||||
mGLThreadManager.waitUntilStarted();
|
||||
mGLThreadManager.setConfigurationAndWait(mPreviewOutputs);
|
||||
mGLThreadManager.allowNewFrames();
|
||||
mPreviewTexture = mGLThreadManager.getCurrentSurfaceTexture();
|
||||
mPreviewTexture.setOnFrameAvailableListener(mPreviewCallback);
|
||||
}
|
||||
|
||||
// Calculate the highest FPS range supported
|
||||
private int[] getPhotoPreviewFpsRange(List<int[]> frameRates) {
|
||||
if (frameRates.size() == 0) {
|
||||
Log.e(TAG, "No supported frame rates returned!");
|
||||
return null;
|
||||
}
|
||||
|
||||
int bestMin = 0;
|
||||
int bestMax = 0;
|
||||
int bestIndex = 0;
|
||||
int index = 0;
|
||||
for (int[] rate : frameRates) {
|
||||
int minFps = rate[Camera.Parameters.PREVIEW_FPS_MIN_INDEX];
|
||||
int maxFps = rate[Camera.Parameters.PREVIEW_FPS_MAX_INDEX];
|
||||
if (maxFps > bestMax || (maxFps == bestMax && minFps > bestMin)) {
|
||||
bestMin = minFps;
|
||||
bestMax = maxFps;
|
||||
bestIndex = index;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
return frameRates.get(bestIndex);
|
||||
}
|
||||
|
||||
private final Handler.Callback mRequestHandlerCb = new Handler.Callback() {
|
||||
private boolean mCleanup = false;
|
||||
private List<RequestHolder> mRepeating = null;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
if (mCleanup) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (msg.what) {
|
||||
case MSG_CONFIGURE_OUTPUTS:
|
||||
ConfigureHolder config = (ConfigureHolder) msg.obj;
|
||||
Log.i(TAG, "Configure outputs: " + config.surfaces.size() +
|
||||
" surfaces configured.");
|
||||
try {
|
||||
configureOutputs(config.surfaces);
|
||||
} catch (IOException e) {
|
||||
// TODO: report error to CameraDevice
|
||||
throw new IOError(e);
|
||||
}
|
||||
config.condition.open();
|
||||
break;
|
||||
case MSG_SUBMIT_CAPTURE_REQUEST:
|
||||
Handler handler = RequestThreadManager.this.mRequestThread.getHandler();
|
||||
|
||||
// Get the next burst from the request queue.
|
||||
Pair<BurstHolder, Long> nextBurst = mRequestQueue.getNext();
|
||||
if (nextBurst == null) {
|
||||
mDeviceState.setIdle();
|
||||
stopPreview();
|
||||
break;
|
||||
} else {
|
||||
// Queue another capture if we did not get the last burst.
|
||||
handler.sendEmptyMessage(MSG_SUBMIT_CAPTURE_REQUEST);
|
||||
}
|
||||
|
||||
// Complete each request in the burst
|
||||
List<RequestHolder> requests =
|
||||
nextBurst.first.produceRequestHolders(nextBurst.second);
|
||||
for (RequestHolder holder : requests) {
|
||||
mDeviceState.setCaptureStart(holder);
|
||||
try {
|
||||
if (holder.hasPreviewTargets()) {
|
||||
mReceivedPreview.close();
|
||||
doPreviewCapture(holder);
|
||||
if (!mReceivedPreview.block(PREVIEW_FRAME_TIMEOUT)) {
|
||||
// TODO: report error to CameraDevice
|
||||
Log.e(TAG, "Hit timeout for preview callback!");
|
||||
}
|
||||
}
|
||||
if (holder.hasJpegTargets()) {
|
||||
mReceivedJpeg.close();
|
||||
doJpegCapture(holder);
|
||||
mReceivedJpeg.block();
|
||||
if (!mReceivedJpeg.block(JPEG_FRAME_TIMEOUT)) {
|
||||
// TODO: report error to CameraDevice
|
||||
Log.e(TAG, "Hit timeout for jpeg callback!");
|
||||
}
|
||||
mInFlightJpeg = null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// TODO: err handling
|
||||
throw new IOError(e);
|
||||
}
|
||||
// TODO: Set fields in result.
|
||||
mDeviceState.setCaptureResult(holder, new CameraMetadataNative());
|
||||
}
|
||||
break;
|
||||
case MSG_CLEANUP:
|
||||
mCleanup = true;
|
||||
if (mGLThreadManager != null) {
|
||||
mGLThreadManager.quit();
|
||||
}
|
||||
if (mCamera != null) {
|
||||
mCamera.release();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unhandled message " + msg.what +
|
||||
" on RequestThread.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new RequestThreadManager.
|
||||
*
|
||||
* @param cameraId the id of the camera to use.
|
||||
* @param camera an open camera object. The RequestThreadManager takes ownership of this camera
|
||||
* object, and is responsible for closing it.
|
||||
* @param deviceState a {@link CameraDeviceState} state machine.
|
||||
*/
|
||||
public RequestThreadManager(int cameraId, Camera camera,
|
||||
CameraDeviceState deviceState) {
|
||||
mCamera = camera;
|
||||
mCameraId = cameraId;
|
||||
String name = String.format("RequestThread-%d", cameraId);
|
||||
TAG = name;
|
||||
mDeviceState = deviceState;
|
||||
mRequestThread = new RequestHandlerThread(name, mRequestHandlerCb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the request thread.
|
||||
*/
|
||||
public void start() {
|
||||
mRequestThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the pending requests.
|
||||
*/
|
||||
public void flush() {
|
||||
// TODO: Implement flush.
|
||||
Log.e(TAG, "flush not yet implemented.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Quit the request thread, and clean up everything.
|
||||
*/
|
||||
public void quit() {
|
||||
Handler handler = mRequestThread.waitAndGetHandler();
|
||||
handler.sendMessageAtFrontOfQueue(handler.obtainMessage(MSG_CLEANUP));
|
||||
mRequestThread.quitSafely();
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit the given burst of requests to be captured.
|
||||
*
|
||||
* <p>If the burst is repeating, replace the current repeating burst.</p>
|
||||
*
|
||||
* @param requests the burst of requests to add to the queue.
|
||||
* @param repeating true if the burst is repeating.
|
||||
* @param frameNumber an output argument that contains either the frame number of the last frame
|
||||
* that will be returned for this request, or the frame number of the last
|
||||
* frame that will be returned for the current repeating request if this
|
||||
* burst is set to be repeating.
|
||||
* @return the request id.
|
||||
*/
|
||||
public int submitCaptureRequests(List<CaptureRequest> requests, boolean repeating,
|
||||
/*out*/LongParcelable frameNumber) {
|
||||
Handler handler = mRequestThread.waitAndGetHandler();
|
||||
int ret = mRequestQueue.submit(requests, repeating, frameNumber);
|
||||
handler.sendEmptyMessage(MSG_SUBMIT_CAPTURE_REQUEST);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a repeating request.
|
||||
*
|
||||
* @param requestId the id of the repeating request to cancel.
|
||||
* @return the last frame to be returned from the HAL for the given repeating request, or
|
||||
* {@code INVALID_FRAME} if none exists.
|
||||
*/
|
||||
public long cancelRepeating(int requestId) {
|
||||
return mRequestQueue.stopRepeating(requestId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Configure with the current output Surfaces.
|
||||
*
|
||||
* <p>
|
||||
* This operation blocks until the configuration is complete.
|
||||
* </p>
|
||||
*
|
||||
* @param outputs a {@link java.util.Collection} of outputs to configure.
|
||||
*/
|
||||
public void configure(Collection<Surface> outputs) {
|
||||
Handler handler = mRequestThread.waitAndGetHandler();
|
||||
final ConditionVariable condition = new ConditionVariable(/*closed*/false);
|
||||
ConfigureHolder holder = new ConfigureHolder(condition, outputs);
|
||||
handler.sendMessage(handler.obtainMessage(MSG_CONFIGURE_OUTPUTS, 0, 0, holder));
|
||||
condition.block();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,522 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.legacy;
|
||||
|
||||
import android.graphics.ImageFormat;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.opengl.EGL14;
|
||||
import android.opengl.EGLConfig;
|
||||
import android.opengl.EGLContext;
|
||||
import android.opengl.EGLDisplay;
|
||||
import android.opengl.EGLSurface;
|
||||
import android.opengl.GLES11Ext;
|
||||
import android.opengl.GLES20;
|
||||
import android.opengl.Matrix;
|
||||
import android.util.Log;
|
||||
import android.view.Surface;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A renderer class that manages the GL state, and can draw a frame into a set of output
|
||||
* {@link Surface}s.
|
||||
*/
|
||||
public class SurfaceTextureRenderer {
|
||||
private static final String TAG = SurfaceTextureRenderer.class.getSimpleName();
|
||||
private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
|
||||
private static final int EGL_RECORDABLE_ANDROID = 0x3142; // from EGL/eglext.h
|
||||
private static final int GL_MATRIX_SIZE = 16;
|
||||
private static final int VERTEX_POS_SIZE = 3;
|
||||
private static final int VERTEX_UV_SIZE = 2;
|
||||
private static final int EGL_COLOR_BITLENGTH = 8;
|
||||
private static final int GLES_VERSION = 2;
|
||||
private static final int PBUFFER_PIXEL_BYTES = 4;
|
||||
|
||||
private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
|
||||
private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
|
||||
private EGLConfig mConfigs;
|
||||
|
||||
private class EGLSurfaceHolder {
|
||||
Surface surface;
|
||||
EGLSurface eglSurface;
|
||||
int width;
|
||||
int height;
|
||||
}
|
||||
|
||||
private List<EGLSurfaceHolder> mSurfaces = new ArrayList<EGLSurfaceHolder>();
|
||||
private List<EGLSurfaceHolder> mConversionSurfaces = new ArrayList<EGLSurfaceHolder>();
|
||||
|
||||
private ByteBuffer mPBufferPixels;
|
||||
|
||||
// Hold this to avoid GC
|
||||
private volatile SurfaceTexture mSurfaceTexture;
|
||||
|
||||
private static final int FLOAT_SIZE_BYTES = 4;
|
||||
private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
|
||||
private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
|
||||
private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
|
||||
private final float[] mTriangleVerticesData = {
|
||||
// X, Y, Z, U, V
|
||||
-1.0f, -1.0f, 0, 0.f, 0.f,
|
||||
1.0f, -1.0f, 0, 1.f, 0.f,
|
||||
-1.0f, 1.0f, 0, 0.f, 1.f,
|
||||
1.0f, 1.0f, 0, 1.f, 1.f,
|
||||
};
|
||||
|
||||
private FloatBuffer mTriangleVertices;
|
||||
|
||||
/**
|
||||
* As used in this file, this vertex shader maps a unit square to the view, and
|
||||
* tells the fragment shader to interpolate over it. Each surface pixel position
|
||||
* is mapped to a 2D homogeneous texture coordinate of the form (s, t, 0, 1) with
|
||||
* s and t in the inclusive range [0, 1], and the matrix from
|
||||
* {@link SurfaceTexture#getTransformMatrix(float[])} is used to map this
|
||||
* coordinate to a texture location.
|
||||
*/
|
||||
private static final String VERTEX_SHADER =
|
||||
"uniform mat4 uMVPMatrix;\n" +
|
||||
"uniform mat4 uSTMatrix;\n" +
|
||||
"attribute vec4 aPosition;\n" +
|
||||
"attribute vec4 aTextureCoord;\n" +
|
||||
"varying vec2 vTextureCoord;\n" +
|
||||
"void main() {\n" +
|
||||
" gl_Position = uMVPMatrix * aPosition;\n" +
|
||||
" vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
|
||||
"}\n";
|
||||
|
||||
/**
|
||||
* This fragment shader simply draws the color in the 2D texture at
|
||||
* the location from the {@code VERTEX_SHADER}.
|
||||
*/
|
||||
private static final String FRAGMENT_SHADER =
|
||||
"#extension GL_OES_EGL_image_external : require\n" +
|
||||
"precision mediump float;\n" +
|
||||
"varying vec2 vTextureCoord;\n" +
|
||||
"uniform samplerExternalOES sTexture;\n" +
|
||||
"void main() {\n" +
|
||||
" gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
|
||||
"}\n";
|
||||
|
||||
private float[] mMVPMatrix = new float[GL_MATRIX_SIZE];
|
||||
private float[] mSTMatrix = new float[GL_MATRIX_SIZE];
|
||||
|
||||
private int mProgram;
|
||||
private int mTextureID = 0;
|
||||
private int muMVPMatrixHandle;
|
||||
private int muSTMatrixHandle;
|
||||
private int maPositionHandle;
|
||||
private int maTextureHandle;
|
||||
|
||||
public SurfaceTextureRenderer() {
|
||||
mTriangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length *
|
||||
FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
|
||||
mTriangleVertices.put(mTriangleVerticesData).position(0);
|
||||
Matrix.setIdentityM(mSTMatrix, 0);
|
||||
}
|
||||
|
||||
private int loadShader(int shaderType, String source) {
|
||||
int shader = GLES20.glCreateShader(shaderType);
|
||||
checkGlError("glCreateShader type=" + shaderType);
|
||||
GLES20.glShaderSource(shader, source);
|
||||
GLES20.glCompileShader(shader);
|
||||
int[] compiled = new int[1];
|
||||
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
|
||||
if (compiled[0] == 0) {
|
||||
Log.e(TAG, "Could not compile shader " + shaderType + ":");
|
||||
Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader));
|
||||
GLES20.glDeleteShader(shader);
|
||||
// TODO: handle this more gracefully
|
||||
throw new IllegalStateException("Could not compile shader " + shaderType);
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
|
||||
private int createProgram(String vertexSource, String fragmentSource) {
|
||||
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
|
||||
if (vertexShader == 0) {
|
||||
return 0;
|
||||
}
|
||||
int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
|
||||
if (pixelShader == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int program = GLES20.glCreateProgram();
|
||||
checkGlError("glCreateProgram");
|
||||
if (program == 0) {
|
||||
Log.e(TAG, "Could not create program");
|
||||
}
|
||||
GLES20.glAttachShader(program, vertexShader);
|
||||
checkGlError("glAttachShader");
|
||||
GLES20.glAttachShader(program, pixelShader);
|
||||
checkGlError("glAttachShader");
|
||||
GLES20.glLinkProgram(program);
|
||||
int[] linkStatus = new int[1];
|
||||
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
|
||||
if (linkStatus[0] != GLES20.GL_TRUE) {
|
||||
Log.e(TAG, "Could not link program: ");
|
||||
Log.e(TAG, GLES20.glGetProgramInfoLog(program));
|
||||
GLES20.glDeleteProgram(program);
|
||||
// TODO: handle this more gracefully
|
||||
throw new IllegalStateException("Could not link program");
|
||||
}
|
||||
return program;
|
||||
}
|
||||
|
||||
private void drawFrame(SurfaceTexture st) {
|
||||
checkGlError("onDrawFrame start");
|
||||
st.getTransformMatrix(mSTMatrix);
|
||||
|
||||
if (DEBUG) {
|
||||
GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
GLES20.glUseProgram(mProgram);
|
||||
checkGlError("glUseProgram");
|
||||
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
|
||||
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
|
||||
|
||||
mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
|
||||
GLES20.glVertexAttribPointer(maPositionHandle, VERTEX_POS_SIZE, GLES20.GL_FLOAT,
|
||||
/*normalized*/ false,TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
|
||||
checkGlError("glVertexAttribPointer maPosition");
|
||||
GLES20.glEnableVertexAttribArray(maPositionHandle);
|
||||
checkGlError("glEnableVertexAttribArray maPositionHandle");
|
||||
|
||||
mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
|
||||
GLES20.glVertexAttribPointer(maTextureHandle, VERTEX_UV_SIZE, GLES20.GL_FLOAT,
|
||||
/*normalized*/ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
|
||||
checkGlError("glVertexAttribPointer maTextureHandle");
|
||||
GLES20.glEnableVertexAttribArray(maTextureHandle);
|
||||
checkGlError("glEnableVertexAttribArray maTextureHandle");
|
||||
|
||||
Matrix.setIdentityM(mMVPMatrix, 0);
|
||||
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, /*count*/ 1, /*transpose*/ false, mMVPMatrix,
|
||||
/*offset*/ 0);
|
||||
GLES20.glUniformMatrix4fv(muSTMatrixHandle, /*count*/ 1, /*transpose*/ false, mSTMatrix,
|
||||
/*offset*/ 0);
|
||||
|
||||
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /*offset*/ 0, /*count*/ 4);
|
||||
checkGlError("glDrawArrays");
|
||||
GLES20.glFinish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes GL state. Call this after the EGL surface has been created and made current.
|
||||
*/
|
||||
private void initializeGLState() {
|
||||
mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
|
||||
if (mProgram == 0) {
|
||||
throw new IllegalStateException("failed creating program");
|
||||
}
|
||||
maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
|
||||
checkGlError("glGetAttribLocation aPosition");
|
||||
if (maPositionHandle == -1) {
|
||||
throw new IllegalStateException("Could not get attrib location for aPosition");
|
||||
}
|
||||
maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
|
||||
checkGlError("glGetAttribLocation aTextureCoord");
|
||||
if (maTextureHandle == -1) {
|
||||
throw new IllegalStateException("Could not get attrib location for aTextureCoord");
|
||||
}
|
||||
|
||||
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
|
||||
checkGlError("glGetUniformLocation uMVPMatrix");
|
||||
if (muMVPMatrixHandle == -1) {
|
||||
throw new IllegalStateException("Could not get attrib location for uMVPMatrix");
|
||||
}
|
||||
|
||||
muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
|
||||
checkGlError("glGetUniformLocation uSTMatrix");
|
||||
if (muSTMatrixHandle == -1) {
|
||||
throw new IllegalStateException("Could not get attrib location for uSTMatrix");
|
||||
}
|
||||
|
||||
int[] textures = new int[1];
|
||||
GLES20.glGenTextures(/*n*/ 1, textures, /*offset*/ 0);
|
||||
|
||||
mTextureID = textures[0];
|
||||
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
|
||||
checkGlError("glBindTexture mTextureID");
|
||||
|
||||
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
|
||||
GLES20.GL_NEAREST);
|
||||
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
|
||||
GLES20.GL_LINEAR);
|
||||
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
|
||||
GLES20.GL_CLAMP_TO_EDGE);
|
||||
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
|
||||
GLES20.GL_CLAMP_TO_EDGE);
|
||||
checkGlError("glTexParameter");
|
||||
}
|
||||
|
||||
private int getTextureId() {
|
||||
return mTextureID;
|
||||
}
|
||||
|
||||
private void clearState() {
|
||||
mSurfaces.clear();
|
||||
mConversionSurfaces.clear();
|
||||
mPBufferPixels = null;
|
||||
mSurfaceTexture = null;
|
||||
}
|
||||
|
||||
private void configureEGLContext() {
|
||||
mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
|
||||
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
|
||||
throw new IllegalStateException("No EGL14 display");
|
||||
}
|
||||
int[] version = new int[2];
|
||||
if (!EGL14.eglInitialize(mEGLDisplay, version, /*offset*/ 0, version, /*offset*/ 1)) {
|
||||
throw new IllegalStateException("Cannot initialize EGL14");
|
||||
}
|
||||
|
||||
int[] attribList = {
|
||||
EGL14.EGL_RED_SIZE, EGL_COLOR_BITLENGTH,
|
||||
EGL14.EGL_GREEN_SIZE, EGL_COLOR_BITLENGTH,
|
||||
EGL14.EGL_BLUE_SIZE, EGL_COLOR_BITLENGTH,
|
||||
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
|
||||
EGL_RECORDABLE_ANDROID, 1,
|
||||
EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT | EGL14.EGL_WINDOW_BIT,
|
||||
EGL14.EGL_NONE
|
||||
};
|
||||
EGLConfig[] configs = new EGLConfig[1];
|
||||
int[] numConfigs = new int[1];
|
||||
EGL14.eglChooseConfig(mEGLDisplay, attribList, /*offset*/ 0, configs, /*offset*/ 0,
|
||||
configs.length, numConfigs, /*offset*/ 0);
|
||||
checkEglError("eglCreateContext RGB888+recordable ES2");
|
||||
mConfigs = configs[0];
|
||||
int[] attrib_list = {
|
||||
EGL14.EGL_CONTEXT_CLIENT_VERSION, GLES_VERSION,
|
||||
EGL14.EGL_NONE
|
||||
};
|
||||
mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
|
||||
attrib_list, /*offset*/ 0);
|
||||
checkEglError("eglCreateContext");
|
||||
if(mEGLContext == EGL14.EGL_NO_CONTEXT) {
|
||||
throw new IllegalStateException("No EGLContext could be made");
|
||||
}
|
||||
}
|
||||
|
||||
private void configureEGLOutputSurfaces(Collection<EGLSurfaceHolder> surfaces) {
|
||||
if (surfaces == null || surfaces.size() == 0) {
|
||||
throw new IllegalStateException("No Surfaces were provided to draw to");
|
||||
}
|
||||
int[] surfaceAttribs = {
|
||||
EGL14.EGL_NONE
|
||||
};
|
||||
for (EGLSurfaceHolder holder : surfaces) {
|
||||
holder.eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mConfigs, holder.surface,
|
||||
surfaceAttribs, 0);
|
||||
checkEglError("eglCreateWindowSurface");
|
||||
}
|
||||
}
|
||||
|
||||
private void configureEGLPbufferSurfaces(Collection<EGLSurfaceHolder> surfaces) {
|
||||
if (surfaces == null || surfaces.size() == 0) {
|
||||
throw new IllegalStateException("No Surfaces were provided to draw to");
|
||||
}
|
||||
|
||||
int maxLength = 0;
|
||||
int[] dimens = new int[2];
|
||||
for (EGLSurfaceHolder holder : surfaces) {
|
||||
LegacyCameraDevice.nativeDetectSurfaceDimens(holder.surface, dimens);
|
||||
int length = dimens[0] * dimens[1];
|
||||
// Find max surface size, ensure PBuffer can hold this many pixels
|
||||
maxLength = (length > maxLength) ? length : maxLength;
|
||||
int[] surfaceAttribs = {
|
||||
EGL14.EGL_WIDTH, dimens[0],
|
||||
EGL14.EGL_HEIGHT, dimens[1],
|
||||
EGL14.EGL_NONE
|
||||
};
|
||||
holder.width = dimens[0];
|
||||
holder.height = dimens[1];
|
||||
holder.eglSurface =
|
||||
EGL14.eglCreatePbufferSurface(mEGLDisplay, mConfigs, surfaceAttribs, 0);
|
||||
checkEglError("eglCreatePbufferSurface");
|
||||
}
|
||||
mPBufferPixels = ByteBuffer.allocateDirect(maxLength * PBUFFER_PIXEL_BYTES)
|
||||
.order(ByteOrder.nativeOrder());
|
||||
}
|
||||
|
||||
private void releaseEGLContext() {
|
||||
if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
|
||||
EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
|
||||
EGL14.EGL_NO_CONTEXT);
|
||||
if (mSurfaces != null) {
|
||||
for (EGLSurfaceHolder holder : mSurfaces) {
|
||||
if (holder.eglSurface != null) {
|
||||
EGL14.eglDestroySurface(mEGLDisplay, holder.eglSurface);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mConversionSurfaces != null) {
|
||||
for (EGLSurfaceHolder holder : mConversionSurfaces) {
|
||||
if (holder.eglSurface != null) {
|
||||
EGL14.eglDestroySurface(mEGLDisplay, holder.eglSurface);
|
||||
}
|
||||
}
|
||||
}
|
||||
EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
|
||||
EGL14.eglReleaseThread();
|
||||
EGL14.eglTerminate(mEGLDisplay);
|
||||
}
|
||||
|
||||
mConfigs = null;
|
||||
mEGLDisplay = EGL14.EGL_NO_DISPLAY;
|
||||
mEGLContext = EGL14.EGL_NO_CONTEXT;
|
||||
clearState();
|
||||
}
|
||||
|
||||
private void makeCurrent(EGLSurface surface) {
|
||||
EGL14.eglMakeCurrent(mEGLDisplay, surface, surface, mEGLContext);
|
||||
checkEglError("makeCurrent");
|
||||
}
|
||||
|
||||
private boolean swapBuffers(EGLSurface surface) {
|
||||
boolean result = EGL14.eglSwapBuffers(mEGLDisplay, surface);
|
||||
checkEglError("swapBuffers");
|
||||
return result;
|
||||
}
|
||||
|
||||
private void checkEglError(String msg) {
|
||||
int error;
|
||||
if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
|
||||
throw new IllegalStateException(msg + ": EGL error: 0x" + Integer.toHexString(error));
|
||||
}
|
||||
}
|
||||
|
||||
private void checkGlError(String msg) {
|
||||
int error;
|
||||
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
|
||||
throw new IllegalStateException(msg + ": GLES20 error: 0x" + Integer.toHexString(error));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the surface texture to draw to - this is the texture use to when producing output
|
||||
* surface buffers.
|
||||
*
|
||||
* @return a {@link SurfaceTexture}.
|
||||
*/
|
||||
public SurfaceTexture getSurfaceTexture() {
|
||||
return mSurfaceTexture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a collection of output {@link Surface}s that can be drawn to.
|
||||
*
|
||||
* @param surfaces a {@link Collection} of surfaces.
|
||||
*/
|
||||
public void configureSurfaces(Collection<Surface> surfaces) {
|
||||
releaseEGLContext();
|
||||
|
||||
for (Surface s : surfaces) {
|
||||
// If pixel conversions aren't handled by egl, use a pbuffer
|
||||
if (LegacyCameraDevice.needsConversion(s)) {
|
||||
LegacyCameraDevice.nativeSetSurfaceFormat(s, ImageFormat.NV21);
|
||||
EGLSurfaceHolder holder = new EGLSurfaceHolder();
|
||||
holder.surface = s;
|
||||
mConversionSurfaces.add(holder);
|
||||
} else {
|
||||
EGLSurfaceHolder holder = new EGLSurfaceHolder();
|
||||
holder.surface = s;
|
||||
mSurfaces.add(holder);
|
||||
}
|
||||
}
|
||||
|
||||
// Set up egl display
|
||||
configureEGLContext();
|
||||
|
||||
// Set up regular egl surfaces if needed
|
||||
if (mSurfaces.size() > 0) {
|
||||
configureEGLOutputSurfaces(mSurfaces);
|
||||
}
|
||||
|
||||
// Set up pbuffer surface if needed
|
||||
if (mConversionSurfaces.size() > 0) {
|
||||
configureEGLPbufferSurfaces(mConversionSurfaces);
|
||||
}
|
||||
makeCurrent((mSurfaces.size() > 0) ? mSurfaces.get(0).eglSurface :
|
||||
mConversionSurfaces.get(0).eglSurface);
|
||||
initializeGLState();
|
||||
mSurfaceTexture = new SurfaceTexture(getTextureId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the current buffer in the {@link SurfaceTexture} returned from
|
||||
* {@link #getSurfaceTexture()} into the given set of target surfaces.
|
||||
*
|
||||
* <p>
|
||||
* The given surfaces must be a subset of the surfaces set in the last
|
||||
* {@link #configureSurfaces(java.util.Collection)} call.
|
||||
* </p>
|
||||
*
|
||||
* @param targetSurfaces the surfaces to draw to.
|
||||
*/
|
||||
public void drawIntoSurfaces(Collection<Surface> targetSurfaces) {
|
||||
if ((mSurfaces == null || mSurfaces.size() == 0)
|
||||
&& (mConversionSurfaces == null || mConversionSurfaces.size() == 0)) {
|
||||
return;
|
||||
}
|
||||
checkGlError("before updateTexImage");
|
||||
mSurfaceTexture.updateTexImage();
|
||||
for (EGLSurfaceHolder holder : mSurfaces) {
|
||||
if (targetSurfaces.contains(holder.surface)) {
|
||||
makeCurrent(holder.eglSurface);
|
||||
drawFrame(mSurfaceTexture);
|
||||
swapBuffers(holder.eglSurface);
|
||||
}
|
||||
|
||||
}
|
||||
for (EGLSurfaceHolder holder : mConversionSurfaces) {
|
||||
if (targetSurfaces.contains(holder.surface)) {
|
||||
makeCurrent(holder.eglSurface);
|
||||
drawFrame(mSurfaceTexture);
|
||||
mPBufferPixels.clear();
|
||||
GLES20.glReadPixels(/*x*/ 0, /*y*/ 0, holder.width, holder.height, GLES20.GL_RGBA,
|
||||
GLES20.GL_UNSIGNED_BYTE, mPBufferPixels);
|
||||
checkGlError("glReadPixels");
|
||||
int format = LegacyCameraDevice.nativeDetectSurfaceType(holder.surface);
|
||||
LegacyCameraDevice.nativeProduceFrame(holder.surface, mPBufferPixels.array(),
|
||||
holder.width, holder.height, format);
|
||||
swapBuffers(holder.eglSurface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the current GL context.
|
||||
*/
|
||||
public void cleanupEGLContext() {
|
||||
releaseEGLContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop all current GL operations on the floor.
|
||||
*/
|
||||
public void flush() {
|
||||
// TODO: implement flush
|
||||
Log.e(TAG, "Flush not yet implemented.");
|
||||
}
|
||||
}
|
||||
3
core/java/android/hardware/camera2/legacy/package.html
Normal file
3
core/java/android/hardware/camera2/legacy/package.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<body>
|
||||
{@hide}
|
||||
</body>
|
||||
@@ -40,6 +40,7 @@ public class CameraBinderDecorator {
|
||||
public static final int ALREADY_EXISTS = -17;
|
||||
public static final int BAD_VALUE = -22;
|
||||
public static final int DEAD_OBJECT = -32;
|
||||
public static final int INVALID_OPERATION = -38;
|
||||
|
||||
/**
|
||||
* TODO: add as error codes in Errors.h
|
||||
@@ -53,6 +54,7 @@ public class CameraBinderDecorator {
|
||||
public static final int EOPNOTSUPP = -95;
|
||||
public static final int EUSERS = -87;
|
||||
|
||||
|
||||
private static class CameraBinderDecoratorListener implements Decorator.DecoratorListener {
|
||||
|
||||
@Override
|
||||
@@ -125,6 +127,9 @@ public class CameraBinderDecorator {
|
||||
case EOPNOTSUPP:
|
||||
UncheckedThrow.throwAnyException(new CameraRuntimeException(
|
||||
CAMERA_DEPRECATED_HAL));
|
||||
case INVALID_OPERATION:
|
||||
UncheckedThrow.throwAnyException(new IllegalStateException(
|
||||
"Illegal state encountered in camera service."));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -135,6 +135,7 @@ LOCAL_SRC_FILES:= \
|
||||
android_media_ToneGenerator.cpp \
|
||||
android_hardware_Camera.cpp \
|
||||
android_hardware_camera2_CameraMetadata.cpp \
|
||||
android_hardware_camera2_legacy_LegacyCameraDevice.cpp \
|
||||
android_hardware_SensorManager.cpp \
|
||||
android_hardware_SerialPort.cpp \
|
||||
android_hardware_UsbDevice.cpp \
|
||||
|
||||
@@ -77,6 +77,7 @@ extern int register_android_opengl_jni_GLES30(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_legacy_LegacyCameraDevice(JNIEnv *env);
|
||||
extern int register_android_hardware_SensorManager(JNIEnv *env);
|
||||
extern int register_android_hardware_SerialPort(JNIEnv *env);
|
||||
extern int register_android_hardware_UsbDevice(JNIEnv *env);
|
||||
@@ -1280,6 +1281,7 @@ static const RegJNIRec gRegJNI[] = {
|
||||
REG_JNI(register_com_android_internal_util_VirtualRefBasePtr),
|
||||
REG_JNI(register_android_hardware_Camera),
|
||||
REG_JNI(register_android_hardware_camera2_CameraMetadata),
|
||||
REG_JNI(register_android_hardware_camera2_legacy_LegacyCameraDevice),
|
||||
REG_JNI(register_android_hardware_SensorManager),
|
||||
REG_JNI(register_android_hardware_SerialPort),
|
||||
REG_JNI(register_android_hardware_UsbDevice),
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include <cutils/properties.h>
|
||||
#include <utils/Vector.h>
|
||||
#include <utils/Errors.h>
|
||||
|
||||
#include <gui/GLConsumer.h>
|
||||
#include <gui/Surface.h>
|
||||
@@ -464,7 +465,7 @@ static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz,
|
||||
}
|
||||
|
||||
// connect to camera service
|
||||
static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
|
||||
static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
|
||||
jobject weak_this, jint cameraId, jstring clientPackageName)
|
||||
{
|
||||
// Convert jstring to String16
|
||||
@@ -477,20 +478,19 @@ static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
|
||||
Camera::USE_CALLING_UID);
|
||||
|
||||
if (camera == NULL) {
|
||||
jniThrowRuntimeException(env, "Fail to connect to camera service");
|
||||
return;
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
// make sure camera hardware is alive
|
||||
if (camera->getStatus() != NO_ERROR) {
|
||||
jniThrowRuntimeException(env, "Camera initialization failed");
|
||||
return;
|
||||
return NO_INIT;
|
||||
}
|
||||
|
||||
jclass clazz = env->GetObjectClass(thiz);
|
||||
if (clazz == NULL) {
|
||||
// This should never happen
|
||||
jniThrowRuntimeException(env, "Can't find android/hardware/Camera");
|
||||
return;
|
||||
return INVALID_OPERATION;
|
||||
}
|
||||
|
||||
// We use a weak reference so the Camera object can be garbage collected.
|
||||
@@ -501,6 +501,7 @@ static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
|
||||
|
||||
// save context in opaque field
|
||||
env->SetLongField(thiz, fields.context, (jlong)context.get());
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
// disconnect from camera service
|
||||
@@ -538,9 +539,9 @@ static void android_hardware_Camera_release(JNIEnv *env, jobject thiz)
|
||||
}
|
||||
}
|
||||
|
||||
static void android_hardware_Camera_setPreviewDisplay(JNIEnv *env, jobject thiz, jobject jSurface)
|
||||
static void android_hardware_Camera_setPreviewSurface(JNIEnv *env, jobject thiz, jobject jSurface)
|
||||
{
|
||||
ALOGV("setPreviewDisplay");
|
||||
ALOGV("setPreviewSurface");
|
||||
sp<Camera> camera = get_native_camera(env, thiz, NULL);
|
||||
if (camera == 0) return;
|
||||
|
||||
@@ -890,14 +891,14 @@ static JNINativeMethod camMethods[] = {
|
||||
"(ILandroid/hardware/Camera$CameraInfo;)V",
|
||||
(void*)android_hardware_Camera_getCameraInfo },
|
||||
{ "native_setup",
|
||||
"(Ljava/lang/Object;ILjava/lang/String;)V",
|
||||
"(Ljava/lang/Object;ILjava/lang/String;)I",
|
||||
(void*)android_hardware_Camera_native_setup },
|
||||
{ "native_release",
|
||||
"()V",
|
||||
(void*)android_hardware_Camera_release },
|
||||
{ "setPreviewDisplay",
|
||||
{ "setPreviewSurface",
|
||||
"(Landroid/view/Surface;)V",
|
||||
(void *)android_hardware_Camera_setPreviewDisplay },
|
||||
(void *)android_hardware_Camera_setPreviewSurface },
|
||||
{ "setPreviewTexture",
|
||||
"(Landroid/graphics/SurfaceTexture;)V",
|
||||
(void *)android_hardware_Camera_setPreviewTexture },
|
||||
|
||||
@@ -518,7 +518,7 @@ static jint CameraMetadata_getTagFromKey(JNIEnv *env, jobject thiz, jstring keyN
|
||||
SortedVector<String8> vendorSections;
|
||||
size_t vendorSectionCount = 0;
|
||||
|
||||
if (vTags != 0) {
|
||||
if (vTags != NULL) {
|
||||
vendorSections = vTags->getAllSectionNames();
|
||||
vendorSectionCount = vendorSections.size();
|
||||
}
|
||||
@@ -592,7 +592,7 @@ static jint CameraMetadata_getTagFromKey(JNIEnv *env, jobject thiz, jstring keyN
|
||||
"Could not find tag name for key '%s')", key);
|
||||
return 0;
|
||||
}
|
||||
} else if (vTags != 0) {
|
||||
} else if (vTags != NULL) {
|
||||
// Match vendor tags (typically com.*)
|
||||
const String8 sectionName(section);
|
||||
const String8 tagName(keyTagName);
|
||||
|
||||
419
core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
Normal file
419
core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
Normal file
@@ -0,0 +1,419 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "Legacy-CameraDevice-JNI"
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/Trace.h>
|
||||
|
||||
#include "jni.h"
|
||||
#include "JNIHelp.h"
|
||||
#include "android_runtime/AndroidRuntime.h"
|
||||
#include "android_runtime/android_view_Surface.h"
|
||||
|
||||
#include <ui/GraphicBuffer.h>
|
||||
#include <system/window.h>
|
||||
|
||||
using namespace android;
|
||||
|
||||
// fully-qualified class name
|
||||
#define CAMERA_DEVICE_CLASS_NAME "android/hardware/camera2/legacy/LegacyCameraDevice"
|
||||
#define CAMERA_DEVICE_BUFFER_SLACK 3
|
||||
|
||||
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(*(a)))
|
||||
|
||||
/**
|
||||
* Convert from RGB 888 to Y'CbCr using the conversion specified in ITU-R BT.601 for
|
||||
* digital RGB with K_b = 0.114, and K_r = 0.299.
|
||||
*/
|
||||
static void rgbToYuv420(uint8_t* rgbBuf, int32_t width, int32_t height, uint8_t* yPlane,
|
||||
uint8_t* uPlane, uint8_t* vPlane, size_t chromaStep, size_t yStride, size_t chromaStride) {
|
||||
uint8_t R, G, B;
|
||||
size_t index = 0;
|
||||
|
||||
int32_t cStrideDiff = chromaStride - width;
|
||||
|
||||
for (int32_t j = 0; j < height; j++) {
|
||||
for (int32_t i = 0; i < width; i++) {
|
||||
R = rgbBuf[index++];
|
||||
G = rgbBuf[index++];
|
||||
B = rgbBuf[index++];
|
||||
*(yPlane + i) = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
|
||||
|
||||
if (j % 2 == 0 && i % 2 == 0){
|
||||
*uPlane = (( -38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
|
||||
*vPlane = (( 112 * R - 94 * G - 18 * B + 128) >> 8) + 128;
|
||||
uPlane += chromaStep;
|
||||
vPlane += chromaStep;
|
||||
}
|
||||
// Skip alpha
|
||||
index++;
|
||||
}
|
||||
yPlane += yStride;
|
||||
if (j % 2 == 0) {
|
||||
uPlane += cStrideDiff;
|
||||
vPlane += cStrideDiff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void rgbToYuv420(uint8_t* rgbBuf, int32_t width, int32_t height, android_ycbcr* ycbcr) {
|
||||
size_t cStep = ycbcr->chroma_step;
|
||||
size_t cStride = ycbcr->cstride;
|
||||
size_t yStride = ycbcr->ystride;
|
||||
rgbToYuv420(rgbBuf, width, height, reinterpret_cast<uint8_t*>(ycbcr->y),
|
||||
reinterpret_cast<uint8_t*>(ycbcr->cb), reinterpret_cast<uint8_t*>(ycbcr->cr),
|
||||
cStep, yStride, cStride);
|
||||
}
|
||||
|
||||
static status_t configureSurface(const sp<ANativeWindow>& anw,
|
||||
int32_t width,
|
||||
int32_t height,
|
||||
int32_t pixelFmt,
|
||||
int32_t maxBufferSlack) {
|
||||
status_t err = NO_ERROR;
|
||||
err = native_window_set_buffers_dimensions(anw.get(), width, height);
|
||||
if (err != NO_ERROR) {
|
||||
ALOGE("%s: Failed to set native window buffer dimensions, error %s (%d).", __FUNCTION__,
|
||||
strerror(-err), err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = native_window_set_buffers_format(anw.get(), pixelFmt);
|
||||
if (err != NO_ERROR) {
|
||||
ALOGE("%s: Failed to set native window buffer format, error %s (%d).", __FUNCTION__,
|
||||
strerror(-err), err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = native_window_set_usage(anw.get(), GRALLOC_USAGE_SW_WRITE_OFTEN);
|
||||
if (err != NO_ERROR) {
|
||||
ALOGE("%s: Failed to set native window usage flag, error %s (%d).", __FUNCTION__,
|
||||
strerror(-err), err);
|
||||
return err;
|
||||
}
|
||||
|
||||
int minUndequeuedBuffers;
|
||||
err = anw.get()->query(anw.get(),
|
||||
NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
|
||||
&minUndequeuedBuffers);
|
||||
if (err != NO_ERROR) {
|
||||
ALOGE("%s: Failed to get native window min undequeued buffers, error %s (%d).",
|
||||
__FUNCTION__, strerror(-err), err);
|
||||
return err;
|
||||
}
|
||||
|
||||
ALOGV("%s: Setting buffer count to %d", __FUNCTION__,
|
||||
maxBufferSlack + 1 + minUndequeuedBuffers);
|
||||
err = native_window_set_buffer_count(anw.get(), maxBufferSlack + 1 + minUndequeuedBuffers);
|
||||
if (err != NO_ERROR) {
|
||||
ALOGE("%s: Failed to set native window buffer count, error %s (%d).", __FUNCTION__,
|
||||
strerror(-err), err);
|
||||
return err;
|
||||
}
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a frame in the given surface.
|
||||
*
|
||||
* Args:
|
||||
* anw - a surface to produce a frame in.
|
||||
* pixelBuffer - image buffer to generate a frame from.
|
||||
* width - width of the pixelBuffer in pixels.
|
||||
* height - height of the pixelBuffer in pixels.
|
||||
* pixelFmt - format of the pixelBuffer, one of:
|
||||
* HAL_PIXEL_FORMAT_YCrCb_420_SP,
|
||||
* HAL_PIXEL_FORMAT_YCbCr_420_888,
|
||||
* HAL_PIXEL_FORMAT_BLOB
|
||||
* bufSize - the size of the pixelBuffer in bytes.
|
||||
*/
|
||||
static status_t produceFrame(const sp<ANativeWindow>& anw,
|
||||
uint8_t* pixelBuffer,
|
||||
int32_t width, // Width of the pixelBuffer
|
||||
int32_t height, // Height of the pixelBuffer
|
||||
int32_t pixelFmt, // Format of the pixelBuffer
|
||||
int64_t bufSize) {
|
||||
ATRACE_CALL();
|
||||
status_t err = NO_ERROR;
|
||||
ANativeWindowBuffer* anb;
|
||||
ALOGV("%s: Dequeue buffer from %p",__FUNCTION__, anw.get());
|
||||
|
||||
// TODO: Switch to using Surface::lock and Surface::unlockAndPost
|
||||
err = native_window_dequeue_buffer_and_wait(anw.get(), &anb);
|
||||
if (err != NO_ERROR) return err;
|
||||
|
||||
sp<GraphicBuffer> buf(new GraphicBuffer(anb, /*keepOwnership*/false));
|
||||
|
||||
switch(pixelFmt) {
|
||||
case HAL_PIXEL_FORMAT_YCrCb_420_SP: {
|
||||
if (bufSize < width * height * 4) {
|
||||
ALOGE("%s: PixelBuffer size %lld to small for given dimensions", __FUNCTION__,
|
||||
bufSize);
|
||||
return BAD_VALUE;
|
||||
}
|
||||
uint8_t* img = NULL;
|
||||
ALOGV("%s: Lock buffer from %p for write", __FUNCTION__, anw.get());
|
||||
err = buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
|
||||
if (err != NO_ERROR) return err;
|
||||
|
||||
uint8_t* yPlane = img;
|
||||
uint8_t* uPlane = img + height * width;
|
||||
uint8_t* vPlane = uPlane + 1;
|
||||
size_t chromaStep = 2;
|
||||
size_t yStride = width;
|
||||
size_t chromaStride = width;
|
||||
|
||||
rgbToYuv420(pixelBuffer, width, height, yPlane,
|
||||
uPlane, vPlane, chromaStep, yStride, chromaStride);
|
||||
break;
|
||||
}
|
||||
case HAL_PIXEL_FORMAT_YCbCr_420_888: {
|
||||
// Software writes with YCbCr_420_888 format are unsupported
|
||||
// by the gralloc module for now
|
||||
if (bufSize < width * height * 4) {
|
||||
ALOGE("%s: PixelBuffer size %lld to small for given dimensions", __FUNCTION__,
|
||||
bufSize);
|
||||
return BAD_VALUE;
|
||||
}
|
||||
android_ycbcr ycbcr = android_ycbcr();
|
||||
ALOGV("%s: Lock buffer from %p for write", __FUNCTION__, anw.get());
|
||||
|
||||
err = buf->lockYCbCr(GRALLOC_USAGE_SW_WRITE_OFTEN, &ycbcr);
|
||||
if (err != NO_ERROR) {
|
||||
ALOGE("%s: Failed to lock ycbcr buffer, error %s (%d).", __FUNCTION__,
|
||||
strerror(-err), err);
|
||||
return err;
|
||||
}
|
||||
rgbToYuv420(pixelBuffer, width, height, &ycbcr);
|
||||
break;
|
||||
}
|
||||
case HAL_PIXEL_FORMAT_BLOB: {
|
||||
if (bufSize != width || height != 1) {
|
||||
ALOGE("%s: Incorrect pixelBuffer size: %lld", __FUNCTION__, bufSize);
|
||||
return BAD_VALUE;
|
||||
}
|
||||
int8_t* img = NULL;
|
||||
|
||||
ALOGV("%s: Lock buffer from %p for write", __FUNCTION__, anw.get());
|
||||
err = buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
|
||||
if (err != NO_ERROR) {
|
||||
ALOGE("%s: Failed to lock buffer, error %s (%d).", __FUNCTION__, strerror(-err),
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
memcpy(img, pixelBuffer, width);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
ALOGE("%s: Invalid pixel format in produceFrame: %x", __FUNCTION__, pixelFmt);
|
||||
return BAD_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
ALOGV("%s: Unlock buffer from %p", __FUNCTION__, anw.get());
|
||||
err = buf->unlock();
|
||||
if (err != NO_ERROR) {
|
||||
ALOGE("%s: Failed to unlock buffer, error %s (%d).", __FUNCTION__, strerror(-err), err);
|
||||
return err;
|
||||
}
|
||||
|
||||
ALOGV("%s: Queue buffer to %p", __FUNCTION__, anw.get());
|
||||
err = anw->queueBuffer(anw.get(), buf->getNativeBuffer(), /*fenceFd*/-1);
|
||||
if (err != NO_ERROR) {
|
||||
ALOGE("%s: Failed to queue buffer, error %s (%d).", __FUNCTION__, strerror(-err), err);
|
||||
return err;
|
||||
}
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
static sp<ANativeWindow> getNativeWindow(JNIEnv* env, jobject surface) {
|
||||
sp<ANativeWindow> anw;
|
||||
if (surface) {
|
||||
anw = android_view_Surface_getNativeWindow(env, surface);
|
||||
if (env->ExceptionCheck()) {
|
||||
return anw;
|
||||
}
|
||||
} else {
|
||||
jniThrowNullPointerException(env, "surface");
|
||||
return anw;
|
||||
}
|
||||
if (anw == NULL) {
|
||||
jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
|
||||
"Surface had no valid native window.");
|
||||
return anw;
|
||||
}
|
||||
return anw;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
static jint LegacyCameraDevice_nativeDetectSurfaceType(JNIEnv* env, jobject thiz, jobject surface) {
|
||||
ALOGV("nativeDetectSurfaceType");
|
||||
sp<ANativeWindow> anw;
|
||||
if ((anw = getNativeWindow(env, surface)) == NULL) {
|
||||
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
|
||||
return 0;
|
||||
}
|
||||
int32_t fmt = 0;
|
||||
status_t err = anw->query(anw.get(), NATIVE_WINDOW_FORMAT, &fmt);
|
||||
if(err != NO_ERROR) {
|
||||
jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
|
||||
"Error while querying surface pixel format (error code %d)", err);
|
||||
return 0;
|
||||
}
|
||||
return fmt;
|
||||
}
|
||||
|
||||
static void LegacyCameraDevice_nativeDetectSurfaceDimens(JNIEnv* env, jobject thiz,
|
||||
jobject surface, jintArray dimens) {
|
||||
ALOGV("nativeGetSurfaceDimens");
|
||||
sp<ANativeWindow> anw;
|
||||
if ((anw = getNativeWindow(env, surface)) == NULL) {
|
||||
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
int32_t dimenBuf[2];
|
||||
status_t err = anw->query(anw.get(), NATIVE_WINDOW_WIDTH, dimenBuf);
|
||||
if(err != NO_ERROR) {
|
||||
jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
|
||||
"Error while querying surface width (error code %d)", err);
|
||||
return;
|
||||
}
|
||||
err = anw->query(anw.get(), NATIVE_WINDOW_HEIGHT, dimenBuf + 1);
|
||||
if(err != NO_ERROR) {
|
||||
jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
|
||||
"Error while querying surface height (error code %d)", err);
|
||||
return;
|
||||
}
|
||||
env->SetIntArrayRegion(dimens, /*start*/0, /*length*/ARRAY_SIZE(dimenBuf), dimenBuf);
|
||||
}
|
||||
|
||||
static void LegacyCameraDevice_nativeConfigureSurface(JNIEnv* env, jobject thiz, jobject surface,
|
||||
jint width, jint height, jint pixelFormat) {
|
||||
ALOGV("nativeConfigureSurface");
|
||||
sp<ANativeWindow> anw;
|
||||
if ((anw = getNativeWindow(env, surface)) == NULL) {
|
||||
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
status_t err = configureSurface(anw, width, height, pixelFormat, CAMERA_DEVICE_BUFFER_SLACK);
|
||||
if (err != NO_ERROR) {
|
||||
jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
|
||||
"Error while producing frame (error code %d)", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void LegacyCameraDevice_nativeProduceFrame(JNIEnv* env, jobject thiz, jobject surface,
|
||||
jbyteArray pixelBuffer, jint width, jint height, jint pixelFormat) {
|
||||
ALOGV("nativeProduceFrame");
|
||||
sp<ANativeWindow> anw;
|
||||
|
||||
if ((anw = getNativeWindow(env, surface)) == NULL) {
|
||||
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pixelBuffer == NULL) {
|
||||
jniThrowNullPointerException(env, "pixelBuffer");
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t bufSize = static_cast<int32_t>(env->GetArrayLength(pixelBuffer));
|
||||
jbyte* pixels = env->GetByteArrayElements(pixelBuffer, /*is_copy*/NULL);
|
||||
|
||||
if (pixels == NULL) {
|
||||
jniThrowNullPointerException(env, "pixels");
|
||||
return;
|
||||
}
|
||||
|
||||
status_t err = produceFrame(anw, reinterpret_cast<uint8_t*>(pixels), width, height,
|
||||
pixelFormat, bufSize);
|
||||
env->ReleaseByteArrayElements(pixelBuffer, pixels, JNI_ABORT);
|
||||
|
||||
if (err != NO_ERROR) {
|
||||
jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
|
||||
"Error while producing frame (error code %d)", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void LegacyCameraDevice_nativeSetSurfaceFormat(JNIEnv* env, jobject thiz, jobject surface,
|
||||
jint pixelFormat) {
|
||||
ALOGV("nativeSetSurfaceType");
|
||||
sp<ANativeWindow> anw;
|
||||
if ((anw = getNativeWindow(env, surface)) == NULL) {
|
||||
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
status_t err = native_window_set_buffers_format(anw.get(), pixelFormat);
|
||||
if (err != NO_ERROR) {
|
||||
jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
|
||||
"Error while setting surface format (error code %d)", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void LegacyCameraDevice_nativeSetSurfaceDimens(JNIEnv* env, jobject thiz, jobject surface,
|
||||
jint width, jint height) {
|
||||
ALOGV("nativeSetSurfaceDimens");
|
||||
sp<ANativeWindow> anw;
|
||||
if ((anw = getNativeWindow(env, surface)) == NULL) {
|
||||
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
status_t err = native_window_set_buffers_dimensions(anw.get(), width, height);
|
||||
if (err != NO_ERROR) {
|
||||
jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
|
||||
"Error while setting surface format (error code %d)", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
static JNINativeMethod gCameraDeviceMethods[] = {
|
||||
{ "nativeDetectSurfaceType",
|
||||
"(Landroid/view/Surface;)I",
|
||||
(void *)LegacyCameraDevice_nativeDetectSurfaceType },
|
||||
{ "nativeDetectSurfaceDimens",
|
||||
"(Landroid/view/Surface;[I)V",
|
||||
(void *)LegacyCameraDevice_nativeDetectSurfaceDimens },
|
||||
{ "nativeConfigureSurface",
|
||||
"(Landroid/view/Surface;III)V",
|
||||
(void *)LegacyCameraDevice_nativeConfigureSurface },
|
||||
{ "nativeProduceFrame",
|
||||
"(Landroid/view/Surface;[BIII)V",
|
||||
(void *)LegacyCameraDevice_nativeProduceFrame },
|
||||
{ "nativeSetSurfaceFormat",
|
||||
"(Landroid/view/Surface;I)V",
|
||||
(void *)LegacyCameraDevice_nativeSetSurfaceFormat },
|
||||
{ "nativeSetSurfaceDimens",
|
||||
"(Landroid/view/Surface;II)V",
|
||||
(void *)LegacyCameraDevice_nativeSetSurfaceDimens },
|
||||
};
|
||||
|
||||
// Get all the required offsets in java class and register native functions
|
||||
int register_android_hardware_camera2_legacy_LegacyCameraDevice(JNIEnv* env)
|
||||
{
|
||||
// Register native functions
|
||||
return AndroidRuntime::registerNativeMethods(env,
|
||||
CAMERA_DEVICE_CLASS_NAME,
|
||||
gCameraDeviceMethods,
|
||||
NELEM(gCameraDeviceMethods));
|
||||
}
|
||||
|
||||
@@ -764,21 +764,30 @@ static jint ImageReader_imageSetup(JNIEnv* env, jobject thiz,
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ctx->getBufferFormat() != buffer->format) {
|
||||
// Return the buffer to the queue.
|
||||
consumer->unlockBuffer(*buffer);
|
||||
ctx->returnLockedBuffer(buffer);
|
||||
int imgReaderFmt = ctx->getBufferFormat();
|
||||
int bufFmt = buffer->format;
|
||||
if (imgReaderFmt != bufFmt) {
|
||||
// Special casing for when producer switches format
|
||||
if (imgReaderFmt == HAL_PIXEL_FORMAT_YCbCr_420_888 && bufFmt ==
|
||||
HAL_PIXEL_FORMAT_YCrCb_420_SP) {
|
||||
ctx->setBufferFormat(HAL_PIXEL_FORMAT_YCrCb_420_SP);
|
||||
ALOGV("%s: Overriding NV21 to YUV_420_888.", __FUNCTION__);
|
||||
} else {
|
||||
// Return the buffer to the queue.
|
||||
consumer->unlockBuffer(*buffer);
|
||||
ctx->returnLockedBuffer(buffer);
|
||||
|
||||
// Throw exception
|
||||
ALOGE("Producer output buffer format: 0x%x, ImageReader configured format: 0x%x",
|
||||
buffer->format, ctx->getBufferFormat());
|
||||
String8 msg;
|
||||
msg.appendFormat("The producer output buffer format 0x%x doesn't "
|
||||
"match the ImageReader's configured buffer format 0x%x.",
|
||||
buffer->format, ctx->getBufferFormat());
|
||||
jniThrowException(env, "java/lang/UnsupportedOperationException",
|
||||
msg.string());
|
||||
return -1;
|
||||
// Throw exception
|
||||
ALOGE("Producer output buffer format: 0x%x, ImageReader configured format: 0x%x",
|
||||
buffer->format, ctx->getBufferFormat());
|
||||
String8 msg;
|
||||
msg.appendFormat("The producer output buffer format 0x%x doesn't "
|
||||
"match the ImageReader's configured buffer format 0x%x.",
|
||||
buffer->format, ctx->getBufferFormat());
|
||||
jniThrowException(env, "java/lang/UnsupportedOperationException",
|
||||
msg.string());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
// Set SurfaceImage instance member variables
|
||||
Image_setBuffer(env, image, buffer);
|
||||
|
||||
Reference in New Issue
Block a user