Home | History | Annotate | Download | only in videosrc
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.filterpacks.videosrc;
     18 
     19 import android.filterfw.core.Filter;
     20 import android.filterfw.core.FilterContext;
     21 import android.filterfw.core.Frame;
     22 import android.filterfw.core.FrameFormat;
     23 import android.filterfw.core.GenerateFieldPort;
     24 import android.filterfw.core.GenerateFinalPort;
     25 import android.filterfw.core.GLFrame;
     26 import android.filterfw.core.MutableFrameFormat;
     27 import android.filterfw.core.ShaderProgram;
     28 import android.filterfw.format.ImageFormat;
     29 import android.graphics.SurfaceTexture;
     30 import android.os.ConditionVariable;
     31 import android.opengl.Matrix;
     32 
     33 import android.util.Log;
     34 
     35 /** <p>A filter that converts textures from a SurfaceTexture object into frames for
     36  * processing in the filter framework.</p>
     37  *
     38  * <p>To use, connect up the sourceListener callback, and then when executing
     39  * the graph, use the SurfaceTexture object passed to the callback to feed
     40  * frames into the filter graph. For example, pass the SurfaceTexture into
     41  * {#link
     42  * android.hardware.Camera.setPreviewTexture(android.graphics.SurfaceTexture)}.
     43  * This filter is intended for applications that need for flexibility than the
     44  * CameraSource and MediaSource provide. Note that the application needs to
     45  * provide width and height information for the SurfaceTextureSource, which it
     46  * should obtain from wherever the SurfaceTexture data is coming from to avoid
     47  * unnecessary resampling.</p>
     48  *
     49  * @hide
     50  */
     51 public class SurfaceTextureSource extends Filter {
     52 
     53     /** User-visible parameters */
     54 
     55     /** The callback interface for the sourceListener parameter */
     56     public interface SurfaceTextureSourceListener {
     57         public void onSurfaceTextureSourceReady(SurfaceTexture source);
     58     }
     59     /** A callback to send the internal SurfaceTexture object to, once it is
     60      * created. This callback will be called when the the filter graph is
     61      * preparing to execute, but before any processing has actually taken
     62      * place. The SurfaceTexture object passed to this callback is the only way
     63      * to feed this filter. When the filter graph is shutting down, this
     64      * callback will be called again with null as the source.
     65      *
     66      * This callback may be called from an arbitrary thread, so it should not
     67      * assume it is running in the UI thread in particular.
     68      */
     69     @GenerateFinalPort(name = "sourceListener")
     70     private SurfaceTextureSourceListener mSourceListener;
     71 
     72     /** The width of the output image frame. If the texture width for the
     73      * SurfaceTexture source is known, use it here to minimize resampling. */
     74     @GenerateFieldPort(name = "width")
     75     private int mWidth;
     76 
     77     /** The height of the output image frame. If the texture height for the
     78      * SurfaceTexture source is known, use it here to minimize resampling. */
     79     @GenerateFieldPort(name = "height")
     80     private int mHeight;
     81 
     82     /** Whether the filter will always wait for a new frame from its
     83      * SurfaceTexture, or whether it will output an old frame again if a new
     84      * frame isn't available. The filter will always wait for the first frame,
     85      * to avoid outputting a blank frame. Defaults to true.
     86      */
     87     @GenerateFieldPort(name = "waitForNewFrame", hasDefault = true)
     88     private boolean mWaitForNewFrame = true;
     89 
     90     /** Maximum timeout before signaling error when waiting for a new frame. Set
     91      * this to zero to disable the timeout and wait indefinitely. In milliseconds.
     92      */
     93     @GenerateFieldPort(name = "waitTimeout", hasDefault = true)
     94     private int mWaitTimeout = 1000;
     95 
     96     /** Whether a timeout is an exception-causing failure, or just causes the
     97      * filter to close.
     98      */
     99     @GenerateFieldPort(name = "closeOnTimeout", hasDefault = true)
    100     private boolean mCloseOnTimeout = false;
    101 
    102     // Variables for input->output conversion
    103     private GLFrame mMediaFrame;
    104     private ShaderProgram mFrameExtractor;
    105     private SurfaceTexture mSurfaceTexture;
    106     private MutableFrameFormat mOutputFormat;
    107     private ConditionVariable mNewFrameAvailable;
    108     private boolean mFirstFrame;
    109 
    110     private float[] mFrameTransform;
    111     private float[] mMappedCoords;
    112     // These default source coordinates perform the necessary flip
    113     // for converting from MFF/Bitmap origin to OpenGL origin.
    114     private static final float[] mSourceCoords = { 0, 1, 0, 1,
    115                                                    1, 1, 0, 1,
    116                                                    0, 0, 0, 1,
    117                                                    1, 0, 0, 1 };
    118     // Shader for output
    119     private final String mRenderShader =
    120             "#extension GL_OES_EGL_image_external : require\n" +
    121             "precision mediump float;\n" +
    122             "uniform samplerExternalOES tex_sampler_0;\n" +
    123             "varying vec2 v_texcoord;\n" +
    124             "void main() {\n" +
    125             "  gl_FragColor = texture2D(tex_sampler_0, v_texcoord);\n" +
    126             "}\n";
    127 
    128     // Variables for logging
    129 
    130     private static final String TAG = "SurfaceTextureSource";
    131     private static final boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
    132 
    133     public SurfaceTextureSource(String name) {
    134         super(name);
    135         mNewFrameAvailable = new ConditionVariable();
    136         mFrameTransform = new float[16];
    137         mMappedCoords = new float[16];
    138     }
    139 
    140     @Override
    141     public void setupPorts() {
    142         // Add input port
    143         addOutputPort("video", ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
    144                                                   FrameFormat.TARGET_GPU));
    145     }
    146 
    147     private void createFormats() {
    148         mOutputFormat = ImageFormat.create(mWidth, mHeight,
    149                                            ImageFormat.COLORSPACE_RGBA,
    150                                            FrameFormat.TARGET_GPU);
    151     }
    152 
    153     @Override
    154     protected void prepare(FilterContext context) {
    155         if (mLogVerbose) Log.v(TAG, "Preparing SurfaceTextureSource");
    156 
    157         createFormats();
    158 
    159         // Prepare input
    160         mMediaFrame = (GLFrame)context.getFrameManager().newBoundFrame(mOutputFormat,
    161                                                                        GLFrame.EXTERNAL_TEXTURE,
    162                                                                        0);
    163 
    164         // Prepare output
    165         mFrameExtractor = new ShaderProgram(context, mRenderShader);
    166     }
    167 
    168     @Override
    169     public void open(FilterContext context) {
    170         if (mLogVerbose) Log.v(TAG, "Opening SurfaceTextureSource");
    171         // Create SurfaceTexture anew each time - it can use substantial memory.
    172         mSurfaceTexture = new SurfaceTexture(mMediaFrame.getTextureId());
    173         // Connect SurfaceTexture to callback
    174         mSurfaceTexture.setOnFrameAvailableListener(onFrameAvailableListener);
    175         // Connect SurfaceTexture to source
    176         mSourceListener.onSurfaceTextureSourceReady(mSurfaceTexture);
    177         mFirstFrame = true;
    178     }
    179 
    180     @Override
    181     public void process(FilterContext context) {
    182         if (mLogVerbose) Log.v(TAG, "Processing new frame");
    183 
    184         // First, get new frame if available
    185         if (mWaitForNewFrame || mFirstFrame) {
    186             boolean gotNewFrame;
    187             if (mWaitTimeout != 0) {
    188                 gotNewFrame = mNewFrameAvailable.block(mWaitTimeout);
    189                 if (!gotNewFrame) {
    190                     if (!mCloseOnTimeout) {
    191                         throw new RuntimeException("Timeout waiting for new frame");
    192                     } else {
    193                         if (mLogVerbose) Log.v(TAG, "Timeout waiting for a new frame. Closing.");
    194                         closeOutputPort("video");
    195                         return;
    196                     }
    197                 }
    198             } else {
    199                 mNewFrameAvailable.block();
    200             }
    201             mNewFrameAvailable.close();
    202             mFirstFrame = false;
    203         }
    204 
    205         mSurfaceTexture.updateTexImage();
    206 
    207         mSurfaceTexture.getTransformMatrix(mFrameTransform);
    208         Matrix.multiplyMM(mMappedCoords, 0,
    209                           mFrameTransform, 0,
    210                           mSourceCoords, 0);
    211         mFrameExtractor.setSourceRegion(mMappedCoords[0], mMappedCoords[1],
    212                                         mMappedCoords[4], mMappedCoords[5],
    213                                         mMappedCoords[8], mMappedCoords[9],
    214                                         mMappedCoords[12], mMappedCoords[13]);
    215         // Next, render to output
    216         Frame output = context.getFrameManager().newFrame(mOutputFormat);
    217         mFrameExtractor.process(mMediaFrame, output);
    218 
    219         output.setTimestamp(mSurfaceTexture.getTimestamp());
    220 
    221         pushOutput("video", output);
    222         output.release();
    223     }
    224 
    225     @Override
    226     public void close(FilterContext context) {
    227         if (mLogVerbose) Log.v(TAG, "SurfaceTextureSource closed");
    228         mSourceListener.onSurfaceTextureSourceReady(null);
    229         mSurfaceTexture.release();
    230         mSurfaceTexture = null;
    231     }
    232 
    233     @Override
    234     public void tearDown(FilterContext context) {
    235         if (mMediaFrame != null) {
    236             mMediaFrame.release();
    237         }
    238     }
    239 
    240     @Override
    241     public void fieldPortValueUpdated(String name, FilterContext context) {
    242         if (name.equals("width") || name.equals("height") ) {
    243             mOutputFormat.setDimensions(mWidth, mHeight);
    244         }
    245     }
    246 
    247     private SurfaceTexture.OnFrameAvailableListener onFrameAvailableListener =
    248             new SurfaceTexture.OnFrameAvailableListener() {
    249         public void onFrameAvailable(SurfaceTexture surfaceTexture) {
    250             if (mLogVerbose) Log.v(TAG, "New frame from SurfaceTexture");
    251             mNewFrameAvailable.open();
    252         }
    253     };
    254 }
    255