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