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; |
- } |
-} |