android_media_MediaMetadataRetriever.cpp revision 5a15d0d31b877ef512b3379eac033c6bb041c86a
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 37using namespace android; 38 39struct 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 49static fields_t fields; 50static Mutex sLock; 51static const char* const kClassPathName = "android/media/MediaMetadataRetriever"; 52 53static 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 70static 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 77static 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 env->SetLongField(thiz, fields.context, (jlong) retriever); 81} 82 83static void 84android_media_MediaMetadataRetriever_setDataSourceAndHeaders( 85 JNIEnv *env, jobject thiz, jobject httpServiceBinderObj, jstring path, 86 jobjectArray keys, jobjectArray values) { 87 88 ALOGV("setDataSource"); 89 MediaMetadataRetriever* retriever = getRetriever(env, thiz); 90 if (retriever == 0) { 91 jniThrowException( 92 env, 93 "java/lang/IllegalStateException", "No retriever available"); 94 95 return; 96 } 97 98 if (!path) { 99 jniThrowException( 100 env, "java/lang/IllegalArgumentException", "Null pointer"); 101 102 return; 103 } 104 105 const char *tmp = env->GetStringUTFChars(path, NULL); 106 if (!tmp) { // OutOfMemoryError exception already thrown 107 return; 108 } 109 110 String8 pathStr(tmp); 111 env->ReleaseStringUTFChars(path, tmp); 112 tmp = NULL; 113 114 // Don't let somebody trick us in to reading some random block of memory 115 if (strncmp("mem://", pathStr.string(), 6) == 0) { 116 jniThrowException( 117 env, "java/lang/IllegalArgumentException", "Invalid pathname"); 118 return; 119 } 120 121 // We build a similar KeyedVector out of it. 122 KeyedVector<String8, String8> headersVector; 123 if (!ConvertKeyValueArraysToKeyedVector( 124 env, keys, values, &headersVector)) { 125 return; 126 } 127 128 sp<IMediaHTTPService> httpService; 129 if (httpServiceBinderObj != NULL) { 130 sp<IBinder> binder = ibinderForJavaObject(env, httpServiceBinderObj); 131 httpService = interface_cast<IMediaHTTPService>(binder); 132 } 133 134 process_media_retriever_call( 135 env, 136 retriever->setDataSource( 137 httpService, 138 pathStr.string(), 139 headersVector.size() > 0 ? &headersVector : NULL), 140 141 "java/lang/RuntimeException", 142 "setDataSource failed"); 143} 144 145static void android_media_MediaMetadataRetriever_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length) 146{ 147 ALOGV("setDataSource"); 148 MediaMetadataRetriever* retriever = getRetriever(env, thiz); 149 if (retriever == 0) { 150 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); 151 return; 152 } 153 if (!fileDescriptor) { 154 jniThrowException(env, "java/lang/IllegalArgumentException", NULL); 155 return; 156 } 157 int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); 158 if (offset < 0 || length < 0 || fd < 0) { 159 if (offset < 0) { 160 ALOGE("negative offset (%lld)", (long long)offset); 161 } 162 if (length < 0) { 163 ALOGE("negative length (%lld)", (long long)length); 164 } 165 if (fd < 0) { 166 ALOGE("invalid file descriptor"); 167 } 168 jniThrowException(env, "java/lang/IllegalArgumentException", NULL); 169 return; 170 } 171 process_media_retriever_call(env, retriever->setDataSource(fd, offset, length), "java/lang/RuntimeException", "setDataSource failed"); 172} 173 174template<typename T> 175static void rotate0(T* dst, const T* src, size_t width, size_t height) 176{ 177 memcpy(dst, src, width * height * sizeof(T)); 178} 179 180template<typename T> 181static void rotate90(T* dst, const T* src, size_t width, size_t height) 182{ 183 for (size_t i = 0; i < height; ++i) { 184 for (size_t j = 0; j < width; ++j) { 185 dst[j * height + height - 1 - i] = src[i * width + j]; 186 } 187 } 188} 189 190template<typename T> 191static void rotate180(T* dst, const T* src, size_t width, size_t height) 192{ 193 for (size_t i = 0; i < height; ++i) { 194 for (size_t j = 0; j < width; ++j) { 195 dst[(height - 1 - i) * width + width - 1 - j] = src[i * width + j]; 196 } 197 } 198} 199 200template<typename T> 201static void rotate270(T* dst, const T* src, size_t width, size_t height) 202{ 203 for (size_t i = 0; i < height; ++i) { 204 for (size_t j = 0; j < width; ++j) { 205 dst[(width - 1 - j) * height + i] = src[i * width + j]; 206 } 207 } 208} 209 210template<typename T> 211static void rotate(T *dst, const T *src, size_t width, size_t height, int angle) 212{ 213 switch (angle) { 214 case 0: 215 rotate0(dst, src, width, height); 216 break; 217 case 90: 218 rotate90(dst, src, width, height); 219 break; 220 case 180: 221 rotate180(dst, src, width, height); 222 break; 223 case 270: 224 rotate270(dst, src, width, height); 225 break; 226 } 227} 228 229static jobject android_media_MediaMetadataRetriever_getFrameAtTime(JNIEnv *env, jobject thiz, jlong timeUs, jint option) 230{ 231 ALOGV("getFrameAtTime: %lld us option: %d", timeUs, option); 232 MediaMetadataRetriever* retriever = getRetriever(env, thiz); 233 if (retriever == 0) { 234 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); 235 return NULL; 236 } 237 238 // Call native method to retrieve a video frame 239 VideoFrame *videoFrame = NULL; 240 sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option); 241 if (frameMemory != 0) { // cast the shared structure to a VideoFrame object 242 videoFrame = static_cast<VideoFrame *>(frameMemory->pointer()); 243 } 244 if (videoFrame == NULL) { 245 ALOGE("getFrameAtTime: videoFrame is a NULL pointer"); 246 return NULL; 247 } 248 249 ALOGV("Dimension = %dx%d and bytes = %d", 250 videoFrame->mDisplayWidth, 251 videoFrame->mDisplayHeight, 252 videoFrame->mSize); 253 254 jobject config = env->CallStaticObjectMethod( 255 fields.configClazz, 256 fields.createConfigMethod, 257 SkBitmap::kRGB_565_Config); 258 259 uint32_t width, height; 260 bool swapWidthAndHeight = false; 261 if (videoFrame->mRotationAngle == 90 || videoFrame->mRotationAngle == 270) { 262 width = videoFrame->mHeight; 263 height = videoFrame->mWidth; 264 swapWidthAndHeight = true; 265 } else { 266 width = videoFrame->mWidth; 267 height = videoFrame->mHeight; 268 } 269 270 jobject jBitmap = env->CallStaticObjectMethod( 271 fields.bitmapClazz, 272 fields.createBitmapMethod, 273 width, 274 height, 275 config); 276 if (jBitmap == NULL) { 277 if (env->ExceptionCheck()) { 278 env->ExceptionClear(); 279 } 280 ALOGE("getFrameAtTime: create Bitmap failed!"); 281 return NULL; 282 } 283 284 SkBitmap *bitmap = 285 (SkBitmap *) env->GetLongField(jBitmap, fields.nativeBitmap); 286 287 bitmap->lockPixels(); 288 rotate((uint16_t*)bitmap->getPixels(), 289 (uint16_t*)((char*)videoFrame + sizeof(VideoFrame)), 290 videoFrame->mWidth, 291 videoFrame->mHeight, 292 videoFrame->mRotationAngle); 293 bitmap->unlockPixels(); 294 295 if (videoFrame->mDisplayWidth != videoFrame->mWidth || 296 videoFrame->mDisplayHeight != videoFrame->mHeight) { 297 uint32_t displayWidth = videoFrame->mDisplayWidth; 298 uint32_t displayHeight = videoFrame->mDisplayHeight; 299 if (swapWidthAndHeight) { 300 displayWidth = videoFrame->mDisplayHeight; 301 displayHeight = videoFrame->mDisplayWidth; 302 } 303 ALOGV("Bitmap dimension is scaled from %dx%d to %dx%d", 304 width, height, displayWidth, displayHeight); 305 jobject scaledBitmap = env->CallStaticObjectMethod(fields.bitmapClazz, 306 fields.createScaledBitmapMethod, 307 jBitmap, 308 displayWidth, 309 displayHeight, 310 true); 311 return scaledBitmap; 312 } 313 314 return jBitmap; 315} 316 317static jbyteArray android_media_MediaMetadataRetriever_getEmbeddedPicture( 318 JNIEnv *env, jobject thiz, jint pictureType) 319{ 320 ALOGV("getEmbeddedPicture: %d", pictureType); 321 MediaMetadataRetriever* retriever = getRetriever(env, thiz); 322 if (retriever == 0) { 323 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); 324 return NULL; 325 } 326 MediaAlbumArt* mediaAlbumArt = NULL; 327 328 // FIXME: 329 // Use pictureType to retrieve the intended embedded picture and also change 330 // the method name to getEmbeddedPicture(). 331 sp<IMemory> albumArtMemory = retriever->extractAlbumArt(); 332 if (albumArtMemory != 0) { // cast the shared structure to a MediaAlbumArt object 333 mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->pointer()); 334 } 335 if (mediaAlbumArt == NULL) { 336 ALOGE("getEmbeddedPicture: Call to getEmbeddedPicture failed."); 337 return NULL; 338 } 339 340 jbyteArray array = env->NewByteArray(mediaAlbumArt->size()); 341 if (!array) { // OutOfMemoryError exception has already been thrown. 342 ALOGE("getEmbeddedPicture: OutOfMemoryError is thrown."); 343 } else { 344 const jbyte* data = 345 reinterpret_cast<const jbyte*>(mediaAlbumArt->data()); 346 env->SetByteArrayRegion(array, 0, mediaAlbumArt->size(), data); 347 } 348 349 // No need to delete mediaAlbumArt here 350 return array; 351} 352 353static jobject android_media_MediaMetadataRetriever_extractMetadata(JNIEnv *env, jobject thiz, jint keyCode) 354{ 355 ALOGV("extractMetadata"); 356 MediaMetadataRetriever* retriever = getRetriever(env, thiz); 357 if (retriever == 0) { 358 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); 359 return NULL; 360 } 361 const char* value = retriever->extractMetadata(keyCode); 362 if (!value) { 363 ALOGV("extractMetadata: Metadata is not found"); 364 return NULL; 365 } 366 ALOGV("extractMetadata: value (%s) for keyCode(%d)", value, keyCode); 367 return env->NewStringUTF(value); 368} 369 370static void android_media_MediaMetadataRetriever_release(JNIEnv *env, jobject thiz) 371{ 372 ALOGV("release"); 373 Mutex::Autolock lock(sLock); 374 MediaMetadataRetriever* retriever = getRetriever(env, thiz); 375 delete retriever; 376 setRetriever(env, thiz, (MediaMetadataRetriever*) 0); 377} 378 379static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz) 380{ 381 ALOGV("native_finalize"); 382 // No lock is needed, since android_media_MediaMetadataRetriever_release() is protected 383 android_media_MediaMetadataRetriever_release(env, thiz); 384} 385 386// This function gets a field ID, which in turn causes class initialization. 387// It is called from a static block in MediaMetadataRetriever, which won't run until the 388// first time an instance of this class is used. 389static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env) 390{ 391 jclass clazz = env->FindClass(kClassPathName); 392 if (clazz == NULL) { 393 return; 394 } 395 396 fields.context = env->GetFieldID(clazz, "mNativeContext", "J"); 397 if (fields.context == NULL) { 398 return; 399 } 400 401 jclass bitmapClazz = env->FindClass("android/graphics/Bitmap"); 402 if (bitmapClazz == NULL) { 403 return; 404 } 405 fields.bitmapClazz = (jclass) env->NewGlobalRef(bitmapClazz); 406 if (fields.bitmapClazz == NULL) { 407 return; 408 } 409 fields.createBitmapMethod = 410 env->GetStaticMethodID(fields.bitmapClazz, "createBitmap", 411 "(IILandroid/graphics/Bitmap$Config;)" 412 "Landroid/graphics/Bitmap;"); 413 if (fields.createBitmapMethod == NULL) { 414 return; 415 } 416 fields.createScaledBitmapMethod = 417 env->GetStaticMethodID(fields.bitmapClazz, "createScaledBitmap", 418 "(Landroid/graphics/Bitmap;IIZ)" 419 "Landroid/graphics/Bitmap;"); 420 if (fields.createScaledBitmapMethod == NULL) { 421 return; 422 } 423 fields.nativeBitmap = env->GetFieldID(fields.bitmapClazz, "mNativeBitmap", "J"); 424 if (fields.nativeBitmap == NULL) { 425 return; 426 } 427 428 jclass configClazz = env->FindClass("android/graphics/Bitmap$Config"); 429 if (configClazz == NULL) { 430 return; 431 } 432 fields.configClazz = (jclass) env->NewGlobalRef(configClazz); 433 if (fields.configClazz == NULL) { 434 return; 435 } 436 fields.createConfigMethod = 437 env->GetStaticMethodID(fields.configClazz, "nativeToConfig", 438 "(I)Landroid/graphics/Bitmap$Config;"); 439 if (fields.createConfigMethod == NULL) { 440 return; 441 } 442} 443 444static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz) 445{ 446 ALOGV("native_setup"); 447 MediaMetadataRetriever* retriever = new MediaMetadataRetriever(); 448 if (retriever == 0) { 449 jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); 450 return; 451 } 452 setRetriever(env, thiz, retriever); 453} 454 455// JNI mapping between Java methods and native methods 456static JNINativeMethod nativeMethods[] = { 457 { 458 "_setDataSource", 459 "(Landroid/os/IBinder;Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V", 460 (void *)android_media_MediaMetadataRetriever_setDataSourceAndHeaders 461 }, 462 463 {"setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD}, 464 {"_getFrameAtTime", "(JI)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime}, 465 {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata}, 466 {"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture}, 467 {"release", "()V", (void *)android_media_MediaMetadataRetriever_release}, 468 {"native_finalize", "()V", (void *)android_media_MediaMetadataRetriever_native_finalize}, 469 {"native_setup", "()V", (void *)android_media_MediaMetadataRetriever_native_setup}, 470 {"native_init", "()V", (void *)android_media_MediaMetadataRetriever_native_init}, 471}; 472 473// This function only registers the native methods, and is called from 474// JNI_OnLoad in android_media_MediaPlayer.cpp 475int register_android_media_MediaMetadataRetriever(JNIEnv *env) 476{ 477 return AndroidRuntime::registerNativeMethods 478 (env, kClassPathName, nativeMethods, NELEM(nativeMethods)); 479} 480