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     LOGV("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 
    135 static void android_media_MediaMetadataRetriever_setDataSource(
    136         JNIEnv *env, jobject thiz, jstring path) {
    137     android_media_MediaMetadataRetriever_setDataSourceAndHeaders(
    138             env, thiz, path, NULL, NULL);
    139 }
    140 
    141 static void android_media_MediaMetadataRetriever_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
    142 {
    143     LOGV("setDataSource");
    144     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
    145     if (retriever == 0) {
    146         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
    147         return;
    148     }
    149     if (!fileDescriptor) {
    150         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
    151         return;
    152     }
    153     int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
    154     if (offset < 0 || length < 0 || fd < 0) {
    155         if (offset < 0) {
    156             LOGE("negative offset (%lld)", offset);
    157         }
    158         if (length < 0) {
    159             LOGE("negative length (%lld)", length);
    160         }
    161         if (fd < 0) {
    162             LOGE("invalid file descriptor");
    163         }
    164         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
    165         return;
    166     }
    167     process_media_retriever_call(env, retriever->setDataSource(fd, offset, length), "java/lang/RuntimeException", "setDataSource failed");
    168 }
    169 
    170 template<typename T>
    171 static void rotate0(T* dst, const T* src, size_t width, size_t height)
    172 {
    173     memcpy(dst, src, width * height * sizeof(T));
    174 }
    175 
    176 template<typename T>
    177 static void rotate90(T* dst, const T* src, size_t width, size_t height)
    178 {
    179     for (size_t i = 0; i < height; ++i) {
    180         for (size_t j = 0; j < width; ++j) {
    181             dst[j * height + height - 1 - i] = src[i * width + j];
    182         }
    183     }
    184 }
    185 
    186 template<typename T>
    187 static void rotate180(T* dst, const T* src, size_t width, size_t height)
    188 {
    189     for (size_t i = 0; i < height; ++i) {
    190         for (size_t j = 0; j < width; ++j) {
    191             dst[(height - 1 - i) * width + width - 1 - j] = src[i * width + j];
    192         }
    193     }
    194 }
    195 
    196 template<typename T>
    197 static void rotate270(T* dst, const T* src, size_t width, size_t height)
    198 {
    199     for (size_t i = 0; i < height; ++i) {
    200         for (size_t j = 0; j < width; ++j) {
    201             dst[(width - 1 - j) * height + i] = src[i * width + j];
    202         }
    203     }
    204 }
    205 
    206 template<typename T>
    207 static void rotate(T *dst, const T *src, size_t width, size_t height, int angle)
    208 {
    209     switch (angle) {
    210         case 0:
    211             rotate0(dst, src, width, height);
    212             break;
    213         case 90:
    214             rotate90(dst, src, width, height);
    215             break;
    216         case 180:
    217             rotate180(dst, src, width, height);
    218             break;
    219         case 270:
    220             rotate270(dst, src, width, height);
    221             break;
    222     }
    223 }
    224 
    225 static jobject android_media_MediaMetadataRetriever_getFrameAtTime(JNIEnv *env, jobject thiz, jlong timeUs, jint option)
    226 {
    227     LOGV("getFrameAtTime: %lld us option: %d", timeUs, option);
    228     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
    229     if (retriever == 0) {
    230         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
    231         return NULL;
    232     }
    233 
    234     // Call native method to retrieve a video frame
    235     VideoFrame *videoFrame = NULL;
    236     sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option);
    237     if (frameMemory != 0) {  // cast the shared structure to a VideoFrame object
    238         videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
    239     }
    240     if (videoFrame == NULL) {
    241         LOGE("getFrameAtTime: videoFrame is a NULL pointer");
    242         return NULL;
    243     }
    244 
    245     LOGV("Dimension = %dx%d and bytes = %d",
    246             videoFrame->mDisplayWidth,
    247             videoFrame->mDisplayHeight,
    248             videoFrame->mSize);
    249 
    250     jobject config = env->CallStaticObjectMethod(
    251                         fields.configClazz,
    252                         fields.createConfigMethod,
    253                         SkBitmap::kRGB_565_Config);
    254 
    255     size_t width, height;
    256     bool swapWidthAndHeight = false;
    257     if (videoFrame->mRotationAngle == 90 || videoFrame->mRotationAngle == 270) {
    258         width = videoFrame->mHeight;
    259         height = videoFrame->mWidth;
    260         swapWidthAndHeight = true;
    261     } else {
    262         width = videoFrame->mWidth;
    263         height = videoFrame->mHeight;
    264     }
    265 
    266     jobject jBitmap = env->CallStaticObjectMethod(
    267                             fields.bitmapClazz,
    268                             fields.createBitmapMethod,
    269                             width,
    270                             height,
    271                             config);
    272 
    273     SkBitmap *bitmap =
    274             (SkBitmap *) env->GetIntField(jBitmap, fields.nativeBitmap);
    275 
    276     bitmap->lockPixels();
    277     rotate((uint16_t*)bitmap->getPixels(),
    278            (uint16_t*)((char*)videoFrame + sizeof(VideoFrame)),
    279            videoFrame->mWidth,
    280            videoFrame->mHeight,
    281            videoFrame->mRotationAngle);
    282     bitmap->unlockPixels();
    283 
    284     if (videoFrame->mDisplayWidth  != videoFrame->mWidth ||
    285         videoFrame->mDisplayHeight != videoFrame->mHeight) {
    286         size_t displayWidth = videoFrame->mDisplayWidth;
    287         size_t displayHeight = videoFrame->mDisplayHeight;
    288         if (swapWidthAndHeight) {
    289             displayWidth = videoFrame->mDisplayHeight;
    290             displayHeight = videoFrame->mDisplayWidth;
    291         }
    292         LOGV("Bitmap dimension is scaled from %dx%d to %dx%d",
    293                 width, height, displayWidth, displayHeight);
    294         jobject scaledBitmap = env->CallStaticObjectMethod(fields.bitmapClazz,
    295                                     fields.createScaledBitmapMethod,
    296                                     jBitmap,
    297                                     displayWidth,
    298                                     displayHeight,
    299                                     true);
    300         return scaledBitmap;
    301     }
    302 
    303     return jBitmap;
    304 }
    305 
    306 static jbyteArray android_media_MediaMetadataRetriever_getEmbeddedPicture(
    307         JNIEnv *env, jobject thiz, jint pictureType)
    308 {
    309     LOGV("getEmbeddedPicture: %d", pictureType);
    310     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
    311     if (retriever == 0) {
    312         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
    313         return NULL;
    314     }
    315     MediaAlbumArt* mediaAlbumArt = NULL;
    316 
    317     // FIXME:
    318     // Use pictureType to retrieve the intended embedded picture and also change
    319     // the method name to getEmbeddedPicture().
    320     sp<IMemory> albumArtMemory = retriever->extractAlbumArt();
    321     if (albumArtMemory != 0) {  // cast the shared structure to a MediaAlbumArt object
    322         mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->pointer());
    323     }
    324     if (mediaAlbumArt == NULL) {
    325         LOGE("getEmbeddedPicture: Call to getEmbeddedPicture failed.");
    326         return NULL;
    327     }
    328 
    329     unsigned int len = mediaAlbumArt->mSize;
    330     char* data = (char*) mediaAlbumArt + sizeof(MediaAlbumArt);
    331     jbyteArray array = env->NewByteArray(len);
    332     if (!array) {  // OutOfMemoryError exception has already been thrown.
    333         LOGE("getEmbeddedPicture: OutOfMemoryError is thrown.");
    334     } else {
    335         jbyte* bytes = env->GetByteArrayElements(array, NULL);
    336         if (bytes != NULL) {
    337             memcpy(bytes, data, len);
    338             env->ReleaseByteArrayElements(array, bytes, 0);
    339         }
    340     }
    341 
    342     // No need to delete mediaAlbumArt here
    343     return array;
    344 }
    345 
    346 static jobject android_media_MediaMetadataRetriever_extractMetadata(JNIEnv *env, jobject thiz, jint keyCode)
    347 {
    348     LOGV("extractMetadata");
    349     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
    350     if (retriever == 0) {
    351         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
    352         return NULL;
    353     }
    354     const char* value = retriever->extractMetadata(keyCode);
    355     if (!value) {
    356         LOGV("extractMetadata: Metadata is not found");
    357         return NULL;
    358     }
    359     LOGV("extractMetadata: value (%s) for keyCode(%d)", value, keyCode);
    360     return env->NewStringUTF(value);
    361 }
    362 
    363 static void android_media_MediaMetadataRetriever_release(JNIEnv *env, jobject thiz)
    364 {
    365     LOGV("release");
    366     Mutex::Autolock lock(sLock);
    367     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
    368     delete retriever;
    369     setRetriever(env, thiz, 0);
    370 }
    371 
    372 static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz)
    373 {
    374     LOGV("native_finalize");
    375     // No lock is needed, since android_media_MediaMetadataRetriever_release() is protected
    376     android_media_MediaMetadataRetriever_release(env, thiz);
    377 }
    378 
    379 // This function gets a field ID, which in turn causes class initialization.
    380 // It is called from a static block in MediaMetadataRetriever, which won't run until the
    381 // first time an instance of this class is used.
    382 static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env)
    383 {
    384     jclass clazz = env->FindClass(kClassPathName);
    385     if (clazz == NULL) {
    386         return;
    387     }
    388 
    389     fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
    390     if (fields.context == NULL) {
    391         return;
    392     }
    393 
    394     jclass bitmapClazz = env->FindClass("android/graphics/Bitmap");
    395     if (bitmapClazz == NULL) {
    396         return;
    397     }
    398     fields.bitmapClazz = (jclass) env->NewGlobalRef(bitmapClazz);
    399     if (fields.bitmapClazz == NULL) {
    400         return;
    401     }
    402     fields.createBitmapMethod =
    403             env->GetStaticMethodID(fields.bitmapClazz, "createBitmap",
    404                     "(IILandroid/graphics/Bitmap$Config;)"
    405                     "Landroid/graphics/Bitmap;");
    406     if (fields.createBitmapMethod == NULL) {
    407         return;
    408     }
    409     fields.createScaledBitmapMethod =
    410             env->GetStaticMethodID(fields.bitmapClazz, "createScaledBitmap",
    411                     "(Landroid/graphics/Bitmap;IIZ)"
    412                     "Landroid/graphics/Bitmap;");
    413     if (fields.createScaledBitmapMethod == NULL) {
    414         return;
    415     }
    416     fields.nativeBitmap = env->GetFieldID(fields.bitmapClazz, "mNativeBitmap", "I");
    417     if (fields.nativeBitmap == NULL) {
    418         return;
    419     }
    420 
    421     jclass configClazz = env->FindClass("android/graphics/Bitmap$Config");
    422     if (configClazz == NULL) {
    423         return;
    424     }
    425     fields.configClazz = (jclass) env->NewGlobalRef(configClazz);
    426     if (fields.configClazz == NULL) {
    427         return;
    428     }
    429     fields.createConfigMethod =
    430             env->GetStaticMethodID(fields.configClazz, "nativeToConfig",
    431                     "(I)Landroid/graphics/Bitmap$Config;");
    432     if (fields.createConfigMethod == NULL) {
    433         return;
    434     }
    435 }
    436 
    437 static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz)
    438 {
    439     LOGV("native_setup");
    440     MediaMetadataRetriever* retriever = new MediaMetadataRetriever();
    441     if (retriever == 0) {
    442         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
    443         return;
    444     }
    445     setRetriever(env, thiz, (int)retriever);
    446 }
    447 
    448 // JNI mapping between Java methods and native methods
    449 static JNINativeMethod nativeMethods[] = {
    450         {"setDataSource",   "(Ljava/lang/String;)V", (void *)android_media_MediaMetadataRetriever_setDataSource},
    451 
    452         {
    453             "_setDataSource",
    454             "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
    455             (void *)android_media_MediaMetadataRetriever_setDataSourceAndHeaders
    456         },
    457 
    458         {"setDataSource",   "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD},
    459         {"_getFrameAtTime", "(JI)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
    460         {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata},
    461         {"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture},
    462         {"release",         "()V", (void *)android_media_MediaMetadataRetriever_release},
    463         {"native_finalize", "()V", (void *)android_media_MediaMetadataRetriever_native_finalize},
    464         {"native_setup",    "()V", (void *)android_media_MediaMetadataRetriever_native_setup},
    465         {"native_init",     "()V", (void *)android_media_MediaMetadataRetriever_native_init},
    466 };
    467 
    468 // This function only registers the native methods, and is called from
    469 // JNI_OnLoad in android_media_MediaPlayer.cpp
    470 int register_android_media_MediaMetadataRetriever(JNIEnv *env)
    471 {
    472     return AndroidRuntime::registerNativeMethods
    473         (env, kClassPathName, nativeMethods, NELEM(nativeMethods));
    474 }
    475