@Override
  public void process(FilterContext context) {
    if (mLogVerbose) Log.v(TAG, "Processing new frame");

    // First, get new frame if available
    if (mWaitForNewFrame || mFirstFrame) {
      boolean gotNewFrame;
      if (mWaitTimeout != 0) {
        gotNewFrame = mNewFrameAvailable.block(mWaitTimeout);
        if (!gotNewFrame) {
          if (!mCloseOnTimeout) {
            throw new RuntimeException("Timeout waiting for new frame");
          } else {
            if (mLogVerbose) Log.v(TAG, "Timeout waiting for a new frame. Closing.");
            closeOutputPort("video");
            return;
          }
        }
      } else {
        mNewFrameAvailable.block();
      }
      mNewFrameAvailable.close();
      mFirstFrame = false;
    }

    mSurfaceTexture.updateTexImage();

    mSurfaceTexture.getTransformMatrix(mFrameTransform);
    Matrix.multiplyMM(
        mMappedCoords, 0,
        mFrameTransform, 0,
        mSourceCoords, 0);
    mFrameExtractor.setSourceRegion(
        mMappedCoords[0], mMappedCoords[1],
        mMappedCoords[4], mMappedCoords[5],
        mMappedCoords[8], mMappedCoords[9],
        mMappedCoords[12], mMappedCoords[13]);
    // Next, render to output
    Frame output = context.getFrameManager().newFrame(mOutputFormat);
    mFrameExtractor.process(mMediaFrame, output);

    output.setTimestamp(mSurfaceTexture.getTimestamp());

    pushOutput("video", output);
    output.release();
  }