android_media_MediaMetadataRetriever.cpp revision 4507cef05e492f65755aa321007d6592d111d01f
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 <core/SkCanvas.h> 26#include <core/SkDevice.h> 27#include <core/SkScalar.h> 28#include <media/mediametadataretriever.h> 29#include <private/media/VideoFrame.h> 30 31#include "jni.h" 32#include "JNIHelp.h" 33#include "android_runtime/AndroidRuntime.h" 34 35 36using namespace android; 37 38struct fields_t { 39 jfieldID context; 40 jclass bitmapClazz; 41 jfieldID nativeBitmap; 42 jmethodID createBitmapMethod; 43 jclass configClazz; 44 jmethodID createConfigMethod; 45}; 46 47static fields_t fields; 48static Mutex sLock; 49static const char* const kClassPathName = "android/media/MediaMetadataRetriever"; 50 51static void process_media_retriever_call(JNIEnv *env, status_t opStatus, const char* exception, const char *message) 52{ 53 if (opStatus == (status_t) INVALID_OPERATION) { 54 jniThrowException(env, "java/lang/IllegalStateException", NULL); 55 } else if (opStatus != (status_t) OK) { 56 if (strlen(message) > 230) { 57 // If the message is too long, don't bother displaying the status code. 58 jniThrowException( env, exception, message); 59 } else { 60 char msg[256]; 61 // Append the status code to the message. 62 sprintf(msg, "%s: status = 0x%X", message, opStatus); 63 jniThrowException( env, exception, msg); 64 } 65 } 66} 67 68static MediaMetadataRetriever* getRetriever(JNIEnv* env, jobject thiz) 69{ 70 // No lock is needed, since it is called internally by other methods that are protected 71 MediaMetadataRetriever* retriever = (MediaMetadataRetriever*) env->GetIntField(thiz, fields.context); 72 return retriever; 73} 74 75static void setRetriever(JNIEnv* env, jobject thiz, int retriever) 76{ 77 // No lock is needed, since it is called internally by other methods that are protected 78 MediaMetadataRetriever *old = (MediaMetadataRetriever*) env->GetIntField(thiz, fields.context); 79 env->SetIntField(thiz, fields.context, retriever); 80} 81 82static void android_media_MediaMetadataRetriever_setDataSource(JNIEnv *env, jobject thiz, jstring path) 83{ 84 LOGV("setDataSource"); 85 MediaMetadataRetriever* retriever = getRetriever(env, thiz); 86 if (retriever == 0) { 87 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); 88 return; 89 } 90 if (!path) { 91 jniThrowException(env, "java/lang/IllegalArgumentException", "Null pointer"); 92 return; 93 } 94 95 const char *pathStr = env->GetStringUTFChars(path, NULL); 96 if (!pathStr) { // OutOfMemoryError exception already thrown 97 return; 98 } 99 100 // Don't let somebody trick us in to reading some random block of memory 101 if (strncmp("mem://", pathStr, 6) == 0) { 102 jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid pathname"); 103 return; 104 } 105 106 process_media_retriever_call(env, retriever->setDataSource(pathStr), "java/lang/RuntimeException", "setDataSource failed"); 107 env->ReleaseStringUTFChars(path, pathStr); 108} 109 110static void android_media_MediaMetadataRetriever_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length) 111{ 112 LOGV("setDataSource"); 113 MediaMetadataRetriever* retriever = getRetriever(env, thiz); 114 if (retriever == 0) { 115 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); 116 return; 117 } 118 if (!fileDescriptor) { 119 jniThrowException(env, "java/lang/IllegalArgumentException", NULL); 120 return; 121 } 122 int fd = getParcelFileDescriptorFD(env, fileDescriptor); 123 if (offset < 0 || length < 0 || fd < 0) { 124 if (offset < 0) { 125 LOGE("negative offset (%lld)", offset); 126 } 127 if (length < 0) { 128 LOGE("negative length (%lld)", length); 129 } 130 if (fd < 0) { 131 LOGE("invalid file descriptor"); 132 } 133 jniThrowException(env, "java/lang/IllegalArgumentException", NULL); 134 return; 135 } 136 process_media_retriever_call(env, retriever->setDataSource(fd, offset, length), "java/lang/RuntimeException", "setDataSource failed"); 137} 138 139static jobject android_media_MediaMetadataRetriever_getFrameAtTime(JNIEnv *env, jobject thiz, jlong timeUs, jint option) 140{ 141 LOGV("getFrameAtTime: %lld us option: %d", timeUs, option); 142 MediaMetadataRetriever* retriever = getRetriever(env, thiz); 143 if (retriever == 0) { 144 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); 145 return NULL; 146 } 147 148 // Call native method to retrieve a video frame 149 VideoFrame *videoFrame = NULL; 150 sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option); 151 if (frameMemory != 0) { // cast the shared structure to a VideoFrame object 152 videoFrame = static_cast<VideoFrame *>(frameMemory->pointer()); 153 } 154 if (videoFrame == NULL) { 155 LOGE("getFrameAtTime: videoFrame is a NULL pointer"); 156 return NULL; 157 } 158 159 LOGV("Dimension = %dx%d and bytes = %d", 160 videoFrame->mDisplayWidth, 161 videoFrame->mDisplayHeight, 162 videoFrame->mSize); 163 164 jobject config = env->CallStaticObjectMethod( 165 fields.configClazz, 166 fields.createConfigMethod, 167 SkBitmap::kRGB_565_Config); 168 169 jobject jBitmap = env->CallStaticObjectMethod( 170 fields.bitmapClazz, 171 fields.createBitmapMethod, 172 videoFrame->mDisplayWidth, 173 videoFrame->mDisplayHeight, 174 config); 175 SkBitmap *bitmap = 176 (SkBitmap *) env->GetIntField(jBitmap, fields.nativeBitmap); 177 178 bitmap->lockPixels(); 179 180 memcpy((uint8_t*)bitmap->getPixels(), 181 (uint8_t*)videoFrame + sizeof(VideoFrame), videoFrame->mSize); 182 183 bitmap->unlockPixels(); 184 185 if (videoFrame->mRotationAngle != 0) { 186 SkDevice device(*bitmap); 187 SkCanvas canvas(&device); 188 canvas.rotate((SkScalar) (videoFrame->mRotationAngle * 1.0)); 189 canvas.drawBitmap(*bitmap, 0, 0); 190 } 191 192 LOGV("Return a new bitmap constructed with the rotation matrix"); 193 return jBitmap; 194} 195 196static jbyteArray android_media_MediaMetadataRetriever_getEmbeddedPicture( 197 JNIEnv *env, jobject thiz, jint pictureType) 198{ 199 LOGV("getEmbeddedPicture: %d", pictureType); 200 MediaMetadataRetriever* retriever = getRetriever(env, thiz); 201 if (retriever == 0) { 202 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); 203 return NULL; 204 } 205 MediaAlbumArt* mediaAlbumArt = NULL; 206 207 // FIXME: 208 // Use pictureType to retrieve the intended embedded picture and also change 209 // the method name to getEmbeddedPicture(). 210 sp<IMemory> albumArtMemory = retriever->extractAlbumArt(); 211 if (albumArtMemory != 0) { // cast the shared structure to a MediaAlbumArt object 212 mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->pointer()); 213 } 214 if (mediaAlbumArt == NULL) { 215 LOGE("getEmbeddedPicture: Call to getEmbeddedPicture failed."); 216 return NULL; 217 } 218 219 unsigned int len = mediaAlbumArt->mSize; 220 char* data = (char*) mediaAlbumArt + sizeof(MediaAlbumArt); 221 jbyteArray array = env->NewByteArray(len); 222 if (!array) { // OutOfMemoryError exception has already been thrown. 223 LOGE("getEmbeddedPicture: OutOfMemoryError is thrown."); 224 } else { 225 jbyte* bytes = env->GetByteArrayElements(array, NULL); 226 if (bytes != NULL) { 227 memcpy(bytes, data, len); 228 env->ReleaseByteArrayElements(array, bytes, 0); 229 } 230 } 231 232 // No need to delete mediaAlbumArt here 233 return array; 234} 235 236static jobject android_media_MediaMetadataRetriever_extractMetadata(JNIEnv *env, jobject thiz, jint keyCode) 237{ 238 LOGV("extractMetadata"); 239 MediaMetadataRetriever* retriever = getRetriever(env, thiz); 240 if (retriever == 0) { 241 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); 242 return NULL; 243 } 244 const char* value = retriever->extractMetadata(keyCode); 245 if (!value) { 246 LOGV("extractMetadata: Metadata is not found"); 247 return NULL; 248 } 249 LOGV("extractMetadata: value (%s) for keyCode(%d)", value, keyCode); 250 return env->NewStringUTF(value); 251} 252 253static void android_media_MediaMetadataRetriever_release(JNIEnv *env, jobject thiz) 254{ 255 LOGV("release"); 256 Mutex::Autolock lock(sLock); 257 MediaMetadataRetriever* retriever = getRetriever(env, thiz); 258 delete retriever; 259 setRetriever(env, thiz, 0); 260} 261 262static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz) 263{ 264 LOGV("native_finalize"); 265 // No lock is needed, since android_media_MediaMetadataRetriever_release() is protected 266 android_media_MediaMetadataRetriever_release(env, thiz); 267} 268 269// This function gets a field ID, which in turn causes class initialization. 270// It is called from a static block in MediaMetadataRetriever, which won't run until the 271// first time an instance of this class is used. 272static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env) 273{ 274 jclass clazz = env->FindClass(kClassPathName); 275 if (clazz == NULL) { 276 jniThrowException(env, "java/lang/RuntimeException", "Can't find android/media/MediaMetadataRetriever"); 277 return; 278 } 279 280 fields.context = env->GetFieldID(clazz, "mNativeContext", "I"); 281 if (fields.context == NULL) { 282 jniThrowException(env, "java/lang/RuntimeException", "Can't find MediaMetadataRetriever.mNativeContext"); 283 return; 284 } 285 286 fields.bitmapClazz = env->FindClass("android/graphics/Bitmap"); 287 if (fields.bitmapClazz == NULL) { 288 jniThrowException(env, "java/lang/RuntimeException", "Can't find android/graphics/Bitmap"); 289 return; 290 } 291#if USE_PRIVATE_NATIVE_BITMAP_CONSTUCTOR 292 fields.bitmapConstructor = env->GetMethodID(fields.bitmapClazz, "<init>", "(I[BZ[BI)V"); 293 if (fields.bitmapConstructor == NULL) { 294 jniThrowException(env, "java/lang/RuntimeException", "Can't find Bitmap constructor"); 295 return; 296 } 297 fields.createBitmapRotationMethod = 298 env->GetStaticMethodID(fields.bitmapClazz, "createBitmap", 299 "(Landroid/graphics/Bitmap;IIIILandroid/graphics/Matrix;Z)" 300 "Landroid/graphics/Bitmap;"); 301 if (fields.createBitmapRotationMethod == NULL) { 302 jniThrowException(env, "java/lang/RuntimeException", 303 "Can't find Bitmap.createBitmap method"); 304 return; 305 } 306#else 307 fields.createBitmapMethod = 308 env->GetStaticMethodID(fields.bitmapClazz, "createBitmap", 309 "(IILandroid/graphics/Bitmap$Config;)" 310 "Landroid/graphics/Bitmap;"); 311 if (fields.createBitmapMethod == NULL) { 312 jniThrowException(env, "java/lang/RuntimeException", 313 "Can't find Bitmap.createBitmap(int, int, Config) method"); 314 return; 315 } 316 fields.nativeBitmap = env->GetFieldID(fields.bitmapClazz, "mNativeBitmap", "I"); 317 if (fields.nativeBitmap == NULL) { 318 jniThrowException(env, "java/lang/RuntimeException", 319 "Can't find Bitmap.mNativeBitmap field"); 320 } 321 322 fields.configClazz = env->FindClass("android/graphics/Bitmap$Config"); 323 if (fields.configClazz == NULL) { 324 jniThrowException(env, "java/lang/RuntimeException", 325 "Can't find Bitmap$Config class"); 326 return; 327 } 328 fields.createConfigMethod = 329 env->GetStaticMethodID(fields.configClazz, "nativeToConfig", 330 "(I)Landroid/graphics/Bitmap$Config;"); 331 if (fields.createConfigMethod == NULL) { 332 jniThrowException(env, "java/lang/RuntimeException", 333 "Can't find Bitmap$Config.nativeToConfig(int) method"); 334 return; 335 } 336#endif 337} 338 339static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz) 340{ 341 LOGV("native_setup"); 342 MediaMetadataRetriever* retriever = new MediaMetadataRetriever(); 343 if (retriever == 0) { 344 jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); 345 return; 346 } 347 setRetriever(env, thiz, (int)retriever); 348} 349 350// JNI mapping between Java methods and native methods 351static JNINativeMethod nativeMethods[] = { 352 {"setDataSource", "(Ljava/lang/String;)V", (void *)android_media_MediaMetadataRetriever_setDataSource}, 353 {"setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD}, 354 {"_getFrameAtTime", "(JI)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime}, 355 {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata}, 356 {"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture}, 357 {"release", "()V", (void *)android_media_MediaMetadataRetriever_release}, 358 {"native_finalize", "()V", (void *)android_media_MediaMetadataRetriever_native_finalize}, 359 {"native_setup", "()V", (void *)android_media_MediaMetadataRetriever_native_setup}, 360 {"native_init", "()V", (void *)android_media_MediaMetadataRetriever_native_init}, 361}; 362 363// This function only registers the native methods, and is called from 364// JNI_OnLoad in android_media_MediaPlayer.cpp 365int register_android_media_MediaMetadataRetriever(JNIEnv *env) 366{ 367 return AndroidRuntime::registerNativeMethods 368 (env, kClassPathName, nativeMethods, NELEM(nativeMethods)); 369} 370