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