webrtc/src/main/java/org/webrtc/VideoTrack.java
public class VideoTrack extends MediaStreamTrack {
  private final LinkedList<VideoRenderer> renderers =
      new LinkedList<VideoRenderer>();

  public VideoTrack(long nativeTrack) {
    super(nativeTrack);
  }

  public void addRenderer(VideoRenderer renderer) {
    renderers.add(renderer);
    nativeAddRenderer(nativeTrack, renderer.nativeVideoRenderer);
  }

  public void removeRenderer(VideoRenderer renderer) {
    if (!renderers.remove(renderer)) {
      return;
    }
    nativeRemoveRenderer(nativeTrack, renderer.nativeVideoRenderer);
    renderer.dispose();
  }

  public void dispose() {
    while (!renderers.isEmpty()) {
      removeRenderer(renderers.getFirst());
    }
    super.dispose();
  }

  private static native void free(long nativeTrack);

  private static native void nativeAddRenderer(
      long nativeTrack, long nativeRenderer);

  private static native void nativeRemoveRenderer(
      long nativeTrack, long nativeRenderer);
}
webrtc/src/main/java/org/webrtc/PeerConnectionFactory.java
public class PeerConnectionFactory {
  static {
    System.loadLibrary("jingle_peerconnection_so");
  }

  private final long nativeFactory;

  // |context| is an android.content.Context object, but we keep it untyped here
  // to allow building on non-Android platforms.
  // Callers may specify either |initializeAudio| or |initializeVideo| as false
  // to skip initializing the respective engine (and avoid the need for the
  // respective permissions).
  public static native boolean initializeAndroidGlobals(
      Object context, boolean initializeAudio, boolean initializeVideo);

  public PeerConnectionFactory() {
    nativeFactory = nativeCreatePeerConnectionFactory();
    if (nativeFactory == 0) {
      throw new RuntimeException("Failed to initialize PeerConnectionFactory!");
    }
  }


  public PeerConnection createPeerConnection(
      List<PeerConnection.IceServer> iceServers,
      MediaConstraints constraints,
      PeerConnection.Observer observer) {
    long nativeObserver = nativeCreateObserver(observer);
    if (nativeObserver == 0) {
      return null;
    }
    long nativePeerConnection = nativeCreatePeerConnection(
        nativeFactory, iceServers, constraints, nativeObserver);
    if (nativePeerConnection == 0) {
      return null;
    }
    return new PeerConnection(nativePeerConnection, nativeObserver);
  }

  public MediaStream createLocalMediaStream(String label) {
    return new MediaStream(
        nativeCreateLocalMediaStream(nativeFactory, label));
  }

  public VideoSource createVideoSource(
      VideoCapturer capturer, MediaConstraints constraints) {
    return new VideoSource(nativeCreateVideoSource(
        nativeFactory, capturer.takeNativeVideoCapturer(), constraints));
  }

  public VideoTrack createVideoTrack(String id, VideoSource source) {
    return new VideoTrack(nativeCreateVideoTrack(
        nativeFactory, id, source.nativeSource));
  }

  public AudioSource createAudioSource(MediaConstraints constraints) {
    return new AudioSource(nativeCreateAudioSource(nativeFactory, constraints));
  }

  public AudioTrack createAudioTrack(String id, AudioSource source) {
    return new AudioTrack(nativeCreateAudioTrack(
        nativeFactory, id, source.nativeSource));
  }

  public void dispose() {
    freeFactory(nativeFactory);
  }

  private static native long nativeCreatePeerConnectionFactory();

  private static native long nativeCreateObserver(
      PeerConnection.Observer observer);

  private static native long nativeCreatePeerConnection(
      long nativeFactory, List<PeerConnection.IceServer> iceServers,
      MediaConstraints constraints, long nativeObserver);

  private static native long nativeCreateLocalMediaStream(
      long nativeFactory, String label);

  private static native long nativeCreateVideoSource(
      long nativeFactory, long nativeVideoCapturer,
      MediaConstraints constraints);

  private static native long nativeCreateVideoTrack(
      long nativeFactory, String id, long nativeVideoSource);

  private static native long nativeCreateAudioSource(
      long nativeFactory, MediaConstraints constraints);

  private static native long nativeCreateAudioTrack(
      long nativeFactory, String id, long nativeSource);

  private static native void freeFactory(long nativeFactory);
}
webrtc/src/main/java/org/webrtc/DataChannel.java
public class DataChannel {
  /** Java wrapper for WebIDL RTCDataChannel. */
  public static class Init {
    public boolean ordered = true;
    // Optional unsigned short in WebIDL, -1 means unspecified.
    public int maxRetransmitTimeMs = -1;
    // Optional unsigned short in WebIDL, -1 means unspecified.
    public int maxRetransmits = -1;
    public String protocol = "";
    public boolean negotiated = false;
    // Optional unsigned short in WebIDL, -1 means unspecified.
    public int id = -1;

    public Init() {}

    // Called only by native code.
    private Init(
        boolean ordered, int maxRetransmitTimeMs, int maxRetransmits,
        String protocol, boolean negotiated, int id) {
      this.ordered = ordered;
      this.maxRetransmitTimeMs = maxRetransmitTimeMs;
      this.maxRetransmits = maxRetransmits;
      this.protocol = protocol;
      this.negotiated = negotiated;
      this.id = id;
    }
  }

  /** Java version of C++ DataBuffer.  The atom of data in a DataChannel. */
  public static class Buffer {
    /** The underlying data. */
    public final ByteBuffer data;

    /**
     * Indicates whether |data| contains UTF-8 text or "binary data"
     * (i.e. anything else).
     */
    public final boolean binary;

    public Buffer(ByteBuffer data, boolean binary) {
      this.data = data;
      this.binary = binary;
    }
  }

  /** Java version of C++ DataChannelObserver. */
  public interface Observer {
    /** The data channel state has changed. */
    public void onStateChange();
    /**
     * A data buffer was successfully received.  NOTE: |buffer.data| will be
     * freed once this function returns so callers who want to use the data
     * asynchronously must make sure to copy it first.
     */
    public void onMessage(Buffer buffer);
  }

  /** Keep in sync with DataChannelInterface::DataState. */
  public enum State { CONNECTING, OPEN, CLOSING, CLOSED };

  private final long nativeDataChannel;
  private long nativeObserver;

  public DataChannel(long nativeDataChannel) {
    this.nativeDataChannel = nativeDataChannel;
  }

  /** Register |observer|, replacing any previously-registered observer. */
  public void registerObserver(Observer observer) {
    if (nativeObserver != 0) {
      unregisterObserverNative(nativeObserver);
    }
    nativeObserver = registerObserverNative(observer);
  }
  private native long registerObserverNative(Observer observer);

  /** Unregister the (only) observer. */
  public void unregisterObserver() {
    unregisterObserverNative(nativeObserver);
  }
  private native void unregisterObserverNative(long nativeObserver);

  public native String label();

  public native State state();

  /**
   * Return the number of bytes of application data (UTF-8 text and binary data)
   * that have been queued using SendBuffer but have not yet been transmitted
   * to the network.
   */
  public native long bufferedAmount();

  /** Close the channel. */
  public native void close();

  /** Send |data| to the remote peer; return success. */
  public boolean send(Buffer buffer) {
    // TODO(fischman): this could be cleverer about avoiding copies if the
    // ByteBuffer is direct and/or is backed by an array.
    byte[] data = new byte[buffer.data.remaining()];
    buffer.data.get(data);
    return sendNative(data, buffer.binary);
  }
  private native boolean sendNative(byte[] data, boolean binary);

  /** Dispose of native resources attached to this channel. */
  public native void dispose();
};
webrtc/src/main/java/org/webrtc/VideoRendererGui.java
public class VideoRendererGui implements GLSurfaceView.Renderer {
  private static VideoRendererGui instance = null;
  private static final String TAG = "VideoRendererGui";
  private GLSurfaceView surface;
  // Indicates if SurfaceView.Renderer.onSurfaceCreated was called.
  // If true then for every newly created yuv image renderer createTexture()
  // should be called. The variable is accessed on multiple threads and
  // all accesses are synchronized on yuvImageRenderers' object lock.
  private boolean onSurfaceCreatedCalled;
  // List of yuv renderers.
  private ArrayList<YuvImageRenderer> yuvImageRenderers;
  private int program;

  private final String VERTEX_SHADER_STRING =
      "varying vec2 interp_tc;\n" +
      "attribute vec4 in_pos;\n" +
      "attribute vec2 in_tc;\n" +
      "\n" +
      "void main() {\n" +
      "  gl_Position = in_pos;\n" +
      "  interp_tc = in_tc;\n" +
      "}\n";

  private final String FRAGMENT_SHADER_STRING =
      "precision mediump float;\n" +
      "varying vec2 interp_tc;\n" +
      "\n" +
      "uniform sampler2D y_tex;\n" +
      "uniform sampler2D u_tex;\n" +
      "uniform sampler2D v_tex;\n" +
      "\n" +
      "void main() {\n" +
      // CSC according to http://www.fourcc.org/fccyvrgb.php
      "  float y = texture2D(y_tex, interp_tc).r;\n" +
      "  float u = texture2D(u_tex, interp_tc).r - 0.5;\n" +
      "  float v = texture2D(v_tex, interp_tc).r - 0.5;\n" +
      "  gl_FragColor = vec4(y + 1.403 * v, " +
      "                      y - 0.344 * u - 0.714 * v, " +
      "                      y + 1.77 * u, 1);\n" +
      "}\n";

  private VideoRendererGui(GLSurfaceView surface) {
    this.surface = surface;
    // Create an OpenGL ES 2.0 context.
    surface.setPreserveEGLContextOnPause(true);
    surface.setEGLContextClientVersion(2);
    surface.setRenderer(this);
    surface.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);

    yuvImageRenderers = new ArrayList<YuvImageRenderer>();
  }

  // Poor-man's assert(): die with |msg| unless |condition| is true.
  private static void abortUnless(boolean condition, String msg) {
    if (!condition) {
      throw new RuntimeException(msg);
    }
  }

  // Assert that no OpenGL ES 2.0 error has been raised.
  private static void checkNoGLES2Error() {
    int error = GLES20.glGetError();
    abortUnless(error == GLES20.GL_NO_ERROR, "GLES20 error: " + error);
  }

  // Wrap a float[] in a direct FloatBuffer using native byte order.
  private static FloatBuffer directNativeFloatBuffer(float[] array) {
    FloatBuffer buffer = ByteBuffer.allocateDirect(array.length * 4).order(
        ByteOrder.nativeOrder()).asFloatBuffer();
    buffer.put(array);
    buffer.flip();
    return buffer;
  }

  // Compile & attach a |type| shader specified by |source| to |program|.
  private static void addShaderTo(
      int type, String source, int program) {
    int[] result = new int[] {
        GLES20.GL_FALSE
    };
    int shader = GLES20.glCreateShader(type);
    GLES20.glShaderSource(shader, source);
    GLES20.glCompileShader(shader);
    GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, 0);
    abortUnless(result[0] == GLES20.GL_TRUE,
        GLES20.glGetShaderInfoLog(shader) + ", source: " + source);
    GLES20.glAttachShader(program, shader);
    GLES20.glDeleteShader(shader);

    checkNoGLES2Error();
  }

  /**
   * Class used to display stream of YUV420 frames at particular location
   * on a screen. New video frames are sent to display using renderFrame()
   * call.
   */
  private static class YuvImageRenderer implements VideoRenderer.Callbacks {
    private GLSurfaceView surface;
    private int program;
    private FloatBuffer textureVertices;
    private int[] yuvTextures = { -1, -1, -1 };

    // Render frame queue - accessed by two threads. renderFrame() call does
    // an offer (writing I420Frame to render) and early-returns (recording
    // a dropped frame) if that queue is full. draw() call does a peek(),
    // copies frame to texture and then removes it from a queue using poll().
    LinkedBlockingQueue<I420Frame> frameToRenderQueue;
    // Local copy of incoming video frame.
    private I420Frame frameToRender;
    // Flag if renderFrame() was ever called
    boolean seenFrame;
    // Total number of video frames received in renderFrame() call.
    private int framesReceived;
    // Number of video frames dropped by renderFrame() because previous
    // frame has not been rendered yet.
    private int framesDropped;
    // Number of rendered video frames.
    private int framesRendered;
    // Time in ns when the first video frame was rendered.
    private long startTimeNs = -1;
    // Time in ns spent in draw() function.
    private long drawTimeNs;
    // Time in ns spent in renderFrame() function - including copying frame
    // data to rendering planes
    private long copyTimeNs;

    // Texture Coordinates mapping the entire texture.
    private final FloatBuffer textureCoords = directNativeFloatBuffer(
        new float[] {
            0, 0, 0, 1, 1, 0, 1, 1
        });

    private YuvImageRenderer(
        GLSurfaceView surface,
        int x, int y, int width, int height) {
      Log.v(TAG, "YuvImageRenderer.Create");
      this.surface = surface;
      frameToRenderQueue = new LinkedBlockingQueue<I420Frame>(1);
      // Create texture vertices.
      float xLeft = (x - 50) / 50.0f;
      float yTop = (50 - y) / 50.0f;
      float xRight = Math.min(1.0f, (x + width - 50) / 50.0f);
      float yBottom = Math.max(-1.0f, (50 - y - height) / 50.0f);
      float textureVeticesFloat[] = new float[] {
          xLeft, yTop,
          xLeft, yBottom,
          xRight, yTop,
          xRight, yBottom
      };
      textureVertices = directNativeFloatBuffer(textureVeticesFloat);
    }

    private void createTextures(int program) {
      Log.v(TAG, "  YuvImageRenderer.createTextures");
      this.program = program;

      // Generate 3 texture ids for Y/U/V and place them into |textures|.
      GLES20.glGenTextures(3, yuvTextures, 0);
      for (int i = 0; i < 3; i++)  {
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]);
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE,
            128, 128, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, null);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
            GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
            GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
            GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
            GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
      }
      checkNoGLES2Error();
    }

    private void draw() {
      long now = System.nanoTime();
      if (!seenFrame) {
        // No frame received yet - nothing to render.
        return;
      }
      I420Frame frameFromQueue;
      synchronized (frameToRenderQueue) {
        frameFromQueue = frameToRenderQueue.peek();
        if (frameFromQueue != null && startTimeNs == -1) {
          startTimeNs = now;
        }
        for (int i = 0; i < 3; ++i) {
          int w = (i == 0) ? frameToRender.width : frameToRender.width / 2;
          int h = (i == 0) ? frameToRender.height : frameToRender.height / 2;
          GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i);
          GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]);
          if (frameFromQueue != null) {
            GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE,
                w, h, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE,
                frameFromQueue.yuvPlanes[i]);
          }
        }
        if (frameFromQueue != null) {
          frameToRenderQueue.poll();
        }
      }
      int posLocation = GLES20.glGetAttribLocation(program, "in_pos");
      GLES20.glEnableVertexAttribArray(posLocation);
      GLES20.glVertexAttribPointer(
          posLocation, 2, GLES20.GL_FLOAT, false, 0, textureVertices);

      int texLocation = GLES20.glGetAttribLocation(program, "in_tc");
      GLES20.glEnableVertexAttribArray(texLocation);
      GLES20.glVertexAttribPointer(
          texLocation, 2, GLES20.GL_FLOAT, false, 0, textureCoords);

      GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

      GLES20.glDisableVertexAttribArray(posLocation);
      GLES20.glDisableVertexAttribArray(texLocation);

      checkNoGLES2Error();

      if (frameFromQueue != null) {
        framesRendered++;
        drawTimeNs += (System.nanoTime() - now);
        if ((framesRendered % 150) == 0) {
          logStatistics();
        }
      }
    }

    private void logStatistics() {
      long timeSinceFirstFrameNs = System.nanoTime() - startTimeNs;
      Log.v(TAG, "Frames received: " + framesReceived + ". Dropped: " +
          framesDropped + ". Rendered: " + framesRendered);
      if (framesReceived > 0 && framesRendered > 0) {
        Log.v(TAG, "Duration: " + (int)(timeSinceFirstFrameNs / 1e6) +
            " ms. FPS: " + (float)framesRendered * 1e9 / timeSinceFirstFrameNs);
        Log.v(TAG, "Draw time: " +
            (int) (drawTimeNs / (1000 * framesRendered)) + " us. Copy time: " +
            (int) (copyTimeNs / (1000 * framesReceived)) + " us");
      }
    }

    @Override
    public void setSize(final int width, final int height) {
      Log.v(TAG, "YuvImageRenderer.setSize: " + width + " x " + height);
      int[] strides = { width, width / 2, width / 2  };
      // Frame re-allocation need to be synchronized with copying
      // frame to textures in draw() function to avoid re-allocating
      // the frame while it is being copied.
      synchronized (frameToRenderQueue) {
        // Clear rendering queue
        frameToRenderQueue.poll();
        // Re-allocate / allocate the frame
        frameToRender = new I420Frame(width, height, strides, null);
      }
    }

    @Override
    public synchronized void renderFrame(I420Frame frame) {
      long now = System.nanoTime();
      framesReceived++;
      // Check input frame parameters.
      if (!(frame.yuvStrides[0] == frame.width &&
          frame.yuvStrides[1] == frame.width / 2 &&
          frame.yuvStrides[2] == frame.width / 2)) {
        Log.e(TAG, "Incorrect strides " + frame.yuvStrides[0] + ", " +
            frame.yuvStrides[1] + ", " + frame.yuvStrides[2]);
        return;
      }
      // Skip rendering of this frame if setSize() was not called.
      if (frameToRender == null) {
        framesDropped++;
        return;
      }
      // Check incoming frame dimensions
      if (frame.width != frameToRender.width ||
          frame.height != frameToRender.height) {
        throw new RuntimeException("Wrong frame size " +
            frame.width + " x " + frame.height);
      }

      if (frameToRenderQueue.size() > 0) {
        // Skip rendering of this frame if previous frame was not rendered yet.
        framesDropped++;
        return;
      }
      frameToRender.copyFrom(frame);
      copyTimeNs += (System.nanoTime() - now);
      frameToRenderQueue.offer(frameToRender);
      seenFrame = true;
      surface.requestRender();
    }
  }

  /** Passes GLSurfaceView to video renderer. */
  public static void setView(GLSurfaceView surface) {
    Log.v(TAG, "VideoRendererGui.setView");
    instance = new VideoRendererGui(surface);
  }

  /**
   * Creates VideoRenderer with top left corner at (x, y) and resolution
   * (width, height). All parameters are in percentage of screen resolution.
   */
  public static VideoRenderer createGui(
      int x, int y, int width, int height) throws Exception {
    YuvImageRenderer javaGuiRenderer = create(x, y, width, height);
    return new VideoRenderer(javaGuiRenderer);
  }

  /**
   * Creates VideoRenderer.Callbacks with top left corner at (x, y) and
   * resolution (width, height). All parameters are in percentage of
   * screen resolution.
   */
  public static YuvImageRenderer create(
      int x, int y, int width, int height) {
    // Check display region parameters.
    if (x < 0 || x > 100 || y < 0 || y > 100 ||
        width < 0 || width > 100 || height < 0 || height > 100 ||
        x + width > 100 || y + height > 100) {
      throw new RuntimeException("Incorrect window parameters.");
    }

    if (instance == null) {
      throw new RuntimeException(
          "Attempt to create yuv renderer before setting GLSurfaceView");
    }
    final YuvImageRenderer yuvImageRenderer = new YuvImageRenderer(
        instance.surface, x, y, width, height);
    synchronized (instance.yuvImageRenderers) {
      if (instance.onSurfaceCreatedCalled) {
        // onSurfaceCreated has already been called for VideoRendererGui -
        // need to create texture for new image and add image to the
        // rendering list.
        final CountDownLatch countDownLatch = new CountDownLatch(1);
        instance.surface.queueEvent(new Runnable() {
          public void run() {
            yuvImageRenderer.createTextures(instance.program);
            countDownLatch.countDown();
          }
        });
        // Wait for task completion.
        try {
          countDownLatch.await();
        } catch (InterruptedException e) {
          throw new RuntimeException(e);
        }
      }
      // Add yuv renderer to rendering list.
      instance.yuvImageRenderers.add(yuvImageRenderer);
    }
    return yuvImageRenderer;
  }

  @Override
  public void onSurfaceCreated(GL10 unused, EGLConfig config) {
    Log.v(TAG, "VideoRendererGui.onSurfaceCreated");

    // Create program.
    program = GLES20.glCreateProgram();
    addShaderTo(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_STRING, program);
    addShaderTo(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_STRING, program);

    GLES20.glLinkProgram(program);
    int[] result = new int[] {
        GLES20.GL_FALSE
    };
    result[0] = GLES20.GL_FALSE;
    GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, result, 0);
    abortUnless(result[0] == GLES20.GL_TRUE,
        GLES20.glGetProgramInfoLog(program));
    GLES20.glUseProgram(program);

    GLES20.glUniform1i(GLES20.glGetUniformLocation(program, "y_tex"), 0);
    GLES20.glUniform1i(GLES20.glGetUniformLocation(program, "u_tex"), 1);
    GLES20.glUniform1i(GLES20.glGetUniformLocation(program, "v_tex"), 2);

    synchronized (yuvImageRenderers) {
      // Create textures for all images.
      for (YuvImageRenderer yuvImageRenderer : yuvImageRenderers) {
        yuvImageRenderer.createTextures(program);
      }
      onSurfaceCreatedCalled = true;
    }
    checkNoGLES2Error();
    GLES20.glClearColor(0.0f, 0.0f, 0.3f, 1.0f);
  }

  @Override
  public void onSurfaceChanged(GL10 unused, int width, int height) {
    Log.v(TAG, "VideoRendererGui.onSurfaceChanged: " +
        width + " x " + height + "  ");
    GLES20.glViewport(0, 0, width, height);
  }

  @Override
  public void onDrawFrame(GL10 unused) {
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
    synchronized (yuvImageRenderers) {
      for (YuvImageRenderer yuvImageRenderer : yuvImageRenderers) {
        yuvImageRenderer.draw();
      }
    }
  }

}
webrtc/src/main/java/org/webrtc/VideoRenderer.java
public class VideoRenderer {

  /** Java version of cricket::VideoFrame. */
  public static class I420Frame {
    public final int width;
    public final int height;
    public final int[] yuvStrides;
    public final ByteBuffer[] yuvPlanes;

    /**
     * Construct a frame of the given dimensions with the specified planar
     * data.  If |yuvPlanes| is null, new planes of the appropriate sizes are
     * allocated.
     */
    public I420Frame(
        int width, int height, int[] yuvStrides, ByteBuffer[] yuvPlanes) {
      this.width = width;
      this.height = height;
      this.yuvStrides = yuvStrides;
      if (yuvPlanes == null) {
        yuvPlanes = new ByteBuffer[3];
        yuvPlanes[0] = ByteBuffer.allocateDirect(yuvStrides[0] * height);
        yuvPlanes[1] = ByteBuffer.allocateDirect(yuvStrides[1] * height);
        yuvPlanes[2] = ByteBuffer.allocateDirect(yuvStrides[2] * height);
      }
      this.yuvPlanes = yuvPlanes;
    }

    /**
     * Copy the planes out of |source| into |this| and return |this|.  Calling
     * this with mismatched frame dimensions is a programming error and will
     * likely crash.
     */
    public I420Frame copyFrom(I420Frame source) {
      if (!Arrays.equals(yuvStrides, source.yuvStrides) ||
          width != source.width || height != source.height) {
        throw new RuntimeException("Mismatched dimensions!  Source: " +
            source.toString() + ", destination: " + toString());
      }
      copyPlane(source.yuvPlanes[0], yuvPlanes[0]);
      copyPlane(source.yuvPlanes[1], yuvPlanes[1]);
      copyPlane(source.yuvPlanes[2], yuvPlanes[2]);
      return this;
    }

    @Override
    public String toString() {
      return width + "x" + height + ":" + yuvStrides[0] + ":" + yuvStrides[1] +
          ":" + yuvStrides[2];
    }

    // Copy the bytes out of |src| and into |dst|, ignoring and overwriting
    // positon & limit in both buffers.
    private void copyPlane(ByteBuffer src, ByteBuffer dst) {
      src.position(0).limit(src.capacity());
      dst.put(src);
      dst.position(0).limit(dst.capacity());
    }
}

  /** The real meat of VideoRendererInterface. */
  public static interface Callbacks {
    public void setSize(int width, int height);
    public void renderFrame(I420Frame frame);
  }

  // |this| either wraps a native (GUI) renderer or a client-supplied Callbacks
  // (Java) implementation; so exactly one of these will be non-0/null.
  final long nativeVideoRenderer;
  private final Callbacks callbacks;

  public static VideoRenderer createGui(int x, int y) {
    long nativeVideoRenderer = nativeCreateGuiVideoRenderer(x, y);
    if (nativeVideoRenderer == 0) {
      return null;
    }
    return new VideoRenderer(nativeVideoRenderer);
  }

  public VideoRenderer(Callbacks callbacks) {
    nativeVideoRenderer = nativeWrapVideoRenderer(callbacks);
    this.callbacks = callbacks;
  }

  private VideoRenderer(long nativeVideoRenderer) {
    this.nativeVideoRenderer = nativeVideoRenderer;
    callbacks = null;
  }

  public void dispose() {
    free(nativeVideoRenderer);
  }

  private static native long nativeCreateGuiVideoRenderer(int x, int y);
  private static native long nativeWrapVideoRenderer(Callbacks callbacks);

  private static native void free(long nativeVideoRenderer);
}
webrtc/src/main/java/org/webrtc/StatsReport.java
public class StatsReport {

  /** Java version of webrtc::StatsReport::Value. */
  public static class Value {
    public final String name;
    public final String value;

    public Value(String name, String value) {
      this.name = name;
      this.value = value;
    }

    public String toString() {
      StringBuilder builder = new StringBuilder();
      builder.append("[").append(name).append(": ").append(value).append("]");
      return builder.toString();
    }
  }

  public final String id;
  public final String type;
  // Time since 1970-01-01T00:00:00Z in milliseconds.
  public final double timestamp;
  public final Value[] values;

  public StatsReport(String id, String type, double timestamp, Value[] values) {
    this.id = id;
    this.type = type;
    this.timestamp = timestamp;
    this.values = values;
  }

  public String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append("id: ").append(id).append(", type: ").append(type)
        .append(", timestamp: ").append(timestamp).append(", values: ");
    for (int i = 0; i < values.length; ++i) {
      builder.append(values[i].toString()).append(", ");
    }
    return builder.toString();
  }
}
webrtc/src/main/java/org/webrtc/VideoCapturer.java
public class VideoCapturer {
  private long nativeVideoCapturer;

  private VideoCapturer(long nativeVideoCapturer) {
    this.nativeVideoCapturer = nativeVideoCapturer;
  }

  public static VideoCapturer create(String deviceName) {
    long nativeVideoCapturer = nativeCreateVideoCapturer(deviceName);
    if (nativeVideoCapturer == 0) {
      return null;
    }
    return new VideoCapturer(nativeVideoCapturer);
  }

  // Package-visible for PeerConnectionFactory.
  long takeNativeVideoCapturer() {
    if (nativeVideoCapturer == 0) {
      throw new RuntimeException("Capturer can only be taken once!");
    }
    long ret = nativeVideoCapturer;
    nativeVideoCapturer = 0;
    return ret;
  }

  public void dispose() {
    // No-op iff this capturer is owned by a source (see comment on
    // PeerConnectionFactoryInterface::CreateVideoSource()).
    if (nativeVideoCapturer != 0) {
      free(nativeVideoCapturer);
    }
  }

  private static native long nativeCreateVideoCapturer(String deviceName);

  private static native void free(long nativeVideoCapturer);
}
webrtc/src/main/java/org/webrtc/MediaConstraints.java
public class MediaConstraints {
  /** Simple String key/value pair. */
  public static class KeyValuePair {
    private final String key;
    private final String value;

    public KeyValuePair(String key, String value) {
      this.key = key;
      this.value = value;
    }

    public String getKey() {
      return key;
    }

    public String getValue() {
      return value;
    }

    public String toString() {
      return key + ": " + value;
    }
  }


  public final List<KeyValuePair> mandatory;
  public final List<KeyValuePair> optional;

  public MediaConstraints() {
    mandatory = new LinkedList<KeyValuePair>();
    optional = new LinkedList<KeyValuePair>();
  }

  private static String stringifyKeyValuePairList(List<KeyValuePair> list) {
    StringBuilder builder = new StringBuilder("[");
    for (KeyValuePair pair : list) {
      if (builder.length() > 1) {
        builder.append(", ");
      }
      builder.append(pair.toString());
    }
    return builder.append("]").toString();
  }

  public String toString() {
    return "mandatory: " + stringifyKeyValuePairList(mandatory) +
        ", optional: " + stringifyKeyValuePairList(optional);
  }
}
webrtc/src/main/java/org/webrtc/PeerConnection.java
public class PeerConnection {
  static {
    System.loadLibrary("jingle_peerconnection_so");
  }

  /** Tracks PeerConnectionInterface::IceGatheringState */
  public enum IceGatheringState { NEW, GATHERING, COMPLETE };


  /** Tracks PeerConnectionInterface::IceConnectionState */
  public enum IceConnectionState {
    NEW, CHECKING, CONNECTED, COMPLETED, FAILED, DISCONNECTED, CLOSED
  };

  /** Tracks PeerConnectionInterface::SignalingState */
  public enum SignalingState {
    STABLE, HAVE_LOCAL_OFFER, HAVE_LOCAL_PRANSWER, HAVE_REMOTE_OFFER,
    HAVE_REMOTE_PRANSWER, CLOSED
  };

  /** Java version of PeerConnectionObserver. */
  public static interface Observer {
    /** Triggered when the SignalingState changes. */
    public void onSignalingChange(SignalingState newState);

    /** Triggered when the IceConnectionState changes. */
    public void onIceConnectionChange(IceConnectionState newState);

    /** Triggered when the IceGatheringState changes. */
    public void onIceGatheringChange(IceGatheringState newState);

    /** Triggered when a new ICE candidate has been found. */
    public void onIceCandidate(IceCandidate candidate);

    /** Triggered on any error. */
    public void onError();

    /** Triggered when media is received on a new stream from remote peer. */
    public void onAddStream(MediaStream stream);

    /** Triggered when a remote peer close a stream. */
    public void onRemoveStream(MediaStream stream);

    /** Triggered when a remote peer opens a DataChannel. */
    public void onDataChannel(DataChannel dataChannel);

    /** Triggered when renegotiation is necessary. */
    public void onRenegotiationNeeded();
  }

  /** Java version of PeerConnectionInterface.IceServer. */
  public static class IceServer {
    public final String uri;
    public final String username;
    public final String password;

    /** Convenience constructor for STUN servers. */
    public IceServer(String uri) {
      this(uri, "", "");
    }

    public IceServer(String uri, String username, String password) {
      this.uri = uri;
      this.username = username;
      this.password = password;
    }

    public String toString() {
      return uri + "[" + username + ":" + password + "]";
    }
  }

  private final List<MediaStream> localStreams;
  private final long nativePeerConnection;
  private final long nativeObserver;

  PeerConnection(long nativePeerConnection, long nativeObserver) {
    this.nativePeerConnection = nativePeerConnection;
    this.nativeObserver = nativeObserver;
    localStreams = new LinkedList<MediaStream>();
  }

  // JsepInterface.
  public native SessionDescription getLocalDescription();

  public native SessionDescription getRemoteDescription();

  public native DataChannel createDataChannel(
      String label, DataChannel.Init init);

  public native void createOffer(
      SdpObserver observer, MediaConstraints constraints);

  public native void createAnswer(
      SdpObserver observer, MediaConstraints constraints);

  public native void setLocalDescription(
      SdpObserver observer, SessionDescription sdp);

  public native void setRemoteDescription(
      SdpObserver observer, SessionDescription sdp);

  public native boolean updateIce(
      List<IceServer> iceServers, MediaConstraints constraints);

  public boolean addIceCandidate(IceCandidate candidate) {
    return nativeAddIceCandidate(
        candidate.sdpMid, candidate.sdpMLineIndex, candidate.sdp);
  }

  public boolean addStream(
      MediaStream stream, MediaConstraints constraints) {
    boolean ret = nativeAddLocalStream(stream.nativeStream, constraints);
    if (!ret) {
      return false;
    }
    localStreams.add(stream);
    return true;
  }

  public void removeStream(MediaStream stream) {
    nativeRemoveLocalStream(stream.nativeStream);
    localStreams.remove(stream);
  }

  public boolean getStats(StatsObserver observer, MediaStreamTrack track) {
    return nativeGetStats(observer, (track == null) ? 0 : track.nativeTrack);
  }

  // TODO(fischman): add support for DTMF-related methods once that API
  // stabilizes.
  public native SignalingState signalingState();

  public native IceConnectionState iceConnectionState();

  public native IceGatheringState iceGatheringState();

  public native void close();

  public void dispose() {
    close();
    for (MediaStream stream : localStreams) {
      nativeRemoveLocalStream(stream.nativeStream);
      stream.dispose();
    }
    localStreams.clear();
    freePeerConnection(nativePeerConnection);
    freeObserver(nativeObserver);
  }

  private static native void freePeerConnection(long nativePeerConnection);

  private static native void freeObserver(long nativeObserver);

  private native boolean nativeAddIceCandidate(
      String sdpMid, int sdpMLineIndex, String iceCandidateSdp);

  private native boolean nativeAddLocalStream(
      long nativeStream, MediaConstraints constraints);

  private native void nativeRemoveLocalStream(long nativeStream);

  private native boolean nativeGetStats(
      StatsObserver observer, long nativeTrack);
}
webrtc/src/main/java/org/webrtc/StatsObserver.java
public interface StatsObserver {
  /** Called when the reports are ready.*/
  public void onComplete(StatsReport[] reports);
}
webrtc/src/main/java/org/webrtc/IceCandidate.java
public class IceCandidate {
  public final String sdpMid;
  public final int sdpMLineIndex;
  public final String sdp;

  public IceCandidate(String sdpMid, int sdpMLineIndex, String sdp) {
    this.sdpMid = sdpMid;
    this.sdpMLineIndex = sdpMLineIndex;
    this.sdp = sdp;
  }

  public String toString() {
    return sdpMid + ":" + sdpMLineIndex + ":" + sdp;
  }
}
webrtc/src/main/java/org/webrtc/SdpObserver.java
public interface SdpObserver {
  /** Called on success of Create{Offer,Answer}(). */
  public void onCreateSuccess(SessionDescription sdp);

  /** Called on success of Set{Local,Remote}Description(). */
  public void onSetSuccess();

  /** Called on error of Create{Offer,Answer}(). */
  public void onCreateFailure(String error);

  /** Called on error of Set{Local,Remote}Description(). */
  public void onSetFailure(String error);
}
webrtc/src/main/java/org/webrtc/VideoSource.java
public class VideoSource extends MediaSource {
  private long nativeVideoFormatAtStop;

  public VideoSource(long nativeSource) {
    super(nativeSource);
  }

  // Stop capture feeding this source.
  public void stop() {
    nativeVideoFormatAtStop = stop(nativeSource);
  }

  // Restart capture feeding this source.  stop() must have been called since
  // the last call to restart() (if any).  Note that this isn't "start()";
  // sources are started by default at birth.
  public void restart() {
    restart(nativeSource, nativeVideoFormatAtStop);
    nativeVideoFormatAtStop = 0;
  }

  @Override
  public void dispose() {
    if (nativeVideoFormatAtStop != 0) {
      freeNativeVideoFormat(nativeVideoFormatAtStop);
      nativeVideoFormatAtStop = 0;
    }
    super.dispose();
  }

  // This stop() returns an owned C++ VideoFormat pointer for use in restart()
  // and dispose().
  private static native long stop(long nativeSource);
  private static native void restart(
      long nativeSource, long nativeVideoFormatAtStop);
  private static native void freeNativeVideoFormat(long nativeVideoFormat);
}
webrtc/src/main/java/org/webrtc/MediaStream.java
public class MediaStream {
  public final LinkedList<AudioTrack> audioTracks;
  public final LinkedList<VideoTrack> videoTracks;
  // Package-protected for PeerConnection.
  final long nativeStream;

  public MediaStream(long nativeStream) {
    audioTracks = new LinkedList<AudioTrack>();
    videoTracks = new LinkedList<VideoTrack>();
    this.nativeStream = nativeStream;
  }

  public boolean addTrack(AudioTrack track) {
    if (nativeAddAudioTrack(nativeStream, track.nativeTrack)) {
      audioTracks.add(track);
      return true;
    }
    return false;
  }

  public boolean addTrack(VideoTrack track) {
    if (nativeAddVideoTrack(nativeStream, track.nativeTrack)) {
      videoTracks.add(track);
      return true;
    }
    return false;
  }

  public boolean removeTrack(AudioTrack track) {
    if (nativeRemoveAudioTrack(nativeStream, track.nativeTrack)) {
      audioTracks.remove(track);
      return true;
    }
    return false;
  }

  public boolean removeTrack(VideoTrack track) {
    if (nativeRemoveVideoTrack(nativeStream, track.nativeTrack)) {
      videoTracks.remove(track);
      return true;
    }
    return false;
  }

  public void dispose() {
    while (!audioTracks.isEmpty()) {
      AudioTrack track = audioTracks.getFirst();
      removeTrack(track);
      track.dispose();
    }
    while (!videoTracks.isEmpty()) {
      VideoTrack track = videoTracks.getFirst();
      removeTrack(track);
      track.dispose();
    }
    free(nativeStream);
  }

  public String label() {
    return nativeLabel(nativeStream);
  }

  public String toString() {
    return "[" + label() + ":A=" + audioTracks.size() +
        ":V=" + videoTracks.size() + "]";
  }

  private static native boolean nativeAddAudioTrack(
      long nativeStream, long nativeAudioTrack);

  private static native boolean nativeAddVideoTrack(
      long nativeStream, long nativeVideoTrack);

  private static native boolean nativeRemoveAudioTrack(
      long nativeStream, long nativeAudioTrack);

  private static native boolean nativeRemoveVideoTrack(
      long nativeStream, long nativeVideoTrack);

  private static native String nativeLabel(long nativeStream);

  private static native void free(long nativeStream);
}
webrtc/src/main/java/org/webrtc/SessionDescription.java
public class SessionDescription {
  /** Java-land enum version of SessionDescriptionInterface's type() string. */
  public static enum Type {
    OFFER, PRANSWER, ANSWER;

    public String canonicalForm() {
      return name().toLowerCase();
    }

    public static Type fromCanonicalForm(String canonical) {
      return Type.valueOf(Type.class, canonical.toUpperCase());
    }
  }

  public final Type type;
  public final String description;

  public SessionDescription(Type type, String description) {
    this.type = type;
    this.description = description;
  }
}


Question: import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.Point;
import android.media.AudioManager;
import android.os.Bundle;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import android.webkit.JavascriptInterface;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.DataChannel;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.SdpObserver;
import org.webrtc.SessionDescription;
import org.webrtc.StatsObserver;
import org.webrtc.StatsReport;
import org.webrtc.VideoCapturer;
import org.webrtc.VideoRenderer;
import org.webrtc.VideoRendererGui;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
  // Toggle visibility of the heads-up display.
  private void toggleHUD() {
    if (hudView.getVisibility() == View.VISIBLE) {
      hudView.setVisibility(View.INVISIBLE);
    } else {
      hudView.setVisibility(View.VISIBLE);
    }
  }

  // Update the heads-up display with information from |reports|.
  private void updateHUD(StatsReport[] reports) {
    StringBuilder builder = new StringBuilder();
    for (StatsReport report : reports) {
      if (!report.id.equals("bweforvideo")) {
        continue;
      }
      for (StatsReport.Value value : report.values) {
        String name = value.name.replace("goog", "").replace("Available", "")
            .replace("Bandwidth", "").replace("Bitrate", "").replace("Enc", "");
        builder.append(name).append("=").append(value.value).append(" ");
      }
      builder.append("\n");
    }
    hudView.setText(builder.toString() + hudView.getText());
  }

  @Override
  public void onPause() {
    super.onPause();
    vsv.onPause();
    if (videoSource != null) {
      videoSource.stop();
      videoSourceStopped = true;
    }
  }

  @Override
  public void onResume() {
    super.onResume();
    vsv.onResume();
    if (videoSource != null && videoSourceStopped) {
      videoSource.restart();
    }
  }

  @Override
  public void onConfigurationChanged (Configuration newConfig) {
    Point displaySize = new Point();
    getWindowManager().getDefaultDisplay().getSize(displaySize);
    vsv.updateDisplaySize(displaySize);
    super.onConfigurationChanged(newConfig);
  }

  // Just for fun (and to regression-test bug 2302) make sure that DataChannels
  // can be created, queried, and disposed.
  private static void createDataChannelToRegressionTestBug2302(
      PeerConnection pc) {
    DataChannel dc = pc.createDataChannel("dcLabel", new DataChannel.Init());
    abortUnless("dcLabel".equals(dc.label()), "Unexpected label corruption?");
    dc.close();
    dc.dispose();
  }

  @Override
  public void onIceServers(List<PeerConnection.IceServer> iceServers) {
    factory = new PeerConnectionFactory();

    MediaConstraints pcConstraints = appRtcClient.pcConstraints();
    pcConstraints.optional.add(
        new MediaConstraints.KeyValuePair("RtpDataChannels", "true"));
    pc = factory.createPeerConnection(iceServers, pcConstraints, pcObserver);

    createDataChannelToRegressionTestBug2302(pc);  // See method comment.

    // Uncomment to get ALL WebRTC tracing and SENSITIVE libjingle logging.
    // NOTE: this _must_ happen while |factory| is alive!
    // Logging.enableTracing(
    //     "logcat:",
    //     EnumSet.of(Logging.TraceLevel.TRACE_ALL),
    //     Logging.Severity.LS_SENSITIVE);

    {
      final PeerConnection finalPC = pc;
      final Runnable repeatedStatsLogger = new Runnable() {
          public void run() {
            synchronized (quit[0]) {
              if (quit[0]) {
                return;
              }
              final Runnable runnableThis = this;
              if (hudView.getVisibility() == View.INVISIBLE) {
                vsv.postDelayed(runnableThis, 1000);
                return;
              }
              boolean success = finalPC.getStats(new StatsObserver() {
                  public void onComplete(final StatsReport[] reports) {
                    runOnUiThread(new Runnable() {
                        public void run() {
                          updateHUD(reports);
                        }
                      });
                    for (StatsReport report : reports) {
                      Log.d(TAG, "Stats: " + report.toString());
                    }
                    vsv.postDelayed(runnableThis, 1000);
                  }
                }, null);
              if (!success) {
                throw new RuntimeException("getStats() return false!");
              }
            }
          }
        };
      vsv.postDelayed(repeatedStatsLogger, 1000);
    }

    {
      logAndToast("Creating local video source...");
      MediaStream lMS = factory.createLocalMediaStream("ARDAMS");
      if (appRtcClient.videoConstraints() != null) {
Answer:         VideoCapturer capturer = getVideoCapturer();