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_MediaDataSource.h"
     34 #include "android_media_Utils.h"
     35 #include "android_util_Binder.h"
     36 
     37 #include "android/graphics/GraphicsJNI.h"
     38 
     39 using namespace android;
     40 
     41 struct fields_t {
     42     jfieldID context;
     43     jclass bitmapClazz;  // Must be a global ref
     44     jmethodID createBitmapMethod;
     45     jmethodID createScaledBitmapMethod;
     46     jclass configClazz;  // Must be a global ref
     47     jmethodID createConfigMethod;
     48 };
     49 
     50 static fields_t fields;
     51 static Mutex sLock;
     52 static const char* const kClassPathName = "android/media/MediaMetadataRetriever";
     53 
     54 static void process_media_retriever_call(JNIEnv *env, status_t opStatus, const char* exception, const char *message)
     55 {
     56     if (opStatus == (status_t) INVALID_OPERATION) {
     57         jniThrowException(env, "java/lang/IllegalStateException", NULL);
     58     } else if (opStatus != (status_t) OK) {
     59         if (strlen(message) > 230) {
     60             // If the message is too long, don't bother displaying the status code.
     61             jniThrowException( env, exception, message);
     62         } else {
     63             char msg[256];
     64             // Append the status code to the message.
     65             sprintf(msg, "%s: status = 0x%X", message, opStatus);
     66             jniThrowException( env, exception, msg);
     67         }
     68     }
     69 }
     70 
     71 static MediaMetadataRetriever* getRetriever(JNIEnv* env, jobject thiz)
     72 {
     73     // No lock is needed, since it is called internally by other methods that are protected
     74     MediaMetadataRetriever* retriever = (MediaMetadataRetriever*) env->GetLongField(thiz, fields.context);
     75     return retriever;
     76 }
     77 
     78 static void setRetriever(JNIEnv* env, jobject thiz, MediaMetadataRetriever* retriever)
     79 {
     80     // No lock is needed, since it is called internally by other methods that are protected
     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 static void android_media_MediaMetadataRetriever_setDataSourceCallback(JNIEnv *env, jobject thiz, jobject dataSource)
    176 {
    177     ALOGV("setDataSourceCallback");
    178     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
    179     if (retriever == 0) {
    180         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
    181         return;
    182     }
    183     if (dataSource == NULL) {
    184         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
    185         return;
    186     }
    187 
    188     sp<IDataSource> callbackDataSource = new JMediaDataSource(env, dataSource);
    189     process_media_retriever_call(env, retriever->setDataSource(callbackDataSource), "java/lang/RuntimeException", "setDataSourceCallback failed");
    190 }
    191 
    192 template<typename T>
    193 static void rotate0(T* dst, const T* src, size_t width, size_t height)
    194 {
    195     memcpy(dst, src, width * height * sizeof(T));
    196 }
    197 
    198 template<typename T>
    199 static void rotate90(T* dst, const T* src, size_t width, size_t height)
    200 {
    201     for (size_t i = 0; i < height; ++i) {
    202         for (size_t j = 0; j < width; ++j) {
    203             dst[j * height + height - 1 - i] = src[i * width + j];
    204         }
    205     }
    206 }
    207 
    208 template<typename T>
    209 static void rotate180(T* dst, const T* src, size_t width, size_t height)
    210 {
    211     for (size_t i = 0; i < height; ++i) {
    212         for (size_t j = 0; j < width; ++j) {
    213             dst[(height - 1 - i) * width + width - 1 - j] = src[i * width + j];
    214         }
    215     }
    216 }
    217 
    218 template<typename T>
    219 static void rotate270(T* dst, const T* src, size_t width, size_t height)
    220 {
    221     for (size_t i = 0; i < height; ++i) {
    222         for (size_t j = 0; j < width; ++j) {
    223             dst[(width - 1 - j) * height + i] = src[i * width + j];
    224         }
    225     }
    226 }
    227 
    228 template<typename T>
    229 static void rotate(T *dst, const T *src, size_t width, size_t height, int angle)
    230 {
    231     switch (angle) {
    232         case 0:
    233             rotate0(dst, src, width, height);
    234             break;
    235         case 90:
    236             rotate90(dst, src, width, height);
    237             break;
    238         case 180:
    239             rotate180(dst, src, width, height);
    240             break;
    241         case 270:
    242             rotate270(dst, src, width, height);
    243             break;
    244     }
    245 }
    246 
    247 static jobject android_media_MediaMetadataRetriever_getFrameAtTime(JNIEnv *env, jobject thiz, jlong timeUs, jint option)
    248 {
    249     ALOGV("getFrameAtTime: %lld us option: %d", (long long)timeUs, option);
    250     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
    251     if (retriever == 0) {
    252         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
    253         return NULL;
    254     }
    255 
    256     // Call native method to retrieve a video frame
    257     VideoFrame *videoFrame = NULL;
    258     sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option);
    259     if (frameMemory != 0) {  // cast the shared structure to a VideoFrame object
    260         videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
    261     }
    262     if (videoFrame == NULL) {
    263         ALOGE("getFrameAtTime: videoFrame is a NULL pointer");
    264         return NULL;
    265     }
    266 
    267     ALOGV("Dimension = %dx%d and bytes = %d",
    268             videoFrame->mDisplayWidth,
    269             videoFrame->mDisplayHeight,
    270             videoFrame->mSize);
    271 
    272     jobject config = env->CallStaticObjectMethod(
    273                         fields.configClazz,
    274                         fields.createConfigMethod,
    275                         GraphicsJNI::colorTypeToLegacyBitmapConfig(kRGB_565_SkColorType));
    276 
    277     uint32_t width, height;
    278     bool swapWidthAndHeight = false;
    279     if (videoFrame->mRotationAngle == 90 || videoFrame->mRotationAngle == 270) {
    280         width = videoFrame->mHeight;
    281         height = videoFrame->mWidth;
    282         swapWidthAndHeight = true;
    283     } else {
    284         width = videoFrame->mWidth;
    285         height = videoFrame->mHeight;
    286     }
    287 
    288     jobject jBitmap = env->CallStaticObjectMethod(
    289                             fields.bitmapClazz,
    290                             fields.createBitmapMethod,
    291                             width,
    292                             height,
    293                             config);
    294     if (jBitmap == NULL) {
    295         if (env->ExceptionCheck()) {
    296             env->ExceptionClear();
    297         }
    298         ALOGE("getFrameAtTime: create Bitmap failed!");
    299         return NULL;
    300     }
    301 
    302     SkBitmap bitmap;
    303     GraphicsJNI::getSkBitmap(env, jBitmap, &bitmap);
    304 
    305     bitmap.lockPixels();
    306     rotate((uint16_t*)bitmap.getPixels(),
    307            (uint16_t*)((char*)videoFrame + sizeof(VideoFrame)),
    308            videoFrame->mWidth,
    309            videoFrame->mHeight,
    310            videoFrame->mRotationAngle);
    311     bitmap.unlockPixels();
    312 
    313     if (videoFrame->mDisplayWidth  != videoFrame->mWidth ||
    314         videoFrame->mDisplayHeight != videoFrame->mHeight) {
    315         uint32_t displayWidth = videoFrame->mDisplayWidth;
    316         uint32_t displayHeight = videoFrame->mDisplayHeight;
    317         if (swapWidthAndHeight) {
    318             displayWidth = videoFrame->mDisplayHeight;
    319             displayHeight = videoFrame->mDisplayWidth;
    320         }
    321         ALOGV("Bitmap dimension is scaled from %dx%d to %dx%d",
    322                 width, height, displayWidth, displayHeight);
    323         jobject scaledBitmap = env->CallStaticObjectMethod(fields.bitmapClazz,
    324                                     fields.createScaledBitmapMethod,
    325                                     jBitmap,
    326                                     displayWidth,
    327                                     displayHeight,
    328                                     true);
    329         return scaledBitmap;
    330     }
    331 
    332     return jBitmap;
    333 }
    334 
    335 static jbyteArray android_media_MediaMetadataRetriever_getEmbeddedPicture(
    336         JNIEnv *env, jobject thiz, jint pictureType)
    337 {
    338     ALOGV("getEmbeddedPicture: %d", pictureType);
    339     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
    340     if (retriever == 0) {
    341         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
    342         return NULL;
    343     }
    344     MediaAlbumArt* mediaAlbumArt = NULL;
    345 
    346     // FIXME:
    347     // Use pictureType to retrieve the intended embedded picture and also change
    348     // the method name to getEmbeddedPicture().
    349     sp<IMemory> albumArtMemory = retriever->extractAlbumArt();
    350     if (albumArtMemory != 0) {  // cast the shared structure to a MediaAlbumArt object
    351         mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->pointer());
    352     }
    353     if (mediaAlbumArt == NULL) {
    354         ALOGE("getEmbeddedPicture: Call to getEmbeddedPicture failed.");
    355         return NULL;
    356     }
    357 
    358     jbyteArray array = env->NewByteArray(mediaAlbumArt->size());
    359     if (!array) {  // OutOfMemoryError exception has already been thrown.
    360         ALOGE("getEmbeddedPicture: OutOfMemoryError is thrown.");
    361     } else {
    362         const jbyte* data =
    363                 reinterpret_cast<const jbyte*>(mediaAlbumArt->data());
    364         env->SetByteArrayRegion(array, 0, mediaAlbumArt->size(), data);
    365     }
    366 
    367     // No need to delete mediaAlbumArt here
    368     return array;
    369 }
    370 
    371 static jobject android_media_MediaMetadataRetriever_extractMetadata(JNIEnv *env, jobject thiz, jint keyCode)
    372 {
    373     ALOGV("extractMetadata");
    374     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
    375     if (retriever == 0) {
    376         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
    377         return NULL;
    378     }
    379     const char* value = retriever->extractMetadata(keyCode);
    380     if (!value) {
    381         ALOGV("extractMetadata: Metadata is not found");
    382         return NULL;
    383     }
    384     ALOGV("extractMetadata: value (%s) for keyCode(%d)", value, keyCode);
    385     return env->NewStringUTF(value);
    386 }
    387 
    388 static void android_media_MediaMetadataRetriever_release(JNIEnv *env, jobject thiz)
    389 {
    390     ALOGV("release");
    391     Mutex::Autolock lock(sLock);
    392     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
    393     delete retriever;
    394     setRetriever(env, thiz, (MediaMetadataRetriever*) 0);
    395 }
    396 
    397 static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz)
    398 {
    399     ALOGV("native_finalize");
    400     // No lock is needed, since android_media_MediaMetadataRetriever_release() is protected
    401     android_media_MediaMetadataRetriever_release(env, thiz);
    402 }
    403 
    404 // This function gets a field ID, which in turn causes class initialization.
    405 // It is called from a static block in MediaMetadataRetriever, which won't run until the
    406 // first time an instance of this class is used.
    407 static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env)
    408 {
    409     jclass clazz = env->FindClass(kClassPathName);
    410     if (clazz == NULL) {
    411         return;
    412     }
    413 
    414     fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
    415     if (fields.context == NULL) {
    416         return;
    417     }
    418 
    419     jclass bitmapClazz = env->FindClass("android/graphics/Bitmap");
    420     if (bitmapClazz == NULL) {
    421         return;
    422     }
    423     fields.bitmapClazz = (jclass) env->NewGlobalRef(bitmapClazz);
    424     if (fields.bitmapClazz == NULL) {
    425         return;
    426     }
    427     fields.createBitmapMethod =
    428             env->GetStaticMethodID(fields.bitmapClazz, "createBitmap",
    429                     "(IILandroid/graphics/Bitmap$Config;)"
    430                     "Landroid/graphics/Bitmap;");
    431     if (fields.createBitmapMethod == NULL) {
    432         return;
    433     }
    434     fields.createScaledBitmapMethod =
    435             env->GetStaticMethodID(fields.bitmapClazz, "createScaledBitmap",
    436                     "(Landroid/graphics/Bitmap;IIZ)"
    437                     "Landroid/graphics/Bitmap;");
    438     if (fields.createScaledBitmapMethod == NULL) {
    439         return;
    440     }
    441 
    442     jclass configClazz = env->FindClass("android/graphics/Bitmap$Config");
    443     if (configClazz == NULL) {
    444         return;
    445     }
    446     fields.configClazz = (jclass) env->NewGlobalRef(configClazz);
    447     if (fields.configClazz == NULL) {
    448         return;
    449     }
    450     fields.createConfigMethod =
    451             env->GetStaticMethodID(fields.configClazz, "nativeToConfig",
    452                     "(I)Landroid/graphics/Bitmap$Config;");
    453     if (fields.createConfigMethod == NULL) {
    454         return;
    455     }
    456 }
    457 
    458 static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz)
    459 {
    460     ALOGV("native_setup");
    461     MediaMetadataRetriever* retriever = new MediaMetadataRetriever();
    462     if (retriever == 0) {
    463         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
    464         return;
    465     }
    466     setRetriever(env, thiz, retriever);
    467 }
    468 
    469 // JNI mapping between Java methods and native methods
    470 static const JNINativeMethod nativeMethods[] = {
    471         {
    472             "_setDataSource",
    473             "(Landroid/os/IBinder;Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
    474             (void *)android_media_MediaMetadataRetriever_setDataSourceAndHeaders
    475         },
    476 
    477         {"setDataSource",   "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD},
    478         {"_setDataSource",   "(Landroid/media/MediaDataSource;)V", (void *)android_media_MediaMetadataRetriever_setDataSourceCallback},
    479         {"_getFrameAtTime", "(JI)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
    480         {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata},
    481         {"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture},
    482         {"release",         "()V", (void *)android_media_MediaMetadataRetriever_release},
    483         {"native_finalize", "()V", (void *)android_media_MediaMetadataRetriever_native_finalize},
    484         {"native_setup",    "()V", (void *)android_media_MediaMetadataRetriever_native_setup},
    485         {"native_init",     "()V", (void *)android_media_MediaMetadataRetriever_native_init},
    486 };
    487 
    488 // This function only registers the native methods, and is called from
    489 // JNI_OnLoad in android_media_MediaPlayer.cpp
    490 int register_android_media_MediaMetadataRetriever(JNIEnv *env)
    491 {
    492     return AndroidRuntime::registerNativeMethods
    493         (env, kClassPathName, nativeMethods, NELEM(nativeMethods));
    494 }
    495