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