Home | History | Annotate | Download | only in jni
      1 /*
      2 **
      3 ** Copyright 2008, The Android Open Source Project
      4 **
      5 ** Licensed under the Apache License, Version 2.0 (the "License");
      6 ** you may not use this file except in compliance with the License.
      7 ** You may obtain a copy of the License at
      8 **
      9 **     http://www.apache.org/licenses/LICENSE-2.0
     10 **
     11 ** Unless required by applicable law or agreed to in writing, software
     12 ** distributed under the License is distributed on an "AS IS" BASIS,
     13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 ** See the License for the specific language governing permissions and
     15 ** limitations under the License.
     16 */
     17 
     18 //#define LOG_NDEBUG 0
     19 #define LOG_TAG "MediaMetadataRetrieverJNI"
     20 
     21 #include <assert.h>
     22 #include <utils/Log.h>
     23 #include <utils/threads.h>
     24 #include <core/SkBitmap.h>
     25 #include <media/mediametadataretriever.h>
     26 #include <private/media/VideoFrame.h>
     27 
     28 #include "jni.h"
     29 #include "JNIHelp.h"
     30 #include "android_runtime/AndroidRuntime.h"
     31 
     32 
     33 using namespace android;
     34 
     35 struct fields_t {
     36     jfieldID context;
     37     jclass bitmapClazz;
     38     jmethodID bitmapConstructor;
     39     jmethodID createBitmapMethod;
     40 };
     41 
     42 static fields_t fields;
     43 static Mutex sLock;
     44 static const char* const kClassPathName = "android/media/MediaMetadataRetriever";
     45 
     46 static void process_media_retriever_call(JNIEnv *env, status_t opStatus, const char* exception, const char *message)
     47 {
     48     if (opStatus == (status_t) INVALID_OPERATION) {
     49         jniThrowException(env, "java/lang/IllegalStateException", NULL);
     50     } else if (opStatus != (status_t) OK) {
     51         if (strlen(message) > 230) {
     52             // If the message is too long, don't bother displaying the status code.
     53             jniThrowException( env, exception, message);
     54         } else {
     55             char msg[256];
     56             // Append the status code to the message.
     57             sprintf(msg, "%s: status = 0x%X", message, opStatus);
     58             jniThrowException( env, exception, msg);
     59         }
     60     }
     61 }
     62 
     63 static MediaMetadataRetriever* getRetriever(JNIEnv* env, jobject thiz)
     64 {
     65     // No lock is needed, since it is called internally by other methods that are protected
     66     MediaMetadataRetriever* retriever = (MediaMetadataRetriever*) env->GetIntField(thiz, fields.context);
     67     return retriever;
     68 }
     69 
     70 static void setRetriever(JNIEnv* env, jobject thiz, int retriever)
     71 {
     72     // No lock is needed, since it is called internally by other methods that are protected
     73     MediaMetadataRetriever *old = (MediaMetadataRetriever*) env->GetIntField(thiz, fields.context);
     74     env->SetIntField(thiz, fields.context, retriever);
     75 }
     76 
     77 static void android_media_MediaMetadataRetriever_setDataSource(JNIEnv *env, jobject thiz, jstring path)
     78 {
     79     LOGV("setDataSource");
     80     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
     81     if (retriever == 0) {
     82         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
     83         return;
     84     }
     85     if (!path) {
     86         jniThrowException(env, "java/lang/IllegalArgumentException", "Null pointer");
     87         return;
     88     }
     89 
     90     const char *pathStr = env->GetStringUTFChars(path, NULL);
     91     if (!pathStr) {  // OutOfMemoryError exception already thrown
     92         return;
     93     }
     94 
     95     // Don't let somebody trick us in to reading some random block of memory
     96     if (strncmp("mem://", pathStr, 6) == 0) {
     97         jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid pathname");
     98         return;
     99     }
    100 
    101     process_media_retriever_call(env, retriever->setDataSource(pathStr), "java/lang/RuntimeException", "setDataSource failed");
    102     env->ReleaseStringUTFChars(path, pathStr);
    103 }
    104 
    105 static void android_media_MediaMetadataRetriever_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
    106 {
    107     LOGV("setDataSource");
    108     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
    109     if (retriever == 0) {
    110         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
    111         return;
    112     }
    113     if (!fileDescriptor) {
    114         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
    115         return;
    116     }
    117     int fd = getParcelFileDescriptorFD(env, fileDescriptor);
    118     if (offset < 0 || length < 0 || fd < 0) {
    119         if (offset < 0) {
    120             LOGE("negative offset (%lld)", offset);
    121         }
    122         if (length < 0) {
    123             LOGE("negative length (%lld)", length);
    124         }
    125         if (fd < 0) {
    126             LOGE("invalid file descriptor");
    127         }
    128         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
    129         return;
    130     }
    131     process_media_retriever_call(env, retriever->setDataSource(fd, offset, length), "java/lang/RuntimeException", "setDataSource failed");
    132 }
    133 
    134 static jobject android_media_MediaMetadataRetriever_getFrameAtTime(JNIEnv *env, jobject thiz, jlong timeUs, jint option)
    135 {
    136     LOGV("getFrameAtTime: %lld us option: %d", timeUs, option);
    137     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
    138     if (retriever == 0) {
    139         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
    140         return NULL;
    141     }
    142 
    143     // Call native method to retrieve a video frame
    144     VideoFrame *videoFrame = NULL;
    145     sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option);
    146     if (frameMemory != 0) {  // cast the shared structure to a VideoFrame object
    147         videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
    148     }
    149     if (videoFrame == NULL) {
    150         LOGE("getFrameAtTime: videoFrame is a NULL pointer");
    151         return NULL;
    152     }
    153 
    154     jobject matrix = NULL;
    155     if (videoFrame->mRotationAngle != 0) {
    156         LOGD("Create a rotation matrix: %d degrees", videoFrame->mRotationAngle);
    157         jclass matrixClazz = env->FindClass("android/graphics/Matrix");
    158         if (matrixClazz == NULL) {
    159             jniThrowException(env, "java/lang/RuntimeException",
    160                 "Can't find android/graphics/Matrix");
    161             return NULL;
    162         }
    163         jmethodID matrixConstructor =
    164             env->GetMethodID(matrixClazz, "<init>", "()V");
    165         if (matrixConstructor == NULL) {
    166             jniThrowException(env, "java/lang/RuntimeException",
    167                 "Can't find Matrix constructor");
    168             return NULL;
    169         }
    170         matrix =
    171             env->NewObject(matrixClazz, matrixConstructor);
    172         if (matrix == NULL) {
    173             LOGE("Could not create a Matrix object");
    174             return NULL;
    175         }
    176 
    177         LOGV("Rotate the matrix: %d degrees", videoFrame->mRotationAngle);
    178         jmethodID setRotateMethod =
    179                 env->GetMethodID(matrixClazz, "setRotate", "(F)V");
    180         if (setRotateMethod == NULL) {
    181             jniThrowException(env, "java/lang/RuntimeException",
    182                 "Can't find Matrix setRotate method");
    183             return NULL;
    184         }
    185         env->CallVoidMethod(matrix, setRotateMethod, 1.0 * videoFrame->mRotationAngle);
    186         env->DeleteLocalRef(matrixClazz);
    187     }
    188 
    189     // Create a SkBitmap to hold the pixels
    190     SkBitmap *bitmap = new SkBitmap();
    191     if (bitmap == NULL) {
    192         LOGE("getFrameAtTime: cannot instantiate a SkBitmap object.");
    193         return NULL;
    194     }
    195     bitmap->setConfig(SkBitmap::kRGB_565_Config, videoFrame->mDisplayWidth, videoFrame->mDisplayHeight);
    196     if (!bitmap->allocPixels()) {
    197         delete bitmap;
    198         LOGE("failed to allocate pixel buffer");
    199         return NULL;
    200     }
    201     memcpy((uint8_t*)bitmap->getPixels(), (uint8_t*)videoFrame + sizeof(VideoFrame), videoFrame->mSize);
    202 
    203     // Since internally SkBitmap uses reference count to manage the reference to
    204     // its pixels, it is important that the pixels (along with SkBitmap) be
    205     // available after creating the Bitmap is returned to Java app.
    206     jobject jSrcBitmap = env->NewObject(fields.bitmapClazz,
    207             fields.bitmapConstructor, (int) bitmap, true, NULL, -1);
    208 
    209     LOGV("Return a new bitmap constructed with the rotation matrix");
    210     return env->CallStaticObjectMethod(
    211                 fields.bitmapClazz, fields.createBitmapMethod,
    212                 jSrcBitmap,                     // source Bitmap
    213                 0,                              // x
    214                 0,                              // y
    215                 videoFrame->mDisplayWidth,      // width
    216                 videoFrame->mDisplayHeight,     // height
    217                 matrix,                         // transform matrix
    218                 false);                         // filter
    219 }
    220 
    221 static jbyteArray android_media_MediaMetadataRetriever_getEmbeddedPicture(
    222         JNIEnv *env, jobject thiz, jint pictureType)
    223 {
    224     LOGV("getEmbeddedPicture: %d", pictureType);
    225     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
    226     if (retriever == 0) {
    227         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
    228         return NULL;
    229     }
    230     MediaAlbumArt* mediaAlbumArt = NULL;
    231 
    232     // FIXME:
    233     // Use pictureType to retrieve the intended embedded picture and also change
    234     // the method name to getEmbeddedPicture().
    235     sp<IMemory> albumArtMemory = retriever->extractAlbumArt();
    236     if (albumArtMemory != 0) {  // cast the shared structure to a MediaAlbumArt object
    237         mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->pointer());
    238     }
    239     if (mediaAlbumArt == NULL) {
    240         LOGE("getEmbeddedPicture: Call to getEmbeddedPicture failed.");
    241         return NULL;
    242     }
    243 
    244     unsigned int len = mediaAlbumArt->mSize;
    245     char* data = (char*) mediaAlbumArt + sizeof(MediaAlbumArt);
    246     jbyteArray array = env->NewByteArray(len);
    247     if (!array) {  // OutOfMemoryError exception has already been thrown.
    248         LOGE("getEmbeddedPicture: OutOfMemoryError is thrown.");
    249     } else {
    250         jbyte* bytes = env->GetByteArrayElements(array, NULL);
    251         if (bytes != NULL) {
    252             memcpy(bytes, data, len);
    253             env->ReleaseByteArrayElements(array, bytes, 0);
    254         }
    255     }
    256 
    257     // No need to delete mediaAlbumArt here
    258     return array;
    259 }
    260 
    261 static jobject android_media_MediaMetadataRetriever_extractMetadata(JNIEnv *env, jobject thiz, jint keyCode)
    262 {
    263     LOGV("extractMetadata");
    264     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
    265     if (retriever == 0) {
    266         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
    267         return NULL;
    268     }
    269     const char* value = retriever->extractMetadata(keyCode);
    270     if (!value) {
    271         LOGV("extractMetadata: Metadata is not found");
    272         return NULL;
    273     }
    274     LOGV("extractMetadata: value (%s) for keyCode(%d)", value, keyCode);
    275     return env->NewStringUTF(value);
    276 }
    277 
    278 static void android_media_MediaMetadataRetriever_release(JNIEnv *env, jobject thiz)
    279 {
    280     LOGV("release");
    281     Mutex::Autolock lock(sLock);
    282     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
    283     delete retriever;
    284     setRetriever(env, thiz, 0);
    285 }
    286 
    287 static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz)
    288 {
    289     LOGV("native_finalize");
    290 
    291     // No lock is needed, since android_media_MediaMetadataRetriever_release() is protected
    292     android_media_MediaMetadataRetriever_release(env, thiz);
    293 }
    294 
    295 // This function gets a field ID, which in turn causes class initialization.
    296 // It is called from a static block in MediaMetadataRetriever, which won't run until the
    297 // first time an instance of this class is used.
    298 static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env)
    299 {
    300     jclass clazz = env->FindClass(kClassPathName);
    301     if (clazz == NULL) {
    302         jniThrowException(env, "java/lang/RuntimeException", "Can't find android/media/MediaMetadataRetriever");
    303         return;
    304     }
    305 
    306     fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
    307     if (fields.context == NULL) {
    308         jniThrowException(env, "java/lang/RuntimeException", "Can't find MediaMetadataRetriever.mNativeContext");
    309         return;
    310     }
    311 
    312     fields.bitmapClazz = env->FindClass("android/graphics/Bitmap");
    313     if (fields.bitmapClazz == NULL) {
    314         jniThrowException(env, "java/lang/RuntimeException", "Can't find android/graphics/Bitmap");
    315         return;
    316     }
    317 
    318     fields.bitmapConstructor = env->GetMethodID(fields.bitmapClazz, "<init>", "(IZ[BI)V");
    319     if (fields.bitmapConstructor == NULL) {
    320         jniThrowException(env, "java/lang/RuntimeException", "Can't find Bitmap constructor");
    321         return;
    322     }
    323     fields.createBitmapMethod =
    324             env->GetStaticMethodID(fields.bitmapClazz, "createBitmap",
    325                     "(Landroid/graphics/Bitmap;IIIILandroid/graphics/Matrix;Z)"
    326                     "Landroid/graphics/Bitmap;");
    327     if (fields.createBitmapMethod == NULL) {
    328         jniThrowException(env, "java/lang/RuntimeException",
    329                 "Can't find Bitmap.createBitmap method");
    330         return;
    331     }
    332 }
    333 
    334 static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz)
    335 {
    336     LOGV("native_setup");
    337     MediaMetadataRetriever* retriever = new MediaMetadataRetriever();
    338     if (retriever == 0) {
    339         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
    340         return;
    341     }
    342     setRetriever(env, thiz, (int)retriever);
    343 }
    344 
    345 // JNI mapping between Java methods and native methods
    346 static JNINativeMethod nativeMethods[] = {
    347         {"setDataSource",   "(Ljava/lang/String;)V", (void *)android_media_MediaMetadataRetriever_setDataSource},
    348         {"setDataSource",   "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD},
    349         {"_getFrameAtTime", "(JI)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
    350         {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata},
    351         {"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture},
    352         {"release",         "()V", (void *)android_media_MediaMetadataRetriever_release},
    353         {"native_finalize", "()V", (void *)android_media_MediaMetadataRetriever_native_finalize},
    354         {"native_setup",    "()V", (void *)android_media_MediaMetadataRetriever_native_setup},
    355         {"native_init",     "()V", (void *)android_media_MediaMetadataRetriever_native_init},
    356 };
    357 
    358 // This function only registers the native methods, and is called from
    359 // JNI_OnLoad in android_media_MediaPlayer.cpp
    360 int register_android_media_MediaMetadataRetriever(JNIEnv *env)
    361 {
    362     return AndroidRuntime::registerNativeMethods
    363         (env, kClassPathName, nativeMethods, NELEM(nativeMethods));
    364 }
    365