| Index: webrtc/examples/android/media_demo/src/org/webrtc/webrtcdemo/MediaCodecVideoDecoder.java
|
| diff --git a/webrtc/examples/android/media_demo/src/org/webrtc/webrtcdemo/MediaCodecVideoDecoder.java b/webrtc/examples/android/media_demo/src/org/webrtc/webrtcdemo/MediaCodecVideoDecoder.java
|
| deleted file mode 100644
|
| index ba811d0d23ed1b786ed67f686d8a6fbc83aadebb..0000000000000000000000000000000000000000
|
| --- a/webrtc/examples/android/media_demo/src/org/webrtc/webrtcdemo/MediaCodecVideoDecoder.java
|
| +++ /dev/null
|
| @@ -1,338 +0,0 @@
|
| -/*
|
| - * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
| - *
|
| - * Use of this source code is governed by a BSD-style license
|
| - * that can be found in the LICENSE file in the root of the source
|
| - * tree. An additional intellectual property rights grant can be found
|
| - * in the file PATENTS. All contributing project authors may
|
| - * be found in the AUTHORS file in the root of the source tree.
|
| - */
|
| -
|
| -package org.webrtc.webrtcdemo;
|
| -
|
| -import android.app.AlertDialog;
|
| -import android.content.Context;
|
| -import android.content.DialogInterface;
|
| -import android.media.MediaCodec;
|
| -import android.media.MediaCrypto;
|
| -import android.media.MediaExtractor;
|
| -import android.media.MediaFormat;
|
| -import android.os.Handler;
|
| -import android.os.Looper;
|
| -import android.os.Message;
|
| -import android.util.Log;
|
| -import android.view.Surface;
|
| -import android.view.SurfaceView;
|
| -
|
| -import java.io.IOException;
|
| -import java.nio.ByteBuffer;
|
| -import java.util.LinkedList;
|
| -
|
| -class MediaCodecVideoDecoder {
|
| - public static final int DECODE = 0;
|
| - private enum CodecName { ON2_VP8, GOOGLE_VPX, EXYNOX_VP8 }
|
| -
|
| - private void check(boolean value, String message) {
|
| - if (value) {
|
| - return;
|
| - }
|
| - Log.e("WEBRTC-CHECK", message);
|
| - AlertDialog alertDialog = new AlertDialog.Builder(context).create();
|
| - alertDialog.setTitle("WebRTC Error");
|
| - alertDialog.setMessage(message);
|
| - alertDialog.setButton(DialogInterface.BUTTON_POSITIVE,
|
| - "OK",
|
| - new DialogInterface.OnClickListener() {
|
| - public void onClick(DialogInterface dialog, int which) {
|
| - return;
|
| - }
|
| - }
|
| - );
|
| - alertDialog.show();
|
| - }
|
| -
|
| - class Frame {
|
| - public ByteBuffer buffer;
|
| - public long timestampUs;
|
| -
|
| - Frame(ByteBuffer buffer, long timestampUs) {
|
| - this.buffer = buffer;
|
| - this.timestampUs = timestampUs;
|
| - }
|
| - }
|
| -
|
| - // This class enables decoding being run on a separate thread.
|
| - class DecodeHandler extends Handler {
|
| - @Override
|
| - public void handleMessage(Message msg) {
|
| - // TODO(dwkang): figure out exceptions just make this thread finish.
|
| - try {
|
| - switch (msg.what) {
|
| - case DECODE:
|
| - decodePendingBuffers();
|
| - long delayMillis = 5; // Don't busy wait.
|
| - handler.sendMessageDelayed(
|
| - handler.obtainMessage(DECODE), delayMillis);
|
| - break;
|
| - default:
|
| - break;
|
| - }
|
| - } catch (Exception e) {
|
| - e.printStackTrace();
|
| - }
|
| - }
|
| - }
|
| -
|
| - private static String TAG;
|
| - private Context context;
|
| - private SurfaceView surfaceView;
|
| -
|
| - private DecodeHandler handler;
|
| - private Thread looperThread;
|
| -
|
| - MediaCodec codec;
|
| - MediaFormat format;
|
| -
|
| - // Buffers supplied by MediaCodec for pushing encoded data to and pulling
|
| - // decoded data from.
|
| - private ByteBuffer[] codecInputBuffers;
|
| - private ByteBuffer[] codecOutputBuffers;
|
| -
|
| - // Frames from the native layer.
|
| - private LinkedList<Frame> frameQueue;
|
| - // Indexes to MediaCodec buffers
|
| - private LinkedList<Integer> availableInputBufferIndices;
|
| - private LinkedList<Integer> availableOutputBufferIndices;
|
| - private LinkedList<MediaCodec.BufferInfo> availableOutputBufferInfos;
|
| -
|
| - // Offset between system time and media time.
|
| - private long deltaTimeUs;
|
| -
|
| - public MediaCodecVideoDecoder(Context context) {
|
| - TAG = context.getString(R.string.tag);
|
| - this.context = context;
|
| - surfaceView = new SurfaceView(context);
|
| - frameQueue = new LinkedList<Frame>();
|
| - availableInputBufferIndices = new LinkedList<Integer>();
|
| - availableOutputBufferIndices = new LinkedList<Integer>();
|
| - availableOutputBufferInfos = new LinkedList<MediaCodec.BufferInfo>();
|
| - }
|
| -
|
| - public void dispose() {
|
| - codec.stop();
|
| - codec.release();
|
| - }
|
| -
|
| - // Return view that is written to by MediaCodec.
|
| - public SurfaceView getView() { return surfaceView; }
|
| -
|
| - // Entry point from the native layer. Called when the class should be ready
|
| - // to start receiving raw frames.
|
| - private boolean start(int width, int height) {
|
| - deltaTimeUs = -1;
|
| - if (!setCodecState(width, height, CodecName.ON2_VP8)) {
|
| - return false;
|
| - }
|
| - startLooperThread();
|
| - // The decoding must happen on |looperThread| thread.
|
| - handler.sendMessage(handler.obtainMessage(DECODE));
|
| - return true;
|
| - }
|
| -
|
| - private boolean setCodecState(int width, int height, CodecName codecName) {
|
| - // TODO(henrike): enable more than ON2_VP8 codec.
|
| - format = new MediaFormat();
|
| - format.setInteger(MediaFormat.KEY_WIDTH, width);
|
| - format.setInteger(MediaFormat.KEY_HEIGHT, height);
|
| - try {
|
| - switch (codecName) {
|
| - case ON2_VP8:
|
| - format.setString(MediaFormat.KEY_MIME, "video/x-vnd.on2.vp8");
|
| - codec = MediaCodec.createDecoderByType("video/x-vnd.on2.vp8");
|
| - break;
|
| - case GOOGLE_VPX:
|
| - // SW VP8 decoder
|
| - codec = MediaCodec.createByCodecName("OMX.google.vpx.decoder");
|
| - break;
|
| - case EXYNOX_VP8:
|
| - // Nexus10 HW VP8 decoder
|
| - codec = MediaCodec.createByCodecName("OMX.Exynos.VP8.Decoder");
|
| - break;
|
| - default:
|
| - return false;
|
| - }
|
| - } catch (Exception e) {
|
| - // TODO(dwkang): replace this instanceof/throw with a narrower catch
|
| - // clause once the SDK advances.
|
| - if (e instanceof IOException) {
|
| - Log.e(TAG, "Failed to create MediaCodec for VP8.", e);
|
| - return false;
|
| - }
|
| - throw new RuntimeException(e);
|
| - }
|
| - Surface surface = surfaceView.getHolder().getSurface();
|
| - MediaCrypto crypto = null; // No crypto.
|
| - int flags = 0; // Decoder (1 for encoder)
|
| - codec.configure(format, surface, crypto, flags);
|
| - codec.start();
|
| - codecInputBuffers = codec.getInputBuffers();
|
| - codecOutputBuffers = codec.getOutputBuffers();
|
| - return true;
|
| - }
|
| -
|
| - private void startLooperThread() {
|
| - looperThread = new Thread() {
|
| - @Override
|
| - public void run() {
|
| - Looper.prepare();
|
| - // Handler that is run by this thread.
|
| - handler = new DecodeHandler();
|
| - // Notify that the thread has created a handler.
|
| - synchronized(MediaCodecVideoDecoder.this) {
|
| - MediaCodecVideoDecoder.this.notify();
|
| - }
|
| - Looper.loop();
|
| - }
|
| - };
|
| - looperThread.start();
|
| - // Wait for thread to notify that Handler has been set up.
|
| - synchronized(this) {
|
| - try {
|
| - wait();
|
| - } catch (InterruptedException e) {
|
| - e.printStackTrace();
|
| - }
|
| - }
|
| - }
|
| -
|
| - // Entry point from the native layer. It pushes the raw buffer to this class.
|
| - private void pushBuffer(ByteBuffer buffer, long renderTimeMs) {
|
| - // TODO(dwkang): figure out why exceptions just make this thread finish.
|
| - try {
|
| - final long renderTimeUs = renderTimeMs * 1000;
|
| - synchronized(frameQueue) {
|
| - frameQueue.add(new Frame(buffer, renderTimeUs));
|
| - }
|
| - } catch (Exception e) {
|
| - e.printStackTrace();
|
| - }
|
| - }
|
| -
|
| - private boolean hasFrame() {
|
| - synchronized(frameQueue) {
|
| - return !frameQueue.isEmpty();
|
| - }
|
| - }
|
| -
|
| - private Frame dequeueFrame() {
|
| - synchronized(frameQueue) {
|
| - return frameQueue.removeFirst();
|
| - }
|
| - }
|
| -
|
| - private void flush() {
|
| - availableInputBufferIndices.clear();
|
| - availableOutputBufferIndices.clear();
|
| - availableOutputBufferInfos.clear();
|
| -
|
| - codec.flush();
|
| - }
|
| -
|
| - // Media time is relative to previous frame.
|
| - private long mediaTimeToSystemTime(long mediaTimeUs) {
|
| - if (deltaTimeUs == -1) {
|
| - long nowUs = System.currentTimeMillis() * 1000;
|
| - deltaTimeUs = nowUs - mediaTimeUs;
|
| - }
|
| - return deltaTimeUs + mediaTimeUs;
|
| - }
|
| -
|
| - private void decodePendingBuffers() {
|
| - int timeoutUs = 0; // Don't block on dequeuing input buffer.
|
| -
|
| - int index = codec.dequeueInputBuffer(timeoutUs);
|
| - if (index != MediaCodec.INFO_TRY_AGAIN_LATER) {
|
| - availableInputBufferIndices.add(index);
|
| - }
|
| - while (feedInputBuffer()) {}
|
| -
|
| - MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
|
| - index = codec.dequeueOutputBuffer(info, timeoutUs);
|
| - if (index > 0) {
|
| - availableOutputBufferIndices.add(index);
|
| - availableOutputBufferInfos.add(info);
|
| - }
|
| - if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
|
| - codecOutputBuffers = codec.getOutputBuffers();
|
| - }
|
| -
|
| - while (drainOutputBuffer()) {}
|
| - }
|
| -
|
| - // Returns true if MediaCodec is ready for more data and there was data
|
| - // available from the native layer.
|
| - private boolean feedInputBuffer() {
|
| - if (availableInputBufferIndices.isEmpty()) {
|
| - return false;
|
| - }
|
| - if (!hasFrame()) {
|
| - return false;
|
| - }
|
| - Frame frame = dequeueFrame();
|
| - ByteBuffer buffer = frame.buffer;
|
| -
|
| - int index = availableInputBufferIndices.pollFirst();
|
| - ByteBuffer codecData = codecInputBuffers[index];
|
| - check(codecData.capacity() >= buffer.capacity(),
|
| - "Buffer is too small to copy a frame.");
|
| - buffer.rewind();
|
| - codecData.rewind();
|
| - codecData.put(buffer);
|
| -
|
| - try {
|
| - int offset = 0;
|
| - int flags = 0;
|
| - codec.queueInputBuffer(index, offset, buffer.capacity(),
|
| - frame.timestampUs, flags);
|
| - } catch (MediaCodec.CryptoException e) {
|
| - check(false, "CryptoException w/ errorCode " + e.getErrorCode() +
|
| - ", '" + e.getMessage() + "'");
|
| - }
|
| - return true;
|
| - }
|
| -
|
| - // Returns true if more output data could be drained.MediaCodec has more data
|
| - // to deliver.
|
| - private boolean drainOutputBuffer() {
|
| - if (availableOutputBufferIndices.isEmpty()) {
|
| - return false;
|
| - }
|
| -
|
| - int index = availableOutputBufferIndices.peekFirst();
|
| - MediaCodec.BufferInfo info = availableOutputBufferInfos.peekFirst();
|
| - if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
|
| - // End of stream is unexpected with streamed video.
|
| - check(false, "Saw output end of stream.");
|
| - return false;
|
| - }
|
| - long realTimeUs = mediaTimeToSystemTime(info.presentationTimeUs);
|
| - long nowUs = System.currentTimeMillis() * 1000;
|
| - long lateUs = nowUs - realTimeUs;
|
| - if (lateUs < -10000) {
|
| - // Frame should not be presented yet.
|
| - return false;
|
| - }
|
| -
|
| - // TODO(dwkang): For some extreme cases, just not doing rendering is not
|
| - // enough. Need to seek to the next key frame.
|
| - boolean render = lateUs <= 30000;
|
| - if (!render) {
|
| - Log.d(TAG, "video late by " + lateUs + " us. Skipping...");
|
| - }
|
| - // Decode and render to surface if desired.
|
| - codec.releaseOutputBuffer(index, render);
|
| - availableOutputBufferIndices.removeFirst();
|
| - availableOutputBufferInfos.removeFirst();
|
| - return true;
|
| - }
|
| -}
|
|
|