Implementation of a java media codec interface and associated tools.

Change-Id: I13e54062d4de584355c5d82bb027a68aeaf2923b
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index 23cc0e2..070d2d9 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -2,6 +2,8 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:= \
+    android_media_MediaCodec.cpp \
+    android_media_MediaExtractor.cpp \
     android_media_MediaPlayer.cpp \
     android_media_MediaRecorder.cpp \
     android_media_MediaScanner.cpp \
@@ -25,6 +27,7 @@
     libcutils \
     libgui \
     libstagefright \
+    libstagefright_foundation \
     libcamera_client \
     libmtp \
     libusbhost \
@@ -39,10 +42,12 @@
     external/tremor/Tremor \
     frameworks/base/core/jni \
     frameworks/base/media/libmedia \
+    frameworks/base/media/libstagefright \
     frameworks/base/media/libstagefright/codecs/amrnb/enc/src \
     frameworks/base/media/libstagefright/codecs/amrnb/common \
     frameworks/base/media/libstagefright/codecs/amrnb/common/include \
     frameworks/base/media/mtp \
+    frameworks/base/include/media/stagefright/openmax \
     $(PV_INCLUDES) \
     $(JNI_H_INCLUDE) \
     $(call include-path-for, corecg graphics)
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
new file mode 100644
index 0000000..43ca263
--- /dev/null
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -0,0 +1,550 @@
+/*
+ * Copyright 2012, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaCodec-JNI"
+#include <utils/Log.h>
+
+#include "android_media_MediaCodec.h"
+
+#include "android_media_Utils.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "android_runtime/android_view_Surface.h"
+#include "jni.h"
+#include "JNIHelp.h"
+
+#include <media/stagefright/MediaCodec.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaErrors.h>
+#include <surfaceflinger/Surface.h>
+
+namespace android {
+
+// Keep these in sync with their equivalents in MediaCodec.java !!!
+enum {
+    DEQUEUE_INFO_TRY_AGAIN_LATER            = -1,
+    DEQUEUE_INFO_OUTPUT_FORMAT_CHANGED      = -2,
+    DEQUEUE_INFO_OUTPUT_BUFFERS_CHANGED     = -3,
+};
+
+struct fields_t {
+    jfieldID context;
+};
+
+static fields_t gFields;
+
+////////////////////////////////////////////////////////////////////////////////
+
+JMediaCodec::JMediaCodec(
+        JNIEnv *env, jobject thiz,
+        const char *name, bool nameIsType, bool encoder)
+    : mClass(NULL),
+      mObject(NULL) {
+    jclass clazz = env->GetObjectClass(thiz);
+    CHECK(clazz != NULL);
+
+    mClass = (jclass)env->NewGlobalRef(clazz);
+    mObject = env->NewWeakGlobalRef(thiz);
+
+    mLooper = new ALooper;
+    mLooper->setName("MediaCodec_looper");
+
+    mLooper->start(
+            false,      // runOnCallingThread
+            false,       // canCallJava
+            PRIORITY_DEFAULT);
+
+    if (nameIsType) {
+        mCodec = MediaCodec::CreateByType(mLooper, name, encoder);
+    } else {
+        mCodec = MediaCodec::CreateByComponentName(mLooper, name);
+    }
+}
+
+status_t JMediaCodec::initCheck() const {
+    return mCodec != NULL ? OK : NO_INIT;
+}
+
+JMediaCodec::~JMediaCodec() {
+    mCodec->stop();
+
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+    env->DeleteWeakGlobalRef(mObject);
+    mObject = NULL;
+    env->DeleteGlobalRef(mClass);
+    mClass = NULL;
+}
+
+status_t JMediaCodec::configure(
+        const sp<AMessage> &format,
+        const sp<ISurfaceTexture> &surfaceTexture,
+        int flags) {
+    sp<SurfaceTextureClient> client;
+    if (surfaceTexture != NULL) {
+        client = new SurfaceTextureClient(surfaceTexture);
+    }
+    return mCodec->configure(format, client, flags);
+}
+
+status_t JMediaCodec::start() {
+    return mCodec->start();
+}
+
+status_t JMediaCodec::stop() {
+    return mCodec->stop();
+}
+
+status_t JMediaCodec::flush() {
+    return mCodec->flush();
+}
+
+status_t JMediaCodec::queueInputBuffer(
+        size_t index,
+        size_t offset, size_t size, int64_t timeUs, uint32_t flags) {
+    return mCodec->queueInputBuffer(index, offset, size, timeUs, flags);
+}
+
+status_t JMediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) {
+    return mCodec->dequeueInputBuffer(index, timeoutUs);
+}
+
+status_t JMediaCodec::dequeueOutputBuffer(
+        JNIEnv *env, jobject bufferInfo, size_t *index, int64_t timeoutUs) {
+    size_t size, offset;
+    int64_t timeUs;
+    uint32_t flags;
+    status_t err;
+    if ((err = mCodec->dequeueOutputBuffer(
+                    index, &size, &offset, &timeUs, &flags, timeoutUs)) != OK) {
+        return err;
+    }
+
+    jclass clazz = env->FindClass("android/media/MediaCodec$BufferInfo");
+
+    jmethodID method = env->GetMethodID(clazz, "set", "(IIJI)V");
+    env->CallVoidMethod(bufferInfo, method, offset, size, timeUs, flags);
+
+    return OK;
+}
+
+status_t JMediaCodec::releaseOutputBuffer(size_t index, bool render) {
+    return render
+        ? mCodec->renderOutputBufferAndRelease(index)
+        : mCodec->releaseOutputBuffer(index);
+}
+
+status_t JMediaCodec::getOutputFormat(JNIEnv *env, jobject *format) const {
+    sp<AMessage> msg;
+    status_t err;
+    if ((err = mCodec->getOutputFormat(&msg)) != OK) {
+        return err;
+    }
+
+    return ConvertMessageToMap(env, msg, format);
+}
+
+status_t JMediaCodec::getBuffers(
+        JNIEnv *env, bool input, jobjectArray *bufArray) const {
+    Vector<sp<ABuffer> > buffers;
+
+    status_t err =
+        input
+            ? mCodec->getInputBuffers(&buffers)
+            : mCodec->getOutputBuffers(&buffers);
+
+    if (err != OK) {
+        return err;
+    }
+
+    jclass byteBufferClass = env->FindClass("java/nio/ByteBuffer");
+
+    *bufArray = (jobjectArray)env->NewObjectArray(
+            buffers.size(), byteBufferClass, NULL);
+
+    for (size_t i = 0; i < buffers.size(); ++i) {
+        const sp<ABuffer> &buffer = buffers.itemAt(i);
+
+        jobject byteBuffer =
+            env->NewDirectByteBuffer(
+                buffer->base(),
+                buffer->capacity());
+
+        env->SetObjectArrayElement(
+                *bufArray, i, byteBuffer);
+
+        env->DeleteLocalRef(byteBuffer);
+        byteBuffer = NULL;
+    }
+
+    return OK;
+}
+
+}  // namespace android
+
+////////////////////////////////////////////////////////////////////////////////
+
+using namespace android;
+
+static sp<JMediaCodec> setMediaCodec(
+        JNIEnv *env, jobject thiz, const sp<JMediaCodec> &codec) {
+    sp<JMediaCodec> old = (JMediaCodec *)env->GetIntField(thiz, gFields.context);
+    if (codec != NULL) {
+        codec->incStrong(thiz);
+    }
+    if (old != NULL) {
+        old->decStrong(thiz);
+    }
+    env->SetIntField(thiz, gFields.context, (int)codec.get());
+
+    return old;
+}
+
+static sp<JMediaCodec> getMediaCodec(JNIEnv *env, jobject thiz) {
+    return (JMediaCodec *)env->GetIntField(thiz, gFields.context);
+}
+
+static void android_media_MediaCodec_release(JNIEnv *env, jobject thiz) {
+    setMediaCodec(env, thiz, NULL);
+}
+
+static jint throwExceptionAsNecessary(JNIEnv *env, status_t err) {
+    switch (err) {
+        case OK:
+            return 0;
+
+        case -EAGAIN:
+            return DEQUEUE_INFO_TRY_AGAIN_LATER;
+
+        case INFO_FORMAT_CHANGED:
+            return DEQUEUE_INFO_OUTPUT_FORMAT_CHANGED;
+
+        case INFO_OUTPUT_BUFFERS_CHANGED:
+            return DEQUEUE_INFO_OUTPUT_BUFFERS_CHANGED;
+
+        default:
+        {
+            jniThrowException(env, "java/lang/IllegalStateException", NULL);
+            break;
+        }
+    }
+
+    return 0;
+}
+
+static void android_media_MediaCodec_native_configure(
+        JNIEnv *env,
+        jobject thiz,
+        jobjectArray keys, jobjectArray values,
+        jobject jsurface,
+        jint flags) {
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    sp<AMessage> format;
+    status_t err = ConvertKeyValueArraysToMessage(env, keys, values, &format);
+
+    if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return;
+    }
+
+    sp<ISurfaceTexture> surfaceTexture;
+    if (jsurface != NULL) {
+        sp<Surface> surface(Surface_getSurface(env, jsurface));
+        if (surface != NULL) {
+            surfaceTexture = surface->getSurfaceTexture();
+        } else {
+            jniThrowException(
+                    env,
+                    "java/lang/IllegalArgumentException",
+                    "The surface has been released");
+            return;
+        }
+    }
+
+    err = codec->configure(format, surfaceTexture, flags);
+
+    throwExceptionAsNecessary(env, err);
+}
+
+static void android_media_MediaCodec_start(JNIEnv *env, jobject thiz) {
+    ALOGV("android_media_MediaCodec_start");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    status_t err = codec->start();
+
+    throwExceptionAsNecessary(env, err);
+}
+
+static void android_media_MediaCodec_stop(JNIEnv *env, jobject thiz) {
+    ALOGV("android_media_MediaCodec_stop");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    status_t err = codec->stop();
+
+    throwExceptionAsNecessary(env, err);
+}
+
+static void android_media_MediaCodec_flush(JNIEnv *env, jobject thiz) {
+    ALOGV("android_media_MediaCodec_flush");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    status_t err = codec->flush();
+
+    throwExceptionAsNecessary(env, err);
+}
+
+static void android_media_MediaCodec_queueInputBuffer(
+        JNIEnv *env,
+        jobject thiz,
+        jint index,
+        jint offset,
+        jint size,
+        jlong timestampUs,
+        jint flags) {
+    ALOGV("android_media_MediaCodec_queueInputBuffer");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    status_t err = codec->queueInputBuffer(
+            index, offset, size, timestampUs, flags);
+
+    throwExceptionAsNecessary(env, err);
+}
+
+static jint android_media_MediaCodec_dequeueInputBuffer(
+        JNIEnv *env, jobject thiz, jlong timeoutUs) {
+    ALOGV("android_media_MediaCodec_dequeueInputBuffer");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return -1;
+    }
+
+    size_t index;
+    status_t err = codec->dequeueInputBuffer(&index, timeoutUs);
+
+    if (err == OK) {
+        return index;
+    }
+
+    return throwExceptionAsNecessary(env, err);
+}
+
+static jint android_media_MediaCodec_dequeueOutputBuffer(
+        JNIEnv *env, jobject thiz, jobject bufferInfo, jlong timeoutUs) {
+    ALOGV("android_media_MediaCodec_dequeueOutputBuffer");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    size_t index;
+    status_t err = codec->dequeueOutputBuffer(
+            env, bufferInfo, &index, timeoutUs);
+
+    if (err == OK) {
+        return index;
+    }
+
+    return throwExceptionAsNecessary(env, err);
+}
+
+static void android_media_MediaCodec_releaseOutputBuffer(
+        JNIEnv *env, jobject thiz, jint index, jboolean render) {
+    ALOGV("android_media_MediaCodec_renderOutputBufferAndRelease");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    status_t err = codec->releaseOutputBuffer(index, render);
+
+    throwExceptionAsNecessary(env, err);
+}
+
+static jobject android_media_MediaCodec_getOutputFormat(
+        JNIEnv *env, jobject thiz) {
+    ALOGV("android_media_MediaCodec_getOutputFormat");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    jobject format;
+    status_t err = codec->getOutputFormat(env, &format);
+
+    if (err == OK) {
+        return format;
+    }
+
+    throwExceptionAsNecessary(env, err);
+
+    return NULL;
+}
+
+static jobjectArray android_media_MediaCodec_getBuffers(
+        JNIEnv *env, jobject thiz, jboolean input) {
+    ALOGV("android_media_MediaCodec_getBuffers");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    jobjectArray buffers;
+    status_t err = codec->getBuffers(env, input, &buffers);
+
+    if (err == OK) {
+        return buffers;
+    }
+
+    throwExceptionAsNecessary(env, err);
+
+    return NULL;
+}
+
+static void android_media_MediaCodec_native_init(JNIEnv *env) {
+    jclass clazz = env->FindClass("android/media/MediaCodec");
+    CHECK(clazz != NULL);
+
+    gFields.context = env->GetFieldID(clazz, "mNativeContext", "I");
+    CHECK(gFields.context != NULL);
+}
+
+static void android_media_MediaCodec_native_setup(
+        JNIEnv *env, jobject thiz,
+        jstring name, jboolean nameIsType, jboolean encoder) {
+    if (name == NULL) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return;
+    }
+
+    const char *tmp = env->GetStringUTFChars(name, NULL);
+
+    if (tmp == NULL) {
+        return;
+    }
+
+    sp<JMediaCodec> codec = new JMediaCodec(env, thiz, tmp, nameIsType, encoder);
+
+    status_t err = codec->initCheck();
+
+    env->ReleaseStringUTFChars(name, tmp);
+    tmp = NULL;
+
+    if (err != OK) {
+        jniThrowException(
+                env,
+                "java/io/IOException",
+                "Failed to allocate component instance");
+        return;
+    }
+
+    setMediaCodec(env,thiz, codec);
+}
+
+static void android_media_MediaCodec_native_finalize(
+        JNIEnv *env, jobject thiz) {
+    android_media_MediaCodec_release(env, thiz);
+}
+
+static JNINativeMethod gMethods[] = {
+    { "release", "()V", (void *)android_media_MediaCodec_release },
+
+    { "native_configure",
+      "([Ljava/lang/String;[Ljava/lang/Object;Landroid/view/Surface;I)V",
+      (void *)android_media_MediaCodec_native_configure },
+
+    { "start", "()V", (void *)android_media_MediaCodec_start },
+    { "stop", "()V", (void *)android_media_MediaCodec_stop },
+    { "flush", "()V", (void *)android_media_MediaCodec_flush },
+
+    { "queueInputBuffer", "(IIIJI)V",
+      (void *)android_media_MediaCodec_queueInputBuffer },
+
+    { "dequeueInputBuffer", "(J)I",
+      (void *)android_media_MediaCodec_dequeueInputBuffer },
+
+    { "dequeueOutputBuffer", "(Landroid/media/MediaCodec$BufferInfo;J)I",
+      (void *)android_media_MediaCodec_dequeueOutputBuffer },
+
+    { "releaseOutputBuffer", "(IZ)V",
+      (void *)android_media_MediaCodec_releaseOutputBuffer },
+
+    { "getOutputFormat", "()Ljava/util/Map;",
+      (void *)android_media_MediaCodec_getOutputFormat },
+
+    { "getBuffers", "(Z)[Ljava/nio/ByteBuffer;",
+      (void *)android_media_MediaCodec_getBuffers },
+
+    { "native_init", "()V", (void *)android_media_MediaCodec_native_init },
+
+    { "native_setup", "(Ljava/lang/String;ZZ)V",
+      (void *)android_media_MediaCodec_native_setup },
+
+    { "native_finalize", "()V",
+      (void *)android_media_MediaCodec_native_finalize },
+};
+
+int register_android_media_MediaCodec(JNIEnv *env) {
+    return AndroidRuntime::registerNativeMethods(env,
+                "android/media/MediaCodec", gMethods, NELEM(gMethods));
+}
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
new file mode 100644
index 0000000..6b1257d
--- /dev/null
+++ b/media/jni/android_media_MediaCodec.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2012, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_MEDIA_MEDIACODEC_H_
+#define _ANDROID_MEDIA_MEDIACODEC_H_
+
+#include "jni.h"
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct ALooper;
+struct AMessage;
+struct ISurfaceTexture;
+struct MediaCodec;
+
+struct JMediaCodec : public RefBase {
+    JMediaCodec(
+            JNIEnv *env, jobject thiz,
+            const char *name, bool nameIsType, bool encoder);
+
+    status_t initCheck() const;
+
+    status_t configure(
+            const sp<AMessage> &format,
+            const sp<ISurfaceTexture> &surfaceTexture,
+            int flags);
+
+    status_t start();
+    status_t stop();
+
+    status_t flush();
+
+    status_t queueInputBuffer(
+            size_t index,
+            size_t offset, size_t size, int64_t timeUs, uint32_t flags);
+
+    status_t dequeueInputBuffer(size_t *index, int64_t timeoutUs);
+
+    status_t dequeueOutputBuffer(
+            JNIEnv *env, jobject bufferInfo, size_t *index, int64_t timeoutUs);
+
+    status_t releaseOutputBuffer(size_t index, bool render);
+
+    status_t getOutputFormat(JNIEnv *env, jobject *format) const;
+
+    status_t getBuffers(
+            JNIEnv *env, bool input, jobjectArray *bufArray) const;
+
+protected:
+    virtual ~JMediaCodec();
+
+private:
+    jclass mClass;
+    jweak mObject;
+
+    sp<ALooper> mLooper;
+    sp<MediaCodec> mCodec;
+
+    DISALLOW_EVIL_CONSTRUCTORS(JMediaCodec);
+};
+
+}  // namespace android
+
+#endif  // _ANDROID_MEDIA_MEDIACODEC_H_
diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
new file mode 100644
index 0000000..4757adf
--- /dev/null
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -0,0 +1,400 @@
+/*
+ * Copyright 2012, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaExtractor-JNI"
+#include <utils/Log.h>
+
+#include "android_media_MediaExtractor.h"
+
+#include "android_media_Utils.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "jni.h"
+#include "JNIHelp.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/NuMediaExtractor.h>
+
+namespace android {
+
+struct fields_t {
+    jfieldID context;
+};
+
+static fields_t gFields;
+
+////////////////////////////////////////////////////////////////////////////////
+
+JMediaExtractor::JMediaExtractor(JNIEnv *env, jobject thiz)
+    : mClass(NULL),
+      mObject(NULL) {
+    jclass clazz = env->GetObjectClass(thiz);
+    CHECK(clazz != NULL);
+
+    mClass = (jclass)env->NewGlobalRef(clazz);
+    mObject = env->NewWeakGlobalRef(thiz);
+
+    mImpl = new NuMediaExtractor;
+}
+
+JMediaExtractor::~JMediaExtractor() {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+    env->DeleteWeakGlobalRef(mObject);
+    mObject = NULL;
+    env->DeleteGlobalRef(mClass);
+    mClass = NULL;
+}
+
+status_t JMediaExtractor::setDataSource(const char *path) {
+    return mImpl->setDataSource(path);
+}
+
+size_t JMediaExtractor::countTracks() const {
+    return mImpl->countTracks();
+}
+
+status_t JMediaExtractor::getTrackFormat(size_t index, jobject *format) const {
+    sp<AMessage> msg;
+    status_t err;
+    if ((err = mImpl->getTrackFormat(index, &msg)) != OK) {
+        return err;
+    }
+
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+    return ConvertMessageToMap(env, msg, format);
+}
+
+status_t JMediaExtractor::selectTrack(size_t index) {
+    return mImpl->selectTrack(index);
+}
+
+status_t JMediaExtractor::seekTo(int64_t timeUs) {
+    return mImpl->seekTo(timeUs);
+}
+
+status_t JMediaExtractor::advance() {
+    return mImpl->advance();
+}
+
+status_t JMediaExtractor::readSampleData(
+        jobject byteBuf, size_t offset, size_t *sampleSize) {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+    void *dst = env->GetDirectBufferAddress(byteBuf);
+
+    if (dst == NULL) {
+        // XXX if dst is NULL also fall back to "array()"
+        return INVALID_OPERATION;
+    }
+
+    jlong dstSize = env->GetDirectBufferCapacity(byteBuf);
+
+    if (dstSize < offset) {
+        return -ERANGE;
+    }
+
+    sp<ABuffer> buffer = new ABuffer((char *)dst + offset, dstSize - offset);
+
+    status_t err = mImpl->readSampleData(buffer);
+
+    if (err != OK) {
+        return err;
+    }
+
+    *sampleSize = buffer->size();
+
+    return OK;
+}
+
+status_t JMediaExtractor::getSampleTrackIndex(size_t *trackIndex) {
+    return mImpl->getSampleTrackIndex(trackIndex);
+}
+
+status_t JMediaExtractor::getSampleTime(int64_t *sampleTimeUs) {
+    return mImpl->getSampleTime(sampleTimeUs);
+}
+
+}  // namespace android
+
+////////////////////////////////////////////////////////////////////////////////
+
+using namespace android;
+
+static sp<JMediaExtractor> setMediaExtractor(
+        JNIEnv *env, jobject thiz, const sp<JMediaExtractor> &extractor) {
+    sp<JMediaExtractor> old =
+        (JMediaExtractor *)env->GetIntField(thiz, gFields.context);
+
+    if (extractor != NULL) {
+        extractor->incStrong(thiz);
+    }
+    if (old != NULL) {
+        old->decStrong(thiz);
+    }
+    env->SetIntField(thiz, gFields.context, (int)extractor.get());
+
+    return old;
+}
+
+static sp<JMediaExtractor> getMediaExtractor(JNIEnv *env, jobject thiz) {
+    return (JMediaExtractor *)env->GetIntField(thiz, gFields.context);
+}
+
+static void android_media_MediaExtractor_release(JNIEnv *env, jobject thiz) {
+    setMediaExtractor(env, thiz, NULL);
+}
+
+static jint android_media_MediaExtractor_countTracks(
+        JNIEnv *env, jobject thiz) {
+    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+    if (extractor == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    return extractor->countTracks();
+}
+
+static jobject android_media_MediaExtractor_getTrackFormat(
+        JNIEnv *env, jobject thiz, jint index) {
+    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+    if (extractor == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    jobject format;
+    status_t err = extractor->getTrackFormat(index, &format);
+
+    if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return NULL;
+    }
+
+    return format;
+}
+
+static void android_media_MediaExtractor_selectTrack(
+        JNIEnv *env, jobject thiz, jint index) {
+    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+    if (extractor == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    status_t err = extractor->selectTrack(index);
+
+    if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return;
+    }
+}
+
+static void android_media_MediaExtractor_seekTo(
+        JNIEnv *env, jobject thiz, jlong timeUs) {
+    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+    if (extractor == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    status_t err = extractor->seekTo(timeUs);
+
+    if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return;
+    }
+}
+
+static jboolean android_media_MediaExtractor_advance(
+        JNIEnv *env, jobject thiz) {
+    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+    if (extractor == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return false;
+    }
+
+    status_t err = extractor->advance();
+
+    if (err == ERROR_END_OF_STREAM) {
+        return false;
+    } else if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return false;
+    }
+
+    return true;
+}
+
+static jint android_media_MediaExtractor_readSampleData(
+        JNIEnv *env, jobject thiz, jobject byteBuf, jint offset) {
+    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+    if (extractor == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return -1;
+    }
+
+    size_t sampleSize;
+    status_t err = extractor->readSampleData(byteBuf, offset, &sampleSize);
+
+    if (err == ERROR_END_OF_STREAM) {
+        return -1;
+    } else if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return false;
+    }
+
+    return sampleSize;
+}
+
+static jint android_media_MediaExtractor_getSampleTrackIndex(
+        JNIEnv *env, jobject thiz) {
+    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+    if (extractor == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return -1;
+    }
+
+    size_t trackIndex;
+    status_t err = extractor->getSampleTrackIndex(&trackIndex);
+
+    if (err == ERROR_END_OF_STREAM) {
+        return -1;
+    } else if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return false;
+    }
+
+    return trackIndex;
+}
+
+static jlong android_media_MediaExtractor_getSampleTime(
+        JNIEnv *env, jobject thiz) {
+    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+    if (extractor == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return -1ll;
+    }
+
+    int64_t sampleTimeUs;
+    status_t err = extractor->getSampleTime(&sampleTimeUs);
+
+    if (err == ERROR_END_OF_STREAM) {
+        return -1ll;
+    } else if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return false;
+    }
+
+    return sampleTimeUs;
+}
+
+static void android_media_MediaExtractor_native_init(JNIEnv *env) {
+    jclass clazz = env->FindClass("android/media/MediaExtractor");
+    CHECK(clazz != NULL);
+
+    gFields.context = env->GetFieldID(clazz, "mNativeContext", "I");
+    CHECK(gFields.context != NULL);
+
+    DataSource::RegisterDefaultSniffers();
+}
+
+static void android_media_MediaExtractor_native_setup(
+        JNIEnv *env, jobject thiz, jstring path) {
+    sp<JMediaExtractor> extractor = new JMediaExtractor(env, thiz);
+
+    if (path == NULL) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return;
+    }
+
+    const char *tmp = env->GetStringUTFChars(path, NULL);
+
+    if (tmp == NULL) {
+        return;
+    }
+
+    status_t err = extractor->setDataSource(tmp);
+
+    env->ReleaseStringUTFChars(path, tmp);
+    tmp = NULL;
+
+    if (err != OK) {
+        jniThrowException(
+                env,
+                "java/io/IOException",
+                "Failed to instantiate extractor.");
+        return;
+    }
+
+    setMediaExtractor(env,thiz, extractor);
+}
+
+static void android_media_MediaExtractor_native_finalize(
+        JNIEnv *env, jobject thiz) {
+    android_media_MediaExtractor_release(env, thiz);
+}
+
+static JNINativeMethod gMethods[] = {
+    { "release", "()V", (void *)android_media_MediaExtractor_release },
+
+    { "countTracks", "()I", (void *)android_media_MediaExtractor_countTracks },
+
+    { "getTrackFormat", "(I)Ljava/util/Map;",
+        (void *)android_media_MediaExtractor_getTrackFormat },
+
+    { "selectTrack", "(I)V", (void *)android_media_MediaExtractor_selectTrack },
+
+    { "seekTo", "(J)V", (void *)android_media_MediaExtractor_seekTo },
+
+    { "advance", "()Z", (void *)android_media_MediaExtractor_advance },
+
+    { "readSampleData", "(Ljava/nio/ByteBuffer;I)I",
+        (void *)android_media_MediaExtractor_readSampleData },
+
+    { "getSampleTrackIndex", "()I",
+        (void *)android_media_MediaExtractor_getSampleTrackIndex },
+
+    { "getSampleTime", "()J",
+        (void *)android_media_MediaExtractor_getSampleTime },
+
+    { "native_init", "()V", (void *)android_media_MediaExtractor_native_init },
+
+    { "native_setup", "(Ljava/lang/String;)V",
+      (void *)android_media_MediaExtractor_native_setup },
+
+    { "native_finalize", "()V",
+      (void *)android_media_MediaExtractor_native_finalize },
+};
+
+int register_android_media_MediaExtractor(JNIEnv *env) {
+    return AndroidRuntime::registerNativeMethods(env,
+                "android/media/MediaExtractor", gMethods, NELEM(gMethods));
+}
diff --git a/media/jni/android_media_MediaExtractor.h b/media/jni/android_media_MediaExtractor.h
new file mode 100644
index 0000000..70e58c6
--- /dev/null
+++ b/media/jni/android_media_MediaExtractor.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_MEDIA_MEDIAEXTRACTOR_H_
+#define _ANDROID_MEDIA_MEDIAEXTRACTOR_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+
+#include "jni.h"
+
+namespace android {
+
+struct NuMediaExtractor;
+
+struct JMediaExtractor : public RefBase {
+    JMediaExtractor(JNIEnv *env, jobject thiz);
+
+    status_t setDataSource(const char *path);
+
+    size_t countTracks() const;
+    status_t getTrackFormat(size_t index, jobject *format) const;
+
+    status_t selectTrack(size_t index);
+
+    status_t seekTo(int64_t timeUs);
+
+    status_t advance();
+    status_t readSampleData(jobject byteBuf, size_t offset, size_t *sampleSize);
+    status_t getSampleTrackIndex(size_t *trackIndex);
+    status_t getSampleTime(int64_t *sampleTimeUs);
+
+protected:
+    virtual ~JMediaExtractor();
+
+private:
+    jclass mClass;
+    jweak mObject;
+    sp<NuMediaExtractor> mImpl;
+
+    DISALLOW_EVIL_CONSTRUCTORS(JMediaExtractor);
+};
+
+}  // namespace android
+
+#endif  // _ANDROID_MEDIA_MEDIAEXTRACTOR_H_
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 8ff9dd3..199d56e4 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -810,6 +810,8 @@
                 "android/media/MediaPlayer", gMethods, NELEM(gMethods));
 }
 
+extern int register_android_media_MediaCodec(JNIEnv *env);
+extern int register_android_media_MediaExtractor(JNIEnv *env);
 extern int register_android_media_MediaMetadataRetriever(JNIEnv *env);
 extern int register_android_media_MediaRecorder(JNIEnv *env);
 extern int register_android_media_MediaScanner(JNIEnv *env);
@@ -881,6 +883,16 @@
         goto bail;
     }
 
+    if (register_android_media_MediaCodec(env) < 0) {
+        ALOGE("ERROR: MediaCodec native registration failed");
+        goto bail;
+    }
+
+    if (register_android_media_MediaExtractor(env) < 0) {
+        ALOGE("ERROR: MediaCodec native registration failed");
+        goto bail;
+    }
+
     /* success -- return valid version number */
     result = JNI_VERSION_1_4;
 
diff --git a/media/jni/android_media_Utils.cpp b/media/jni/android_media_Utils.cpp
index 47963b1..7dacdcd 100644
--- a/media/jni/android_media_Utils.cpp
+++ b/media/jni/android_media_Utils.cpp
@@ -20,6 +20,10 @@
 #include <utils/Log.h>
 #include "android_media_Utils.h"
 
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/AMessage.h>
+
 namespace android {
 
 bool ConvertKeyValueArraysToKeyedVector(
@@ -71,5 +75,266 @@
     return true;
 }
 
+static jobject makeIntegerObject(JNIEnv *env, int32_t value) {
+    jclass clazz = env->FindClass("java/lang/Integer");
+    CHECK(clazz != NULL);
+
+    jmethodID integerConstructID = env->GetMethodID(clazz, "<init>", "(I)V");
+    CHECK(integerConstructID != NULL);
+
+    return env->NewObject(clazz, integerConstructID, value);
+}
+
+static jobject makeFloatObject(JNIEnv *env, float value) {
+    jclass clazz = env->FindClass("java/lang/Float");
+    CHECK(clazz != NULL);
+
+    jmethodID floatConstructID = env->GetMethodID(clazz, "<init>", "(F)V");
+    CHECK(floatConstructID != NULL);
+
+    return env->NewObject(clazz, floatConstructID, value);
+}
+
+static jobject makeByteBufferObject(
+        JNIEnv *env, const void *data, size_t size) {
+    jbyteArray byteArrayObj = env->NewByteArray(size);
+    env->SetByteArrayRegion(byteArrayObj, 0, size, (const jbyte *)data);
+
+    jclass clazz = env->FindClass("java/nio/ByteBuffer");
+    CHECK(clazz != NULL);
+
+    jmethodID byteBufWrapID =
+        env->GetStaticMethodID(clazz, "wrap", "([B)Ljava/nio/ByteBuffer;");
+    CHECK(byteBufWrapID != NULL);
+
+    jobject byteBufObj = env->CallStaticObjectMethod(
+            clazz, byteBufWrapID, byteArrayObj);
+
+    env->DeleteLocalRef(byteArrayObj); byteArrayObj = NULL;
+
+    return byteBufObj;
+}
+
+status_t ConvertMessageToMap(
+        JNIEnv *env, const sp<AMessage> &msg, jobject *map) {
+    jclass hashMapClazz = env->FindClass("java/util/HashMap");
+
+    if (hashMapClazz == NULL) {
+        return -EINVAL;
+    }
+
+    jmethodID hashMapConstructID =
+        env->GetMethodID(hashMapClazz, "<init>", "()V");
+
+    if (hashMapConstructID == NULL) {
+        return -EINVAL;
+    }
+
+    jmethodID hashMapPutID =
+        env->GetMethodID(
+                hashMapClazz,
+                "put",
+                "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+
+    if (hashMapPutID == NULL) {
+        return -EINVAL;
+    }
+
+    jobject hashMap = env->NewObject(hashMapClazz, hashMapConstructID);
+
+    for (size_t i = 0; i < msg->countEntries(); ++i) {
+        AMessage::Type valueType;
+        const char *key = msg->getEntryNameAt(i, &valueType);
+
+        jobject valueObj = NULL;
+
+        switch (valueType) {
+            case AMessage::kTypeInt32:
+            {
+                int32_t val;
+                CHECK(msg->findInt32(key, &val));
+
+                valueObj = makeIntegerObject(env, val);
+                break;
+            }
+
+            case AMessage::kTypeFloat:
+            {
+                float val;
+                CHECK(msg->findFloat(key, &val));
+
+                valueObj = makeFloatObject(env, val);
+                break;
+            }
+
+            case AMessage::kTypeString:
+            {
+                AString val;
+                CHECK(msg->findString(key, &val));
+
+                valueObj = env->NewStringUTF(val.c_str());
+                break;
+            }
+
+            case AMessage::kTypeObject:
+            {
+                sp<RefBase> obj;
+                CHECK(msg->findObject(key, &obj));
+
+                // XXX dangerous, object is not guaranteed to be a buffer.
+                sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get());
+
+                valueObj = makeByteBufferObject(
+                        env, buffer->data(), buffer->size());
+                break;
+            }
+
+            default:
+                break;
+        }
+
+        if (valueObj != NULL) {
+            jstring keyObj = env->NewStringUTF(key);
+
+            jobject res = env->CallObjectMethod(
+                    hashMap, hashMapPutID, keyObj, valueObj);
+
+            env->DeleteLocalRef(keyObj); keyObj = NULL;
+            env->DeleteLocalRef(valueObj); valueObj = NULL;
+        }
+    }
+
+    *map = hashMap;
+
+    return OK;
+}
+
+status_t ConvertKeyValueArraysToMessage(
+        JNIEnv *env, jobjectArray keys, jobjectArray values,
+        sp<AMessage> *out) {
+    jclass stringClass = env->FindClass("java/lang/String");
+    CHECK(stringClass != NULL);
+
+    jclass integerClass = env->FindClass("java/lang/Integer");
+    CHECK(integerClass != NULL);
+
+    jclass floatClass = env->FindClass("java/lang/Float");
+    CHECK(floatClass != NULL);
+
+    jclass byteBufClass = env->FindClass("java/nio/ByteBuffer");
+    CHECK(byteBufClass != NULL);
+
+    sp<AMessage> msg = new AMessage;
+
+    jsize numEntries = 0;
+
+    if (keys != NULL) {
+        if (values == NULL) {
+            return -EINVAL;
+        }
+
+        numEntries = env->GetArrayLength(keys);
+
+        if (numEntries != env->GetArrayLength(values)) {
+            return -EINVAL;
+        }
+    } else if (values != NULL) {
+        return -EINVAL;
+    }
+
+    for (jsize i = 0; i < numEntries; ++i) {
+        jobject keyObj = env->GetObjectArrayElement(keys, i);
+
+        if (!env->IsInstanceOf(keyObj, stringClass)) {
+            return -EINVAL;
+        }
+
+        const char *tmp = env->GetStringUTFChars((jstring)keyObj, NULL);
+
+        if (tmp == NULL) {
+            return -ENOMEM;
+        }
+
+        AString key = tmp;
+
+        env->ReleaseStringUTFChars((jstring)keyObj, tmp);
+        tmp = NULL;
+
+        jobject valueObj = env->GetObjectArrayElement(values, i);
+
+        if (env->IsInstanceOf(valueObj, stringClass)) {
+            const char *value = env->GetStringUTFChars((jstring)valueObj, NULL);
+
+            if (value == NULL) {
+                return -ENOMEM;
+            }
+
+            msg->setString(key.c_str(), value);
+
+            env->ReleaseStringUTFChars((jstring)valueObj, value);
+            value = NULL;
+        } else if (env->IsInstanceOf(valueObj, integerClass)) {
+            jmethodID intValueID =
+                env->GetMethodID(integerClass, "intValue", "()I");
+            CHECK(intValueID != NULL);
+
+            jint value = env->CallIntMethod(valueObj, intValueID);
+
+            msg->setInt32(key.c_str(), value);
+        } else if (env->IsInstanceOf(valueObj, floatClass)) {
+            jmethodID floatValueID =
+                env->GetMethodID(floatClass, "floatValue", "()F");
+            CHECK(floatValueID != NULL);
+
+            jfloat value = env->CallFloatMethod(valueObj, floatValueID);
+
+            msg->setFloat(key.c_str(), value);
+        } else if (env->IsInstanceOf(valueObj, byteBufClass)) {
+            jmethodID positionID =
+                env->GetMethodID(byteBufClass, "position", "()I");
+            CHECK(positionID != NULL);
+
+            jmethodID limitID =
+                env->GetMethodID(byteBufClass, "limit", "()I");
+            CHECK(limitID != NULL);
+
+            jint position = env->CallIntMethod(valueObj, positionID);
+            jint limit = env->CallIntMethod(valueObj, limitID);
+
+            sp<ABuffer> buffer = new ABuffer(limit - position);
+
+            void *data = env->GetDirectBufferAddress(valueObj);
+
+            if (data != NULL) {
+                memcpy(buffer->data(),
+                       (const uint8_t *)data + position,
+                       buffer->size());
+            } else {
+                jmethodID arrayID =
+                    env->GetMethodID(byteBufClass, "array", "()[B");
+                CHECK(arrayID != NULL);
+
+                jbyteArray byteArray =
+                    (jbyteArray)env->CallObjectMethod(valueObj, arrayID);
+                CHECK(byteArray != NULL);
+
+                env->GetByteArrayRegion(
+                        byteArray,
+                        position,
+                        buffer->size(),
+                        (jbyte *)buffer->data());
+
+                env->DeleteLocalRef(byteArray); byteArray = NULL;
+            }
+
+            msg->setObject(key.c_str(), buffer);
+        }
+    }
+
+    *out = msg;
+
+    return OK;
+}
+
 }  // namespace android
 
diff --git a/media/jni/android_media_Utils.h b/media/jni/android_media_Utils.h
index a2c155a..635bceb 100644
--- a/media/jni/android_media_Utils.h
+++ b/media/jni/android_media_Utils.h
@@ -33,6 +33,14 @@
     JNIEnv *env, jobjectArray keys, jobjectArray values,
     KeyedVector<String8, String8>* vector);
 
+struct AMessage;
+status_t ConvertMessageToMap(
+        JNIEnv *env, const sp<AMessage> &msg, jobject *map);
+
+status_t ConvertKeyValueArraysToMessage(
+        JNIEnv *env, jobjectArray keys, jobjectArray values,
+        sp<AMessage> *msg);
+
 };  // namespace android
 
 #endif //  _ANDROID_MEDIA_UTILS_H_