OSDN Git Service

am 79ad6310: Merge "docs: changed references to Google+ sign-in to Google sign-in...
[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 <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_MediaDataSource.h"
34 #include "android_media_Utils.h"
35 #include "android_util_Binder.h"
36
37 #include "android/graphics/GraphicsJNI.h"
38
39 using namespace android;
40
41 struct fields_t {
42     jfieldID context;
43     jclass bitmapClazz;  // Must be a global ref
44     jmethodID createBitmapMethod;
45     jmethodID createScaledBitmapMethod;
46     jclass configClazz;  // Must be a global ref
47     jmethodID createConfigMethod;
48 };
49
50 static fields_t fields;
51 static Mutex sLock;
52 static const char* const kClassPathName = "android/media/MediaMetadataRetriever";
53
54 static void process_media_retriever_call(JNIEnv *env, status_t opStatus, const char* exception, const char *message)
55 {
56     if (opStatus == (status_t) INVALID_OPERATION) {
57         jniThrowException(env, "java/lang/IllegalStateException", NULL);
58     } else if (opStatus != (status_t) OK) {
59         if (strlen(message) > 230) {
60             // If the message is too long, don't bother displaying the status code.
61             jniThrowException( env, exception, message);
62         } else {
63             char msg[256];
64             // Append the status code to the message.
65             sprintf(msg, "%s: status = 0x%X", message, opStatus);
66             jniThrowException( env, exception, msg);
67         }
68     }
69 }
70
71 static MediaMetadataRetriever* getRetriever(JNIEnv* env, jobject thiz)
72 {
73     // No lock is needed, since it is called internally by other methods that are protected
74     MediaMetadataRetriever* retriever = (MediaMetadataRetriever*) env->GetLongField(thiz, fields.context);
75     return retriever;
76 }
77
78 static void setRetriever(JNIEnv* env, jobject thiz, MediaMetadataRetriever* retriever)
79 {
80     // No lock is needed, since it is called internally by other methods that are protected
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 static void android_media_MediaMetadataRetriever_setDataSourceCallback(JNIEnv *env, jobject thiz, jobject dataSource)
176 {
177     ALOGV("setDataSourceCallback");
178     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
179     if (retriever == 0) {
180         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
181         return;
182     }
183     if (dataSource == NULL) {
184         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
185         return;
186     }
187
188     sp<IDataSource> callbackDataSource = new JMediaDataSource(env, dataSource);
189     process_media_retriever_call(env, retriever->setDataSource(callbackDataSource), "java/lang/RuntimeException", "setDataSourceCallback failed");
190 }
191
192 template<typename T>
193 static void rotate0(T* dst, const T* src, size_t width, size_t height)
194 {
195     memcpy(dst, src, width * height * sizeof(T));
196 }
197
198 template<typename T>
199 static void rotate90(T* dst, const T* src, size_t width, size_t height)
200 {
201     for (size_t i = 0; i < height; ++i) {
202         for (size_t j = 0; j < width; ++j) {
203             dst[j * height + height - 1 - i] = src[i * width + j];
204         }
205     }
206 }
207
208 template<typename T>
209 static void rotate180(T* dst, const T* src, size_t width, size_t height)
210 {
211     for (size_t i = 0; i < height; ++i) {
212         for (size_t j = 0; j < width; ++j) {
213             dst[(height - 1 - i) * width + width - 1 - j] = src[i * width + j];
214         }
215     }
216 }
217
218 template<typename T>
219 static void rotate270(T* dst, const T* src, size_t width, size_t height)
220 {
221     for (size_t i = 0; i < height; ++i) {
222         for (size_t j = 0; j < width; ++j) {
223             dst[(width - 1 - j) * height + i] = src[i * width + j];
224         }
225     }
226 }
227
228 template<typename T>
229 static void rotate(T *dst, const T *src, size_t width, size_t height, int angle)
230 {
231     switch (angle) {
232         case 0:
233             rotate0(dst, src, width, height);
234             break;
235         case 90:
236             rotate90(dst, src, width, height);
237             break;
238         case 180:
239             rotate180(dst, src, width, height);
240             break;
241         case 270:
242             rotate270(dst, src, width, height);
243             break;
244     }
245 }
246
247 static jobject android_media_MediaMetadataRetriever_getFrameAtTime(JNIEnv *env, jobject thiz, jlong timeUs, jint option)
248 {
249     ALOGV("getFrameAtTime: %lld us option: %d", (long long)timeUs, option);
250     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
251     if (retriever == 0) {
252         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
253         return NULL;
254     }
255
256     // Call native method to retrieve a video frame
257     VideoFrame *videoFrame = NULL;
258     sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option);
259     if (frameMemory != 0) {  // cast the shared structure to a VideoFrame object
260         videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
261     }
262     if (videoFrame == NULL) {
263         ALOGE("getFrameAtTime: videoFrame is a NULL pointer");
264         return NULL;
265     }
266
267     ALOGV("Dimension = %dx%d and bytes = %d",
268             videoFrame->mDisplayWidth,
269             videoFrame->mDisplayHeight,
270             videoFrame->mSize);
271
272     jobject config = env->CallStaticObjectMethod(
273                         fields.configClazz,
274                         fields.createConfigMethod,
275                         GraphicsJNI::colorTypeToLegacyBitmapConfig(kRGB_565_SkColorType));
276
277     uint32_t width, height;
278     bool swapWidthAndHeight = false;
279     if (videoFrame->mRotationAngle == 90 || videoFrame->mRotationAngle == 270) {
280         width = videoFrame->mHeight;
281         height = videoFrame->mWidth;
282         swapWidthAndHeight = true;
283     } else {
284         width = videoFrame->mWidth;
285         height = videoFrame->mHeight;
286     }
287
288     jobject jBitmap = env->CallStaticObjectMethod(
289                             fields.bitmapClazz,
290                             fields.createBitmapMethod,
291                             width,
292                             height,
293                             config);
294     if (jBitmap == NULL) {
295         if (env->ExceptionCheck()) {
296             env->ExceptionClear();
297         }
298         ALOGE("getFrameAtTime: create Bitmap failed!");
299         return NULL;
300     }
301
302     SkBitmap bitmap;
303     GraphicsJNI::getSkBitmap(env, jBitmap, &bitmap);
304
305     bitmap.lockPixels();
306     rotate((uint16_t*)bitmap.getPixels(),
307            (uint16_t*)((char*)videoFrame + sizeof(VideoFrame)),
308            videoFrame->mWidth,
309            videoFrame->mHeight,
310            videoFrame->mRotationAngle);
311     bitmap.unlockPixels();
312
313     if (videoFrame->mDisplayWidth  != videoFrame->mWidth ||
314         videoFrame->mDisplayHeight != videoFrame->mHeight) {
315         uint32_t displayWidth = videoFrame->mDisplayWidth;
316         uint32_t displayHeight = videoFrame->mDisplayHeight;
317         if (swapWidthAndHeight) {
318             displayWidth = videoFrame->mDisplayHeight;
319             displayHeight = videoFrame->mDisplayWidth;
320         }
321         ALOGV("Bitmap dimension is scaled from %dx%d to %dx%d",
322                 width, height, displayWidth, displayHeight);
323         jobject scaledBitmap = env->CallStaticObjectMethod(fields.bitmapClazz,
324                                     fields.createScaledBitmapMethod,
325                                     jBitmap,
326                                     displayWidth,
327                                     displayHeight,
328                                     true);
329         return scaledBitmap;
330     }
331
332     return jBitmap;
333 }
334
335 static jbyteArray android_media_MediaMetadataRetriever_getEmbeddedPicture(
336         JNIEnv *env, jobject thiz, jint pictureType)
337 {
338     ALOGV("getEmbeddedPicture: %d", pictureType);
339     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
340     if (retriever == 0) {
341         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
342         return NULL;
343     }
344     MediaAlbumArt* mediaAlbumArt = NULL;
345
346     // FIXME:
347     // Use pictureType to retrieve the intended embedded picture and also change
348     // the method name to getEmbeddedPicture().
349     sp<IMemory> albumArtMemory = retriever->extractAlbumArt();
350     if (albumArtMemory != 0) {  // cast the shared structure to a MediaAlbumArt object
351         mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->pointer());
352     }
353     if (mediaAlbumArt == NULL) {
354         ALOGE("getEmbeddedPicture: Call to getEmbeddedPicture failed.");
355         return NULL;
356     }
357
358     jbyteArray array = env->NewByteArray(mediaAlbumArt->size());
359     if (!array) {  // OutOfMemoryError exception has already been thrown.
360         ALOGE("getEmbeddedPicture: OutOfMemoryError is thrown.");
361     } else {
362         const jbyte* data =
363                 reinterpret_cast<const jbyte*>(mediaAlbumArt->data());
364         env->SetByteArrayRegion(array, 0, mediaAlbumArt->size(), data);
365     }
366
367     // No need to delete mediaAlbumArt here
368     return array;
369 }
370
371 static jobject android_media_MediaMetadataRetriever_extractMetadata(JNIEnv *env, jobject thiz, jint keyCode)
372 {
373     ALOGV("extractMetadata");
374     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
375     if (retriever == 0) {
376         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
377         return NULL;
378     }
379     const char* value = retriever->extractMetadata(keyCode);
380     if (!value) {
381         ALOGV("extractMetadata: Metadata is not found");
382         return NULL;
383     }
384     ALOGV("extractMetadata: value (%s) for keyCode(%d)", value, keyCode);
385     return env->NewStringUTF(value);
386 }
387
388 static void android_media_MediaMetadataRetriever_release(JNIEnv *env, jobject thiz)
389 {
390     ALOGV("release");
391     Mutex::Autolock lock(sLock);
392     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
393     delete retriever;
394     setRetriever(env, thiz, (MediaMetadataRetriever*) 0);
395 }
396
397 static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz)
398 {
399     ALOGV("native_finalize");
400     // No lock is needed, since android_media_MediaMetadataRetriever_release() is protected
401     android_media_MediaMetadataRetriever_release(env, thiz);
402 }
403
404 // This function gets a field ID, which in turn causes class initialization.
405 // It is called from a static block in MediaMetadataRetriever, which won't run until the
406 // first time an instance of this class is used.
407 static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env)
408 {
409     jclass clazz = env->FindClass(kClassPathName);
410     if (clazz == NULL) {
411         return;
412     }
413
414     fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
415     if (fields.context == NULL) {
416         return;
417     }
418
419     jclass bitmapClazz = env->FindClass("android/graphics/Bitmap");
420     if (bitmapClazz == NULL) {
421         return;
422     }
423     fields.bitmapClazz = (jclass) env->NewGlobalRef(bitmapClazz);
424     if (fields.bitmapClazz == NULL) {
425         return;
426     }
427     fields.createBitmapMethod =
428             env->GetStaticMethodID(fields.bitmapClazz, "createBitmap",
429                     "(IILandroid/graphics/Bitmap$Config;)"
430                     "Landroid/graphics/Bitmap;");
431     if (fields.createBitmapMethod == NULL) {
432         return;
433     }
434     fields.createScaledBitmapMethod =
435             env->GetStaticMethodID(fields.bitmapClazz, "createScaledBitmap",
436                     "(Landroid/graphics/Bitmap;IIZ)"
437                     "Landroid/graphics/Bitmap;");
438     if (fields.createScaledBitmapMethod == NULL) {
439         return;
440     }
441
442     jclass configClazz = env->FindClass("android/graphics/Bitmap$Config");
443     if (configClazz == NULL) {
444         return;
445     }
446     fields.configClazz = (jclass) env->NewGlobalRef(configClazz);
447     if (fields.configClazz == NULL) {
448         return;
449     }
450     fields.createConfigMethod =
451             env->GetStaticMethodID(fields.configClazz, "nativeToConfig",
452                     "(I)Landroid/graphics/Bitmap$Config;");
453     if (fields.createConfigMethod == NULL) {
454         return;
455     }
456 }
457
458 static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz)
459 {
460     ALOGV("native_setup");
461     MediaMetadataRetriever* retriever = new MediaMetadataRetriever();
462     if (retriever == 0) {
463         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
464         return;
465     }
466     setRetriever(env, thiz, retriever);
467 }
468
469 // JNI mapping between Java methods and native methods
470 static JNINativeMethod nativeMethods[] = {
471         {
472             "_setDataSource",
473             "(Landroid/os/IBinder;Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
474             (void *)android_media_MediaMetadataRetriever_setDataSourceAndHeaders
475         },
476
477         {"setDataSource",   "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD},
478         {"_setDataSource",   "(Landroid/media/MediaDataSource;)V", (void *)android_media_MediaMetadataRetriever_setDataSourceCallback},
479         {"_getFrameAtTime", "(JI)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
480         {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata},
481         {"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture},
482         {"release",         "()V", (void *)android_media_MediaMetadataRetriever_release},
483         {"native_finalize", "()V", (void *)android_media_MediaMetadataRetriever_native_finalize},
484         {"native_setup",    "()V", (void *)android_media_MediaMetadataRetriever_native_setup},
485         {"native_init",     "()V", (void *)android_media_MediaMetadataRetriever_native_init},
486 };
487
488 // This function only registers the native methods, and is called from
489 // JNI_OnLoad in android_media_MediaPlayer.cpp
490 int register_android_media_MediaMetadataRetriever(JNIEnv *env)
491 {
492     return AndroidRuntime::registerNativeMethods
493         (env, kClassPathName, nativeMethods, NELEM(nativeMethods));
494 }