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