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 #include "android_media_Utils.h"
     32 
     33 
     34 using namespace android;
     35 
     36 struct fields_t {
     37     jfieldID context;
     38     jclass bitmapClazz;  // Must be a global ref
     39     jfieldID nativeBitmap;
     40     jmethodID createBitmapMethod;
     41     jmethodID createScaledBitmapMethod;
     42     jclass configClazz;  // Must be a global ref
     43     jmethodID createConfigMethod;
     44 };
     45 
     46 static fields_t fields;
     47 static Mutex sLock;
     48 static const char* const kClassPathName = "android/media/MediaMetadataRetriever";
     49 
     50 static void process_media_retriever_call(JNIEnv *env, status_t opStatus, const char* exception, const char *message)
     51 {
     52     if (opStatus == (status_t) INVALID_OPERATION) {
     53         jniThrowException(env, "java/lang/IllegalStateException", NULL);
     54     } else if (opStatus != (status_t) OK) {
     55         if (strlen(message) > 230) {
     56             // If the message is too long, don't bother displaying the status code.
     57             jniThrowException( env, exception, message);
     58         } else {
     59             char msg[256];
     60             // Append the status code to the message.
     61             sprintf(msg, "%s: status = 0x%X", message, opStatus);
     62             jniThrowException( env, exception, msg);
     63         }
     64     }
     65 }
     66 
     67 static MediaMetadataRetriever* getRetriever(JNIEnv* env, jobject thiz)
     68 {
     69     // No lock is needed, since it is called internally by other methods that are protected
     70     MediaMetadataRetriever* retriever = (MediaMetadataRetriever*) env->GetIntField(thiz, fields.context);
     71     return retriever;
     72 }
     73 
     74 static void setRetriever(JNIEnv* env, jobject thiz, int retriever)
     75 {
     76     // No lock is needed, since it is called internally by other methods that are protected
     77     MediaMetadataRetriever *old = (MediaMetadataRetriever*) env->GetIntField(thiz, fields.context);
     78     env->SetIntField(thiz, fields.context, retriever);
     79 }
     80 
     81 static void
     82 android_media_MediaMetadataRetriever_setDataSourceAndHeaders(
     83         JNIEnv *env, jobject thiz, jstring path,
     84         jobjectArray keys, jobjectArray values) {
     85 
     86     ALOGV("setDataSource");
     87     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
     88     if (retriever == 0) {
     89         jniThrowException(
     90                 env,
     91                 "java/lang/IllegalStateException", "No retriever available");
     92 
     93         return;
     94     }
     95 
     96     if (!path) {
     97         jniThrowException(
     98                 env, "java/lang/IllegalArgumentException", "Null pointer");
     99 
    100         return;
    101     }
    102 
    103     const char *tmp = env->GetStringUTFChars(path, NULL);
    104     if (!tmp) {  // OutOfMemoryError exception already thrown
    105         return;
    106     }
    107 
    108     String8 pathStr(tmp);
    109     env->ReleaseStringUTFChars(path, tmp);
    110     tmp = NULL;
    111 
    112     // Don't let somebody trick us in to reading some random block of memory
    113     if (strncmp("mem://", pathStr.string(), 6) == 0) {
    114         jniThrowException(
    115                 env, "java/lang/IllegalArgumentException", "Invalid pathname");
    116         return;
    117     }
    118 
    119     // We build a similar KeyedVector out of it.
    120     KeyedVector<String8, String8> headersVector;
    121     if (!ConvertKeyValueArraysToKeyedVector(
    122             env, keys, values, &headersVector)) {
    123         return;
    124     }
    125     process_media_retriever_call(
    126             env,
    127             retriever->setDataSource(
    128                 pathStr.string(), headersVector.size() > 0 ? &headersVector : NULL),
    129 
    130             "java/lang/RuntimeException",
    131             "setDataSource failed");
    132 }
    133 
    134 static void android_media_MediaMetadataRetriever_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
    135 {
    136     ALOGV("setDataSource");
    137     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
    138     if (retriever == 0) {
    139         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
    140         return;
    141     }
    142     if (!fileDescriptor) {
    143         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
    144         return;
    145     }
    146     int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
    147     if (offset < 0 || length < 0 || fd < 0) {
    148         if (offset < 0) {
    149             ALOGE("negative offset (%lld)", offset);
    150         }
    151         if (length < 0) {
    152             ALOGE("negative length (%lld)", length);
    153         }
    154         if (fd < 0) {
    155             ALOGE("invalid file descriptor");
    156         }
    157         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
    158         return;
    159     }
    160     process_media_retriever_call(env, retriever->setDataSource(fd, offset, length), "java/lang/RuntimeException", "setDataSource failed");
    161 }
    162 
    163 template<typename T>
    164 static void rotate0(T* dst, const T* src, size_t width, size_t height)
    165 {
    166     memcpy(dst, src, width * height * sizeof(T));
    167 }
    168 
    169 template<typename T>
    170 static void rotate90(T* dst, const T* src, size_t width, size_t height)
    171 {
    172     for (size_t i = 0; i < height; ++i) {
    173         for (size_t j = 0; j < width; ++j) {
    174             dst[j * height + height - 1 - i] = src[i * width + j];
    175         }
    176     }
    177 }
    178 
    179 template<typename T>
    180 static void rotate180(T* dst, const T* src, size_t width, size_t height)
    181 {
    182     for (size_t i = 0; i < height; ++i) {
    183         for (size_t j = 0; j < width; ++j) {
    184             dst[(height - 1 - i) * width + width - 1 - j] = src[i * width + j];
    185         }
    186     }
    187 }
    188 
    189 template<typename T>
    190 static void rotate270(T* dst, const T* src, size_t width, size_t height)
    191 {
    192     for (size_t i = 0; i < height; ++i) {
    193         for (size_t j = 0; j < width; ++j) {
    194             dst[(width - 1 - j) * height + i] = src[i * width + j];
    195         }
    196     }
    197 }
    198 
    199 template<typename T>
    200 static void rotate(T *dst, const T *src, size_t width, size_t height, int angle)
    201 {
    202     switch (angle) {
    203         case 0:
    204             rotate0(dst, src, width, height);
    205             break;
    206         case 90:
    207             rotate90(dst, src, width, height);
    208             break;
    209         case 180:
    210             rotate180(dst, src, width, height);
    211             break;
    212         case 270:
    213             rotate270(dst, src, width, height);
    214             break;
    215     }
    216 }
    217 
    218 static jobject android_media_MediaMetadataRetriever_getFrameAtTime(JNIEnv *env, jobject thiz, jlong timeUs, jint option)
    219 {
    220     ALOGV("getFrameAtTime: %lld us option: %d", timeUs, option);
    221     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
    222     if (retriever == 0) {
    223         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
    224         return NULL;
    225     }
    226 
    227     // Call native method to retrieve a video frame
    228     VideoFrame *videoFrame = NULL;
    229     sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option);
    230     if (frameMemory != 0) {  // cast the shared structure to a VideoFrame object
    231         videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
    232     }
    233     if (videoFrame == NULL) {
    234         ALOGE("getFrameAtTime: videoFrame is a NULL pointer");
    235         return NULL;
    236     }
    237 
    238     ALOGV("Dimension = %dx%d and bytes = %d",
    239             videoFrame->mDisplayWidth,
    240             videoFrame->mDisplayHeight,
    241             videoFrame->mSize);
    242 
    243     jobject config = env->CallStaticObjectMethod(
    244                         fields.configClazz,
    245                         fields.createConfigMethod,
    246                         SkBitmap::kRGB_565_Config);
    247 
    248     size_t width, height;
    249     bool swapWidthAndHeight = false;
    250     if (videoFrame->mRotationAngle == 90 || videoFrame->mRotationAngle == 270) {
    251         width = videoFrame->mHeight;
    252         height = videoFrame->mWidth;
    253         swapWidthAndHeight = true;
    254     } else {
    255         width = videoFrame->mWidth;
    256         height = videoFrame->mHeight;
    257     }
    258 
    259     jobject jBitmap = env->CallStaticObjectMethod(
    260                             fields.bitmapClazz,
    261                             fields.createBitmapMethod,
    262                             width,
    263                             height,
    264                             config);
    265 
    266     SkBitmap *bitmap =
    267             (SkBitmap *) env->GetIntField(jBitmap, fields.nativeBitmap);
    268 
    269     bitmap->lockPixels();
    270     rotate((uint16_t*)bitmap->getPixels(),
    271            (uint16_t*)((char*)videoFrame + sizeof(VideoFrame)),
    272            videoFrame->mWidth,
    273            videoFrame->mHeight,
    274            videoFrame->mRotationAngle);
    275     bitmap->unlockPixels();
    276 
    277     if (videoFrame->mDisplayWidth  != videoFrame->mWidth ||
    278         videoFrame->mDisplayHeight != videoFrame->mHeight) {
    279         size_t displayWidth = videoFrame->mDisplayWidth;
    280         size_t displayHeight = videoFrame->mDisplayHeight;
    281         if (swapWidthAndHeight) {
    282             displayWidth = videoFrame->mDisplayHeight;
    283             displayHeight = videoFrame->mDisplayWidth;
    284         }
    285         ALOGV("Bitmap dimension is scaled from %dx%d to %dx%d",
    286                 width, height, displayWidth, displayHeight);
    287         jobject scaledBitmap = env->CallStaticObjectMethod(fields.bitmapClazz,
    288                                     fields.createScaledBitmapMethod,
    289                                     jBitmap,
    290                                     displayWidth,
    291                                     displayHeight,
    292                                     true);
    293         return scaledBitmap;
    294     }
    295 
    296     return jBitmap;
    297 }
    298 
    299 static jbyteArray android_media_MediaMetadataRetriever_getEmbeddedPicture(
    300         JNIEnv *env, jobject thiz, jint pictureType)
    301 {
    302     ALOGV("getEmbeddedPicture: %d", pictureType);
    303     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
    304     if (retriever == 0) {
    305         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
    306         return NULL;
    307     }
    308     MediaAlbumArt* mediaAlbumArt = NULL;
    309 
    310     // FIXME:
    311     // Use pictureType to retrieve the intended embedded picture and also change
    312     // the method name to getEmbeddedPicture().
    313     sp<IMemory> albumArtMemory = retriever->extractAlbumArt();
    314     if (albumArtMemory != 0) {  // cast the shared structure to a MediaAlbumArt object
    315         mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->pointer());
    316     }
    317     if (mediaAlbumArt == NULL) {
    318         ALOGE("getEmbeddedPicture: Call to getEmbeddedPicture failed.");
    319         return NULL;
    320     }
    321 
    322     unsigned int len = mediaAlbumArt->mSize;
    323     char* data = (char*) mediaAlbumArt + sizeof(MediaAlbumArt);
    324     jbyteArray array = env->NewByteArray(len);
    325     if (!array) {  // OutOfMemoryError exception has already been thrown.
    326         ALOGE("getEmbeddedPicture: OutOfMemoryError is thrown.");
    327     } else {
    328         jbyte* bytes = env->GetByteArrayElements(array, NULL);
    329         if (bytes != NULL) {
    330             memcpy(bytes, data, len);
    331             env->ReleaseByteArrayElements(array, bytes, 0);
    332         }
    333     }
    334 
    335     // No need to delete mediaAlbumArt here
    336     return array;
    337 }
    338 
    339 static jobject android_media_MediaMetadataRetriever_extractMetadata(JNIEnv *env, jobject thiz, jint keyCode)
    340 {
    341     ALOGV("extractMetadata");
    342     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
    343     if (retriever == 0) {
    344         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
    345         return NULL;
    346     }
    347     const char* value = retriever->extractMetadata(keyCode);
    348     if (!value) {
    349         ALOGV("extractMetadata: Metadata is not found");
    350         return NULL;
    351     }
    352     ALOGV("extractMetadata: value (%s) for keyCode(%d)", value, keyCode);
    353     return env->NewStringUTF(value);
    354 }
    355 
    356 static void android_media_MediaMetadataRetriever_release(JNIEnv *env, jobject thiz)
    357 {
    358     ALOGV("release");
    359     Mutex::Autolock lock(sLock);
    360     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
    361     delete retriever;
    362     setRetriever(env, thiz, 0);
    363 }
    364 
    365 static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz)
    366 {
    367     ALOGV("native_finalize");
    368     // No lock is needed, since android_media_MediaMetadataRetriever_release() is protected
    369     android_media_MediaMetadataRetriever_release(env, thiz);
    370 }
    371 
    372 // This function gets a field ID, which in turn causes class initialization.
    373 // It is called from a static block in MediaMetadataRetriever, which won't run until the
    374 // first time an instance of this class is used.
    375 static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env)
    376 {
    377     jclass clazz = env->FindClass(kClassPathName);
    378     if (clazz == NULL) {
    379         return;
    380     }
    381 
    382     fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
    383     if (fields.context == NULL) {
    384         return;
    385     }
    386 
    387     jclass bitmapClazz = env->FindClass("android/graphics/Bitmap");
    388     if (bitmapClazz == NULL) {
    389         return;
    390     }
    391     fields.bitmapClazz = (jclass) env->NewGlobalRef(bitmapClazz);
    392     if (fields.bitmapClazz == NULL) {
    393         return;
    394     }
    395     fields.createBitmapMethod =
    396             env->GetStaticMethodID(fields.bitmapClazz, "createBitmap",
    397                     "(IILandroid/graphics/Bitmap$Config;)"
    398                     "Landroid/graphics/Bitmap;");
    399     if (fields.createBitmapMethod == NULL) {
    400         return;
    401     }
    402     fields.createScaledBitmapMethod =
    403             env->GetStaticMethodID(fields.bitmapClazz, "createScaledBitmap",
    404                     "(Landroid/graphics/Bitmap;IIZ)"
    405                     "Landroid/graphics/Bitmap;");
    406     if (fields.createScaledBitmapMethod == NULL) {
    407         return;
    408     }
    409     fields.nativeBitmap = env->GetFieldID(fields.bitmapClazz, "mNativeBitmap", "I");
    410     if (fields.nativeBitmap == NULL) {
    411         return;
    412     }
    413 
    414     jclass configClazz = env->FindClass("android/graphics/Bitmap$Config");
    415     if (configClazz == NULL) {
    416         return;
    417     }
    418     fields.configClazz = (jclass) env->NewGlobalRef(configClazz);
    419     if (fields.configClazz == NULL) {
    420         return;
    421     }
    422     fields.createConfigMethod =
    423             env->GetStaticMethodID(fields.configClazz, "nativeToConfig",
    424                     "(I)Landroid/graphics/Bitmap$Config;");
    425     if (fields.createConfigMethod == NULL) {
    426         return;
    427     }
    428 }
    429 
    430 static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz)
    431 {
    432     ALOGV("native_setup");
    433     MediaMetadataRetriever* retriever = new MediaMetadataRetriever();
    434     if (retriever == 0) {
    435         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
    436         return;
    437     }
    438     setRetriever(env, thiz, (int)retriever);
    439 }
    440 
    441 // JNI mapping between Java methods and native methods
    442 static JNINativeMethod nativeMethods[] = {
    443         {
    444             "_setDataSource",
    445             "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
    446             (void *)android_media_MediaMetadataRetriever_setDataSourceAndHeaders
    447         },
    448 
    449         {"setDataSource",   "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD},
    450         {"_getFrameAtTime", "(JI)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
    451         {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata},
    452         {"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture},
    453         {"release",         "()V", (void *)android_media_MediaMetadataRetriever_release},
    454         {"native_finalize", "()V", (void *)android_media_MediaMetadataRetriever_native_finalize},
    455         {"native_setup",    "()V", (void *)android_media_MediaMetadataRetriever_native_setup},
    456         {"native_init",     "()V", (void *)android_media_MediaMetadataRetriever_native_init},
    457 };
    458 
    459 // This function only registers the native methods, and is called from
    460 // JNI_OnLoad in android_media_MediaPlayer.cpp
    461 int register_android_media_MediaMetadataRetriever(JNIEnv *env)
    462 {
    463     return AndroidRuntime::registerNativeMethods
    464         (env, kClassPathName, nativeMethods, NELEM(nativeMethods));
    465 }
    466