blob: 54998aca07908b918c91574fcaf3e63238675f69 [file] [log] [blame]
/*
* 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 com.android.camera.gl;
import android.graphics.SurfaceTexture;
import android.graphics.SurfaceTexture.OnFrameAvailableListener;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Size;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Implementation of {@link FrameDistributor}.
* <p>
* The typical way to use this is as follows:
* <ol>
* <li>Create a new instance and add all the {@link FrameConsumer} instances.
* </li>
* <li>Call {@link #start()} on the distributor.</li>
* <li>Obtain the {@link SurfaceTexture} used by the distributor and hook it up
* to your producer e.g. a Camera instance.</li>
* <li>Your consumers now start receiving
* {@link FrameConsumer#onNewFrameAvailable(FrameDistributor)} callbacks for new
* frames.</li>
* <li>Each consumer grabs the most current frame from its GL thread by calling
* {@link #acquireNextFrame(int, float[])}.</li>
* <li>After accessing the data, the consumer calls {@link #releaseFrame()}.
* </li>
* <li>When all processing is complete, call {@link #stop()}.</li>
* </ol>
*/
class FrameDistributorImpl implements FrameDistributor, AutoCloseable {
private final DistributionHandler mDistributionHandler;
private final HandlerThread mDistributionThread;
private static class DistributionHandler extends Handler implements OnFrameAvailableListener {
public static final int MSG_SETUP = 1;
public static final int MSG_RELEASE = 2;
public static final int MSG_UPDATE_SURFACE = 3;
public static final int MSG_UPDATE_PREVIEW_BUFFER_SIZE = 4;
private static final int DEFAULT_SURFACE_BUFFER_WIDTH = 1440;
private static final int DEFAULT_SURFACE_BUFFER_HEIGHT = 1080;
private final FrameDistributorImpl mDistributor;
private final AtomicBoolean mNewFrame = new AtomicBoolean(false);
final ConditionVariable mCommandDoneCondition = new ConditionVariable(true);
private final Lock mSurfaceTextureAccessLock = new ReentrantLock();
private final List<FrameConsumer> mConsumers;
private SurfaceTexture mSurfaceTexture;
private long mTimestamp;
private int mTexture;
private RenderTarget mServerTarget;
private boolean mIsSetup = false;
public DistributionHandler(FrameDistributorImpl distributor, Looper looper,
List<FrameConsumer> consumers) {
super(looper);
mDistributor = distributor;
mConsumers = consumers;
}
@Override
public void handleMessage(Message message) {
try {
switch (message.what) {
case MSG_SETUP:
setup();
break;
case MSG_UPDATE_SURFACE:
updateSurfaceTexture();
break;
case MSG_RELEASE:
release();
break;
case MSG_UPDATE_PREVIEW_BUFFER_SIZE:
updatePreviewBufferSize((Size) message.obj);
break;
default:
throw new IllegalStateException("Unknown message: " + message + "!");
}
} finally {
mCommandDoneCondition.open();
}
}
private synchronized void setup() {
if (!mIsSetup) {
mServerTarget = RenderTarget.newTarget(1, 1);
mServerTarget.focus();
mSurfaceTexture = createSurfaceTexture();
informListenersOfStart();
mIsSetup = true;
}
}
private synchronized void release() {
if (mIsSetup) {
// Notify listeners
informListenersOfStop();
// Release our resources
mServerTarget.close();
mServerTarget = null;
mSurfaceTexture.release();
mSurfaceTexture = null;
GLToolbox.deleteTexture(mTexture);
// It is VERY important we unfocus the current EGL context, as
// the SurfaceTextures
// will not properly detach if this is not done.
RenderTarget.focusNone();
// Update internal state
mNewFrame.set(false);
mIsSetup = false;
}
}
private void updateSurfaceTexture() {
if (mIsSetup) {
mSurfaceTextureAccessLock.lock();
mSurfaceTexture.attachToGLContext(mTexture);
mSurfaceTexture.updateTexImage();
mSurfaceTexture.detachFromGLContext();
mSurfaceTextureAccessLock.unlock();
mTimestamp = mSurfaceTexture.getTimestamp();
informListenersOfNewFrame(mTimestamp);
}
}
private void updatePreviewBufferSize(Size size) {
if (size != null && mIsSetup) {
mSurfaceTexture.setDefaultBufferSize(size.getWidth(), size.getHeight());
}
}
public void postMessageType(int kind) {
mCommandDoneCondition.close();
sendMessage(Message.obtain(this, kind));
}
public void postMessageType(int kind, Object params) {
mCommandDoneCondition.close();
sendMessage(Message.obtain(this, kind, params));
}
private void informListenersOfStart() {
synchronized (mConsumers) {
for (FrameConsumer consumer : mConsumers) {
consumer.onStart();
}
}
}
private void informListenersOfNewFrame(long timestamp) {
synchronized (mConsumers) {
for (FrameConsumer consumer : mConsumers) {
consumer.onNewFrameAvailable(mDistributor, timestamp);
}
}
}
private void informListenersOfStop() {
synchronized (mConsumers) {
for (FrameConsumer consumer : mConsumers) {
consumer.onStop();
}
}
}
public long acquireNextFrame(int textureName, float[] transform) {
if (transform == null || transform.length != 16) {
throw new
IllegalArgumentException("acquireNextFrame: invalid transform array.");
}
mSurfaceTextureAccessLock.lock();
mSurfaceTexture.attachToGLContext(textureName);
mSurfaceTexture.getTransformMatrix(transform);
return mTimestamp;
}
public void releaseFrame() {
mSurfaceTexture.detachFromGLContext();
mSurfaceTextureAccessLock.unlock();
}
public synchronized SurfaceTexture getSurfaceTexture() {
return mSurfaceTexture;
}
public synchronized RenderTarget getRenderTarget() {
return mServerTarget;
}
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
postMessageType(MSG_UPDATE_SURFACE);
}
private SurfaceTexture createSurfaceTexture() {
mTexture = GLToolbox.generateTexture();
SurfaceTexture surfaceTexture = new SurfaceTexture(mTexture);
surfaceTexture.setDefaultBufferSize(DEFAULT_SURFACE_BUFFER_WIDTH,
DEFAULT_SURFACE_BUFFER_HEIGHT);
surfaceTexture.setOnFrameAvailableListener(this);
surfaceTexture.detachFromGLContext();
return surfaceTexture;
}
}
/**
* Creates a new distributor with the specified consumers.
*
* @param consumers list of consumers that will process incoming frames.
*/
public FrameDistributorImpl(List<FrameConsumer> consumers) {
mDistributionThread = new HandlerThread("FrameDistributor");
mDistributionThread.start();
mDistributionHandler = new DistributionHandler(this, mDistributionThread.getLooper(),
consumers);
}
/**
* Start processing frames and sending them to consumers.
*/
public void start() {
mDistributionHandler.postMessageType(DistributionHandler.MSG_SETUP);
}
/**
* Stop processing frames and release any resources required for doing so.
*/
public void stop() {
mDistributionHandler.postMessageType(DistributionHandler.MSG_RELEASE);
}
/**
* Wait until the current start/stop command has finished executing.
* <p>
* Use this command if you need to make sure that the distributor has fully
* started or stopped.
*/
public void waitForCommand() {
mDistributionHandler.mCommandDoneCondition.block();
}
/**
* Close the current distributor and release its resources.
* <p>
* You must not use the distributor after calling this method.
*/
@Override
public void close() {
stop();
mDistributionThread.quitSafely();
}
@Override
public long acquireNextFrame(int textureName, float[] transform) {
return mDistributionHandler.acquireNextFrame(textureName, transform);
}
@Override
public void releaseFrame() {
mDistributionHandler.releaseFrame();
}
/**
* Get the {@link SurfaceTexture} whose frames will be distributed.
* <p>
* You must call this after distribution has started with a call to
* {@link #start()}.
*
* @return the input SurfaceTexture or null, if none is yet available.
*/
public SurfaceTexture getInputSurfaceTexture() {
return mDistributionHandler.getSurfaceTexture();
}
/**
* Get the {@link RenderTarget} that the distributor uses for GL operations.
* <p>
* You should rarely need to use this method. It is used exclusively by
* consumers that reuse the FrameDistributor's EGL context, and must be
* handled with great care.
*
* @return the RenderTarget used by the FrameDistributor.
*/
@Override
public RenderTarget getRenderTarget() {
return mDistributionHandler.getRenderTarget();
}
/**
* Update the default buffer size of the input {@link SurfaceTexture}.
*
* @param width the new value of width of the preview buffer.
* @param height the new value of height of the preview buffer.
*/
public void updatePreviewBufferSize(int width, int height) {
mDistributionHandler.postMessageType(DistributionHandler.MSG_UPDATE_PREVIEW_BUFFER_SIZE,
new Size(width, height));
}
}