blob: 4ccdd9a249f1ca9f2e08c394850f033480f87210 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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>
James Dong0e4b5352010-12-19 13:05:33 -080025
26// Please do not enable "USE_PRIVATE_NATIVE_BITMAP_CONSTUCTOR"
27// This mode will be removed and it is kept here for
28// convenient comparsion just in case. Will be removed soon.
29// Tests show that this mode is also ~30ms slower,
30// when rotation is involved.
31#define USE_PRIVATE_NATIVE_BITMAP_CONSTUCTOR 0
32
33#if (!USE_PRIVATE_NATIVE_BITMAP_CONSTUCTOR)
34#include <core/SkCanvas.h>
35#include <core/SkDevice.h>
36#include <core/SkScalar.h>
37#endif
38
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039#include <media/mediametadataretriever.h>
40#include <private/media/VideoFrame.h>
41
42#include "jni.h"
43#include "JNIHelp.h"
44#include "android_runtime/AndroidRuntime.h"
45
46
47using namespace android;
48
49struct fields_t {
50 jfieldID context;
51 jclass bitmapClazz;
James Dong0e4b5352010-12-19 13:05:33 -080052#if USE_PRIVATE_NATIVE_BITMAP_CONSTUCTOR
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053 jmethodID bitmapConstructor;
James Dong0e4b5352010-12-19 13:05:33 -080054 jmethodID createBitmapRotationMethod;
55#else
56 jfieldID nativeBitmap;
James Dong53ebc722010-11-08 16:04:27 -080057 jmethodID createBitmapMethod;
James Dong0e4b5352010-12-19 13:05:33 -080058 jclass configClazz;
59 jmethodID createConfigMethod;
60#endif
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061};
62
63static fields_t fields;
64static Mutex sLock;
Marco Nelissen4935d052009-08-03 11:12:58 -070065static const char* const kClassPathName = "android/media/MediaMetadataRetriever";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080066
67static void process_media_retriever_call(JNIEnv *env, status_t opStatus, const char* exception, const char *message)
68{
69 if (opStatus == (status_t) INVALID_OPERATION) {
70 jniThrowException(env, "java/lang/IllegalStateException", NULL);
71 } else if (opStatus != (status_t) OK) {
72 if (strlen(message) > 230) {
73 // If the message is too long, don't bother displaying the status code.
74 jniThrowException( env, exception, message);
75 } else {
76 char msg[256];
77 // Append the status code to the message.
78 sprintf(msg, "%s: status = 0x%X", message, opStatus);
79 jniThrowException( env, exception, msg);
80 }
81 }
82}
83
84static MediaMetadataRetriever* getRetriever(JNIEnv* env, jobject thiz)
85{
86 // No lock is needed, since it is called internally by other methods that are protected
87 MediaMetadataRetriever* retriever = (MediaMetadataRetriever*) env->GetIntField(thiz, fields.context);
88 return retriever;
89}
90
91static void setRetriever(JNIEnv* env, jobject thiz, int retriever)
92{
93 // No lock is needed, since it is called internally by other methods that are protected
94 MediaMetadataRetriever *old = (MediaMetadataRetriever*) env->GetIntField(thiz, fields.context);
95 env->SetIntField(thiz, fields.context, retriever);
96}
97
98static void android_media_MediaMetadataRetriever_setDataSource(JNIEnv *env, jobject thiz, jstring path)
99{
100 LOGV("setDataSource");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101 MediaMetadataRetriever* retriever = getRetriever(env, thiz);
102 if (retriever == 0) {
103 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
104 return;
105 }
106 if (!path) {
107 jniThrowException(env, "java/lang/IllegalArgumentException", "Null pointer");
108 return;
109 }
110
111 const char *pathStr = env->GetStringUTFChars(path, NULL);
112 if (!pathStr) { // OutOfMemoryError exception already thrown
113 return;
114 }
115
116 // Don't let somebody trick us in to reading some random block of memory
117 if (strncmp("mem://", pathStr, 6) == 0) {
118 jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid pathname");
119 return;
120 }
121
122 process_media_retriever_call(env, retriever->setDataSource(pathStr), "java/lang/RuntimeException", "setDataSource failed");
123 env->ReleaseStringUTFChars(path, pathStr);
124}
125
126static void android_media_MediaMetadataRetriever_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
127{
128 LOGV("setDataSource");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800129 MediaMetadataRetriever* retriever = getRetriever(env, thiz);
130 if (retriever == 0) {
131 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
132 return;
133 }
134 if (!fileDescriptor) {
135 jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
136 return;
137 }
138 int fd = getParcelFileDescriptorFD(env, fileDescriptor);
139 if (offset < 0 || length < 0 || fd < 0) {
140 if (offset < 0) {
141 LOGE("negative offset (%lld)", offset);
142 }
143 if (length < 0) {
144 LOGE("negative length (%lld)", length);
145 }
146 if (fd < 0) {
147 LOGE("invalid file descriptor");
148 }
149 jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
150 return;
151 }
152 process_media_retriever_call(env, retriever->setDataSource(fd, offset, length), "java/lang/RuntimeException", "setDataSource failed");
153}
154
155static void android_media_MediaMetadataRetriever_setMode(JNIEnv *env, jobject thiz, jint mode)
156{
157 LOGV("setMode");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800158 MediaMetadataRetriever* retriever = getRetriever(env, thiz);
159 if (retriever == 0) {
160 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
161 return;
162 }
163 process_media_retriever_call(env, retriever->setMode(mode), "java/lang/RuntimeException", "setMode failed");
164}
165
James Dongfaf09ba2010-12-02 17:42:08 -0800166static jobject android_media_MediaMetadataRetriever_getFrameAtTime(JNIEnv *env, jobject thiz, jlong timeUs, jint option)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800167{
James Dongfaf09ba2010-12-02 17:42:08 -0800168 LOGV("getFrameAtTime: %lld us option: %d", timeUs, option);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800169 MediaMetadataRetriever* retriever = getRetriever(env, thiz);
170 if (retriever == 0) {
171 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
172 return NULL;
173 }
174
175 // Call native method to retrieve a video frame
176 VideoFrame *videoFrame = NULL;
James Dongfaf09ba2010-12-02 17:42:08 -0800177 sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178 if (frameMemory != 0) { // cast the shared structure to a VideoFrame object
179 videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
180 }
181 if (videoFrame == NULL) {
James Dongfaf09ba2010-12-02 17:42:08 -0800182 LOGE("getFrameAtTime: videoFrame is a NULL pointer");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800183 return NULL;
184 }
185
James Dong0e4b5352010-12-19 13:05:33 -0800186 LOGV("Dimension = %dx%d and bytes = %d",
187 videoFrame->mDisplayWidth,
188 videoFrame->mDisplayHeight,
189 videoFrame->mSize);
190
191#if USE_PRIVATE_NATIVE_BITMAP_CONSTUCTOR
James Dong53ebc722010-11-08 16:04:27 -0800192 jobject matrix = NULL;
193 if (videoFrame->mRotationAngle != 0) {
194 LOGD("Create a rotation matrix: %d degrees", videoFrame->mRotationAngle);
195 jclass matrixClazz = env->FindClass("android/graphics/Matrix");
196 if (matrixClazz == NULL) {
197 jniThrowException(env, "java/lang/RuntimeException",
198 "Can't find android/graphics/Matrix");
199 return NULL;
200 }
201 jmethodID matrixConstructor =
202 env->GetMethodID(matrixClazz, "<init>", "()V");
203 if (matrixConstructor == NULL) {
204 jniThrowException(env, "java/lang/RuntimeException",
205 "Can't find Matrix constructor");
206 return NULL;
207 }
208 matrix =
209 env->NewObject(matrixClazz, matrixConstructor);
210 if (matrix == NULL) {
211 LOGE("Could not create a Matrix object");
212 return NULL;
213 }
214
215 LOGV("Rotate the matrix: %d degrees", videoFrame->mRotationAngle);
216 jmethodID setRotateMethod =
217 env->GetMethodID(matrixClazz, "setRotate", "(F)V");
218 if (setRotateMethod == NULL) {
219 jniThrowException(env, "java/lang/RuntimeException",
220 "Can't find Matrix setRotate method");
221 return NULL;
222 }
223 env->CallVoidMethod(matrix, setRotateMethod, 1.0 * videoFrame->mRotationAngle);
224 env->DeleteLocalRef(matrixClazz);
225 }
226
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800227 // Create a SkBitmap to hold the pixels
228 SkBitmap *bitmap = new SkBitmap();
229 if (bitmap == NULL) {
James Dongfaf09ba2010-12-02 17:42:08 -0800230 LOGE("getFrameAtTime: cannot instantiate a SkBitmap object.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800231 return NULL;
232 }
233 bitmap->setConfig(SkBitmap::kRGB_565_Config, videoFrame->mDisplayWidth, videoFrame->mDisplayHeight);
234 if (!bitmap->allocPixels()) {
235 delete bitmap;
236 LOGE("failed to allocate pixel buffer");
237 return NULL;
238 }
239 memcpy((uint8_t*)bitmap->getPixels(), (uint8_t*)videoFrame + sizeof(VideoFrame), videoFrame->mSize);
240
241 // Since internally SkBitmap uses reference count to manage the reference to
242 // its pixels, it is important that the pixels (along with SkBitmap) be
243 // available after creating the Bitmap is returned to Java app.
James Dong53ebc722010-11-08 16:04:27 -0800244 jobject jSrcBitmap = env->NewObject(fields.bitmapClazz,
James Dong88b77412010-12-14 22:09:24 -0800245 fields.bitmapConstructor, (int) bitmap, NULL, true, NULL, -1);
James Dong53ebc722010-11-08 16:04:27 -0800246
James Dong0e4b5352010-12-19 13:05:33 -0800247 jobject jBitmap = env->CallStaticObjectMethod(
248 fields.bitmapClazz,
249 fields.createBitmapRotationMethod,
James Dong53ebc722010-11-08 16:04:27 -0800250 jSrcBitmap, // source Bitmap
251 0, // x
252 0, // y
253 videoFrame->mDisplayWidth, // width
254 videoFrame->mDisplayHeight, // height
255 matrix, // transform matrix
256 false); // filter
James Dong0e4b5352010-12-19 13:05:33 -0800257
258#else
259
260 jobject config = env->CallStaticObjectMethod(
261 fields.configClazz,
262 fields.createConfigMethod,
263 SkBitmap::kRGB_565_Config);
264
265 jobject jBitmap = env->CallStaticObjectMethod(
266 fields.bitmapClazz,
267 fields.createBitmapMethod,
268 videoFrame->mDisplayWidth,
269 videoFrame->mDisplayHeight,
270 config);
271 SkBitmap *bitmap =
272 (SkBitmap *) env->GetIntField(jBitmap, fields.nativeBitmap);
273
274 bitmap->lockPixels();
275
276 memcpy((uint8_t*)bitmap->getPixels(),
277 (uint8_t*)videoFrame + sizeof(VideoFrame), videoFrame->mSize);
278
279 bitmap->unlockPixels();
280
281 if (videoFrame->mRotationAngle != 0) {
282 SkDevice device(*bitmap);
283 SkCanvas canvas(&device);
284 canvas.rotate((SkScalar) (videoFrame->mRotationAngle * 1.0));
285 canvas.drawBitmap(*bitmap, 0, 0);
286 }
287#endif
288 LOGV("Return a new bitmap constructed with the rotation matrix");
289 return jBitmap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800290}
291
292static jbyteArray android_media_MediaMetadataRetriever_extractAlbumArt(JNIEnv *env, jobject thiz)
293{
294 LOGV("extractAlbumArt");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800295 MediaMetadataRetriever* retriever = getRetriever(env, thiz);
296 if (retriever == 0) {
297 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
298 return NULL;
299 }
300 MediaAlbumArt* mediaAlbumArt = NULL;
301 sp<IMemory> albumArtMemory = retriever->extractAlbumArt();
302 if (albumArtMemory != 0) { // cast the shared structure to a MediaAlbumArt object
303 mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->pointer());
304 }
305 if (mediaAlbumArt == NULL) {
306 LOGE("extractAlbumArt: Call to extractAlbumArt failed.");
307 return NULL;
308 }
309
310 unsigned int len = mediaAlbumArt->mSize;
311 char* data = (char*) mediaAlbumArt + sizeof(MediaAlbumArt);
312 jbyteArray array = env->NewByteArray(len);
313 if (!array) { // OutOfMemoryError exception has already been thrown.
314 LOGE("extractAlbumArt: OutOfMemoryError is thrown.");
315 } else {
316 jbyte* bytes = env->GetByteArrayElements(array, NULL);
317 if (bytes != NULL) {
318 memcpy(bytes, data, len);
319 env->ReleaseByteArrayElements(array, bytes, 0);
320 }
321 }
322
323 // No need to delete mediaAlbumArt here
324 return array;
325}
326
327static jobject android_media_MediaMetadataRetriever_extractMetadata(JNIEnv *env, jobject thiz, jint keyCode)
328{
329 LOGV("extractMetadata");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800330 MediaMetadataRetriever* retriever = getRetriever(env, thiz);
331 if (retriever == 0) {
332 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
333 return NULL;
334 }
335 const char* value = retriever->extractMetadata(keyCode);
336 if (!value) {
337 LOGV("extractMetadata: Metadata is not found");
338 return NULL;
339 }
340 LOGV("extractMetadata: value (%s) for keyCode(%d)", value, keyCode);
341 return env->NewStringUTF(value);
342}
343
344static void android_media_MediaMetadataRetriever_release(JNIEnv *env, jobject thiz)
345{
346 LOGV("release");
347 Mutex::Autolock lock(sLock);
348 MediaMetadataRetriever* retriever = getRetriever(env, thiz);
349 delete retriever;
350 setRetriever(env, thiz, 0);
351}
352
353static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz)
354{
355 LOGV("native_finalize");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800356 // No lock is needed, since android_media_MediaMetadataRetriever_release() is protected
357 android_media_MediaMetadataRetriever_release(env, thiz);
358}
359
Marco Nelissen4935d052009-08-03 11:12:58 -0700360// This function gets a field ID, which in turn causes class initialization.
361// It is called from a static block in MediaMetadataRetriever, which won't run until the
362// first time an instance of this class is used.
363static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env)
364{
365 jclass clazz = env->FindClass(kClassPathName);
366 if (clazz == NULL) {
367 jniThrowException(env, "java/lang/RuntimeException", "Can't find android/media/MediaMetadataRetriever");
368 return;
369 }
370
371 fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
372 if (fields.context == NULL) {
373 jniThrowException(env, "java/lang/RuntimeException", "Can't find MediaMetadataRetriever.mNativeContext");
374 return;
375 }
376
377 fields.bitmapClazz = env->FindClass("android/graphics/Bitmap");
378 if (fields.bitmapClazz == NULL) {
379 jniThrowException(env, "java/lang/RuntimeException", "Can't find android/graphics/Bitmap");
380 return;
381 }
James Dong0e4b5352010-12-19 13:05:33 -0800382#if USE_PRIVATE_NATIVE_BITMAP_CONSTUCTOR
James Dong88b77412010-12-14 22:09:24 -0800383 fields.bitmapConstructor = env->GetMethodID(fields.bitmapClazz, "<init>", "(I[BZ[BI)V");
Marco Nelissen4935d052009-08-03 11:12:58 -0700384 if (fields.bitmapConstructor == NULL) {
385 jniThrowException(env, "java/lang/RuntimeException", "Can't find Bitmap constructor");
386 return;
387 }
James Dong0e4b5352010-12-19 13:05:33 -0800388 fields.createBitmapRotationMethod =
James Dong53ebc722010-11-08 16:04:27 -0800389 env->GetStaticMethodID(fields.bitmapClazz, "createBitmap",
390 "(Landroid/graphics/Bitmap;IIIILandroid/graphics/Matrix;Z)"
391 "Landroid/graphics/Bitmap;");
James Dong0e4b5352010-12-19 13:05:33 -0800392 if (fields.createBitmapRotationMethod == NULL) {
James Dong53ebc722010-11-08 16:04:27 -0800393 jniThrowException(env, "java/lang/RuntimeException",
394 "Can't find Bitmap.createBitmap method");
395 return;
396 }
James Dong0e4b5352010-12-19 13:05:33 -0800397#else
398 fields.createBitmapMethod =
399 env->GetStaticMethodID(fields.bitmapClazz, "createBitmap",
400 "(IILandroid/graphics/Bitmap$Config;)"
401 "Landroid/graphics/Bitmap;");
402 if (fields.createBitmapMethod == NULL) {
403 jniThrowException(env, "java/lang/RuntimeException",
404 "Can't find Bitmap.createBitmap(int, int, Config) method");
405 return;
406 }
407 fields.nativeBitmap = env->GetFieldID(fields.bitmapClazz, "mNativeBitmap", "I");
408 if (fields.nativeBitmap == NULL) {
409 jniThrowException(env, "java/lang/RuntimeException",
410 "Can't find Bitmap.mNativeBitmap field");
411 }
412
413 fields.configClazz = env->FindClass("android/graphics/Bitmap$Config");
414 if (fields.configClazz == NULL) {
415 jniThrowException(env, "java/lang/RuntimeException",
416 "Can't find Bitmap$Config class");
417 return;
418 }
419 fields.createConfigMethod =
420 env->GetStaticMethodID(fields.configClazz, "nativeToConfig",
421 "(I)Landroid/graphics/Bitmap$Config;");
422 if (fields.createConfigMethod == NULL) {
423 jniThrowException(env, "java/lang/RuntimeException",
424 "Can't find Bitmap$Config.nativeToConfig(int) method");
425 return;
426 }
427#endif
Marco Nelissen4935d052009-08-03 11:12:58 -0700428}
429
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800430static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz)
431{
432 LOGV("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
442static JNINativeMethod nativeMethods[] = {
443 {"setDataSource", "(Ljava/lang/String;)V", (void *)android_media_MediaMetadataRetriever_setDataSource},
444 {"setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD},
445 {"setMode", "(I)V", (void *)android_media_MediaMetadataRetriever_setMode},
James Dongfaf09ba2010-12-02 17:42:08 -0800446 {"_getFrameAtTime", "(JI)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800447 {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata},
448 {"extractAlbumArt", "()[B", (void *)android_media_MediaMetadataRetriever_extractAlbumArt},
449 {"release", "()V", (void *)android_media_MediaMetadataRetriever_release},
450 {"native_finalize", "()V", (void *)android_media_MediaMetadataRetriever_native_finalize},
451 {"native_setup", "()V", (void *)android_media_MediaMetadataRetriever_native_setup},
Marco Nelissen4935d052009-08-03 11:12:58 -0700452 {"native_init", "()V", (void *)android_media_MediaMetadataRetriever_native_init},
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800453};
454
Marco Nelissen4935d052009-08-03 11:12:58 -0700455// This function only registers the native methods, and is called from
456// JNI_OnLoad in android_media_MediaPlayer.cpp
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800457int register_android_media_MediaMetadataRetriever(JNIEnv *env)
458{
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800459 return AndroidRuntime::registerNativeMethods
Marco Nelissen4935d052009-08-03 11:12:58 -0700460 (env, kClassPathName, nativeMethods, NELEM(nativeMethods));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800461}