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:
Ruben Brunk
2014-05-09 19:58:49 -07:00
parent 2a2dace665
commit feb50af361
25 changed files with 3108 additions and 41 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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.
*

View File

@@ -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

View File

@@ -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." +

View File

@@ -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();

View File

@@ -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;

View 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;
}
}

View 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);
}
}
}

View File

@@ -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;
}
}

View 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);
}
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View 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;
}
}

View 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");
}
}

View File

@@ -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();
}
}

View File

@@ -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.");
}
}

View File

@@ -0,0 +1,3 @@
<body>
{@hide}
</body>

View File

@@ -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."));
}
/**

View File

@@ -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 \

View File

@@ -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),

View File

@@ -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 },

View File

@@ -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);

View 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));
}

View File

@@ -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);