• 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 
32 
33 using namespace android;
34 
35 struct fields_t {
36     jfieldID context;
37     jclass bitmapClazz;
38     jmethodID bitmapConstructor;
39     jmethodID createBitmapMethod;
40 };
41 
42 static fields_t fields;
43 static Mutex sLock;
44 static const char* const kClassPathName = "android/media/MediaMetadataRetriever";
45 
process_media_retriever_call(JNIEnv * env,status_t opStatus,const char * exception,const char * message)46 static void process_media_retriever_call(JNIEnv *env, status_t opStatus, const char* exception, const char *message)
47 {
48     if (opStatus == (status_t) INVALID_OPERATION) {
49         jniThrowException(env, "java/lang/IllegalStateException", NULL);
50     } else if (opStatus != (status_t) OK) {
51         if (strlen(message) > 230) {
52             // If the message is too long, don't bother displaying the status code.
53             jniThrowException( env, exception, message);
54         } else {
55             char msg[256];
56             // Append the status code to the message.
57             sprintf(msg, "%s: status = 0x%X", message, opStatus);
58             jniThrowException( env, exception, msg);
59         }
60     }
61 }
62 
getRetriever(JNIEnv * env,jobject thiz)63 static MediaMetadataRetriever* getRetriever(JNIEnv* env, jobject thiz)
64 {
65     // No lock is needed, since it is called internally by other methods that are protected
66     MediaMetadataRetriever* retriever = (MediaMetadataRetriever*) env->GetIntField(thiz, fields.context);
67     return retriever;
68 }
69 
setRetriever(JNIEnv * env,jobject thiz,int retriever)70 static void setRetriever(JNIEnv* env, jobject thiz, int retriever)
71 {
72     // No lock is needed, since it is called internally by other methods that are protected
73     MediaMetadataRetriever *old = (MediaMetadataRetriever*) env->GetIntField(thiz, fields.context);
74     env->SetIntField(thiz, fields.context, retriever);
75 }
76 
android_media_MediaMetadataRetriever_setDataSource(JNIEnv * env,jobject thiz,jstring path)77 static void android_media_MediaMetadataRetriever_setDataSource(JNIEnv *env, jobject thiz, jstring path)
78 {
79     LOGV("setDataSource");
80     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
81     if (retriever == 0) {
82         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
83         return;
84     }
85     if (!path) {
86         jniThrowException(env, "java/lang/IllegalArgumentException", "Null pointer");
87         return;
88     }
89 
90     const char *pathStr = env->GetStringUTFChars(path, NULL);
91     if (!pathStr) {  // OutOfMemoryError exception already thrown
92         return;
93     }
94 
95     // Don't let somebody trick us in to reading some random block of memory
96     if (strncmp("mem://", pathStr, 6) == 0) {
97         jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid pathname");
98         return;
99     }
100 
101     process_media_retriever_call(env, retriever->setDataSource(pathStr), "java/lang/RuntimeException", "setDataSource failed");
102     env->ReleaseStringUTFChars(path, pathStr);
103 }
104 
android_media_MediaMetadataRetriever_setDataSourceFD(JNIEnv * env,jobject thiz,jobject fileDescriptor,jlong offset,jlong length)105 static void android_media_MediaMetadataRetriever_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
106 {
107     LOGV("setDataSource");
108     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
109     if (retriever == 0) {
110         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
111         return;
112     }
113     if (!fileDescriptor) {
114         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
115         return;
116     }
117     int fd = getParcelFileDescriptorFD(env, fileDescriptor);
118     if (offset < 0 || length < 0 || fd < 0) {
119         if (offset < 0) {
120             LOGE("negative offset (%lld)", offset);
121         }
122         if (length < 0) {
123             LOGE("negative length (%lld)", length);
124         }
125         if (fd < 0) {
126             LOGE("invalid file descriptor");
127         }
128         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
129         return;
130     }
131     process_media_retriever_call(env, retriever->setDataSource(fd, offset, length), "java/lang/RuntimeException", "setDataSource failed");
132 }
133 
android_media_MediaMetadataRetriever_getFrameAtTime(JNIEnv * env,jobject thiz,jlong timeUs,jint option)134 static jobject android_media_MediaMetadataRetriever_getFrameAtTime(JNIEnv *env, jobject thiz, jlong timeUs, jint option)
135 {
136     LOGV("getFrameAtTime: %lld us option: %d", timeUs, option);
137     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
138     if (retriever == 0) {
139         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
140         return NULL;
141     }
142 
143     // Call native method to retrieve a video frame
144     VideoFrame *videoFrame = NULL;
145     sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option);
146     if (frameMemory != 0) {  // cast the shared structure to a VideoFrame object
147         videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
148     }
149     if (videoFrame == NULL) {
150         LOGE("getFrameAtTime: videoFrame is a NULL pointer");
151         return NULL;
152     }
153 
154     jobject matrix = NULL;
155     if (videoFrame->mRotationAngle != 0) {
156         LOGD("Create a rotation matrix: %d degrees", videoFrame->mRotationAngle);
157         jclass matrixClazz = env->FindClass("android/graphics/Matrix");
158         if (matrixClazz == NULL) {
159             jniThrowException(env, "java/lang/RuntimeException",
160                 "Can't find android/graphics/Matrix");
161             return NULL;
162         }
163         jmethodID matrixConstructor =
164             env->GetMethodID(matrixClazz, "<init>", "()V");
165         if (matrixConstructor == NULL) {
166             jniThrowException(env, "java/lang/RuntimeException",
167                 "Can't find Matrix constructor");
168             return NULL;
169         }
170         matrix =
171             env->NewObject(matrixClazz, matrixConstructor);
172         if (matrix == NULL) {
173             LOGE("Could not create a Matrix object");
174             return NULL;
175         }
176 
177         LOGV("Rotate the matrix: %d degrees", videoFrame->mRotationAngle);
178         jmethodID setRotateMethod =
179                 env->GetMethodID(matrixClazz, "setRotate", "(F)V");
180         if (setRotateMethod == NULL) {
181             jniThrowException(env, "java/lang/RuntimeException",
182                 "Can't find Matrix setRotate method");
183             return NULL;
184         }
185         env->CallVoidMethod(matrix, setRotateMethod, 1.0 * videoFrame->mRotationAngle);
186         env->DeleteLocalRef(matrixClazz);
187     }
188 
189     // Create a SkBitmap to hold the pixels
190     SkBitmap *bitmap = new SkBitmap();
191     if (bitmap == NULL) {
192         LOGE("getFrameAtTime: cannot instantiate a SkBitmap object.");
193         return NULL;
194     }
195     bitmap->setConfig(SkBitmap::kRGB_565_Config, videoFrame->mDisplayWidth, videoFrame->mDisplayHeight);
196     if (!bitmap->allocPixels()) {
197         delete bitmap;
198         LOGE("failed to allocate pixel buffer");
199         return NULL;
200     }
201     memcpy((uint8_t*)bitmap->getPixels(), (uint8_t*)videoFrame + sizeof(VideoFrame), videoFrame->mSize);
202 
203     // Since internally SkBitmap uses reference count to manage the reference to
204     // its pixels, it is important that the pixels (along with SkBitmap) be
205     // available after creating the Bitmap is returned to Java app.
206     jobject jSrcBitmap = env->NewObject(fields.bitmapClazz,
207             fields.bitmapConstructor, (int) bitmap, true, NULL, -1);
208 
209     LOGV("Return a new bitmap constructed with the rotation matrix");
210     return env->CallStaticObjectMethod(
211                 fields.bitmapClazz, fields.createBitmapMethod,
212                 jSrcBitmap,                     // source Bitmap
213                 0,                              // x
214                 0,                              // y
215                 videoFrame->mDisplayWidth,      // width
216                 videoFrame->mDisplayHeight,     // height
217                 matrix,                         // transform matrix
218                 false);                         // filter
219 }
220 
android_media_MediaMetadataRetriever_getEmbeddedPicture(JNIEnv * env,jobject thiz,jint pictureType)221 static jbyteArray android_media_MediaMetadataRetriever_getEmbeddedPicture(
222         JNIEnv *env, jobject thiz, jint pictureType)
223 {
224     LOGV("getEmbeddedPicture: %d", pictureType);
225     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
226     if (retriever == 0) {
227         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
228         return NULL;
229     }
230     MediaAlbumArt* mediaAlbumArt = NULL;
231 
232     // FIXME:
233     // Use pictureType to retrieve the intended embedded picture and also change
234     // the method name to getEmbeddedPicture().
235     sp<IMemory> albumArtMemory = retriever->extractAlbumArt();
236     if (albumArtMemory != 0) {  // cast the shared structure to a MediaAlbumArt object
237         mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->pointer());
238     }
239     if (mediaAlbumArt == NULL) {
240         LOGE("getEmbeddedPicture: Call to getEmbeddedPicture failed.");
241         return NULL;
242     }
243 
244     unsigned int len = mediaAlbumArt->mSize;
245     char* data = (char*) mediaAlbumArt + sizeof(MediaAlbumArt);
246     jbyteArray array = env->NewByteArray(len);
247     if (!array) {  // OutOfMemoryError exception has already been thrown.
248         LOGE("getEmbeddedPicture: OutOfMemoryError is thrown.");
249     } else {
250         jbyte* bytes = env->GetByteArrayElements(array, NULL);
251         if (bytes != NULL) {
252             memcpy(bytes, data, len);
253             env->ReleaseByteArrayElements(array, bytes, 0);
254         }
255     }
256 
257     // No need to delete mediaAlbumArt here
258     return array;
259 }
260 
android_media_MediaMetadataRetriever_extractMetadata(JNIEnv * env,jobject thiz,jint keyCode)261 static jobject android_media_MediaMetadataRetriever_extractMetadata(JNIEnv *env, jobject thiz, jint keyCode)
262 {
263     LOGV("extractMetadata");
264     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
265     if (retriever == 0) {
266         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
267         return NULL;
268     }
269     const char* value = retriever->extractMetadata(keyCode);
270     if (!value) {
271         LOGV("extractMetadata: Metadata is not found");
272         return NULL;
273     }
274     LOGV("extractMetadata: value (%s) for keyCode(%d)", value, keyCode);
275     return env->NewStringUTF(value);
276 }
277 
android_media_MediaMetadataRetriever_release(JNIEnv * env,jobject thiz)278 static void android_media_MediaMetadataRetriever_release(JNIEnv *env, jobject thiz)
279 {
280     LOGV("release");
281     Mutex::Autolock lock(sLock);
282     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
283     delete retriever;
284     setRetriever(env, thiz, 0);
285 }
286 
android_media_MediaMetadataRetriever_native_finalize(JNIEnv * env,jobject thiz)287 static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz)
288 {
289     LOGV("native_finalize");
290 
291     // No lock is needed, since android_media_MediaMetadataRetriever_release() is protected
292     android_media_MediaMetadataRetriever_release(env, thiz);
293 }
294 
295 // This function gets a field ID, which in turn causes class initialization.
296 // It is called from a static block in MediaMetadataRetriever, which won't run until the
297 // first time an instance of this class is used.
android_media_MediaMetadataRetriever_native_init(JNIEnv * env)298 static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env)
299 {
300     jclass clazz = env->FindClass(kClassPathName);
301     if (clazz == NULL) {
302         jniThrowException(env, "java/lang/RuntimeException", "Can't find android/media/MediaMetadataRetriever");
303         return;
304     }
305 
306     fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
307     if (fields.context == NULL) {
308         jniThrowException(env, "java/lang/RuntimeException", "Can't find MediaMetadataRetriever.mNativeContext");
309         return;
310     }
311 
312     fields.bitmapClazz = env->FindClass("android/graphics/Bitmap");
313     if (fields.bitmapClazz == NULL) {
314         jniThrowException(env, "java/lang/RuntimeException", "Can't find android/graphics/Bitmap");
315         return;
316     }
317 
318     fields.bitmapConstructor = env->GetMethodID(fields.bitmapClazz, "<init>", "(IZ[BI)V");
319     if (fields.bitmapConstructor == NULL) {
320         jniThrowException(env, "java/lang/RuntimeException", "Can't find Bitmap constructor");
321         return;
322     }
323     fields.createBitmapMethod =
324             env->GetStaticMethodID(fields.bitmapClazz, "createBitmap",
325                     "(Landroid/graphics/Bitmap;IIIILandroid/graphics/Matrix;Z)"
326                     "Landroid/graphics/Bitmap;");
327     if (fields.createBitmapMethod == NULL) {
328         jniThrowException(env, "java/lang/RuntimeException",
329                 "Can't find Bitmap.createBitmap method");
330         return;
331     }
332 }
333 
android_media_MediaMetadataRetriever_native_setup(JNIEnv * env,jobject thiz)334 static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz)
335 {
336     LOGV("native_setup");
337     MediaMetadataRetriever* retriever = new MediaMetadataRetriever();
338     if (retriever == 0) {
339         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
340         return;
341     }
342     setRetriever(env, thiz, (int)retriever);
343 }
344 
345 // JNI mapping between Java methods and native methods
346 static JNINativeMethod nativeMethods[] = {
347         {"setDataSource",   "(Ljava/lang/String;)V", (void *)android_media_MediaMetadataRetriever_setDataSource},
348         {"setDataSource",   "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD},
349         {"_getFrameAtTime", "(JI)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
350         {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata},
351         {"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture},
352         {"release",         "()V", (void *)android_media_MediaMetadataRetriever_release},
353         {"native_finalize", "()V", (void *)android_media_MediaMetadataRetriever_native_finalize},
354         {"native_setup",    "()V", (void *)android_media_MediaMetadataRetriever_native_setup},
355         {"native_init",     "()V", (void *)android_media_MediaMetadataRetriever_native_init},
356 };
357 
358 // This function only registers the native methods, and is called from
359 // JNI_OnLoad in android_media_MediaPlayer.cpp
register_android_media_MediaMetadataRetriever(JNIEnv * env)360 int register_android_media_MediaMetadataRetriever(JNIEnv *env)
361 {
362     return AndroidRuntime::registerNativeMethods
363         (env, kClassPathName, nativeMethods, NELEM(nativeMethods));
364 }
365