diff --git a/webrtc/engine_configurations.h b/webrtc/engine_configurations.h
index 553aed4def..67effa0690 100644
--- a/webrtc/engine_configurations.h
+++ b/webrtc/engine_configurations.h
@@ -112,7 +112,7 @@
#define WEBRTC_VIDEO_ENGINE_NETWORK_API
#define WEBRTC_VIDEO_ENGINE_RENDER_API
#define WEBRTC_VIDEO_ENGINE_RTP_RTCP_API
-// #define WEBRTC_VIDEO_ENGINE_EXTERNAL_CODEC_API
+#define WEBRTC_VIDEO_ENGINE_EXTERNAL_CODEC_API
// Now handled by gyp:
// WEBRTC_VIDEO_ENGINE_FILE_API
diff --git a/webrtc/video_engine/test/android/build.xml b/webrtc/video_engine/test/android/build.xml
index 49fe07cdd4..8fd7a1902a 100644
--- a/webrtc/video_engine/test/android/build.xml
+++ b/webrtc/video_engine/test/android/build.xml
@@ -221,7 +221,7 @@ Notice for all the files in this folder.
-
+
diff --git a/webrtc/video_engine/test/android/jni/Android.mk b/webrtc/video_engine/test/android/jni/Android.mk
index 4bdbae61e8..b3c0822bc2 100644
--- a/webrtc/video_engine/test/android/jni/Android.mk
+++ b/webrtc/video_engine/test/android/jni/Android.mk
@@ -288,7 +288,9 @@ include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_MODULE := libwebrtc-video-demo-jni
LOCAL_CPP_EXTENSION := .cc
-LOCAL_SRC_FILES := vie_android_java_api.cc
+LOCAL_SRC_FILES := \
+ vie_android_java_api.cc \
+ android_media_codec_decoder.cc
LOCAL_CFLAGS := \
'-DWEBRTC_TARGET_PC' \
'-DWEBRTC_ANDROID'
@@ -296,6 +298,7 @@ LOCAL_CFLAGS := \
LOCAL_C_INCLUDES := \
external/gtest/include \
$(LOCAL_PATH)/../../../.. \
+ $(LOCAL_PATH)/../../../../.. \
$(LOCAL_PATH)/../../../include \
$(LOCAL_PATH)/../../../../voice_engine/include
diff --git a/webrtc/video_engine/test/android/jni/android_media_codec_decoder.cc b/webrtc/video_engine/test/android/jni/android_media_codec_decoder.cc
new file mode 100644
index 0000000000..0e2ec50f5b
--- /dev/null
+++ b/webrtc/video_engine/test/android/jni/android_media_codec_decoder.cc
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2012 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.
+ */
+
+#include
+#define LOG_TAG "AndroidMediaCodecDecoder"
+
+#include
+
+#include "android_media_codec_decoder.h"
+
+namespace webrtc {
+
+AndroidMediaCodecDecoder::AndroidMediaCodecDecoder(
+ JavaVM* vm, jobject surface, jclass decoderClass)
+ : decode_complete_callback_(NULL),
+ vm_(vm),
+ surface_(surface),
+ mediaCodecDecoder_(NULL),
+ decoderClass_(decoderClass),
+ env_(NULL),
+ setEncodedImageID_(NULL),
+ vm_attached_(false) {
+}
+
+WebRtc_Word32 AndroidMediaCodecDecoder::InitDecode(
+ const VideoCodec* codecSettings, WebRtc_Word32 numberOfCores) {
+ __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "%s", __func__);
+
+ // TODO(dwkang): Detach this thread from VM. => this leads to a crash on
+ // "StopCall".
+ int ret = vm_->AttachCurrentThread(&env_, NULL);
+ // Get the JNI env for this thread
+ if ((ret < 0) || !env_) {
+ __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG,
+ "Could not attach thread to JVM (%d, %p)", ret,
+ env_);
+ return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
+ } else {
+ vm_attached_ = true;
+ }
+
+ // Initialize the media codec java decoder class.
+ jmethodID mid = env_->GetMethodID(decoderClass_, "", "()V");
+ mediaCodecDecoder_ = env_->NewGlobalRef(env_->NewObject(decoderClass_, mid));
+
+ mid = env_->GetMethodID(
+ decoderClass_, "configure", "(Landroid/view/SurfaceView;II)V");
+ env_->CallVoidMethod(mediaCodecDecoder_, mid, surface_,
+ codecSettings->width, codecSettings->height);
+
+ setEncodedImageID_ = env_->GetMethodID(
+ decoderClass_, "setEncodedImage", "(Ljava/nio/ByteBuffer;J)V");
+
+ // Call start()
+ jmethodID startID = env_->GetMethodID(decoderClass_, "start", "()V");
+ env_->CallVoidMethod(mediaCodecDecoder_, startID);
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+WebRtc_Word32 AndroidMediaCodecDecoder::Decode(
+ const EncodedImage& inputImage,
+ bool missingFrames,
+ const RTPFragmentationHeader* fragmentation,
+ const CodecSpecificInfo* codecSpecificInfo,
+ WebRtc_Word64 renderTimeMs) {
+ if (!vm_attached_) {
+ return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
+ }
+
+ jobject byteBuffer =
+ env_->NewDirectByteBuffer(inputImage._buffer, inputImage._length);
+ env_->CallVoidMethod(
+ mediaCodecDecoder_, setEncodedImageID_, byteBuffer, renderTimeMs);
+ env_->DeleteLocalRef(byteBuffer);
+
+ return WEBRTC_VIDEO_CODEC_NO_OUTPUT;
+}
+
+WebRtc_Word32 AndroidMediaCodecDecoder::RegisterDecodeCompleteCallback(
+ DecodedImageCallback* callback) {
+ __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "%s", __func__);
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+WebRtc_Word32 AndroidMediaCodecDecoder::Release() {
+ __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "%s", __func__);
+ env_->DeleteGlobalRef(mediaCodecDecoder_);
+ mediaCodecDecoder_ = NULL;
+
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+WebRtc_Word32 AndroidMediaCodecDecoder::Reset() {
+ __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "%s", __func__);
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+} // namespace webrtc
diff --git a/webrtc/video_engine/test/android/jni/android_media_codec_decoder.h b/webrtc/video_engine/test/android/jni/android_media_codec_decoder.h
new file mode 100644
index 0000000000..13707eb82f
--- /dev/null
+++ b/webrtc/video_engine/test/android/jni/android_media_codec_decoder.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2012 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.
+ */
+
+#ifndef WEBRTC_VIDEO_ENGINE_TEST_ANDROID_JNI_ANDROID_MEDIA_CODEC_DECODER_H_
+#define WEBRTC_VIDEO_ENGINE_TEST_ANDROID_JNI_ANDROID_MEDIA_CODEC_DECODER_H_
+
+#include "modules/video_coding/codecs/interface/video_codec_interface.h"
+
+namespace webrtc {
+
+class AndroidMediaCodecDecoder : public VideoDecoder {
+ public:
+ AndroidMediaCodecDecoder(JavaVM* vm, jobject surface, jclass decoderClass);
+ virtual ~AndroidMediaCodecDecoder() { }
+
+ // Initialize the decoder with the information from the VideoCodec.
+ //
+ // Input:
+ // - inst : Codec settings
+ // - numberOfCores : Number of cores available for the decoder
+ //
+ // Return value : WEBRTC_VIDEO_CODEC_OK if OK, < 0 otherwise.
+ virtual WebRtc_Word32 InitDecode(
+ const VideoCodec* codecSettings, WebRtc_Word32 numberOfCores);
+
+ // Decode encoded image (as a part of a video stream). The decoded image
+ // will be returned to the user through the decode complete callback.
+ //
+ // Input:
+ // - inputImage : Encoded image to be decoded
+ // - missingFrames : True if one or more frames have been lost
+ // since the previous decode call.
+ // - fragmentation : Specifies where the encoded frame can be
+ // split into separate fragments. The meaning
+ // of fragment is codec specific, but often
+ // means that each fragment is decodable by
+ // itself.
+ // - codecSpecificInfo : Pointer to codec specific data
+ // - renderTimeMs : System time to render in milliseconds. Only
+ // used by decoders with internal rendering.
+ //
+ // Return value : WEBRTC_VIDEO_CODEC_OK if OK, < 0 otherwise.
+ virtual WebRtc_Word32
+ Decode(const EncodedImage& inputImage,
+ bool missingFrames,
+ const RTPFragmentationHeader* fragmentation,
+ const CodecSpecificInfo* codecSpecificInfo = NULL,
+ WebRtc_Word64 renderTimeMs = -1);
+
+ // Register an decode complete callback object.
+ //
+ // Input:
+ // - callback : Callback object which handles decoded images.
+ //
+ // Return value : WEBRTC_VIDEO_CODEC_OK if OK, < 0 otherwise.
+ virtual WebRtc_Word32 RegisterDecodeCompleteCallback(
+ DecodedImageCallback* callback);
+
+ // Free decoder memory.
+ //
+ // Return value : WEBRTC_VIDEO_CODEC_OK if OK, < 0 otherwise.
+ virtual WebRtc_Word32 Release();
+
+ // Reset decoder state and prepare for a new call.
+ //
+ // Return value : WEBRTC_VIDEO_CODEC_OK if OK, < 0 otherwise.
+ virtual WebRtc_Word32 Reset();
+
+ // Codec configuration data sent out-of-band, i.e. in SIP call setup
+ //
+ // Input/Output:
+ // - buffer : Buffer pointer to the configuration data
+ // - size : The size of the configuration data in
+ // bytes
+ //
+ // Return value : WEBRTC_VIDEO_CODEC_OK if OK, < 0 otherwise.
+ virtual WebRtc_Word32 SetCodecConfigParameters(
+ const WebRtc_UWord8* /*buffer*/, WebRtc_Word32 /*size*/) {
+ return WEBRTC_VIDEO_CODEC_ERROR;
+ }
+
+ // Create a copy of the codec and its internal state.
+ //
+ // Return value : A copy of the instance if OK, NULL otherwise.
+ virtual VideoDecoder* Copy() { return NULL; }
+
+ private:
+ DecodedImageCallback* decode_complete_callback_;
+ JavaVM* vm_;
+ jobject surface_;
+ jobject mediaCodecDecoder_;
+ jclass decoderClass_;
+ JNIEnv* env_;
+ jmethodID setEncodedImageID_;
+ bool vm_attached_;
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_VIDEO_ENGINE_TEST_ANDROID_JNI_ANDROID_MEDIA_CODEC_DECODER_H_
diff --git a/webrtc/video_engine/test/android/jni/org_webrtc_videoengineapp_vie_android_java_api.h b/webrtc/video_engine/test/android/jni/org_webrtc_videoengineapp_vie_android_java_api.h
index 935f4c1cd6..ccfa6a4e8c 100644
--- a/webrtc/video_engine/test/android/jni/org_webrtc_videoengineapp_vie_android_java_api.h
+++ b/webrtc/video_engine/test/android/jni/org_webrtc_videoengineapp_vie_android_java_api.h
@@ -17,7 +17,6 @@
#ifdef __cplusplus
extern "C" {
#endif
-
/*
* Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI
* Method: NativeInit
@@ -109,19 +108,18 @@ JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_SetLocal
/*
* Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI
* Method: SetSendDestination
- * Signature: (IILjava/lang/String)I
+ * Signature: (IILjava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_SetSendDestination
(JNIEnv *, jobject, jint, jint, jstring);
/*
* Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI
- * Method: GetCodecs(
- * Signature: ()I
+ * Method: GetCodecs
+ * Signature: ()[Ljava/lang/String;
*/
-JNIEXPORT jobjectArray JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_GetCodecs(
- JNIEnv *env,
- jobject);
+JNIEXPORT jobjectArray JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_GetCodecs
+ (JNIEnv *, jobject);
/*
* Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI
@@ -195,6 +193,14 @@ JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_GetCamer
JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_SetRotation
(JNIEnv *, jobject, jint, jint);
+/*
+ * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI
+ * Method: SetExternalMediaCodecDecoderRenderer
+ * Signature: (ILjava/lang/Object;)I
+ */
+JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_SetExternalMediaCodecDecoderRenderer
+ (JNIEnv *, jobject, jint, jobject);
+
/*
* Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI
* Method: EnableNACK
@@ -238,7 +244,7 @@ JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_StopInco
/*
* Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI
* Method: VoE_Create
- * Signature: (Landroid/content/Context)Z
+ * Signature: (Landroid/content/Context;)Z
*/
JNIEXPORT jboolean JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VoE_1Create
(JNIEnv *, jobject, jobject);
@@ -355,8 +361,6 @@ JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VoE_1Sto
JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VoE_1SetSpeakerVolume
(JNIEnv *, jobject, jint);
-
-
/*
* Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI
* Method: VoE_SetLoudspeakerStatus
@@ -407,8 +411,8 @@ JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VoE_1Num
/*
* Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI
- * Method: VoE_NumOfCodecs
- * Signature: ()Z
+ * Method: VoE_GetCodecs
+ * Signature: ()[Ljava/lang/String;
*/
JNIEXPORT jobjectArray JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VoE_1GetCodecs
(JNIEnv *, jobject);
@@ -440,7 +444,7 @@ JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VoE_1Set
/*
* Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI
* Method: VoE_SetNSStatus
- * Signature: (ZI)I
+ * Signature: (Z)I
*/
JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VoE_1SetNSStatus
(JNIEnv *, jobject, jboolean);
@@ -448,7 +452,7 @@ JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VoE_1Set
/*
* Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI
* Method: VoE_StartDebugRecording
- * Signature: (Ljava/lang/String)I
+ * Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VoE_1StartDebugRecording
(JNIEnv *, jobject, jstring);
@@ -467,7 +471,7 @@ JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VoE_1Sto
* Signature: (ILjava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VoE_1StartIncomingRTPDump
- (JNIEnv *, jobject, jint, jstring);
+ (JNIEnv *, jobject, jint, jstring);
/*
* Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI
@@ -475,7 +479,7 @@ JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VoE_1Sta
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VoE_1StopIncomingRTPDump
- (JNIEnv *, jobject, jint);
+ (JNIEnv *, jobject, jint);
#ifdef __cplusplus
}
diff --git a/webrtc/video_engine/test/android/jni/vie_android_java_api.cc b/webrtc/video_engine/test/android/jni/vie_android_java_api.cc
index c544682f7c..6168f22b60 100644
--- a/webrtc/video_engine/test/android/jni/vie_android_java_api.cc
+++ b/webrtc/video_engine/test/android/jni/vie_android_java_api.cc
@@ -26,11 +26,13 @@
#include "vie_base.h"
#include "vie_codec.h"
#include "vie_capture.h"
+#include "vie_external_codec.h"
#include "vie_network.h"
#include "vie_render.h"
#include "vie_rtp_rtcp.h"
#include "common_types.h"
+#include "android_media_codec_decoder.h"
#define WEBRTC_LOG_TAG "*WEBRTCN*"
#define VALIDATE_BASE_POINTER \
@@ -118,6 +120,7 @@ typedef struct
ViERTP_RTCP* rtp;
ViERender* render;
ViECapture* capture;
+ ViEExternalCodec* externalCodec;
VideoCallbackAndroid* callback;
} VideoEngineData;
@@ -335,6 +338,13 @@ JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_GetVideo
return -1;
}
+ vieData.externalCodec = ViEExternalCodec::GetInterface(vieData.vie);
+ if (!vieData.capture) {
+ __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG,
+ "Get External Codec sub-API failed");
+ return -1;
+ }
+
return 0;
}
@@ -440,6 +450,11 @@ JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_Terminat
"Failed to release Base sub-API");
}
+ if (!vieData.externalCodec || vieData.externalCodec->Release()) {
+ __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG,
+ "Failed to release External Codec sub-API");
+ }
+
// Delete Vie
if (!VideoEngine::Delete(vieData.vie)) {
__android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG,
@@ -960,6 +975,32 @@ JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_SetRotat
return ret;
}
+/*
+ * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI
+ * Method: SetExternalMediaCodecDecoderRenderer
+ * Signature: (ILjava/lang/Object;)I
+ */
+JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_SetExternalMediaCodecDecoderRenderer(
+ JNIEnv *env,
+ jobject,
+ jint channel,
+ jobject glSurface)
+{
+ __android_log_write(
+ ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "SetExternalMediaCodecDecoder");
+
+ jclass cls = env->FindClass("org/webrtc/videoengine/ViEMediaCodecDecoder");
+ env->NewGlobalRef(cls);
+
+ AndroidMediaCodecDecoder* mediaCodecDecoder =
+ new AndroidMediaCodecDecoder(webrtcGlobalVM, glSurface, cls);
+
+ // TODO(dwkang): Check the ownership of decoder object and release it
+ // if needed.
+ return vieData.externalCodec->RegisterExternalReceiveCodec(
+ channel, 120, mediaCodecDecoder, true);
+}
+
/*
* Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI
* Method: EnableNACK
diff --git a/webrtc/video_engine/test/android/res/layout/main.xml b/webrtc/video_engine/test/android/res/layout/main.xml
index aa6bb88eec..a845a860a1 100644
--- a/webrtc/video_engine/test/android/res/layout/main.xml
+++ b/webrtc/video_engine/test/android/res/layout/main.xml
@@ -81,6 +81,13 @@
android:layout_height="wrap_content"
android:text="@string/surfaceview"
android:textColor="#fff" />
+
diff --git a/webrtc/video_engine/test/android/res/values/strings.xml b/webrtc/video_engine/test/android/res/values/strings.xml
index 82760b0a32..106d691a76 100644
--- a/webrtc/video_engine/test/android/res/values/strings.xml
+++ b/webrtc/video_engine/test/android/res/values/strings.xml
@@ -36,5 +36,6 @@
APMRecord
rtpdump
SurfaceView
+MediaCodec Decoder/Renderer
OpenGL
diff --git a/webrtc/video_engine/test/android/src/org/webrtc/videoengine/ViEMediaCodecDecoder.java b/webrtc/video_engine/test/android/src/org/webrtc/videoengine/ViEMediaCodecDecoder.java
new file mode 100644
index 0000000000..9ca4125455
--- /dev/null
+++ b/webrtc/video_engine/test/android/src/org/webrtc/videoengine/ViEMediaCodecDecoder.java
@@ -0,0 +1,404 @@
+/*
+ * Copyright (c) 2012 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.videoengine;
+
+import android.media.MediaCodec;
+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.nio.ByteBuffer;
+import java.util.LinkedList;
+
+class CodecState {
+ private static final String TAG = "CodecState";
+
+ private ViEMediaCodecDecoder mView;
+ private MediaFormat mFormat;
+ private boolean mSawInputEOS, mSawOutputEOS;
+
+ private MediaCodec mCodec;
+ private MediaFormat mOutputFormat;
+ private ByteBuffer[] mCodecInputBuffers;
+ private ByteBuffer[] mCodecOutputBuffers;
+
+ private LinkedList mAvailableInputBufferIndices;
+ private LinkedList mAvailableOutputBufferIndices;
+ private LinkedList mAvailableOutputBufferInfos;
+
+ private long mLastMediaTimeUs;
+
+ public CodecState(
+ ViEMediaCodecDecoder view,
+ MediaFormat format,
+ MediaCodec codec) {
+ mView = view;
+ mFormat = format;
+ mSawInputEOS = mSawOutputEOS = false;
+
+ mCodec = codec;
+
+ mCodec.start();
+ mCodecInputBuffers = mCodec.getInputBuffers();
+ mCodecOutputBuffers = mCodec.getOutputBuffers();
+
+ mAvailableInputBufferIndices = new LinkedList();
+ mAvailableOutputBufferIndices = new LinkedList();
+ mAvailableOutputBufferInfos = new LinkedList();
+
+ mLastMediaTimeUs = 0;
+ }
+
+ public void release() {
+ mCodec.stop();
+ mCodecInputBuffers = null;
+ mCodecOutputBuffers = null;
+ mOutputFormat = null;
+
+ mAvailableOutputBufferInfos = null;
+ mAvailableOutputBufferIndices = null;
+ mAvailableInputBufferIndices = null;
+
+ mCodec.release();
+ mCodec = null;
+ }
+
+ public void start() {
+ }
+
+ public void pause() {
+ }
+
+ public long getCurrentPositionUs() {
+ return mLastMediaTimeUs;
+ }
+
+ public void flush() {
+ mAvailableInputBufferIndices.clear();
+ mAvailableOutputBufferIndices.clear();
+ mAvailableOutputBufferInfos.clear();
+
+ mSawInputEOS = false;
+ mSawOutputEOS = false;
+
+ mCodec.flush();
+ }
+
+ public void doSomeWork() {
+ int index = mCodec.dequeueInputBuffer(0 /* timeoutUs */);
+
+ if (index != MediaCodec.INFO_TRY_AGAIN_LATER) {
+ mAvailableInputBufferIndices.add(new Integer(index));
+ }
+
+ while (feedInputBuffer()) {}
+
+ MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+ index = mCodec.dequeueOutputBuffer(info, 0 /* timeoutUs */);
+
+ if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ mOutputFormat = mCodec.getOutputFormat();
+ } else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ mCodecOutputBuffers = mCodec.getOutputBuffers();
+ } else if (index != MediaCodec.INFO_TRY_AGAIN_LATER) {
+ mAvailableOutputBufferIndices.add(new Integer(index));
+ mAvailableOutputBufferInfos.add(info);
+ }
+
+ while (drainOutputBuffer()) {}
+ }
+
+ /** returns true if more input data could be fed */
+ private boolean feedInputBuffer() {
+ if (mSawInputEOS || mAvailableInputBufferIndices.isEmpty()) {
+ return false;
+ }
+
+ int index = mAvailableInputBufferIndices.peekFirst().intValue();
+
+ ByteBuffer codecData = mCodecInputBuffers[index];
+
+ if (mView.hasFrame()) {
+ Frame frame = mView.dequeueFrame();
+ ByteBuffer buffer = frame.mBuffer;
+ if (buffer == null) {
+ return false;
+ }
+ if (codecData.capacity() < buffer.capacity()) {
+ Log.e(TAG, "Buffer is too small to copy a frame.");
+ // TODO(dwkang): split the frame into the multiple buffer.
+ }
+ buffer.rewind();
+ codecData.rewind();
+ codecData.put(buffer);
+ codecData.rewind();
+
+ try {
+ mCodec.queueInputBuffer(
+ index, 0 /* offset */, buffer.capacity(), frame.mTimeStampUs,
+ 0 /* flags */);
+
+ mAvailableInputBufferIndices.removeFirst();
+ } catch (MediaCodec.CryptoException e) {
+ Log.d(TAG, "CryptoException w/ errorCode "
+ + e.getErrorCode() + ", '" + e.getMessage() + "'");
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+
+ /** returns true if more output data could be drained */
+ private boolean drainOutputBuffer() {
+ if (mSawOutputEOS || mAvailableOutputBufferIndices.isEmpty()) {
+ return false;
+ }
+
+ int index = mAvailableOutputBufferIndices.peekFirst().intValue();
+ MediaCodec.BufferInfo info = mAvailableOutputBufferInfos.peekFirst();
+
+ if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ Log.d(TAG, "saw output EOS.");
+
+ mSawOutputEOS = true;
+ return false;
+ }
+
+ long realTimeUs =
+ mView.getRealTimeUsForMediaTime(info.presentationTimeUs);
+ long nowUs = System.currentTimeMillis() * 1000;
+ long lateUs = nowUs - realTimeUs;
+
+ // video
+ boolean render;
+
+ // TODO(dwkang): For some extreme cases, just not doing rendering is not enough.
+ // Need to seek to the next key frame.
+ if (lateUs < -10000) {
+ // too early;
+ return false;
+ } else if (lateUs > 30000) {
+ Log.d(TAG, "video late by " + lateUs + " us. Skipping...");
+ render = false;
+ } else {
+ render = true;
+ mLastMediaTimeUs = info.presentationTimeUs;
+ }
+
+ MediaFormat format= mCodec.getOutputFormat();
+ Log.d(TAG, "Video output format :" + format.getInteger(MediaFormat.KEY_COLOR_FORMAT));
+ mCodec.releaseOutputBuffer(index, render);
+
+ mAvailableOutputBufferIndices.removeFirst();
+ mAvailableOutputBufferInfos.removeFirst();
+ return true;
+ }
+}
+
+class Frame {
+ public ByteBuffer mBuffer;
+ public long mTimeStampUs;
+
+ Frame(ByteBuffer buffer, long timeStampUs) {
+ mBuffer = buffer;
+ mTimeStampUs = timeStampUs;
+ }
+}
+
+class ViEMediaCodecDecoder {
+ private static final String TAG = "ViEMediaCodecDecoder";
+
+ private MediaExtractor mExtractor;
+
+ private CodecState mCodecState;
+
+ private int mState;
+ private static final int STATE_IDLE = 1;
+ private static final int STATE_PREPARING = 2;
+ private static final int STATE_PLAYING = 3;
+ private static final int STATE_PAUSED = 4;
+
+ private Handler mHandler;
+ private static final int EVENT_PREPARE = 1;
+ private static final int EVENT_DO_SOME_WORK = 2;
+
+ private long mDeltaTimeUs;
+ private long mDurationUs;
+
+ private SurfaceView mSurfaceView;
+ private LinkedList mFrameQueue = new LinkedList();
+
+ private Thread mLooperThread;
+
+ public void configure(SurfaceView surfaceView, int width, int height) {
+ mSurfaceView = surfaceView;
+ Log.d(TAG, "configure " + "width" + width + "height" + height + mSurfaceView.toString());
+
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, "video/x-vnd.on2.vp8");
+ format.setInteger(MediaFormat.KEY_WIDTH, width);
+ format.setInteger(MediaFormat.KEY_HEIGHT, height);
+ MediaCodec codec = MediaCodec.createDecoderByType("video/x-vnd.on2.vp8");
+ // SW VP8 decoder
+ // MediaCodec codec = MediaCodec.createByCodecName("OMX.google.vpx.decoder");
+ // Nexus10 HW VP8 decoder
+ // MediaCodec codec = MediaCodec.createByCodecName("OMX.Exynos.VP8.Decoder");
+ Surface surface = mSurfaceView.getHolder().getSurface();
+ Log.d(TAG, "Surface " + surface.isValid());
+ codec.configure(
+ format, surface, null, 0);
+ mCodecState = new CodecState(this, format, codec);
+
+ initMediaCodecView();
+ }
+
+ public void setEncodedImage(ByteBuffer buffer, long renderTimeMs) {
+ // TODO(dwkang): figure out why exceptions just make this thread finish.
+ try {
+ final long renderTimeUs = renderTimeMs * 1000;
+ ByteBuffer buf = ByteBuffer.allocate(buffer.capacity());
+ buf.put(buffer);
+ buf.rewind();
+ synchronized(mFrameQueue) {
+ mFrameQueue.add(new Frame(buf, renderTimeUs));
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public boolean hasFrame() {
+ synchronized(mFrameQueue) {
+ return !mFrameQueue.isEmpty();
+ }
+ }
+
+ public Frame dequeueFrame() {
+ synchronized(mFrameQueue) {
+ return mFrameQueue.removeFirst();
+ }
+ }
+
+ private void initMediaCodecView() {
+ Log.d(TAG, "initMediaCodecView");
+ mState = STATE_IDLE;
+
+ mLooperThread = new Thread()
+ {
+ @Override
+ public void run() {
+ Log.d(TAG, "Looper prepare");
+ Looper.prepare();
+ mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ // TODO(dwkang): figure out exceptions just make this thread finish.
+ try {
+ switch (msg.what) {
+ case EVENT_PREPARE:
+ {
+ mState = STATE_PAUSED;
+ ViEMediaCodecDecoder.this.start();
+ break;
+ }
+
+ case EVENT_DO_SOME_WORK:
+ {
+ ViEMediaCodecDecoder.this.doSomeWork();
+
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(EVENT_DO_SOME_WORK), 5);
+ break;
+ }
+
+ default:
+ break;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ };
+ Log.d(TAG, "Looper loop");
+ synchronized(ViEMediaCodecDecoder.this) {
+ ViEMediaCodecDecoder.this.notify();
+ }
+ Looper.loop();
+ }
+ };
+ mLooperThread.start();
+
+ // Wait until handler is set up.
+ synchronized(ViEMediaCodecDecoder.this) {
+ try {
+ ViEMediaCodecDecoder.this.wait(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ Log.d(TAG, "initMediaCodecView end");
+ }
+
+ public void start() {
+ Log.d(TAG, "start");
+
+ if (mState == STATE_PLAYING || mState == STATE_PREPARING) {
+ return;
+ } else if (mState == STATE_IDLE) {
+ mState = STATE_PREPARING;
+ Log.d(TAG, "Sending EVENT_PREPARE");
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_PREPARE));
+ return;
+ } else if (mState != STATE_PAUSED) {
+ throw new IllegalStateException();
+ }
+
+ mCodecState.start();
+
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_DO_SOME_WORK));
+
+ mDeltaTimeUs = -1;
+ mState = STATE_PLAYING;
+
+ Log.d(TAG, "start end");
+ }
+
+ public void reset() {
+ if (mState == STATE_PLAYING) {
+ mCodecState.pause();
+ }
+
+ mCodecState.release();
+
+ mDurationUs = -1;
+ mState = STATE_IDLE;
+ }
+
+ private void doSomeWork() {
+ mCodecState.doSomeWork();
+ }
+
+ public long getRealTimeUsForMediaTime(long mediaTimeUs) {
+ if (mDeltaTimeUs == -1) {
+ long nowUs = System.currentTimeMillis() * 1000;
+ mDeltaTimeUs = nowUs - mediaTimeUs;
+ }
+
+ return mDeltaTimeUs + mediaTimeUs;
+ }
+}
diff --git a/webrtc/video_engine/test/android/src/org/webrtc/videoengineapp/ViEAndroidJavaAPI.java b/webrtc/video_engine/test/android/src/org/webrtc/videoengineapp/ViEAndroidJavaAPI.java
index 4b5cfbb971..9801f766ce 100644
--- a/webrtc/video_engine/test/android/src/org/webrtc/videoengineapp/ViEAndroidJavaAPI.java
+++ b/webrtc/video_engine/test/android/src/org/webrtc/videoengineapp/ViEAndroidJavaAPI.java
@@ -72,6 +72,10 @@ public class ViEAndroidJavaAPI {
public native int GetCameraOrientation(int cameraNum);
public native int SetRotation(int cameraId,int degrees);
+ // External Codec
+ public native int SetExternalMediaCodecDecoderRenderer(
+ int channel, Object glSurface);
+
// NACK
public native int EnableNACK(int channel, boolean enable);
diff --git a/webrtc/video_engine/test/android/src/org/webrtc/videoengineapp/WebRTCDemo.java b/webrtc/video_engine/test/android/src/org/webrtc/videoengineapp/WebRTCDemo.java
index 7e16756d25..1b22d1273e 100644
--- a/webrtc/video_engine/test/android/src/org/webrtc/videoengineapp/WebRTCDemo.java
+++ b/webrtc/video_engine/test/android/src/org/webrtc/videoengineapp/WebRTCDemo.java
@@ -122,7 +122,12 @@ public class WebRTCDemo extends TabActivity implements IViEAndroidCallback,
private boolean loopbackMode = true;
private CheckBox cbStats;
private boolean isStatsOn = true;
- private boolean useOpenGLRender = true;
+ public enum RenderType {
+ OPENGL,
+ SURFACE,
+ MEDIACODEC
+ }
+ RenderType renderType = RenderType.OPENGL;
// Video settings
private Spinner spCodecType;
@@ -499,10 +504,12 @@ public class WebRTCDemo extends TabActivity implements IViEAndroidCallback,
RadioGroup radioGroup = (RadioGroup) findViewById(R.id.radio_group1);
radioGroup.clearCheck();
- if (useOpenGLRender == true) {
+ if (renderType == RenderType.OPENGL) {
radioGroup.check(R.id.radio_opengl);
- } else {
+ } else if (renderType == RenderType.SURFACE) {
radioGroup.check(R.id.radio_surface);
+ } else if (renderType == RenderType.MEDIACODEC) {
+ radioGroup.check(R.id.radio_mediacodec);
}
etRemoteIp = (EditText) findViewById(R.id.etRemoteIp);
@@ -604,13 +611,25 @@ public class WebRTCDemo extends TabActivity implements IViEAndroidCallback,
getRemoteIPString());
if (enableVideoReceive) {
- if (useOpenGLRender) {
+ if (renderType == RenderType.OPENGL) {
Log.v(TAG, "Create OpenGL Render");
remoteSurfaceView = ViERenderer.CreateRenderer(this, true);
- ret = vieAndroidAPI.AddRemoteRenderer(channel, remoteSurfaceView);
- } else {
+ } else if (renderType == RenderType.SURFACE) {
Log.v(TAG, "Create SurfaceView Render");
remoteSurfaceView = ViERenderer.CreateRenderer(this, false);
+ } else if (renderType == RenderType.MEDIACODEC) {
+ Log.v(TAG, "Create MediaCodec Decoder/Renderer");
+ remoteSurfaceView = new SurfaceView(this);
+ }
+
+ if (mLlRemoteSurface != null) {
+ mLlRemoteSurface.addView(remoteSurfaceView);
+ }
+
+ if (renderType == RenderType.MEDIACODEC) {
+ ret = vieAndroidAPI.SetExternalMediaCodecDecoderRenderer(
+ channel, remoteSurfaceView);
+ } else {
ret = vieAndroidAPI.AddRemoteRenderer(channel, remoteSurfaceView);
}
@@ -653,12 +672,6 @@ public class WebRTCDemo extends TabActivity implements IViEAndroidCallback,
}
}
- if (enableVideoReceive) {
- if (mLlRemoteSurface != null) {
- mLlRemoteSurface.addView(remoteSurfaceView);
- }
- }
-
isStatsOn = cbStats.isChecked();
if (isStatsOn) {
addStatusView();
@@ -841,10 +854,13 @@ public class WebRTCDemo extends TabActivity implements IViEAndroidCallback,
}
break;
case R.id.radio_surface:
- useOpenGLRender = false;
+ renderType = RenderType.SURFACE;
break;
case R.id.radio_opengl:
- useOpenGLRender = true;
+ renderType = RenderType.OPENGL;
+ break;
+ case R.id.radio_mediacodec:
+ renderType = RenderType.MEDIACODEC;
break;
case R.id.cbNack:
enableNack = cbEnableNack.isChecked();