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 };
40
41 static fields_t fields;
42 static Mutex sLock;
43 static const char* const kClassPathName = "android/media/MediaMetadataRetriever";
44
process_media_retriever_call(JNIEnv * env,status_t opStatus,const char * exception,const char * message)45 static void process_media_retriever_call(JNIEnv *env, status_t opStatus, const char* exception, const char *message)
46 {
47 if (opStatus == (status_t) INVALID_OPERATION) {
48 jniThrowException(env, "java/lang/IllegalStateException", NULL);
49 } else if (opStatus != (status_t) OK) {
50 if (strlen(message) > 230) {
51 // If the message is too long, don't bother displaying the status code.
52 jniThrowException( env, exception, message);
53 } else {
54 char msg[256];
55 // Append the status code to the message.
56 sprintf(msg, "%s: status = 0x%X", message, opStatus);
57 jniThrowException( env, exception, msg);
58 }
59 }
60 }
61
getRetriever(JNIEnv * env,jobject thiz)62 static MediaMetadataRetriever* getRetriever(JNIEnv* env, jobject thiz)
63 {
64 // No lock is needed, since it is called internally by other methods that are protected
65 MediaMetadataRetriever* retriever = (MediaMetadataRetriever*) env->GetIntField(thiz, fields.context);
66 return retriever;
67 }
68
setRetriever(JNIEnv * env,jobject thiz,int retriever)69 static void setRetriever(JNIEnv* env, jobject thiz, int retriever)
70 {
71 // No lock is needed, since it is called internally by other methods that are protected
72 MediaMetadataRetriever *old = (MediaMetadataRetriever*) env->GetIntField(thiz, fields.context);
73 env->SetIntField(thiz, fields.context, retriever);
74 }
75
android_media_MediaMetadataRetriever_setDataSource(JNIEnv * env,jobject thiz,jstring path)76 static void android_media_MediaMetadataRetriever_setDataSource(JNIEnv *env, jobject thiz, jstring path)
77 {
78 LOGV("setDataSource");
79 Mutex::Autolock lock(sLock);
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 Mutex::Autolock lock(sLock);
109 MediaMetadataRetriever* retriever = getRetriever(env, thiz);
110 if (retriever == 0) {
111 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
112 return;
113 }
114 if (!fileDescriptor) {
115 jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
116 return;
117 }
118 int fd = getParcelFileDescriptorFD(env, fileDescriptor);
119 if (offset < 0 || length < 0 || fd < 0) {
120 if (offset < 0) {
121 LOGE("negative offset (%lld)", offset);
122 }
123 if (length < 0) {
124 LOGE("negative length (%lld)", length);
125 }
126 if (fd < 0) {
127 LOGE("invalid file descriptor");
128 }
129 jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
130 return;
131 }
132 process_media_retriever_call(env, retriever->setDataSource(fd, offset, length), "java/lang/RuntimeException", "setDataSource failed");
133 }
134
android_media_MediaMetadataRetriever_setMode(JNIEnv * env,jobject thiz,jint mode)135 static void android_media_MediaMetadataRetriever_setMode(JNIEnv *env, jobject thiz, jint mode)
136 {
137 LOGV("setMode");
138 Mutex::Autolock lock(sLock);
139 MediaMetadataRetriever* retriever = getRetriever(env, thiz);
140 if (retriever == 0) {
141 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
142 return;
143 }
144 process_media_retriever_call(env, retriever->setMode(mode), "java/lang/RuntimeException", "setMode failed");
145 }
146
android_media_MediaMetadataRetriever_getMode(JNIEnv * env,jobject thiz)147 static int android_media_MediaMetadataRetriever_getMode(JNIEnv *env, jobject thiz)
148 {
149 LOGV("getMode");
150 Mutex::Autolock lock(sLock);
151 MediaMetadataRetriever* retriever = getRetriever(env, thiz);
152 if (retriever == 0) {
153 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
154 return -1; // Error
155 }
156 int mode = -1;
157 retriever->getMode(&mode);
158 return mode;
159 }
160
android_media_MediaMetadataRetriever_captureFrame(JNIEnv * env,jobject thiz)161 static jobject android_media_MediaMetadataRetriever_captureFrame(JNIEnv *env, jobject thiz)
162 {
163 LOGV("captureFrame");
164 Mutex::Autolock lock(sLock);
165 MediaMetadataRetriever* retriever = getRetriever(env, thiz);
166 if (retriever == 0) {
167 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
168 return NULL;
169 }
170
171 // Call native method to retrieve a video frame
172 VideoFrame *videoFrame = NULL;
173 sp<IMemory> frameMemory = retriever->captureFrame();
174 if (frameMemory != 0) { // cast the shared structure to a VideoFrame object
175 videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
176 }
177 if (videoFrame == NULL) {
178 LOGE("captureFrame: videoFrame is a NULL pointer");
179 return NULL;
180 }
181
182 // Create a SkBitmap to hold the pixels
183 SkBitmap *bitmap = new SkBitmap();
184 if (bitmap == NULL) {
185 LOGE("captureFrame: cannot instantiate a SkBitmap object.");
186 return NULL;
187 }
188 bitmap->setConfig(SkBitmap::kRGB_565_Config, videoFrame->mDisplayWidth, videoFrame->mDisplayHeight);
189 if (!bitmap->allocPixels()) {
190 delete bitmap;
191 LOGE("failed to allocate pixel buffer");
192 return NULL;
193 }
194 memcpy((uint8_t*)bitmap->getPixels(), (uint8_t*)videoFrame + sizeof(VideoFrame), videoFrame->mSize);
195
196 // Since internally SkBitmap uses reference count to manage the reference to
197 // its pixels, it is important that the pixels (along with SkBitmap) be
198 // available after creating the Bitmap is returned to Java app.
199 return env->NewObject(fields.bitmapClazz, fields.bitmapConstructor, (int) bitmap, true, NULL, -1);
200 }
201
android_media_MediaMetadataRetriever_extractAlbumArt(JNIEnv * env,jobject thiz)202 static jbyteArray android_media_MediaMetadataRetriever_extractAlbumArt(JNIEnv *env, jobject thiz)
203 {
204 LOGV("extractAlbumArt");
205 Mutex::Autolock lock(sLock);
206 MediaMetadataRetriever* retriever = getRetriever(env, thiz);
207 if (retriever == 0) {
208 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
209 return NULL;
210 }
211 MediaAlbumArt* mediaAlbumArt = NULL;
212 sp<IMemory> albumArtMemory = retriever->extractAlbumArt();
213 if (albumArtMemory != 0) { // cast the shared structure to a MediaAlbumArt object
214 mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->pointer());
215 }
216 if (mediaAlbumArt == NULL) {
217 LOGE("extractAlbumArt: Call to extractAlbumArt failed.");
218 return NULL;
219 }
220
221 unsigned int len = mediaAlbumArt->mSize;
222 char* data = (char*) mediaAlbumArt + sizeof(MediaAlbumArt);
223 jbyteArray array = env->NewByteArray(len);
224 if (!array) { // OutOfMemoryError exception has already been thrown.
225 LOGE("extractAlbumArt: OutOfMemoryError is thrown.");
226 } else {
227 jbyte* bytes = env->GetByteArrayElements(array, NULL);
228 if (bytes != NULL) {
229 memcpy(bytes, data, len);
230 env->ReleaseByteArrayElements(array, bytes, 0);
231 }
232 }
233
234 // No need to delete mediaAlbumArt here
235 return array;
236 }
237
android_media_MediaMetadataRetriever_extractMetadata(JNIEnv * env,jobject thiz,jint keyCode)238 static jobject android_media_MediaMetadataRetriever_extractMetadata(JNIEnv *env, jobject thiz, jint keyCode)
239 {
240 LOGV("extractMetadata");
241 Mutex::Autolock lock(sLock);
242 MediaMetadataRetriever* retriever = getRetriever(env, thiz);
243 if (retriever == 0) {
244 jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
245 return NULL;
246 }
247 const char* value = retriever->extractMetadata(keyCode);
248 if (!value) {
249 LOGV("extractMetadata: Metadata is not found");
250 return NULL;
251 }
252 LOGV("extractMetadata: value (%s) for keyCode(%d)", value, keyCode);
253 return env->NewStringUTF(value);
254 }
255
android_media_MediaMetadataRetriever_release(JNIEnv * env,jobject thiz)256 static void android_media_MediaMetadataRetriever_release(JNIEnv *env, jobject thiz)
257 {
258 LOGV("release");
259 Mutex::Autolock lock(sLock);
260 MediaMetadataRetriever* retriever = getRetriever(env, thiz);
261 delete retriever;
262 setRetriever(env, thiz, 0);
263 }
264
android_media_MediaMetadataRetriever_native_finalize(JNIEnv * env,jobject thiz)265 static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz)
266 {
267 LOGV("native_finalize");
268
269 // No lock is needed, since android_media_MediaMetadataRetriever_release() is protected
270 android_media_MediaMetadataRetriever_release(env, thiz);
271 }
272
273 // This function gets a field ID, which in turn causes class initialization.
274 // It is called from a static block in MediaMetadataRetriever, which won't run until the
275 // first time an instance of this class is used.
android_media_MediaMetadataRetriever_native_init(JNIEnv * env)276 static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env)
277 {
278 jclass clazz = env->FindClass(kClassPathName);
279 if (clazz == NULL) {
280 jniThrowException(env, "java/lang/RuntimeException", "Can't find android/media/MediaMetadataRetriever");
281 return;
282 }
283
284 fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
285 if (fields.context == NULL) {
286 jniThrowException(env, "java/lang/RuntimeException", "Can't find MediaMetadataRetriever.mNativeContext");
287 return;
288 }
289
290 fields.bitmapClazz = env->FindClass("android/graphics/Bitmap");
291 if (fields.bitmapClazz == NULL) {
292 jniThrowException(env, "java/lang/RuntimeException", "Can't find android/graphics/Bitmap");
293 return;
294 }
295
296 fields.bitmapConstructor = env->GetMethodID(fields.bitmapClazz, "<init>", "(IZ[BI)V");
297 if (fields.bitmapConstructor == NULL) {
298 jniThrowException(env, "java/lang/RuntimeException", "Can't find Bitmap constructor");
299 return;
300 }
301 }
302
android_media_MediaMetadataRetriever_native_setup(JNIEnv * env,jobject thiz)303 static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz)
304 {
305 LOGV("native_setup");
306 MediaMetadataRetriever* retriever = new MediaMetadataRetriever();
307 if (retriever == 0) {
308 jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
309 return;
310 }
311 setRetriever(env, thiz, (int)retriever);
312 }
313
314 // JNI mapping between Java methods and native methods
315 static JNINativeMethod nativeMethods[] = {
316 {"setDataSource", "(Ljava/lang/String;)V", (void *)android_media_MediaMetadataRetriever_setDataSource},
317 {"setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD},
318 {"setMode", "(I)V", (void *)android_media_MediaMetadataRetriever_setMode},
319 {"getMode", "()I", (void *)android_media_MediaMetadataRetriever_getMode},
320 {"captureFrame", "()Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_captureFrame},
321 {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata},
322 {"extractAlbumArt", "()[B", (void *)android_media_MediaMetadataRetriever_extractAlbumArt},
323 {"release", "()V", (void *)android_media_MediaMetadataRetriever_release},
324 {"native_finalize", "()V", (void *)android_media_MediaMetadataRetriever_native_finalize},
325 {"native_setup", "()V", (void *)android_media_MediaMetadataRetriever_native_setup},
326 {"native_init", "()V", (void *)android_media_MediaMetadataRetriever_native_init},
327 };
328
329 // This function only registers the native methods, and is called from
330 // JNI_OnLoad in android_media_MediaPlayer.cpp
register_android_media_MediaMetadataRetriever(JNIEnv * env)331 int register_android_media_MediaMetadataRetriever(JNIEnv *env)
332 {
333 return AndroidRuntime::registerNativeMethods
334 (env, kClassPathName, nativeMethods, NELEM(nativeMethods));
335 }
336