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