예제 #1
0
 /**
  * Method to set byte array to the MediaCodec encoder
  *
  * @param buffer
  * @param length length of byte array, zero means EOS.
  * @param presentationTimeUs
  */
 protected void encode(byte[] buffer, int length, long presentationTimeUs) {
   if (!mIsCapturing) return;
   int ix = 0, sz;
   final ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
   while (mIsCapturing && ix < length) {
     final int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_USEC);
     if (inputBufferIndex >= 0) {
       final ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
       inputBuffer.clear();
       sz = inputBuffer.remaining();
       sz = (ix + sz < length) ? sz : length - ix;
       if (sz > 0 && (buffer != null)) {
         inputBuffer.put(buffer, ix, sz);
       }
       ix += sz;
       //	            if (DEBUG) Log.v(TAG, "encode:queueInputBuffer");
       if (length <= 0) {
         // send EOS
         mIsEOS = true;
         if (DEBUG) Log.i(TAG, "send BUFFER_FLAG_END_OF_STREAM");
         mMediaCodec.queueInputBuffer(
             inputBufferIndex, 0, 0, presentationTimeUs, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
         break;
       } else {
         mMediaCodec.queueInputBuffer(inputBufferIndex, 0, sz, presentationTimeUs, 0);
       }
     } else if (inputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
       // wait for MediaCodec encoder is ready to encode
       // nothing to do here because MediaCodec#dequeueInputBuffer(TIMEOUT_USEC)
       // will wait for maximum TIMEOUT_USEC(10msec) on each call
     }
   }
 }
 private final void handleStart() {
   if (DEBUG) Log.v(TAG, "handleStart:");
   synchronized (mSync) {
     if (mState != STATE_PREPARED) throw new RuntimeException("invalid state:" + mState);
     mState = STATE_PLAYING;
   }
   if (mRequestTime > 0) {
     handleSeek(mRequestTime);
   }
   previousVideoPresentationTimeUs = -1;
   mVideoInputDone = mVideoOutputDone = true;
   Thread videoThread = null;
   if (mVideoTrackIndex >= 0) {
     final MediaCodec codec = internal_start_video(mVideoMediaExtractor, mVideoTrackIndex);
     if (codec != null) {
       mVideoMediaCodec = codec;
       mVideoBufferInfo = new MediaCodec.BufferInfo();
       mVideoInputBuffers = codec.getInputBuffers();
       mVideoOutputBuffers = codec.getOutputBuffers();
     }
     mVideoInputDone = mVideoOutputDone = false;
     videoThread = new Thread(mVideoTask, "VideoTask");
   }
   if (videoThread != null) videoThread.start();
 }
 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);
 }
  /**
   * Checks the video data.
   *
   * @return the number of bad frames
   */
  private int checkVideoData(VideoChunks inputData, MediaCodec decoder, OutputSurface surface) {
    final int TIMEOUT_USEC = 1000;
    ByteBuffer[] decoderInputBuffers = decoder.getInputBuffers();
    ByteBuffer[] decoderOutputBuffers = decoder.getOutputBuffers();
    MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
    int inputChunk = 0;
    int checkIndex = 0;
    int badFrames = 0;

    boolean outputDone = false;
    boolean inputDone = false;
    while (!outputDone) {
      if (VERBOSE) Log.d(TAG, "check loop");

      // Feed more data to the decoder.
      if (!inputDone) {
        int inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC);
        if (inputBufIndex >= 0) {
          if (inputChunk == inputData.getNumChunks()) {
            // End of stream -- send empty frame with EOS flag set.
            decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
            inputDone = true;
            if (VERBOSE) Log.d(TAG, "sent input EOS");
          } else {
            // Copy a chunk of input to the decoder.  The first chunk should have
            // the BUFFER_FLAG_CODEC_CONFIG flag set.
            ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
            inputBuf.clear();
            inputData.getChunkData(inputChunk, inputBuf);
            int flags = inputData.getChunkFlags(inputChunk);
            long time = inputData.getChunkTime(inputChunk);
            decoder.queueInputBuffer(inputBufIndex, 0, inputBuf.position(), time, flags);
            if (VERBOSE) {
              Log.d(
                  TAG,
                  "submitted frame "
                      + inputChunk
                      + " to dec, size="
                      + inputBuf.position()
                      + " flags="
                      + flags);
            }
            inputChunk++;
          }
        } else {
          if (VERBOSE) Log.d(TAG, "input buffer not available");
        }
      }

      if (!outputDone) {
        int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
        if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
          // no output available yet
          if (VERBOSE) Log.d(TAG, "no output from decoder available");
        } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
          decoderOutputBuffers = decoder.getOutputBuffers();
          if (VERBOSE) Log.d(TAG, "decoder output buffers changed");
        } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
          MediaFormat newFormat = decoder.getOutputFormat();
          if (VERBOSE) Log.d(TAG, "decoder output format changed: " + newFormat);
        } else if (decoderStatus < 0) {
          fail("unexpected result from decoder.dequeueOutputBuffer: " + decoderStatus);
        } else { // decoderStatus >= 0
          ByteBuffer decodedData = decoderOutputBuffers[decoderStatus];

          if (VERBOSE)
            Log.d(
                TAG, "surface decoder given buffer " + decoderStatus + " (size=" + info.size + ")");
          if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
            if (VERBOSE) Log.d(TAG, "output EOS");
            outputDone = true;
          }

          boolean doRender = (info.size != 0);

          // As soon as we call releaseOutputBuffer, the buffer will be forwarded
          // to SurfaceTexture to convert to a texture.  The API doesn't guarantee
          // that the texture will be available before the call returns, so we
          // need to wait for the onFrameAvailable callback to fire.
          decoder.releaseOutputBuffer(decoderStatus, doRender);
          if (doRender) {
            if (VERBOSE) Log.d(TAG, "awaiting frame " + checkIndex);
            assertEquals(
                "Wrong time stamp", computePresentationTime(checkIndex), info.presentationTimeUs);
            surface.awaitNewImage();
            surface.drawImage();
            if (!checkSurfaceFrame(checkIndex++)) {
              badFrames++;
            }
          }
        }
      }
    }

    return badFrames;
  }
  /** Edits a stream of video data. */
  private void editVideoData(
      VideoChunks inputData,
      MediaCodec decoder,
      OutputSurface outputSurface,
      InputSurface inputSurface,
      MediaCodec encoder,
      VideoChunks outputData) {
    final int TIMEOUT_USEC = 10000;
    ByteBuffer[] decoderInputBuffers = decoder.getInputBuffers();
    ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
    MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
    int inputChunk = 0;
    int outputCount = 0;

    boolean outputDone = false;
    boolean inputDone = false;
    boolean decoderDone = false;
    while (!outputDone) {
      if (VERBOSE) Log.d(TAG, "edit loop");

      // Feed more data to the decoder.
      if (!inputDone) {
        int inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC);
        if (inputBufIndex >= 0) {
          if (inputChunk == inputData.getNumChunks()) {
            // End of stream -- send empty frame with EOS flag set.
            decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
            inputDone = true;
            if (VERBOSE) Log.d(TAG, "sent input EOS (with zero-length frame)");
          } else {
            // Copy a chunk of input to the decoder.  The first chunk should have
            // the BUFFER_FLAG_CODEC_CONFIG flag set.
            ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
            inputBuf.clear();
            inputData.getChunkData(inputChunk, inputBuf);
            int flags = inputData.getChunkFlags(inputChunk);
            long time = inputData.getChunkTime(inputChunk);
            decoder.queueInputBuffer(inputBufIndex, 0, inputBuf.position(), time, flags);
            if (VERBOSE) {
              Log.d(
                  TAG,
                  "submitted frame "
                      + inputChunk
                      + " to dec, size="
                      + inputBuf.position()
                      + " flags="
                      + flags);
            }
            inputChunk++;
          }
        } else {
          if (VERBOSE) Log.d(TAG, "input buffer not available");
        }
      }

      // Assume output is available.  Loop until both assumptions are false.
      boolean decoderOutputAvailable = !decoderDone;
      boolean encoderOutputAvailable = true;
      while (decoderOutputAvailable || encoderOutputAvailable) {
        // Start by draining any pending output from the encoder.  It's important to
        // do this before we try to stuff any more data in.
        int encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
        if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
          // no output available yet
          if (VERBOSE) Log.d(TAG, "no output from encoder available");
          encoderOutputAvailable = false;
        } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
          encoderOutputBuffers = encoder.getOutputBuffers();
          if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
        } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
          MediaFormat newFormat = encoder.getOutputFormat();
          if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
        } else if (encoderStatus < 0) {
          fail("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
        } else { // encoderStatus >= 0
          ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
          if (encodedData == null) {
            fail("encoderOutputBuffer " + encoderStatus + " was null");
          }

          // Write the data to the output "file".
          if (info.size != 0) {
            encodedData.position(info.offset);
            encodedData.limit(info.offset + info.size);

            outputData.addChunk(encodedData, info.flags, info.presentationTimeUs);
            outputCount++;

            if (VERBOSE) Log.d(TAG, "encoder output " + info.size + " bytes");
          }
          outputDone = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
          encoder.releaseOutputBuffer(encoderStatus, false);
        }
        if (encoderStatus != MediaCodec.INFO_TRY_AGAIN_LATER) {
          // Continue attempts to drain output.
          continue;
        }

        // Encoder is drained, check to see if we've got a new frame of output from
        // the decoder.  (The output is going to a Surface, rather than a ByteBuffer,
        // but we still get information through BufferInfo.)
        if (!decoderDone) {
          int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
          if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
            // no output available yet
            if (VERBOSE) Log.d(TAG, "no output from decoder available");
            decoderOutputAvailable = false;
          } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
            // decoderOutputBuffers = decoder.getOutputBuffers();
            if (VERBOSE) Log.d(TAG, "decoder output buffers changed (we don't care)");
          } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            // expected before first buffer of data
            MediaFormat newFormat = decoder.getOutputFormat();
            if (VERBOSE) Log.d(TAG, "decoder output format changed: " + newFormat);
          } else if (decoderStatus < 0) {
            fail("unexpected result from decoder.dequeueOutputBuffer: " + decoderStatus);
          } else { // decoderStatus >= 0
            if (VERBOSE)
              Log.d(
                  TAG,
                  "surface decoder given buffer " + decoderStatus + " (size=" + info.size + ")");
            // The ByteBuffers are null references, but we still get a nonzero
            // size for the decoded data.
            boolean doRender = (info.size != 0);

            // As soon as we call releaseOutputBuffer, the buffer will be forwarded
            // to SurfaceTexture to convert to a texture.  The API doesn't
            // guarantee that the texture will be available before the call
            // returns, so we need to wait for the onFrameAvailable callback to
            // fire.  If we don't wait, we risk rendering from the previous frame.
            decoder.releaseOutputBuffer(decoderStatus, doRender);
            if (doRender) {
              // This waits for the image and renders it after it arrives.
              if (VERBOSE) Log.d(TAG, "awaiting frame");
              outputSurface.awaitNewImage();
              outputSurface.drawImage();

              // Send it to the encoder.
              inputSurface.setPresentationTime(info.presentationTimeUs * 1000);
              if (VERBOSE) Log.d(TAG, "swapBuffers");
              inputSurface.swapBuffers();
            }
            if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
              // forward decoder EOS to encoder
              if (VERBOSE) Log.d(TAG, "signaling input EOS");
              if (WORK_AROUND_BUGS) {
                // Bail early, possibly dropping a frame.
                return;
              } else {
                encoder.signalEndOfInputStream();
              }
            }
          }
        }
      }
    }

    if (inputChunk != outputCount) {
      throw new RuntimeException("frame lost: " + inputChunk + " in, " + outputCount + " out");
    }
  }
  // Pass null in |sharedContext| to configure the codec for ByteBuffer output.
  private boolean initDecode(VideoCodecType type, int width, int height, EGLContext sharedContext) {
    if (mediaCodecThread != null) {
      throw new RuntimeException("Forgot to release()?");
    }
    useSurface = (sharedContext != null);
    String mime = null;
    String[] supportedCodecPrefixes = null;
    if (type == VideoCodecType.VIDEO_CODEC_VP8) {
      mime = VP8_MIME_TYPE;
      supportedCodecPrefixes = supportedVp8HwCodecPrefixes;
    } else if (type == VideoCodecType.VIDEO_CODEC_H264) {
      mime = H264_MIME_TYPE;
      supportedCodecPrefixes = supportedH264HwCodecPrefixes;
    } else {
      throw new RuntimeException("Non supported codec " + type);
    }
    DecoderProperties properties = findDecoder(mime, supportedCodecPrefixes);
    if (properties == null) {
      throw new RuntimeException("Cannot find HW decoder for " + type);
    }
    Logging.d(
        TAG,
        "Java initDecode: "
            + type
            + " : "
            + width
            + " x "
            + height
            + ". Color: 0x"
            + Integer.toHexString(properties.colorFormat)
            + ". Use Surface: "
            + useSurface);
    if (sharedContext != null) {
      Logging.d(TAG, "Decoder shared EGL Context: " + sharedContext);
    }
    mediaCodecThread = Thread.currentThread();
    try {
      Surface decodeSurface = null;
      this.width = width;
      this.height = height;
      stride = width;
      sliceHeight = height;

      if (useSurface) {
        // Create shared EGL context.
        eglBase = new EglBase(sharedContext, EglBase.ConfigType.PIXEL_BUFFER);
        eglBase.createDummyPbufferSurface();
        eglBase.makeCurrent();

        // Create output surface
        textureID = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
        Logging.d(TAG, "Video decoder TextureID = " + textureID);
        surfaceTexture = new SurfaceTexture(textureID);
        surface = new Surface(surfaceTexture);
        decodeSurface = surface;
      }

      MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
      if (!useSurface) {
        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
      }
      Logging.d(TAG, "  Format: " + format);
      mediaCodec = MediaCodecVideoEncoder.createByCodecName(properties.codecName);
      if (mediaCodec == null) {
        return false;
      }
      mediaCodec.configure(format, decodeSurface, null, 0);
      mediaCodec.start();
      colorFormat = properties.colorFormat;
      outputBuffers = mediaCodec.getOutputBuffers();
      inputBuffers = mediaCodec.getInputBuffers();
      Logging.d(
          TAG,
          "Input buffers: " + inputBuffers.length + ". Output buffers: " + outputBuffers.length);
      return true;
    } catch (IllegalStateException e) {
      Logging.e(TAG, "initDecode failed", e);
      return false;
    }
  }
예제 #7
0
  @CalledByNative
  private static boolean decodeAudioFile(
      Context ctx, int nativeMediaCodecBridge, int inputFD, long dataSize) {

    if (dataSize < 0 || dataSize > 0x7fffffff) return false;

    MediaExtractor extractor = new MediaExtractor();

    ParcelFileDescriptor encodedFD;
    encodedFD = ParcelFileDescriptor.adoptFd(inputFD);
    try {
      extractor.setDataSource(encodedFD.getFileDescriptor(), 0, dataSize);
    } catch (Exception e) {
      e.printStackTrace();
      encodedFD.detachFd();
      return false;
    }

    if (extractor.getTrackCount() <= 0) {
      encodedFD.detachFd();
      return false;
    }

    MediaFormat format = extractor.getTrackFormat(0);

    // Number of channels specified in the file
    int inputChannelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);

    // Number of channels the decoder will provide. (Not
    // necessarily the same as inputChannelCount.  See
    // crbug.com/266006.)
    int outputChannelCount = inputChannelCount;

    int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
    String mime = format.getString(MediaFormat.KEY_MIME);

    long durationMicroseconds = 0;
    if (format.containsKey(MediaFormat.KEY_DURATION)) {
      try {
        durationMicroseconds = format.getLong(MediaFormat.KEY_DURATION);
      } catch (Exception e) {
        Log.d(LOG_TAG, "Cannot get duration");
      }
    }

    if (DEBUG) {
      Log.d(
          LOG_TAG,
          "Tracks: "
              + extractor.getTrackCount()
              + " Rate: "
              + sampleRate
              + " Channels: "
              + inputChannelCount
              + " Mime: "
              + mime
              + " Duration: "
              + durationMicroseconds
              + " microsec");
    }

    nativeInitializeDestination(
        nativeMediaCodecBridge, inputChannelCount, sampleRate, durationMicroseconds);

    // Create decoder
    MediaCodec codec = MediaCodec.createDecoderByType(mime);
    codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
    codec.start();

    ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
    ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();

    // A track must be selected and will be used to read samples.
    extractor.selectTrack(0);

    boolean sawInputEOS = false;
    boolean sawOutputEOS = false;

    // Keep processing until the output is done.
    while (!sawOutputEOS) {
      if (!sawInputEOS) {
        // Input side
        int inputBufIndex = codec.dequeueInputBuffer(TIMEOUT_MICROSECONDS);

        if (inputBufIndex >= 0) {
          ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
          int sampleSize = extractor.readSampleData(dstBuf, 0);
          long presentationTimeMicroSec = 0;

          if (sampleSize < 0) {
            sawInputEOS = true;
            sampleSize = 0;
          } else {
            presentationTimeMicroSec = extractor.getSampleTime();
          }

          codec.queueInputBuffer(
              inputBufIndex,
              0, /* offset */
              sampleSize,
              presentationTimeMicroSec,
              sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);

          if (!sawInputEOS) {
            extractor.advance();
          }
        }
      }

      // Output side
      MediaCodec.BufferInfo info = new BufferInfo();
      final int outputBufIndex = codec.dequeueOutputBuffer(info, TIMEOUT_MICROSECONDS);

      if (outputBufIndex >= 0) {
        ByteBuffer buf = codecOutputBuffers[outputBufIndex];

        if (info.size > 0) {
          nativeOnChunkDecoded(
              nativeMediaCodecBridge, buf, info.size, inputChannelCount, outputChannelCount);
        }

        buf.clear();
        codec.releaseOutputBuffer(outputBufIndex, false /* render */);

        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
          sawOutputEOS = true;
        }
      } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
        codecOutputBuffers = codec.getOutputBuffers();
      } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
        MediaFormat newFormat = codec.getOutputFormat();
        outputChannelCount = newFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
        Log.d(LOG_TAG, "output format changed to " + newFormat);
      }
    }

    encodedFD.detachFd();

    codec.stop();
    codec.release();
    codec = null;

    return true;
  }
예제 #8
0
    @Override
    public void run() {
      setup();

      synchronized (setupSignal) {
        setupSignal.notify();
      }

      if (!valid) {
        return;
      }

      decoder.start();

      ByteBuffer[] inputBuffers = decoder.getInputBuffers();
      ByteBuffer[] outputBuffers = decoder.getOutputBuffers();
      MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();

      boolean isEOS = false;
      long startMs = System.currentTimeMillis();
      long timeoutUs = 10000;

      int iframe = 0;

      while (!Thread.interrupted()) {
        if (!isEOS) {
          int inIndex = decoder.dequeueInputBuffer(10000);
          if (inIndex >= 0) {
            ByteBuffer buffer = inputBuffers[inIndex];
            int sampleSize = extractor.readSampleData(buffer, 0);
            if (sampleSize < 0) {
              if (LOCAL_LOGD) {
                Log.d("VideoDecoderForOpenCV", "InputBuffer BUFFER_FLAG_END_OF_STREAM");
              }
              decoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
              isEOS = true;
            } else {
              decoder.queueInputBuffer(inIndex, 0, sampleSize, extractor.getSampleTime(), 0);
              extractor.advance();
            }
          }
        }

        int outIndex = decoder.dequeueOutputBuffer(info, 10000);
        MediaFormat outFormat;
        switch (outIndex) {
          case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
            if (LOCAL_LOGD) {
              Log.d("VideoDecoderForOpenCV", "INFO_OUTPUT_BUFFERS_CHANGED");
            }
            outputBuffers = decoder.getOutputBuffers();
            break;
          case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
            outFormat = decoder.getOutputFormat();
            if (LOCAL_LOGD) {
              Log.d("VideoDecoderForOpenCV", "New format " + outFormat);
            }
            break;
          case MediaCodec.INFO_TRY_AGAIN_LATER:
            if (LOCAL_LOGD) {
              Log.d("VideoDecoderForOpenCV", "dequeueOutputBuffer timed out!");
            }
            break;
          default:
            ByteBuffer buffer = outputBuffers[outIndex];
            boolean doRender = (info.size != 0);

            // As soon as we call releaseOutputBuffer, the buffer will be forwarded
            // to SurfaceTexture to convert to a texture.  The API doesn't
            // guarantee that the texture will be available before the call
            // returns, so we need to wait for the onFrameAvailable callback to
            // fire.  If we don't wait, we risk rendering from the previous frame.
            decoder.releaseOutputBuffer(outIndex, doRender);

            if (doRender) {
              surface.awaitNewImage();
              surface.drawImage();
              if (LOCAL_LOGD) {
                Log.d("VideoDecoderForOpenCV", "Finish drawing a frame!");
              }
              if ((iframe++ % mDecimation) == 0) {
                // Send the frame for processing
                mMatBuffer.put();
              }
            }
            break;
        }

        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
          if (LOCAL_LOGD) {
            Log.d("VideoDecoderForOpenCV", "OutputBuffer BUFFER_FLAG_END_OF_STREAM");
          }
          break;
        }
      }
      mMatBuffer.invalidate();

      decoder.stop();

      teardown();
      mThread = null;
    }
        @Override
        public void run() {
          isDecoding = true;
          codec.start();
          @SuppressWarnings("deprecation")
          ByteBuffer[] inputBuffers = codec.getInputBuffers();
          @SuppressWarnings("deprecation")
          ByteBuffer[] outputBuffers = codec.getOutputBuffers();
          boolean sawInputEOS = false;
          boolean sawOutputEOS = false;
          while (!sawInputEOS && !sawOutputEOS && continuing) {
            if (state == State.PAUSED) {
              try {
                synchronized (decoderLock) {
                  decoderLock.wait();
                }
              } catch (InterruptedException e) {
                // Purposely not doing anything here
              }
              continue;
            }
            if (sonic != null) {
              sonic.setSpeed(speed);
              sonic.setPitch(1);
            }

            int inputBufIndex = codec.dequeueInputBuffer(200);
            if (inputBufIndex >= 0) {
              ByteBuffer dstBuf = inputBuffers[inputBufIndex];
              int sampleSize = extractor.readSampleData(dstBuf, 0);
              long presentationTimeUs = 0;
              if (sampleSize < 0) {
                sawInputEOS = true;
                sampleSize = 0;
              } else {
                presentationTimeUs = extractor.getSampleTime();
              }
              codec.queueInputBuffer(
                  inputBufIndex,
                  0,
                  sampleSize,
                  presentationTimeUs,
                  sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
              if (flushCodec) {
                codec.flush();
                flushCodec = false;
              }
              if (!sawInputEOS) {
                extractor.advance();
              }
            }
            final MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
            byte[] modifiedSamples = new byte[info.size];
            int res;
            //noinspection deprecation
            do {
              res = codec.dequeueOutputBuffer(info, 200);
              if (res >= 0) {
                final byte[] chunk = new byte[info.size];
                outputBuffers[res].get(chunk);
                outputBuffers[res].clear();
                if (chunk.length > 0) {
                  sonic.writeBytesToStream(chunk, chunk.length);
                } else {
                  sonic.flushStream();
                }
                int available = sonic.availableBytes();
                if (available > 0) {
                  if (modifiedSamples.length < available) {
                    modifiedSamples = new byte[available];
                  }
                  sonic.readBytesFromStream(modifiedSamples, available);
                  track.write(modifiedSamples, 0, available);
                }
                codec.releaseOutputBuffer(res, false);
                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                  sawOutputEOS = true;
                }
              } else //noinspection deprecation
              if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                //noinspection deprecation
                outputBuffers = codec.getOutputBuffers();
              } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                track.stop();
                lock.lock();
                try {
                  track.release();
                  final MediaFormat oFormat = codec.getOutputFormat();

                  initDevice(
                      oFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE),
                      oFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
                  //noinspection deprecation
                  outputBuffers = codec.getOutputBuffers();
                  track.play();
                } catch (IOException e) {
                  e.printStackTrace();
                } finally {
                  lock.unlock();
                }
              }
            } while (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED
                || res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
          }

          codec.stop();
          track.stop();
          isDecoding = false;
          if (continuing && (sawInputEOS || sawOutputEOS)) {
            state = State.PLAYBACK_COMPLETED;
            L.d(TAG, "State changed to: " + state);
            Thread t =
                new Thread(
                    new Runnable() {
                      @Override
                      public void run() {
                        if (onCompletionListener != null) {
                          onCompletionListener.onCompletion();
                        }
                        stayAwake(false);
                      }
                    });
            t.setDaemon(true);
            t.start();
          }
          synchronized (decoderLock) {
            decoderLock.notifyAll();
          }
        }