android_media_MediaMetadataRetriever.cpp revision 17524dc0d296146c8ffb3f692dc8ab05fee5b1e0
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
33using namespace android;
34
35struct fields_t {
36    jfieldID context;
37    jclass bitmapClazz;  // Must be a global ref
38    jfieldID nativeBitmap;
39    jmethodID createBitmapMethod;
40    jmethodID createScaledBitmapMethod;
41    jclass configClazz;  // Must be a global ref
42    jmethodID createConfigMethod;
43};
44
45static fields_t fields;
46static Mutex sLock;
47static const char* const kClassPathName = "android/media/MediaMetadataRetriever";
48
49static void process_media_retriever_call(JNIEnv *env, status_t opStatus, const char* exception, const char *message)
50{
51    if (opStatus == (status_t) INVALID_OPERATION) {
52        jniThrowException(env, "java/lang/IllegalStateException", NULL);
53    } else if (opStatus != (status_t) OK) {
54        if (strlen(message) > 230) {
55            // If the message is too long, don't bother displaying the status code.
56            jniThrowException( env, exception, message);
57        } else {
58            char msg[256];
59            // Append the status code to the message.
60            sprintf(msg, "%s: status = 0x%X", message, opStatus);
61            jniThrowException( env, exception, msg);
62        }
63    }
64}
65
66static MediaMetadataRetriever* getRetriever(JNIEnv* env, jobject thiz)
67{
68    // No lock is needed, since it is called internally by other methods that are protected
69    MediaMetadataRetriever* retriever = (MediaMetadataRetriever*) env->GetIntField(thiz, fields.context);
70    return retriever;
71}
72
73static void setRetriever(JNIEnv* env, jobject thiz, int retriever)
74{
75    // No lock is needed, since it is called internally by other methods that are protected
76    MediaMetadataRetriever *old = (MediaMetadataRetriever*) env->GetIntField(thiz, fields.context);
77    env->SetIntField(thiz, fields.context, retriever);
78}
79
80static void
81android_media_MediaMetadataRetriever_setDataSourceAndHeaders(
82        JNIEnv *env, jobject thiz, jstring path,
83        jobjectArray keys, jobjectArray values) {
84
85    LOGV("setDataSource");
86    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
87    if (retriever == 0) {
88        jniThrowException(
89                env,
90                "java/lang/IllegalStateException", "No retriever available");
91
92        return;
93    }
94
95    if (!path) {
96        jniThrowException(
97                env, "java/lang/IllegalArgumentException", "Null pointer");
98
99        return;
100    }
101
102    const char *tmp = env->GetStringUTFChars(path, NULL);
103    if (!tmp) {  // OutOfMemoryError exception already thrown
104        return;
105    }
106
107    String8 pathStr(tmp);
108    env->ReleaseStringUTFChars(path, tmp);
109    tmp = NULL;
110
111    int nKeyValuePairs = env->GetArrayLength(keys);
112    if (nKeyValuePairs != env->GetArrayLength(values)) {
113        LOGE("keys and values have different length: %d <--> %d",
114            nKeyValuePairs, env->GetArrayLength(values));
115        jniThrowException(
116                env, "java/lang/IllegalArgumentException", NULL);
117        return;
118    }
119    // Don't let somebody trick us in to reading some random block of memory
120    if (strncmp("mem://", pathStr.string(), 6) == 0) {
121        jniThrowException(
122                env, "java/lang/IllegalArgumentException", "Invalid pathname");
123        return;
124    }
125
126    // We build a similar KeyedVector out of it.
127    KeyedVector<String8, String8> headersVector;
128    for (int i = 0; i < nKeyValuePairs; ++i) {
129        // No need to check on the ArrayIndexOutOfBoundsException, since
130        // it won't happen here.
131        jstring key = (jstring) env->GetObjectArrayElement(keys, i);
132        jstring value = (jstring) env->GetObjectArrayElement(values, i);
133
134        const char* keyStr = env->GetStringUTFChars(key, NULL);
135        if (!keyStr) {  // OutOfMemoryError
136            return;
137        }
138
139        const char* valueStr = env->GetStringUTFChars(value, NULL);
140        if (!valueStr) {  // OutOfMemoryError
141            env->ReleaseStringUTFChars(key, keyStr);
142            return;
143        }
144
145        headersVector.add(String8(keyStr), String8(valueStr));
146
147        env->ReleaseStringUTFChars(key, keyStr);
148        env->ReleaseStringUTFChars(value, valueStr);
149        env->DeleteLocalRef(key);
150        env->DeleteLocalRef(value);
151
152    }
153
154    process_media_retriever_call(
155            env,
156            retriever->setDataSource(
157                pathStr.string(), nKeyValuePairs > 0 ? &headersVector : NULL),
158
159            "java/lang/RuntimeException",
160            "setDataSource failed");
161}
162
163static void android_media_MediaMetadataRetriever_setDataSource(
164        JNIEnv *env, jobject thiz, jstring path) {
165    android_media_MediaMetadataRetriever_setDataSourceAndHeaders(
166            env, thiz, path, NULL, NULL);
167}
168
169static void android_media_MediaMetadataRetriever_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
170{
171    LOGV("setDataSource");
172    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
173    if (retriever == 0) {
174        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
175        return;
176    }
177    if (!fileDescriptor) {
178        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
179        return;
180    }
181    int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
182    if (offset < 0 || length < 0 || fd < 0) {
183        if (offset < 0) {
184            LOGE("negative offset (%lld)", offset);
185        }
186        if (length < 0) {
187            LOGE("negative length (%lld)", length);
188        }
189        if (fd < 0) {
190            LOGE("invalid file descriptor");
191        }
192        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
193        return;
194    }
195    process_media_retriever_call(env, retriever->setDataSource(fd, offset, length), "java/lang/RuntimeException", "setDataSource failed");
196}
197
198template<typename T>
199static void rotate0(T* dst, const T* src, size_t width, size_t height)
200{
201    memcpy(dst, src, width * height * sizeof(T));
202}
203
204template<typename T>
205static void rotate90(T* dst, const T* src, size_t width, size_t height)
206{
207    for (size_t i = 0; i < height; ++i) {
208        for (size_t j = 0; j < width; ++j) {
209            dst[j * height + height - 1 - i] = src[i * width + j];
210        }
211    }
212}
213
214template<typename T>
215static void rotate180(T* dst, const T* src, size_t width, size_t height)
216{
217    for (size_t i = 0; i < height; ++i) {
218        for (size_t j = 0; j < width; ++j) {
219            dst[(height - 1 - i) * width + width - 1 - j] = src[i * width + j];
220        }
221    }
222}
223
224template<typename T>
225static void rotate270(T* dst, const T* src, size_t width, size_t height)
226{
227    for (size_t i = 0; i < height; ++i) {
228        for (size_t j = 0; j < width; ++j) {
229            dst[(width - 1 - j) * height + i] = src[i * width + j];
230        }
231    }
232}
233
234template<typename T>
235static void rotate(T *dst, const T *src, size_t width, size_t height, int angle)
236{
237    switch (angle) {
238        case 0:
239            rotate0(dst, src, width, height);
240            break;
241        case 90:
242            rotate90(dst, src, width, height);
243            break;
244        case 180:
245            rotate180(dst, src, width, height);
246            break;
247        case 270:
248            rotate270(dst, src, width, height);
249            break;
250    }
251}
252
253static jobject android_media_MediaMetadataRetriever_getFrameAtTime(JNIEnv *env, jobject thiz, jlong timeUs, jint option)
254{
255    LOGV("getFrameAtTime: %lld us option: %d", timeUs, option);
256    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
257    if (retriever == 0) {
258        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
259        return NULL;
260    }
261
262    // Call native method to retrieve a video frame
263    VideoFrame *videoFrame = NULL;
264    sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option);
265    if (frameMemory != 0) {  // cast the shared structure to a VideoFrame object
266        videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
267    }
268    if (videoFrame == NULL) {
269        LOGE("getFrameAtTime: videoFrame is a NULL pointer");
270        return NULL;
271    }
272
273    LOGV("Dimension = %dx%d and bytes = %d",
274            videoFrame->mDisplayWidth,
275            videoFrame->mDisplayHeight,
276            videoFrame->mSize);
277
278    jobject config = env->CallStaticObjectMethod(
279                        fields.configClazz,
280                        fields.createConfigMethod,
281                        SkBitmap::kRGB_565_Config);
282
283    size_t width, height;
284    bool swapWidthAndHeight = false;
285    if (videoFrame->mRotationAngle == 90 || videoFrame->mRotationAngle == 270) {
286        width = videoFrame->mHeight;
287        height = videoFrame->mWidth;
288        swapWidthAndHeight = true;
289    } else {
290        width = videoFrame->mWidth;
291        height = videoFrame->mHeight;
292    }
293
294    jobject jBitmap = env->CallStaticObjectMethod(
295                            fields.bitmapClazz,
296                            fields.createBitmapMethod,
297                            width,
298                            height,
299                            config);
300
301    SkBitmap *bitmap =
302            (SkBitmap *) env->GetIntField(jBitmap, fields.nativeBitmap);
303
304    bitmap->lockPixels();
305    rotate((uint16_t*)bitmap->getPixels(),
306           (uint16_t*)((char*)videoFrame + sizeof(VideoFrame)),
307           videoFrame->mWidth,
308           videoFrame->mHeight,
309           videoFrame->mRotationAngle);
310    bitmap->unlockPixels();
311
312    if (videoFrame->mDisplayWidth  != videoFrame->mWidth ||
313        videoFrame->mDisplayHeight != videoFrame->mHeight) {
314        size_t displayWidth = videoFrame->mDisplayWidth;
315        size_t displayHeight = videoFrame->mDisplayHeight;
316        if (swapWidthAndHeight) {
317            displayWidth = videoFrame->mDisplayHeight;
318            displayHeight = videoFrame->mDisplayWidth;
319        }
320        LOGV("Bitmap dimension is scaled from %dx%d to %dx%d",
321                width, height, displayWidth, displayHeight);
322        jobject scaledBitmap = env->CallStaticObjectMethod(fields.bitmapClazz,
323                                    fields.createScaledBitmapMethod,
324                                    jBitmap,
325                                    displayWidth,
326                                    displayHeight,
327                                    true);
328        return scaledBitmap;
329    }
330
331    return jBitmap;
332}
333
334static jbyteArray android_media_MediaMetadataRetriever_getEmbeddedPicture(
335        JNIEnv *env, jobject thiz, jint pictureType)
336{
337    LOGV("getEmbeddedPicture: %d", pictureType);
338    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
339    if (retriever == 0) {
340        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
341        return NULL;
342    }
343    MediaAlbumArt* mediaAlbumArt = NULL;
344
345    // FIXME:
346    // Use pictureType to retrieve the intended embedded picture and also change
347    // the method name to getEmbeddedPicture().
348    sp<IMemory> albumArtMemory = retriever->extractAlbumArt();
349    if (albumArtMemory != 0) {  // cast the shared structure to a MediaAlbumArt object
350        mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->pointer());
351    }
352    if (mediaAlbumArt == NULL) {
353        LOGE("getEmbeddedPicture: Call to getEmbeddedPicture failed.");
354        return NULL;
355    }
356
357    unsigned int len = mediaAlbumArt->mSize;
358    char* data = (char*) mediaAlbumArt + sizeof(MediaAlbumArt);
359    jbyteArray array = env->NewByteArray(len);
360    if (!array) {  // OutOfMemoryError exception has already been thrown.
361        LOGE("getEmbeddedPicture: OutOfMemoryError is thrown.");
362    } else {
363        jbyte* bytes = env->GetByteArrayElements(array, NULL);
364        if (bytes != NULL) {
365            memcpy(bytes, data, len);
366            env->ReleaseByteArrayElements(array, bytes, 0);
367        }
368    }
369
370    // No need to delete mediaAlbumArt here
371    return array;
372}
373
374static jobject android_media_MediaMetadataRetriever_extractMetadata(JNIEnv *env, jobject thiz, jint keyCode)
375{
376    LOGV("extractMetadata");
377    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
378    if (retriever == 0) {
379        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
380        return NULL;
381    }
382    const char* value = retriever->extractMetadata(keyCode);
383    if (!value) {
384        LOGV("extractMetadata: Metadata is not found");
385        return NULL;
386    }
387    LOGV("extractMetadata: value (%s) for keyCode(%d)", value, keyCode);
388    return env->NewStringUTF(value);
389}
390
391static void android_media_MediaMetadataRetriever_release(JNIEnv *env, jobject thiz)
392{
393    LOGV("release");
394    Mutex::Autolock lock(sLock);
395    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
396    delete retriever;
397    setRetriever(env, thiz, 0);
398}
399
400static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz)
401{
402    LOGV("native_finalize");
403    // No lock is needed, since android_media_MediaMetadataRetriever_release() is protected
404    android_media_MediaMetadataRetriever_release(env, thiz);
405}
406
407// This function gets a field ID, which in turn causes class initialization.
408// It is called from a static block in MediaMetadataRetriever, which won't run until the
409// first time an instance of this class is used.
410static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env)
411{
412    jclass clazz = env->FindClass(kClassPathName);
413    if (clazz == NULL) {
414        return;
415    }
416
417    fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
418    if (fields.context == NULL) {
419        return;
420    }
421
422    jclass bitmapClazz = env->FindClass("android/graphics/Bitmap");
423    if (bitmapClazz == NULL) {
424        return;
425    }
426    fields.bitmapClazz = (jclass) env->NewGlobalRef(bitmapClazz);
427    if (fields.bitmapClazz == NULL) {
428        return;
429    }
430    fields.createBitmapMethod =
431            env->GetStaticMethodID(fields.bitmapClazz, "createBitmap",
432                    "(IILandroid/graphics/Bitmap$Config;)"
433                    "Landroid/graphics/Bitmap;");
434    if (fields.createBitmapMethod == NULL) {
435        return;
436    }
437    fields.createScaledBitmapMethod =
438            env->GetStaticMethodID(fields.bitmapClazz, "createScaledBitmap",
439                    "(Landroid/graphics/Bitmap;IIZ)"
440                    "Landroid/graphics/Bitmap;");
441    if (fields.createScaledBitmapMethod == NULL) {
442        return;
443    }
444    fields.nativeBitmap = env->GetFieldID(fields.bitmapClazz, "mNativeBitmap", "I");
445    if (fields.nativeBitmap == NULL) {
446        return;
447    }
448
449    jclass configClazz = env->FindClass("android/graphics/Bitmap$Config");
450    if (configClazz == NULL) {
451        return;
452    }
453    fields.configClazz = (jclass) env->NewGlobalRef(configClazz);
454    if (fields.configClazz == NULL) {
455        return;
456    }
457    fields.createConfigMethod =
458            env->GetStaticMethodID(fields.configClazz, "nativeToConfig",
459                    "(I)Landroid/graphics/Bitmap$Config;");
460    if (fields.createConfigMethod == NULL) {
461        return;
462    }
463}
464
465static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz)
466{
467    LOGV("native_setup");
468    MediaMetadataRetriever* retriever = new MediaMetadataRetriever();
469    if (retriever == 0) {
470        jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
471        return;
472    }
473    setRetriever(env, thiz, (int)retriever);
474}
475
476// JNI mapping between Java methods and native methods
477static JNINativeMethod nativeMethods[] = {
478        {"setDataSource",   "(Ljava/lang/String;)V", (void *)android_media_MediaMetadataRetriever_setDataSource},
479
480        {
481            "_setDataSource",
482            "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
483            (void *)android_media_MediaMetadataRetriever_setDataSourceAndHeaders
484        },
485
486        {"setDataSource",   "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD},
487        {"_getFrameAtTime", "(JI)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
488        {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata},
489        {"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture},
490        {"release",         "()V", (void *)android_media_MediaMetadataRetriever_release},
491        {"native_finalize", "()V", (void *)android_media_MediaMetadataRetriever_native_finalize},
492        {"native_setup",    "()V", (void *)android_media_MediaMetadataRetriever_native_setup},
493        {"native_init",     "()V", (void *)android_media_MediaMetadataRetriever_native_init},
494};
495
496// This function only registers the native methods, and is called from
497// JNI_OnLoad in android_media_MediaPlayer.cpp
498int register_android_media_MediaMetadataRetriever(JNIEnv *env)
499{
500    return AndroidRuntime::registerNativeMethods
501        (env, kClassPathName, nativeMethods, NELEM(nativeMethods));
502}
503