blob: 3d7dbf9dc45234029b8c3c3a13b5d422827dcd20 [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>
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
33using namespace android;
34
35struct fields_t {
36 jfieldID context;
37 jclass bitmapClazz;
James Dong0e4b5352010-12-19 13:05:33 -080038 jfieldID nativeBitmap;
James Dong53ebc722010-11-08 16:04:27 -080039 jmethodID createBitmapMethod;
James Dong0e4b5352010-12-19 13:05:33 -080040 jclass configClazz;
41 jmethodID createConfigMethod;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042};
43
44static fields_t fields;
45static Mutex sLock;
Marco Nelissen4935d052009-08-03 11:12:58 -070046static const char* const kClassPathName = "android/media/MediaMetadataRetriever";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047
48static void process_media_retriever_call(JNIEnv *env, status_t opStatus, const char* exception, const char *message)
49{
50 if (opStatus == (status_t) INVALID_OPERATION) {
51 jniThrowException(env, "java/lang/IllegalStateException", NULL);
52 } else if (opStatus != (status_t) OK) {
53 if (strlen(message) > 230) {
54 // If the message is too long, don't bother displaying the status code.
55 jniThrowException( env, exception, message);
56 } else {
57 char msg[256];
58 // Append the status code to the message.
59 sprintf(msg, "%s: status = 0x%X", message, opStatus);
60 jniThrowException( env, exception, msg);
61 }
62 }
63}
64
65static MediaMetadataRetriever* getRetriever(JNIEnv* env, jobject thiz)
66{
67 // No lock is needed, since it is called internally by other methods that are protected
68 MediaMetadataRetriever* retriever = (MediaMetadataRetriever*) env->GetIntField(thiz, fields.context);
69 return retriever;
70}
71
72static void setRetriever(JNIEnv* env, jobject thiz, int retriever)
73{
74 // No lock is needed, since it is called internally by other methods that are protected
75 MediaMetadataRetriever *old = (MediaMetadataRetriever*) env->GetIntField(thiz, fields.context);
76 env->SetIntField(thiz, fields.context, retriever);
77}
78
79static void android_media_MediaMetadataRetriever_setDataSource(JNIEnv *env, jobject thiz, jstring path)
80{
81 LOGV("setDataSource");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080082 MediaMetadataRetriever* retriever = getRetriever(env, thiz);
83 if (retriever == 0) {
84 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
85 return;
86 }
87 if (!path) {
88 jniThrowException(env, "java/lang/IllegalArgumentException", "Null pointer");
89 return;
90 }
91
92 const char *pathStr = env->GetStringUTFChars(path, NULL);
93 if (!pathStr) { // OutOfMemoryError exception already thrown
94 return;
95 }
96
97 // Don't let somebody trick us in to reading some random block of memory
98 if (strncmp("mem://", pathStr, 6) == 0) {
99 jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid pathname");
100 return;
101 }
102
103 process_media_retriever_call(env, retriever->setDataSource(pathStr), "java/lang/RuntimeException", "setDataSource failed");
104 env->ReleaseStringUTFChars(path, pathStr);
105}
106
107static void android_media_MediaMetadataRetriever_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
108{
109 LOGV("setDataSource");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110 MediaMetadataRetriever* retriever = getRetriever(env, thiz);
111 if (retriever == 0) {
112 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
113 return;
114 }
115 if (!fileDescriptor) {
116 jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
117 return;
118 }
119 int fd = getParcelFileDescriptorFD(env, fileDescriptor);
120 if (offset < 0 || length < 0 || fd < 0) {
121 if (offset < 0) {
122 LOGE("negative offset (%lld)", offset);
123 }
124 if (length < 0) {
125 LOGE("negative length (%lld)", length);
126 }
127 if (fd < 0) {
128 LOGE("invalid file descriptor");
129 }
130 jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
131 return;
132 }
133 process_media_retriever_call(env, retriever->setDataSource(fd, offset, length), "java/lang/RuntimeException", "setDataSource failed");
134}
135
Carl Shapiroae12a502011-01-20 23:13:09 -0800136template<typename T>
137static void rotate0(T* dst, const T* src, size_t width, size_t height)
138{
139 memcpy(dst, src, width * height * sizeof(T));
140}
141
142template<typename T>
143static void rotate90(T* dst, const T* src, size_t width, size_t height)
144{
145 for (size_t i = 0; i < height; ++i) {
146 for (size_t j = 0; j < width; ++j) {
147 dst[j * height + height - 1 - i] = src[i * width + j];
148 }
149 }
150}
151
152template<typename T>
153static void rotate180(T* dst, const T* src, size_t width, size_t height)
154{
155 for (size_t i = 0; i < height; ++i) {
156 for (size_t j = 0; j < width; ++j) {
157 dst[(height - 1 - i) * width + width - 1 - j] = src[i * width + j];
158 }
159 }
160}
161
162template<typename T>
163static void rotate270(T* dst, const T* src, size_t width, size_t height)
164{
165 for (size_t i = 0; i < height; ++i) {
166 for (size_t j = 0; j < width; ++j) {
167 dst[(width - 1 - j) * height + i] = src[i * width + j];
168 }
169 }
170}
171
172template<typename T>
173static void rotate(T *dst, const T *src, size_t width, size_t height, int angle)
174{
175 switch (angle) {
176 case 0:
177 rotate0(dst, src, width, height);
178 break;
179 case 90:
180 rotate90(dst, src, width, height);
181 break;
182 case 180:
183 rotate180(dst, src, width, height);
184 break;
185 case 270:
186 rotate270(dst, src, width, height);
187 break;
188 }
189}
190
James Dongfaf09ba2010-12-02 17:42:08 -0800191static jobject android_media_MediaMetadataRetriever_getFrameAtTime(JNIEnv *env, jobject thiz, jlong timeUs, jint option)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800192{
James Dongfaf09ba2010-12-02 17:42:08 -0800193 LOGV("getFrameAtTime: %lld us option: %d", timeUs, option);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800194 MediaMetadataRetriever* retriever = getRetriever(env, thiz);
195 if (retriever == 0) {
196 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
197 return NULL;
198 }
199
200 // Call native method to retrieve a video frame
201 VideoFrame *videoFrame = NULL;
James Dongfaf09ba2010-12-02 17:42:08 -0800202 sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800203 if (frameMemory != 0) { // cast the shared structure to a VideoFrame object
204 videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
205 }
206 if (videoFrame == NULL) {
James Dongfaf09ba2010-12-02 17:42:08 -0800207 LOGE("getFrameAtTime: videoFrame is a NULL pointer");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800208 return NULL;
209 }
210
James Dong0e4b5352010-12-19 13:05:33 -0800211 LOGV("Dimension = %dx%d and bytes = %d",
212 videoFrame->mDisplayWidth,
213 videoFrame->mDisplayHeight,
214 videoFrame->mSize);
215
James Dong0e4b5352010-12-19 13:05:33 -0800216 jobject config = env->CallStaticObjectMethod(
217 fields.configClazz,
218 fields.createConfigMethod,
219 SkBitmap::kRGB_565_Config);
220
Carl Shapiroae12a502011-01-20 23:13:09 -0800221 size_t width, height;
222 if (videoFrame->mRotationAngle == 90 || videoFrame->mRotationAngle == 270) {
223 width = videoFrame->mDisplayHeight;
224 height = videoFrame->mDisplayWidth;
225 } else {
226 width = videoFrame->mDisplayWidth;
227 height = videoFrame->mDisplayHeight;
228 }
229
James Dong0e4b5352010-12-19 13:05:33 -0800230 jobject jBitmap = env->CallStaticObjectMethod(
231 fields.bitmapClazz,
232 fields.createBitmapMethod,
Carl Shapiroae12a502011-01-20 23:13:09 -0800233 width,
234 height,
James Dong0e4b5352010-12-19 13:05:33 -0800235 config);
Carl Shapiroae12a502011-01-20 23:13:09 -0800236
James Dong0e4b5352010-12-19 13:05:33 -0800237 SkBitmap *bitmap =
238 (SkBitmap *) env->GetIntField(jBitmap, fields.nativeBitmap);
239
240 bitmap->lockPixels();
Carl Shapiroae12a502011-01-20 23:13:09 -0800241 rotate((uint16_t*)bitmap->getPixels(),
242 (uint16_t*)((char*)videoFrame + sizeof(VideoFrame)),
243 videoFrame->mDisplayWidth,
244 videoFrame->mDisplayHeight,
245 videoFrame->mRotationAngle);
James Dong0e4b5352010-12-19 13:05:33 -0800246 bitmap->unlockPixels();
247
James Dong0e4b5352010-12-19 13:05:33 -0800248 return jBitmap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800249}
250
James Dongdf9b3492011-01-04 15:03:48 -0800251static jbyteArray android_media_MediaMetadataRetriever_getEmbeddedPicture(
252 JNIEnv *env, jobject thiz, jint pictureType)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800253{
James Dongdf9b3492011-01-04 15:03:48 -0800254 LOGV("getEmbeddedPicture: %d", pictureType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800255 MediaMetadataRetriever* retriever = getRetriever(env, thiz);
256 if (retriever == 0) {
257 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
258 return NULL;
259 }
260 MediaAlbumArt* mediaAlbumArt = NULL;
James Dongdf9b3492011-01-04 15:03:48 -0800261
262 // FIXME:
263 // Use pictureType to retrieve the intended embedded picture and also change
264 // the method name to getEmbeddedPicture().
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800265 sp<IMemory> albumArtMemory = retriever->extractAlbumArt();
266 if (albumArtMemory != 0) { // cast the shared structure to a MediaAlbumArt object
267 mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->pointer());
268 }
269 if (mediaAlbumArt == NULL) {
James Dongdf9b3492011-01-04 15:03:48 -0800270 LOGE("getEmbeddedPicture: Call to getEmbeddedPicture failed.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800271 return NULL;
272 }
273
274 unsigned int len = mediaAlbumArt->mSize;
275 char* data = (char*) mediaAlbumArt + sizeof(MediaAlbumArt);
276 jbyteArray array = env->NewByteArray(len);
277 if (!array) { // OutOfMemoryError exception has already been thrown.
James Dongdf9b3492011-01-04 15:03:48 -0800278 LOGE("getEmbeddedPicture: OutOfMemoryError is thrown.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800279 } else {
280 jbyte* bytes = env->GetByteArrayElements(array, NULL);
281 if (bytes != NULL) {
282 memcpy(bytes, data, len);
283 env->ReleaseByteArrayElements(array, bytes, 0);
284 }
285 }
286
287 // No need to delete mediaAlbumArt here
288 return array;
289}
290
291static jobject android_media_MediaMetadataRetriever_extractMetadata(JNIEnv *env, jobject thiz, jint keyCode)
292{
293 LOGV("extractMetadata");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800294 MediaMetadataRetriever* retriever = getRetriever(env, thiz);
295 if (retriever == 0) {
296 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
297 return NULL;
298 }
299 const char* value = retriever->extractMetadata(keyCode);
300 if (!value) {
301 LOGV("extractMetadata: Metadata is not found");
302 return NULL;
303 }
304 LOGV("extractMetadata: value (%s) for keyCode(%d)", value, keyCode);
305 return env->NewStringUTF(value);
306}
307
308static void android_media_MediaMetadataRetriever_release(JNIEnv *env, jobject thiz)
309{
310 LOGV("release");
311 Mutex::Autolock lock(sLock);
312 MediaMetadataRetriever* retriever = getRetriever(env, thiz);
313 delete retriever;
314 setRetriever(env, thiz, 0);
315}
316
317static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz)
318{
319 LOGV("native_finalize");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800320 // No lock is needed, since android_media_MediaMetadataRetriever_release() is protected
321 android_media_MediaMetadataRetriever_release(env, thiz);
322}
323
Marco Nelissen4935d052009-08-03 11:12:58 -0700324// This function gets a field ID, which in turn causes class initialization.
325// It is called from a static block in MediaMetadataRetriever, which won't run until the
326// first time an instance of this class is used.
327static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env)
328{
329 jclass clazz = env->FindClass(kClassPathName);
330 if (clazz == NULL) {
331 jniThrowException(env, "java/lang/RuntimeException", "Can't find android/media/MediaMetadataRetriever");
332 return;
333 }
334
335 fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
336 if (fields.context == NULL) {
337 jniThrowException(env, "java/lang/RuntimeException", "Can't find MediaMetadataRetriever.mNativeContext");
338 return;
339 }
340
341 fields.bitmapClazz = env->FindClass("android/graphics/Bitmap");
342 if (fields.bitmapClazz == NULL) {
343 jniThrowException(env, "java/lang/RuntimeException", "Can't find android/graphics/Bitmap");
344 return;
345 }
James Dong0e4b5352010-12-19 13:05:33 -0800346 fields.createBitmapMethod =
347 env->GetStaticMethodID(fields.bitmapClazz, "createBitmap",
348 "(IILandroid/graphics/Bitmap$Config;)"
349 "Landroid/graphics/Bitmap;");
350 if (fields.createBitmapMethod == NULL) {
351 jniThrowException(env, "java/lang/RuntimeException",
352 "Can't find Bitmap.createBitmap(int, int, Config) method");
353 return;
354 }
355 fields.nativeBitmap = env->GetFieldID(fields.bitmapClazz, "mNativeBitmap", "I");
356 if (fields.nativeBitmap == NULL) {
357 jniThrowException(env, "java/lang/RuntimeException",
358 "Can't find Bitmap.mNativeBitmap field");
359 }
360
361 fields.configClazz = env->FindClass("android/graphics/Bitmap$Config");
362 if (fields.configClazz == NULL) {
363 jniThrowException(env, "java/lang/RuntimeException",
364 "Can't find Bitmap$Config class");
365 return;
366 }
367 fields.createConfigMethod =
368 env->GetStaticMethodID(fields.configClazz, "nativeToConfig",
369 "(I)Landroid/graphics/Bitmap$Config;");
370 if (fields.createConfigMethod == NULL) {
371 jniThrowException(env, "java/lang/RuntimeException",
372 "Can't find Bitmap$Config.nativeToConfig(int) method");
373 return;
374 }
Marco Nelissen4935d052009-08-03 11:12:58 -0700375}
376
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800377static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz)
378{
379 LOGV("native_setup");
380 MediaMetadataRetriever* retriever = new MediaMetadataRetriever();
381 if (retriever == 0) {
382 jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
383 return;
384 }
385 setRetriever(env, thiz, (int)retriever);
386}
387
388// JNI mapping between Java methods and native methods
389static JNINativeMethod nativeMethods[] = {
390 {"setDataSource", "(Ljava/lang/String;)V", (void *)android_media_MediaMetadataRetriever_setDataSource},
391 {"setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD},
James Dongfaf09ba2010-12-02 17:42:08 -0800392 {"_getFrameAtTime", "(JI)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800393 {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata},
James Dongdf9b3492011-01-04 15:03:48 -0800394 {"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture},
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800395 {"release", "()V", (void *)android_media_MediaMetadataRetriever_release},
396 {"native_finalize", "()V", (void *)android_media_MediaMetadataRetriever_native_finalize},
397 {"native_setup", "()V", (void *)android_media_MediaMetadataRetriever_native_setup},
Marco Nelissen4935d052009-08-03 11:12:58 -0700398 {"native_init", "()V", (void *)android_media_MediaMetadataRetriever_native_init},
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800399};
400
Marco Nelissen4935d052009-08-03 11:12:58 -0700401// This function only registers the native methods, and is called from
402// JNI_OnLoad in android_media_MediaPlayer.cpp
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800403int register_android_media_MediaMetadataRetriever(JNIEnv *env)
404{
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800405 return AndroidRuntime::registerNativeMethods
Marco Nelissen4935d052009-08-03 11:12:58 -0700406 (env, kClassPathName, nativeMethods, NELEM(nativeMethods));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800407}