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