@Override
    public synchronized void renderFrame(I420Frame frame) {
      if (surface == null) {
        // This object has been released.
        VideoRenderer.renderFrameDone(frame);
        return;
      }
      if (!seenFrame && rendererEvents != null) {
        Logging.d(TAG, "ID: " + id + ". Reporting first rendered frame.");
        rendererEvents.onFirstFrameRendered();
      }
      framesReceived++;
      synchronized (pendingFrameLock) {
        // Check input frame parameters.
        if (frame.yuvFrame) {
          if (frame.yuvStrides[0] < frame.width
              || frame.yuvStrides[1] < frame.width / 2
              || frame.yuvStrides[2] < frame.width / 2) {
            Logging.e(
                TAG,
                "Incorrect strides "
                    + frame.yuvStrides[0]
                    + ", "
                    + frame.yuvStrides[1]
                    + ", "
                    + frame.yuvStrides[2]);
            VideoRenderer.renderFrameDone(frame);
            return;
          }
        }

        if (pendingFrame != null) {
          // Skip rendering of this frame if previous frame was not rendered yet.
          framesDropped++;
          VideoRenderer.renderFrameDone(frame);
          return;
        }
        pendingFrame = frame;
      }
      setSize(frame.width, frame.height, frame.rotationDegree);
      seenFrame = true;

      // Request rendering.
      surface.requestRender();
    }
 @Override
 public void onDestroy() {
   Log.d(TAG, "VideoEngine onDestroy()");
   super.onDestroy();
   if (renderer != null) {
     renderer.release();
   }
   renderer = null;
 }
 private synchronized void release() {
   surface = null;
   synchronized (pendingFrameLock) {
     if (pendingFrame != null) {
       VideoRenderer.renderFrameDone(pendingFrame);
       pendingFrame = null;
     }
   }
 }
    private void draw(GlRectDrawer drawer) {
      if (!seenFrame) {
        // No frame received yet - nothing to render.
        return;
      }
      long now = System.nanoTime();

      final boolean isNewFrame;
      synchronized (pendingFrameLock) {
        isNewFrame = (pendingFrame != null);
        if (isNewFrame && startTimeNs == -1) {
          startTimeNs = now;
        }

        if (isNewFrame) {
          if (pendingFrame.yuvFrame) {
            rendererType = RendererType.RENDERER_YUV;
            drawer.uploadYuvData(
                yuvTextures,
                pendingFrame.width,
                pendingFrame.height,
                pendingFrame.yuvStrides,
                pendingFrame.yuvPlanes);
            // The convention in WebRTC is that the first element in a ByteBuffer corresponds to the
            // top-left corner of the image, but in glTexImage2D() the first element corresponds to
            // the bottom-left corner. We correct this discrepancy by setting a vertical flip as
            // sampling matrix.
            final float[] samplingMatrix = RendererCommon.verticalFlipMatrix();
            rotatedSamplingMatrix =
                RendererCommon.rotateTextureMatrix(samplingMatrix, pendingFrame.rotationDegree);
          } else {
            rendererType = RendererType.RENDERER_TEXTURE;
            // External texture rendering. Update texture image to latest and make a deep copy of
            // the external texture.
            // TODO(magjed): Move updateTexImage() to the video source instead.
            final SurfaceTexture surfaceTexture = (SurfaceTexture) pendingFrame.textureObject;
            surfaceTexture.updateTexImage();
            final float[] samplingMatrix = new float[16];
            surfaceTexture.getTransformMatrix(samplingMatrix);
            rotatedSamplingMatrix =
                RendererCommon.rotateTextureMatrix(samplingMatrix, pendingFrame.rotationDegree);

            // Reallocate offscreen texture if necessary.
            textureCopy.setSize(pendingFrame.rotatedWidth(), pendingFrame.rotatedHeight());

            // Bind our offscreen framebuffer.
            GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, textureCopy.getFrameBufferId());
            GlUtil.checkNoGLES2Error("glBindFramebuffer");

            // Copy the OES texture content. This will also normalize the sampling matrix.
            GLES20.glViewport(0, 0, textureCopy.getWidth(), textureCopy.getHeight());
            drawer.drawOes(pendingFrame.textureId, rotatedSamplingMatrix);
            rotatedSamplingMatrix = RendererCommon.identityMatrix();

            // Restore normal framebuffer.
            GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
          }
          copyTimeNs += (System.nanoTime() - now);
          VideoRenderer.renderFrameDone(pendingFrame);
          pendingFrame = null;
        }
      }

      // OpenGL defaults to lower left origin - flip vertically.
      GLES20.glViewport(
          displayLayout.left,
          screenHeight - displayLayout.bottom,
          displayLayout.width(),
          displayLayout.height());

      updateLayoutMatrix();
      final float[] texMatrix =
          RendererCommon.multiplyMatrices(rotatedSamplingMatrix, layoutMatrix);
      if (rendererType == RendererType.RENDERER_YUV) {
        drawer.drawYuv(yuvTextures, texMatrix);
      } else {
        drawer.drawRgb(textureCopy.getTextureId(), texMatrix);
      }

      if (isNewFrame) {
        framesRendered++;
        drawTimeNs += (System.nanoTime() - now);
        if ((framesRendered % 300) == 0) {
          logStatistics();
        }
      }
    }
  /**
   * Open the player
   *
   * @param remoteHost Remote host
   * @param remotePort Remote port
   */
  public void open(String remoteHost, int remotePort) {
    if (opened) {
      // Already opened
      return;
    }

    // Check video codec
    if (selectedVideoCodec == null) {
      notifyPlayerEventError("Video codec not selected");
      return;
    }

    // Init video encoder
    try {
      NativeH264EncoderParams nativeH264EncoderParams = new NativeH264EncoderParams();

      // Codec dimensions
      nativeH264EncoderParams.setFrameWidth(selectedVideoCodec.getWidth());
      nativeH264EncoderParams.setFrameHeight(selectedVideoCodec.getHeight());
      nativeH264EncoderParams.setFrameRate(selectedVideoCodec.getFramerate());
      nativeH264EncoderParams.setBitRate(selectedVideoCodec.getBitrate());

      // Codec profile and level
      nativeH264EncoderParams.setProfilesAndLevel(selectedVideoCodec.getCodecParams());

      // Codec settings optimization
      nativeH264EncoderParams.setEncMode(NativeH264EncoderParams.ENCODING_MODE_STREAMING);
      nativeH264EncoderParams.setSceneDetection(false);

      if (logger.isActivated()) {
        logger.info(
            "Init H264Encoder "
                + selectedVideoCodec.getCodecParams()
                + " "
                + selectedVideoCodec.getWidth()
                + "x"
                + selectedVideoCodec.getHeight()
                + " "
                + selectedVideoCodec.getFramerate()
                + " "
                + selectedVideoCodec.getBitrate());
      }
      int result = NativeH264Encoder.InitEncoder(nativeH264EncoderParams);
      if (result != 0) {
        notifyPlayerEventError("Encoder init failed with error code " + result);
        return;
      }
    } catch (UnsatisfiedLinkError e) {
      notifyPlayerEventError(e.getMessage());
      return;
    }

    // Init the RTP layer
    try {
      releasePort();
      rtpSender = new VideoRtpSender(videoFormat, localRtpPort);
      rtpInput = new MediaRtpInput();
      rtpInput.open();
      if (videoRenderer != null) {
        // The video renderer is supposed to be opened and so we used its RTP stream
        if (logger.isActivated()) {
          logger.debug("Player shares the renderer RTP stream");
        }
        rtpSender.prepareSession(
            rtpInput, remoteHost, remotePort, videoRenderer.getRtpInputStream(), this);
      } else {
        // The video renderer doesn't exist and so we create a new RTP stream
        rtpSender.prepareSession(rtpInput, remoteHost, remotePort, this);
      }

    } catch (Exception e) {
      notifyPlayerEventError(e.getMessage());
      return;
    }

    // Player is opened
    opened = true;
    notifyPlayerEventOpened();
  }