OSDN Git Service

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