• 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 };
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