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