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