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