blob: 7919723b80a89b17cd18a4b655d4fe2c3c918f7b [file] [log] [blame]
/*
* Copyright (C) 2011 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.filterpacks.videosrc;
import android.filterfw.core.Filter;
import android.filterfw.core.FilterContext;
import android.filterfw.core.Frame;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.GenerateFieldPort;
import android.filterfw.core.GenerateFinalPort;
import android.filterfw.core.GLFrame;
import android.filterfw.core.MutableFrameFormat;
import android.filterfw.core.ShaderProgram;
import android.filterfw.format.ImageFormat;
import android.graphics.SurfaceTexture;
import android.os.ConditionVariable;
import android.opengl.Matrix;
import android.util.Log;
/** <p>A filter that converts textures from a SurfaceTexture object into frames for
* processing in the filter framework.</p>
*
* <p>To use, connect up the sourceListener callback, and then when executing
* the graph, use the SurfaceTexture object passed to the callback to feed
* frames into the filter graph. For example, pass the SurfaceTexture into
* {@link
* android.hardware.Camera.setPreviewTexture(android.graphics.SurfaceTexture)}.
* This filter is intended for applications that need for flexibility than the
* CameraSource and MediaSource provide. Note that the application needs to
* provide width and height information for the SurfaceTextureSource, which it
* should obtain from wherever the SurfaceTexture data is coming from to avoid
* unnecessary resampling.</p>
*
* @hide
*/
public class SurfaceTextureSource extends Filter {
/** User-visible parameters */
/** The callback interface for the sourceListener parameter */
public interface SurfaceTextureSourceListener {
public void onSurfaceTextureSourceReady(SurfaceTexture source);
}
/** A callback to send the internal SurfaceTexture object to, once it is
* created. This callback will be called when the the filter graph is
* preparing to execute, but before any processing has actually taken
* place. The SurfaceTexture object passed to this callback is the only way
* to feed this filter. When the filter graph is shutting down, this
* callback will be called again with null as the source.
*
* This callback may be called from an arbitrary thread, so it should not
* assume it is running in the UI thread in particular.
*/
@GenerateFinalPort(name = "sourceListener")
private SurfaceTextureSourceListener mSourceListener;
/** The width of the output image frame. If the texture width for the
* SurfaceTexture source is known, use it here to minimize resampling. */
@GenerateFieldPort(name = "width")
private int mWidth;
/** The height of the output image frame. If the texture height for the
* SurfaceTexture source is known, use it here to minimize resampling. */
@GenerateFieldPort(name = "height")
private int mHeight;
/** Whether the filter will always wait for a new frame from its
* SurfaceTexture, or whether it will output an old frame again if a new
* frame isn't available. The filter will always wait for the first frame,
* to avoid outputting a blank frame. Defaults to true.
*/
@GenerateFieldPort(name = "waitForNewFrame", hasDefault = true)
private boolean mWaitForNewFrame = true;
/** Maximum timeout before signaling error when waiting for a new frame. Set
* this to zero to disable the timeout and wait indefinitely. In milliseconds.
*/
@GenerateFieldPort(name = "waitTimeout", hasDefault = true)
private int mWaitTimeout = 1000;
/** Whether a timeout is an exception-causing failure, or just causes the
* filter to close.
*/
@GenerateFieldPort(name = "closeOnTimeout", hasDefault = true)
private boolean mCloseOnTimeout = false;
// Variables for input->output conversion
private GLFrame mMediaFrame;
private ShaderProgram mFrameExtractor;
private SurfaceTexture mSurfaceTexture;
private MutableFrameFormat mOutputFormat;
private ConditionVariable mNewFrameAvailable;
private boolean mFirstFrame;
private float[] mFrameTransform;
private float[] mMappedCoords;
// These default source coordinates perform the necessary flip
// for converting from MFF/Bitmap origin to OpenGL origin.
private static final float[] mSourceCoords = { 0, 1, 0, 1,
1, 1, 0, 1,
0, 0, 0, 1,
1, 0, 0, 1 };
// Shader for output
private final String mRenderShader =
"#extension GL_OES_EGL_image_external : require\n" +
"precision mediump float;\n" +
"uniform samplerExternalOES tex_sampler_0;\n" +
"varying vec2 v_texcoord;\n" +
"void main() {\n" +
" gl_FragColor = texture2D(tex_sampler_0, v_texcoord);\n" +
"}\n";
// Variables for logging
private static final String TAG = "SurfaceTextureSource";
private static final boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
public SurfaceTextureSource(String name) {
super(name);
mNewFrameAvailable = new ConditionVariable();
mFrameTransform = new float[16];
mMappedCoords = new float[16];
}
@Override
public void setupPorts() {
// Add input port
addOutputPort("video", ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
FrameFormat.TARGET_GPU));
}
private void createFormats() {
mOutputFormat = ImageFormat.create(mWidth, mHeight,
ImageFormat.COLORSPACE_RGBA,
FrameFormat.TARGET_GPU);
}
@Override
protected void prepare(FilterContext context) {
if (mLogVerbose) Log.v(TAG, "Preparing SurfaceTextureSource");
createFormats();
// Prepare input
mMediaFrame = (GLFrame)context.getFrameManager().newBoundFrame(mOutputFormat,
GLFrame.EXTERNAL_TEXTURE,
0);
// Prepare output
mFrameExtractor = new ShaderProgram(context, mRenderShader);
}
@Override
public void open(FilterContext context) {
if (mLogVerbose) Log.v(TAG, "Opening SurfaceTextureSource");
// Create SurfaceTexture anew each time - it can use substantial memory.
mSurfaceTexture = new SurfaceTexture(mMediaFrame.getTextureId());
// Connect SurfaceTexture to callback
mSurfaceTexture.setOnFrameAvailableListener(onFrameAvailableListener);
// Connect SurfaceTexture to source
mSourceListener.onSurfaceTextureSourceReady(mSurfaceTexture);
mFirstFrame = true;
}
@Override
public void process(FilterContext context) {
if (mLogVerbose) Log.v(TAG, "Processing new frame");
// First, get new frame if available
if (mWaitForNewFrame || mFirstFrame) {
boolean gotNewFrame;
if (mWaitTimeout != 0) {
gotNewFrame = mNewFrameAvailable.block(mWaitTimeout);
if (!gotNewFrame) {
if (!mCloseOnTimeout) {
throw new RuntimeException("Timeout waiting for new frame");
} else {
if (mLogVerbose) Log.v(TAG, "Timeout waiting for a new frame. Closing.");
closeOutputPort("video");
return;
}
}
} else {
mNewFrameAvailable.block();
}
mNewFrameAvailable.close();
mFirstFrame = false;
}
mSurfaceTexture.updateTexImage();
mSurfaceTexture.getTransformMatrix(mFrameTransform);
Matrix.multiplyMM(mMappedCoords, 0,
mFrameTransform, 0,
mSourceCoords, 0);
mFrameExtractor.setSourceRegion(mMappedCoords[0], mMappedCoords[1],
mMappedCoords[4], mMappedCoords[5],
mMappedCoords[8], mMappedCoords[9],
mMappedCoords[12], mMappedCoords[13]);
// Next, render to output
Frame output = context.getFrameManager().newFrame(mOutputFormat);
mFrameExtractor.process(mMediaFrame, output);
output.setTimestamp(mSurfaceTexture.getTimestamp());
pushOutput("video", output);
output.release();
}
@Override
public void close(FilterContext context) {
if (mLogVerbose) Log.v(TAG, "SurfaceTextureSource closed");
mSourceListener.onSurfaceTextureSourceReady(null);
mSurfaceTexture.release();
mSurfaceTexture = null;
}
@Override
public void tearDown(FilterContext context) {
if (mMediaFrame != null) {
mMediaFrame.release();
}
}
@Override
public void fieldPortValueUpdated(String name, FilterContext context) {
if (name.equals("width") || name.equals("height") ) {
mOutputFormat.setDimensions(mWidth, mHeight);
}
}
private SurfaceTexture.OnFrameAvailableListener onFrameAvailableListener =
new SurfaceTexture.OnFrameAvailableListener() {
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
if (mLogVerbose) Log.v(TAG, "New frame from SurfaceTexture");
mNewFrameAvailable.open();
}
};
}