android_media_MediaMetadataRetriever.cpp revision 4507cef05e492f65755aa321007d6592d111d01f
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 <core/SkCanvas.h>
26#include <core/SkDevice.h>
27#include <core/SkScalar.h>
28#include <media/mediametadataretriever.h>
29#include <private/media/VideoFrame.h>
30
31#include "jni.h"
32#include "JNIHelp.h"
33#include "android_runtime/AndroidRuntime.h"
34
35
36using namespace android;
37
38struct fields_t {
39    jfieldID context;
40    jclass bitmapClazz;
41    jfieldID nativeBitmap;
42    jmethodID createBitmapMethod;
43    jclass configClazz;
44    jmethodID createConfigMethod;
45};
46
47static fields_t fields;
48static Mutex sLock;
49static const char* const kClassPathName = "android/media/MediaMetadataRetriever";
50
51static void process_media_retriever_call(JNIEnv *env, status_t opStatus, const char* exception, const char *message)
52{
53    if (opStatus == (status_t) INVALID_OPERATION) {
54        jniThrowException(env, "java/lang/IllegalStateException", NULL);
55    } else if (opStatus != (status_t) OK) {
56        if (strlen(message) > 230) {
57            // If the message is too long, don't bother displaying the status code.
58            jniThrowException( env, exception, message);
59        } else {
60            char msg[256];
61            // Append the status code to the message.
62            sprintf(msg, "%s: status = 0x%X", message, opStatus);
63            jniThrowException( env, exception, msg);
64        }
65    }
66}
67
68static MediaMetadataRetriever* getRetriever(JNIEnv* env, jobject thiz)
69{
70    // No lock is needed, since it is called internally by other methods that are protected
71    MediaMetadataRetriever* retriever = (MediaMetadataRetriever*) env->GetIntField(thiz, fields.context);
72    return retriever;
73}
74
75static void setRetriever(JNIEnv* env, jobject thiz, int retriever)
76{
77    // No lock is needed, since it is called internally by other methods that are protected
78    MediaMetadataRetriever *old = (MediaMetadataRetriever*) env->GetIntField(thiz, fields.context);
79    env->SetIntField(thiz, fields.context, retriever);
80}
81
82static void android_media_MediaMetadataRetriever_setDataSource(JNIEnv *env, jobject thiz, jstring path)
83{
84    LOGV("setDataSource");
85    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
86    if (retriever == 0) {
87        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
88        return;
89    }
90    if (!path) {
91        jniThrowException(env, "java/lang/IllegalArgumentException", "Null pointer");
92        return;
93    }
94
95    const char *pathStr = env->GetStringUTFChars(path, NULL);
96    if (!pathStr) {  // OutOfMemoryError exception already thrown
97        return;
98    }
99
100    // Don't let somebody trick us in to reading some random block of memory
101    if (strncmp("mem://", pathStr, 6) == 0) {
102        jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid pathname");
103        return;
104    }
105
106    process_media_retriever_call(env, retriever->setDataSource(pathStr), "java/lang/RuntimeException", "setDataSource failed");
107    env->ReleaseStringUTFChars(path, pathStr);
108}
109
110static void android_media_MediaMetadataRetriever_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
111{
112    LOGV("setDataSource");
113    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
114    if (retriever == 0) {
115        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
116        return;
117    }
118    if (!fileDescriptor) {
119        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
120        return;
121    }
122    int fd = getParcelFileDescriptorFD(env, fileDescriptor);
123    if (offset < 0 || length < 0 || fd < 0) {
124        if (offset < 0) {
125            LOGE("negative offset (%lld)", offset);
126        }
127        if (length < 0) {
128            LOGE("negative length (%lld)", length);
129        }
130        if (fd < 0) {
131            LOGE("invalid file descriptor");
132        }
133        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
134        return;
135    }
136    process_media_retriever_call(env, retriever->setDataSource(fd, offset, length), "java/lang/RuntimeException", "setDataSource failed");
137}
138
139static jobject android_media_MediaMetadataRetriever_getFrameAtTime(JNIEnv *env, jobject thiz, jlong timeUs, jint option)
140{
141    LOGV("getFrameAtTime: %lld us option: %d", timeUs, option);
142    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
143    if (retriever == 0) {
144        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
145        return NULL;
146    }
147
148    // Call native method to retrieve a video frame
149    VideoFrame *videoFrame = NULL;
150    sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option);
151    if (frameMemory != 0) {  // cast the shared structure to a VideoFrame object
152        videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
153    }
154    if (videoFrame == NULL) {
155        LOGE("getFrameAtTime: videoFrame is a NULL pointer");
156        return NULL;
157    }
158
159    LOGV("Dimension = %dx%d and bytes = %d",
160            videoFrame->mDisplayWidth,
161            videoFrame->mDisplayHeight,
162            videoFrame->mSize);
163
164    jobject config = env->CallStaticObjectMethod(
165                        fields.configClazz,
166                        fields.createConfigMethod,
167                        SkBitmap::kRGB_565_Config);
168
169    jobject jBitmap = env->CallStaticObjectMethod(
170                            fields.bitmapClazz,
171                            fields.createBitmapMethod,
172                            videoFrame->mDisplayWidth,
173                            videoFrame->mDisplayHeight,
174                            config);
175    SkBitmap *bitmap =
176            (SkBitmap *) env->GetIntField(jBitmap, fields.nativeBitmap);
177
178    bitmap->lockPixels();
179
180    memcpy((uint8_t*)bitmap->getPixels(),
181            (uint8_t*)videoFrame + sizeof(VideoFrame), videoFrame->mSize);
182
183    bitmap->unlockPixels();
184
185    if (videoFrame->mRotationAngle != 0) {
186        SkDevice device(*bitmap);
187        SkCanvas canvas(&device);
188        canvas.rotate((SkScalar) (videoFrame->mRotationAngle * 1.0));
189        canvas.drawBitmap(*bitmap, 0, 0);
190    }
191
192    LOGV("Return a new bitmap constructed with the rotation matrix");
193    return jBitmap;
194}
195
196static jbyteArray android_media_MediaMetadataRetriever_getEmbeddedPicture(
197        JNIEnv *env, jobject thiz, jint pictureType)
198{
199    LOGV("getEmbeddedPicture: %d", pictureType);
200    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
201    if (retriever == 0) {
202        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
203        return NULL;
204    }
205    MediaAlbumArt* mediaAlbumArt = NULL;
206
207    // FIXME:
208    // Use pictureType to retrieve the intended embedded picture and also change
209    // the method name to getEmbeddedPicture().
210    sp<IMemory> albumArtMemory = retriever->extractAlbumArt();
211    if (albumArtMemory != 0) {  // cast the shared structure to a MediaAlbumArt object
212        mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->pointer());
213    }
214    if (mediaAlbumArt == NULL) {
215        LOGE("getEmbeddedPicture: Call to getEmbeddedPicture failed.");
216        return NULL;
217    }
218
219    unsigned int len = mediaAlbumArt->mSize;
220    char* data = (char*) mediaAlbumArt + sizeof(MediaAlbumArt);
221    jbyteArray array = env->NewByteArray(len);
222    if (!array) {  // OutOfMemoryError exception has already been thrown.
223        LOGE("getEmbeddedPicture: OutOfMemoryError is thrown.");
224    } else {
225        jbyte* bytes = env->GetByteArrayElements(array, NULL);
226        if (bytes != NULL) {
227            memcpy(bytes, data, len);
228            env->ReleaseByteArrayElements(array, bytes, 0);
229        }
230    }
231
232    // No need to delete mediaAlbumArt here
233    return array;
234}
235
236static jobject android_media_MediaMetadataRetriever_extractMetadata(JNIEnv *env, jobject thiz, jint keyCode)
237{
238    LOGV("extractMetadata");
239    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
240    if (retriever == 0) {
241        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
242        return NULL;
243    }
244    const char* value = retriever->extractMetadata(keyCode);
245    if (!value) {
246        LOGV("extractMetadata: Metadata is not found");
247        return NULL;
248    }
249    LOGV("extractMetadata: value (%s) for keyCode(%d)", value, keyCode);
250    return env->NewStringUTF(value);
251}
252
253static void android_media_MediaMetadataRetriever_release(JNIEnv *env, jobject thiz)
254{
255    LOGV("release");
256    Mutex::Autolock lock(sLock);
257    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
258    delete retriever;
259    setRetriever(env, thiz, 0);
260}
261
262static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz)
263{
264    LOGV("native_finalize");
265    // No lock is needed, since android_media_MediaMetadataRetriever_release() is protected
266    android_media_MediaMetadataRetriever_release(env, thiz);
267}
268
269// This function gets a field ID, which in turn causes class initialization.
270// It is called from a static block in MediaMetadataRetriever, which won't run until the
271// first time an instance of this class is used.
272static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env)
273{
274    jclass clazz = env->FindClass(kClassPathName);
275    if (clazz == NULL) {
276        jniThrowException(env, "java/lang/RuntimeException", "Can't find android/media/MediaMetadataRetriever");
277        return;
278    }
279
280    fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
281    if (fields.context == NULL) {
282        jniThrowException(env, "java/lang/RuntimeException", "Can't find MediaMetadataRetriever.mNativeContext");
283        return;
284    }
285
286    fields.bitmapClazz = env->FindClass("android/graphics/Bitmap");
287    if (fields.bitmapClazz == NULL) {
288        jniThrowException(env, "java/lang/RuntimeException", "Can't find android/graphics/Bitmap");
289        return;
290    }
291#if USE_PRIVATE_NATIVE_BITMAP_CONSTUCTOR
292    fields.bitmapConstructor = env->GetMethodID(fields.bitmapClazz, "<init>", "(I[BZ[BI)V");
293    if (fields.bitmapConstructor == NULL) {
294        jniThrowException(env, "java/lang/RuntimeException", "Can't find Bitmap constructor");
295        return;
296    }
297    fields.createBitmapRotationMethod =
298            env->GetStaticMethodID(fields.bitmapClazz, "createBitmap",
299                    "(Landroid/graphics/Bitmap;IIIILandroid/graphics/Matrix;Z)"
300                    "Landroid/graphics/Bitmap;");
301    if (fields.createBitmapRotationMethod == NULL) {
302        jniThrowException(env, "java/lang/RuntimeException",
303                "Can't find Bitmap.createBitmap method");
304        return;
305    }
306#else
307    fields.createBitmapMethod =
308            env->GetStaticMethodID(fields.bitmapClazz, "createBitmap",
309                    "(IILandroid/graphics/Bitmap$Config;)"
310                    "Landroid/graphics/Bitmap;");
311    if (fields.createBitmapMethod == NULL) {
312        jniThrowException(env, "java/lang/RuntimeException",
313                "Can't find Bitmap.createBitmap(int, int, Config)  method");
314        return;
315    }
316    fields.nativeBitmap = env->GetFieldID(fields.bitmapClazz, "mNativeBitmap", "I");
317    if (fields.nativeBitmap == NULL) {
318        jniThrowException(env, "java/lang/RuntimeException",
319                "Can't find Bitmap.mNativeBitmap field");
320    }
321
322    fields.configClazz = env->FindClass("android/graphics/Bitmap$Config");
323    if (fields.configClazz == NULL) {
324        jniThrowException(env, "java/lang/RuntimeException",
325                               "Can't find Bitmap$Config class");
326        return;
327    }
328    fields.createConfigMethod =
329            env->GetStaticMethodID(fields.configClazz, "nativeToConfig",
330                    "(I)Landroid/graphics/Bitmap$Config;");
331    if (fields.createConfigMethod == NULL) {
332        jniThrowException(env, "java/lang/RuntimeException",
333                "Can't find Bitmap$Config.nativeToConfig(int)  method");
334        return;
335    }
336#endif
337}
338
339static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz)
340{
341    LOGV("native_setup");
342    MediaMetadataRetriever* retriever = new MediaMetadataRetriever();
343    if (retriever == 0) {
344        jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
345        return;
346    }
347    setRetriever(env, thiz, (int)retriever);
348}
349
350// JNI mapping between Java methods and native methods
351static JNINativeMethod nativeMethods[] = {
352        {"setDataSource",   "(Ljava/lang/String;)V", (void *)android_media_MediaMetadataRetriever_setDataSource},
353        {"setDataSource",   "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD},
354        {"_getFrameAtTime", "(JI)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
355        {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata},
356        {"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture},
357        {"release",         "()V", (void *)android_media_MediaMetadataRetriever_release},
358        {"native_finalize", "()V", (void *)android_media_MediaMetadataRetriever_native_finalize},
359        {"native_setup",    "()V", (void *)android_media_MediaMetadataRetriever_native_setup},
360        {"native_init",     "()V", (void *)android_media_MediaMetadataRetriever_native_init},
361};
362
363// This function only registers the native methods, and is called from
364// JNI_OnLoad in android_media_MediaPlayer.cpp
365int register_android_media_MediaMetadataRetriever(JNIEnv *env)
366{
367    return AndroidRuntime::registerNativeMethods
368        (env, kClassPathName, nativeMethods, NELEM(nativeMethods));
369}
370