| OLD | NEW | 
|---|
|  | (Empty) | 
| 1 /* |  | 
| 2  *  Copyright 2015 The WebRTC project authors. All Rights Reserved. |  | 
| 3  * |  | 
| 4  *  Use of this source code is governed by a BSD-style license |  | 
| 5  *  that can be found in the LICENSE file in the root of the source |  | 
| 6  *  tree. An additional intellectual property rights grant can be found |  | 
| 7  *  in the file PATENTS.  All contributing project authors may |  | 
| 8  *  be found in the AUTHORS file in the root of the source tree. |  | 
| 9  */ |  | 
| 10 |  | 
| 11 package org.webrtc; |  | 
| 12 |  | 
| 13 import android.graphics.SurfaceTexture; |  | 
| 14 import android.opengl.GLES11Ext; |  | 
| 15 import android.opengl.GLES20; |  | 
| 16 import android.os.Build; |  | 
| 17 import android.os.Handler; |  | 
| 18 import android.os.HandlerThread; |  | 
| 19 import android.os.SystemClock; |  | 
| 20 |  | 
| 21 import java.nio.ByteBuffer; |  | 
| 22 import java.nio.FloatBuffer; |  | 
| 23 import java.util.concurrent.Callable; |  | 
| 24 import java.util.concurrent.TimeUnit; |  | 
| 25 |  | 
| 26 /** |  | 
| 27  * Helper class to create and synchronize access to a SurfaceTexture. The caller
      will get notified |  | 
| 28  * of new frames in onTextureFrameAvailable(), and should call returnTextureFram
     e() when done with |  | 
| 29  * the frame. Only one texture frame can be in flight at once, so returnTextureF
     rame() must be |  | 
| 30  * called in order to receive a new frame. Call stopListening() to stop receivei
     ng new frames. Call |  | 
| 31  * dispose to release all resources once the texture frame is returned. |  | 
| 32  * Note that there is a C++ counter part of this class that optionally can be us
     ed. It is used for |  | 
| 33  * wrapping texture frames into webrtc::VideoFrames and also handles calling ret
     urnTextureFrame() |  | 
| 34  * when the webrtc::VideoFrame is no longer used. |  | 
| 35  */ |  | 
| 36 public class SurfaceTextureHelper { |  | 
| 37   private static final String TAG = "SurfaceTextureHelper"; |  | 
| 38   /** |  | 
| 39    * Callback interface for being notified that a new texture frame is available
     . The calls will be |  | 
| 40    * made on a dedicated thread with a bound EGLContext. The thread will be the 
     same throughout the |  | 
| 41    * lifetime of the SurfaceTextureHelper instance, but different from the threa
     d calling the |  | 
| 42    * SurfaceTextureHelper constructor. The callee is not allowed to make another
      EGLContext current |  | 
| 43    * on the calling thread. |  | 
| 44    */ |  | 
| 45   public interface OnTextureFrameAvailableListener { |  | 
| 46     abstract void onTextureFrameAvailable( |  | 
| 47         int oesTextureId, float[] transformMatrix, long timestampNs); |  | 
| 48   } |  | 
| 49 |  | 
| 50   /** |  | 
| 51    * Construct a new SurfaceTextureHelper sharing OpenGL resources with |sharedC
     ontext|. A dedicated |  | 
| 52    * thread and handler is created for handling the SurfaceTexture. May return n
     ull if EGL fails to |  | 
| 53    * initialize a pixel buffer surface and make it current. |  | 
| 54    */ |  | 
| 55   public static SurfaceTextureHelper create( |  | 
| 56       final String threadName, final EglBase.Context sharedContext) { |  | 
| 57     final HandlerThread thread = new HandlerThread(threadName); |  | 
| 58     thread.start(); |  | 
| 59     final Handler handler = new Handler(thread.getLooper()); |  | 
| 60 |  | 
| 61     // The onFrameAvailable() callback will be executed on the SurfaceTexture ct
     or thread. See: |  | 
| 62     // http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.andr
     oid/android/5.1.1_r1/android/graphics/SurfaceTexture.java#195. |  | 
| 63     // Therefore, in order to control the callback thread on API lvl < 21, the S
     urfaceTextureHelper |  | 
| 64     // is constructed on the |handler| thread. |  | 
| 65     return ThreadUtils.invokeAtFrontUninterruptibly(handler, new Callable<Surfac
     eTextureHelper>() { |  | 
| 66       @Override |  | 
| 67       public SurfaceTextureHelper call() { |  | 
| 68         try { |  | 
| 69           return new SurfaceTextureHelper(sharedContext, handler); |  | 
| 70         } catch (RuntimeException e) { |  | 
| 71           Logging.e(TAG, threadName + " create failure", e); |  | 
| 72           return null; |  | 
| 73         } |  | 
| 74       } |  | 
| 75     }); |  | 
| 76   } |  | 
| 77 |  | 
| 78   private final Handler handler; |  | 
| 79   private final EglBase eglBase; |  | 
| 80   private final SurfaceTexture surfaceTexture; |  | 
| 81   private final int oesTextureId; |  | 
| 82   private YuvConverter yuvConverter; |  | 
| 83 |  | 
| 84   // These variables are only accessed from the |handler| thread. |  | 
| 85   private OnTextureFrameAvailableListener listener; |  | 
| 86   // The possible states of this class. |  | 
| 87   private boolean hasPendingTexture = false; |  | 
| 88   private volatile boolean isTextureInUse = false; |  | 
| 89   private boolean isQuitting = false; |  | 
| 90   // |pendingListener| is set in setListener() and the runnable is posted to the
      handler thread. |  | 
| 91   // setListener() is not allowed to be called again before stopListening(), so 
     this is thread safe. |  | 
| 92   private OnTextureFrameAvailableListener pendingListener; |  | 
| 93   final Runnable setListenerRunnable = new Runnable() { |  | 
| 94     @Override |  | 
| 95     public void run() { |  | 
| 96       Logging.d(TAG, "Setting listener to " + pendingListener); |  | 
| 97       listener = pendingListener; |  | 
| 98       pendingListener = null; |  | 
| 99       // May have a pending frame from the previous capture session - drop it. |  | 
| 100       if (hasPendingTexture) { |  | 
| 101         // Calling updateTexImage() is neccessary in order to receive new frames
     . |  | 
| 102         updateTexImage(); |  | 
| 103         hasPendingTexture = false; |  | 
| 104       } |  | 
| 105     } |  | 
| 106   }; |  | 
| 107 |  | 
| 108   private SurfaceTextureHelper(EglBase.Context sharedContext, Handler handler) { |  | 
| 109     if (handler.getLooper().getThread() != Thread.currentThread()) { |  | 
| 110       throw new IllegalStateException("SurfaceTextureHelper must be created on t
     he handler thread"); |  | 
| 111     } |  | 
| 112     this.handler = handler; |  | 
| 113 |  | 
| 114     eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PIXEL_BUFFER); |  | 
| 115     try { |  | 
| 116       // Both these statements have been observed to fail on rare occasions, see
      BUG=webrtc:5682. |  | 
| 117       eglBase.createDummyPbufferSurface(); |  | 
| 118       eglBase.makeCurrent(); |  | 
| 119     } catch (RuntimeException e) { |  | 
| 120       // Clean up before rethrowing the exception. |  | 
| 121       eglBase.release(); |  | 
| 122       handler.getLooper().quit(); |  | 
| 123       throw e; |  | 
| 124     } |  | 
| 125 |  | 
| 126     oesTextureId = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES); |  | 
| 127     surfaceTexture = new SurfaceTexture(oesTextureId); |  | 
| 128     surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailab
     leListener() { |  | 
| 129       @Override |  | 
| 130       public void onFrameAvailable(SurfaceTexture surfaceTexture) { |  | 
| 131         hasPendingTexture = true; |  | 
| 132         tryDeliverTextureFrame(); |  | 
| 133       } |  | 
| 134     }); |  | 
| 135   } |  | 
| 136 |  | 
| 137   /** |  | 
| 138    * Start to stream textures to the given |listener|. If you need to change lis
     tener, you need to |  | 
| 139    * call stopListening() first. |  | 
| 140    */ |  | 
| 141   public void startListening(final OnTextureFrameAvailableListener listener) { |  | 
| 142     if (this.listener != null || this.pendingListener != null) { |  | 
| 143       throw new IllegalStateException("SurfaceTextureHelper listener has already
      been set."); |  | 
| 144     } |  | 
| 145     this.pendingListener = listener; |  | 
| 146     handler.post(setListenerRunnable); |  | 
| 147   } |  | 
| 148 |  | 
| 149   /** |  | 
| 150    * Stop listening. The listener set in startListening() is guaranteded to not 
     receive any more |  | 
| 151    * onTextureFrameAvailable() callbacks after this function returns. |  | 
| 152    */ |  | 
| 153   public void stopListening() { |  | 
| 154     Logging.d(TAG, "stopListening()"); |  | 
| 155     handler.removeCallbacks(setListenerRunnable); |  | 
| 156     ThreadUtils.invokeAtFrontUninterruptibly(handler, new Runnable() { |  | 
| 157       @Override |  | 
| 158       public void run() { |  | 
| 159         listener = null; |  | 
| 160         pendingListener = null; |  | 
| 161       } |  | 
| 162     }); |  | 
| 163   } |  | 
| 164 |  | 
| 165   /** |  | 
| 166    * Retrieve the underlying SurfaceTexture. The SurfaceTexture should be passed
      in to a video |  | 
| 167    * producer such as a camera or decoder. |  | 
| 168    */ |  | 
| 169   public SurfaceTexture getSurfaceTexture() { |  | 
| 170     return surfaceTexture; |  | 
| 171   } |  | 
| 172 |  | 
| 173   /** |  | 
| 174    * Retrieve the handler that calls onTextureFrameAvailable(). This handler is 
     valid until |  | 
| 175    * dispose() is called. |  | 
| 176    */ |  | 
| 177   public Handler getHandler() { |  | 
| 178     return handler; |  | 
| 179   } |  | 
| 180 |  | 
| 181   /** |  | 
| 182    * Call this function to signal that you are done with the frame received in |  | 
| 183    * onTextureFrameAvailable(). Only one texture frame can be in flight at once,
      so you must call |  | 
| 184    * this function in order to receive a new frame. |  | 
| 185    */ |  | 
| 186   public void returnTextureFrame() { |  | 
| 187     handler.post(new Runnable() { |  | 
| 188       @Override |  | 
| 189       public void run() { |  | 
| 190         isTextureInUse = false; |  | 
| 191         if (isQuitting) { |  | 
| 192           release(); |  | 
| 193         } else { |  | 
| 194           tryDeliverTextureFrame(); |  | 
| 195         } |  | 
| 196       } |  | 
| 197     }); |  | 
| 198   } |  | 
| 199 |  | 
| 200   public boolean isTextureInUse() { |  | 
| 201     return isTextureInUse; |  | 
| 202   } |  | 
| 203 |  | 
| 204   /** |  | 
| 205    * Call disconnect() to stop receiving frames. OpenGL resources are released a
     nd the handler is |  | 
| 206    * stopped when the texture frame has been returned by a call to returnTexture
     Frame(). You are |  | 
| 207    * guaranteed to not receive any more onTextureFrameAvailable() after this fun
     ction returns. |  | 
| 208    */ |  | 
| 209   public void dispose() { |  | 
| 210     Logging.d(TAG, "dispose()"); |  | 
| 211     ThreadUtils.invokeAtFrontUninterruptibly(handler, new Runnable() { |  | 
| 212       @Override |  | 
| 213       public void run() { |  | 
| 214         isQuitting = true; |  | 
| 215         if (!isTextureInUse) { |  | 
| 216           release(); |  | 
| 217         } |  | 
| 218       } |  | 
| 219     }); |  | 
| 220   } |  | 
| 221 |  | 
| 222   public void textureToYUV(final ByteBuffer buf, final int width, final int heig
     ht, |  | 
| 223       final int stride, final int textureId, final float[] transformMatrix) { |  | 
| 224     if (textureId != oesTextureId) { |  | 
| 225       throw new IllegalStateException("textureToByteBuffer called with unexpecte
     d textureId"); |  | 
| 226     } |  | 
| 227 |  | 
| 228     ThreadUtils.invokeAtFrontUninterruptibly(handler, new Runnable() { |  | 
| 229       @Override |  | 
| 230       public void run() { |  | 
| 231         if (yuvConverter == null) { |  | 
| 232           yuvConverter = new YuvConverter(); |  | 
| 233         } |  | 
| 234         yuvConverter.convert(buf, width, height, stride, textureId, transformMat
     rix); |  | 
| 235       } |  | 
| 236     }); |  | 
| 237   } |  | 
| 238 |  | 
| 239   private void updateTexImage() { |  | 
| 240     // SurfaceTexture.updateTexImage apparently can compete and deadlock with eg
     lSwapBuffers, |  | 
| 241     // as observed on Nexus 5. Therefore, synchronize it with the EGL functions. |  | 
| 242     // See https://bugs.chromium.org/p/webrtc/issues/detail?id=5702 for more inf
     o. |  | 
| 243     synchronized (EglBase.lock) { |  | 
| 244       surfaceTexture.updateTexImage(); |  | 
| 245     } |  | 
| 246   } |  | 
| 247 |  | 
| 248   private void tryDeliverTextureFrame() { |  | 
| 249     if (handler.getLooper().getThread() != Thread.currentThread()) { |  | 
| 250       throw new IllegalStateException("Wrong thread."); |  | 
| 251     } |  | 
| 252     if (isQuitting || !hasPendingTexture || isTextureInUse || listener == null) 
     { |  | 
| 253       return; |  | 
| 254     } |  | 
| 255     isTextureInUse = true; |  | 
| 256     hasPendingTexture = false; |  | 
| 257 |  | 
| 258     updateTexImage(); |  | 
| 259 |  | 
| 260     final float[] transformMatrix = new float[16]; |  | 
| 261     surfaceTexture.getTransformMatrix(transformMatrix); |  | 
| 262     final long timestampNs = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_C
     REAM_SANDWICH) |  | 
| 263         ? surfaceTexture.getTimestamp() |  | 
| 264         : TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime()); |  | 
| 265     listener.onTextureFrameAvailable(oesTextureId, transformMatrix, timestampNs)
     ; |  | 
| 266   } |  | 
| 267 |  | 
| 268   private void release() { |  | 
| 269     if (handler.getLooper().getThread() != Thread.currentThread()) { |  | 
| 270       throw new IllegalStateException("Wrong thread."); |  | 
| 271     } |  | 
| 272     if (isTextureInUse || !isQuitting) { |  | 
| 273       throw new IllegalStateException("Unexpected release."); |  | 
| 274     } |  | 
| 275     if (yuvConverter != null) { |  | 
| 276       yuvConverter.release(); |  | 
| 277     } |  | 
| 278     GLES20.glDeleteTextures(1, new int[] {oesTextureId}, 0); |  | 
| 279     surfaceTexture.release(); |  | 
| 280     eglBase.release(); |  | 
| 281     handler.getLooper().quit(); |  | 
| 282   } |  | 
| 283 } |  | 
| OLD | NEW | 
|---|