/*
* 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.hardware.camera2.impl.CameraDeviceImpl;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.util.Pair;
import android.util.Size;
import android.view.Surface;
import java.util.Collection;
import static com.android.internal.util.Preconditions.*;
/**
* GLThreadManager handles the thread used for rendering into the configured output surfaces.
*/
public class GLThreadManager {
private final String TAG;
private static final boolean DEBUG = false;
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 CaptureCollector mCaptureCollector;
private final CameraDeviceState mDeviceState;
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<Pair<Surface, Size>> surfaces;
public final CaptureCollector collector;
public ConfigureHolder(ConditionVariable condition, Collection<Pair<Surface,
Size>> surfaces, CaptureCollector collector) {
this.condition = condition;
this.surfaces = surfaces;
this.collector = collector;
}
}
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;
}
try {
switch (msg.what) {
case MSG_NEW_CONFIGURATION:
ConfigureHolder configure = (ConfigureHolder) msg.obj;
mTextureRenderer.cleanupEGLContext();
mTextureRenderer.configureSurfaces(configure.surfaces);
mCaptureCollector = checkNotNull(configure.collector);
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(mCaptureCollector);
break;
case MSG_CLEANUP:
mTextureRenderer.cleanupEGLContext();
mCleanup = true;
mConfigured = false;
break;
case MSG_DROP_FRAMES:
mDroppingFrames = true;
break;
case MSG_ALLOW_FRAMES:
mDroppingFrames = false;
break;
case RequestHandlerThread.MSG_POKE_IDLE_HANDLER:
// OK: Ignore message.
break;
default:
Log.e(TAG, "Unhandled message " + msg.what + " on GLThread.");
break;
}
} catch (Exception e) {
Log.e(TAG, "Received exception on GL render thread: ", e);
mDeviceState.setError(CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
}
return true;
}
};
/**
* Create a new GL thread and renderer.
*
* @param cameraId the camera id for this thread.
* @param facing direction the camera is facing.
* @param state {@link CameraDeviceState} to use for error handling.
*/
public GLThreadManager(int cameraId, int facing, CameraDeviceState state) {
mTextureRenderer = new SurfaceTextureRenderer(facing);
TAG = String.format("CameraDeviceGLThread-%d", cameraId);
mGLHandlerThread = new RequestHandlerThread(TAG, mGLHandlerCb);
mDeviceState = state;
}
/**
* 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();
try {
mGLHandlerThread.join();
} catch (InterruptedException e) {
Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
mGLHandlerThread.getName(), mGLHandlerThread.getId()));
}
}
/**
* Queue a new call to draw into the surfaces specified in the next available preview
* request from the {@link CaptureCollector} passed to
* {@link #setConfigurationAndWait(java.util.Collection, CaptureCollector)};
*/
public void queueNewFrame() {
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));
} 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 pairs of {@link android.view.Surface}s and their
* corresponding sizes to configure.
* @param collector a {@link CaptureCollector} to retrieve requests from.
*/
public void setConfigurationAndWait(Collection<Pair<Surface, Size>> surfaces,
CaptureCollector collector) {
checkNotNull(collector, "collector must not be null");
Handler handler = mGLHandlerThread.getHandler();
final ConditionVariable condition = new ConditionVariable(/*closed*/false);
ConfigureHolder configure = new ConfigureHolder(condition, surfaces, collector);
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);
}
}