/**
   * Write a media sample to the decoder.
   *
   * <p>A "sample" here refers to a single atomic access unit in the media stream. The definition of
   * "access unit" is dependent on the type of encoding used, but it typically refers to a single
   * frame of video or a few seconds of audio. {@link android.media.MediaExtractor} extracts data
   * from a stream one sample at a time.
   *
   * @param extractor Instance of {@link android.media.MediaExtractor} wrapping the media.
   * @param presentationTimeUs The time, relative to the beginning of the media stream, at which
   *     this buffer should be rendered.
   * @param flags Flags to pass to the decoder. See {@link MediaCodec#queueInputBuffer(int, int,
   *     int, long, int)}
   * @throws MediaCodec.CryptoException
   */
  public boolean writeSample(
      final MediaExtractor extractor,
      final boolean isSecure,
      final long presentationTimeUs,
      int flags) {
    boolean result = false;
    boolean isEos = false;

    if (!mAvailableInputBuffers.isEmpty()) {
      int index = mAvailableInputBuffers.remove();
      ByteBuffer buffer = mInputBuffers[index];

      // reads the sample from the file using extractor into the buffer
      int size = extractor.readSampleData(buffer, 0);
      if (size <= 0) {
        flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
      }

      // Submit the buffer to the codec for decoding. The presentationTimeUs
      // indicates the position (play time) for the current sample.
      if (!isSecure) {
        mDecoder.queueInputBuffer(index, 0, size, presentationTimeUs, flags);
      } else {
        extractor.getSampleCryptoInfo(cryptoInfo);
        mDecoder.queueSecureInputBuffer(index, 0, cryptoInfo, presentationTimeUs, flags);
      }

      result = true;
    }
    return result;
  }
  /**
   * Constructs the {@link MediaCodecWrapper} wrapper object around the video codec. The codec is
   * created using the encapsulated information in the {@link MediaFormat} object.
   *
   * @param trackFormat The format of the media object to be decoded.
   * @param surface Surface to render the decoded frames.
   * @return
   */
  public static MediaCodecWrapper fromVideoFormat(final MediaFormat trackFormat, Surface surface) {
    MediaCodecWrapper result = null;
    MediaCodec videoCodec = null;

    // BEGIN_INCLUDE(create_codec)
    final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);

    // Check to see if this is actually a video mime type. If it is, then create
    // a codec that can decode this mime type.
    if (mimeType.contains("video/")) {
      try {
        videoCodec = MediaCodec.createDecoderByType(mimeType);
        videoCodec.configure(trackFormat, surface, null, 0);
      } catch (Exception e) {

      }
    }

    // If codec creation was successful, then create a wrapper object around the
    // newly created codec.
    if (videoCodec != null) {
      result = new MediaCodecWrapper(videoCodec);
    }
    // END_INCLUDE(create_codec)

    return result;
  }
 private MediaCodecWrapper(MediaCodec codec) {
   mDecoder = codec;
   codec.start();
   mInputBuffers = codec.getInputBuffers();
   mOutputBuffers = codec.getOutputBuffers();
   mOutputBufferInfo = new MediaCodec.BufferInfo[mOutputBuffers.length];
   mAvailableInputBuffers = new ArrayDeque<Integer>(mOutputBuffers.length);
   mAvailableOutputBuffers = new ArrayDeque<Integer>(mInputBuffers.length);
 }
  /** Synchronize this object's state with the internal state of the wrapped MediaCodec. */
  private void update() {
    // BEGIN_INCLUDE(update_codec_state)
    int index;

    // Get valid input buffers from the codec to fill later in the same order they were
    // made available by the codec.
    while ((index = mDecoder.dequeueInputBuffer(0)) != MediaCodec.INFO_TRY_AGAIN_LATER) {
      mAvailableInputBuffers.add(index);
    }

    // Likewise with output buffers. If the output buffers have changed, start using the
    // new set of output buffers. If the output format has changed, notify listeners.
    MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
    while ((index = mDecoder.dequeueOutputBuffer(info, 0)) != MediaCodec.INFO_TRY_AGAIN_LATER) {
      switch (index) {
        case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
          mOutputBuffers = mDecoder.getOutputBuffers();
          mOutputBufferInfo = new MediaCodec.BufferInfo[mOutputBuffers.length];
          mAvailableOutputBuffers.clear();
          break;
        case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
          if (mOutputFormatChangedListener != null) {
            mHandler.post(
                new Runnable() {
                  @Override
                  public void run() {
                    mOutputFormatChangedListener.outputFormatChanged(
                        MediaCodecWrapper.this, mDecoder.getOutputFormat());
                  }
                });
          }
          break;
        default:
          // Making sure the index is valid before adding to output buffers. We've already
          // handled INFO_TRY_AGAIN_LATER, INFO_OUTPUT_FORMAT_CHANGED &
          // INFO_OUTPUT_BUFFERS_CHANGED i.e all the other possible return codes but
          // asserting index value anyways for future-proofing the code.
          if (index >= 0) {
            mOutputBufferInfo[index] = info;
            mAvailableOutputBuffers.add(index);
          } else {
            throw new IllegalStateException("Unknown status from dequeueOutputBuffer");
          }
          break;
      }
    }
    // END_INCLUDE(update_codec_state)

  }
  /**
   * Write a media sample to the decoder.
   *
   * <p>A "sample" here refers to a single atomic access unit in the media stream. The definition of
   * "access unit" is dependent on the type of encoding used, but it typically refers to a single
   * frame of video or a few seconds of audio. {@link android.media.MediaExtractor} extracts data
   * from a stream one sample at a time.
   *
   * @param input A ByteBuffer containing the input data for one sample. The buffer must be set up
   *     for reading, with its position set to the beginning of the sample data and its limit set to
   *     the end of the sample data.
   * @param presentationTimeUs The time, relative to the beginning of the media stream, at which
   *     this buffer should be rendered.
   * @param flags Flags to pass to the decoder. See {@link MediaCodec#queueInputBuffer(int, int,
   *     int, long, int)}
   * @throws MediaCodec.CryptoException
   */
  public boolean writeSample(
      final ByteBuffer input,
      final MediaCodec.CryptoInfo crypto,
      final long presentationTimeUs,
      final int flags)
      throws MediaCodec.CryptoException, WriteException {
    boolean result = false;
    int size = input.remaining();

    // check if we have dequed input buffers available from the codec
    if (size > 0 && !mAvailableInputBuffers.isEmpty()) {
      int index = mAvailableInputBuffers.remove();
      ByteBuffer buffer = mInputBuffers[index];

      // we can't write our sample to a lesser capacity input buffer.
      if (size > buffer.capacity()) {
        throw new MediaCodecWrapper.WriteException(
            String.format(
                "Insufficient capacity in MediaCodec buffer: "
                    + "tried to write %d, buffer capacity is %d.",
                input.remaining(), buffer.capacity()));
      }

      buffer.clear();
      buffer.put(input);

      // Submit the buffer to the codec for decoding. The presentationTimeUs
      // indicates the position (play time) for the current sample.
      if (crypto == null) {
        mDecoder.queueInputBuffer(index, 0, size, presentationTimeUs, flags);
      } else {
        mDecoder.queueSecureInputBuffer(index, 0, crypto, presentationTimeUs, flags);
      }
      result = true;
    }
    return result;
  }
  /**
   * Processes, releases and optionally renders the output buffer available at the head of the
   * queue. All observers are notified with a callback. See {@link
   * OutputSampleListener#outputSample(MediaCodecWrapper, android.media.MediaCodec.BufferInfo,
   * java.nio.ByteBuffer)}
   *
   * @param render True, if the buffer is to be rendered on the {@link Surface} configured
   */
  public void popSample(boolean render) {
    // dequeue available buffers and synchronize our data structures with the codec.
    update();
    if (!mAvailableOutputBuffers.isEmpty()) {
      int index = mAvailableOutputBuffers.remove();

      if (render && mOutputSampleListener != null) {
        ByteBuffer buffer = mOutputBuffers[index];
        MediaCodec.BufferInfo info = mOutputBufferInfo[index];
        mOutputSampleListener.outputSample(this, info, buffer);
      }

      // releases the buffer back to the codec
      mDecoder.releaseOutputBuffer(index, render);
    }
  }
 /** Releases resources and ends the encoding/decoding session. */
 public void stopAndRelease() {
   mDecoder.stop();
   mDecoder.release();
   mDecoder = null;
   mHandler = null;
 }