android_media_MediaMetadataRetriever.cpp revision c847a4835b2233d28f6479deeed0592d267ee7bd
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->GetLongField(thiz, fields.context);
71    return retriever;
72}
73
74static void setRetriever(JNIEnv* env, jobject thiz, MediaMetadataRetriever* retriever)
75{
76    // No lock is needed, since it is called internally by other methods that are protected
77    MediaMetadataRetriever *old = (MediaMetadataRetriever*) env->GetLongField(thiz, fields.context);
78    env->SetLongField(thiz, fields.context, (jlong) retriever);
79}
80
81static void
82android_media_MediaMetadataRetriever_setDataSourceAndHeaders(
83        JNIEnv *env, jobject thiz, jstring path,
84        jobjectArray keys, jobjectArray values) {
85
86    ALOGV("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
134static void android_media_MediaMetadataRetriever_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
135{
136    ALOGV("setDataSource");
137    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
138    if (retriever == 0) {
139        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
140        return;
141    }
142    if (!fileDescriptor) {
143        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
144        return;
145    }
146    int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
147    if (offset < 0 || length < 0 || fd < 0) {
148        if (offset < 0) {
149            ALOGE("negative offset (%lld)", (long long)offset);
150        }
151        if (length < 0) {
152            ALOGE("negative length (%lld)", (long long)length);
153        }
154        if (fd < 0) {
155            ALOGE("invalid file descriptor");
156        }
157        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
158        return;
159    }
160    process_media_retriever_call(env, retriever->setDataSource(fd, offset, length), "java/lang/RuntimeException", "setDataSource failed");
161}
162
163template<typename T>
164static void rotate0(T* dst, const T* src, size_t width, size_t height)
165{
166    memcpy(dst, src, width * height * sizeof(T));
167}
168
169template<typename T>
170static void rotate90(T* dst, const T* src, size_t width, size_t height)
171{
172    for (size_t i = 0; i < height; ++i) {
173        for (size_t j = 0; j < width; ++j) {
174            dst[j * height + height - 1 - i] = src[i * width + j];
175        }
176    }
177}
178
179template<typename T>
180static void rotate180(T* dst, const T* src, size_t width, size_t height)
181{
182    for (size_t i = 0; i < height; ++i) {
183        for (size_t j = 0; j < width; ++j) {
184            dst[(height - 1 - i) * width + width - 1 - j] = src[i * width + j];
185        }
186    }
187}
188
189template<typename T>
190static void rotate270(T* dst, const T* src, size_t width, size_t height)
191{
192    for (size_t i = 0; i < height; ++i) {
193        for (size_t j = 0; j < width; ++j) {
194            dst[(width - 1 - j) * height + i] = src[i * width + j];
195        }
196    }
197}
198
199template<typename T>
200static void rotate(T *dst, const T *src, size_t width, size_t height, int angle)
201{
202    switch (angle) {
203        case 0:
204            rotate0(dst, src, width, height);
205            break;
206        case 90:
207            rotate90(dst, src, width, height);
208            break;
209        case 180:
210            rotate180(dst, src, width, height);
211            break;
212        case 270:
213            rotate270(dst, src, width, height);
214            break;
215    }
216}
217
218static jobject android_media_MediaMetadataRetriever_getFrameAtTime(JNIEnv *env, jobject thiz, jlong timeUs, jint option)
219{
220    ALOGV("getFrameAtTime: %lld us option: %d", timeUs, option);
221    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
222    if (retriever == 0) {
223        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
224        return NULL;
225    }
226
227    // Call native method to retrieve a video frame
228    VideoFrame *videoFrame = NULL;
229    sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option);
230    if (frameMemory != 0) {  // cast the shared structure to a VideoFrame object
231        videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
232    }
233    if (videoFrame == NULL) {
234        ALOGE("getFrameAtTime: videoFrame is a NULL pointer");
235        return NULL;
236    }
237
238    ALOGV("Dimension = %dx%d and bytes = %d",
239            videoFrame->mDisplayWidth,
240            videoFrame->mDisplayHeight,
241            videoFrame->mSize);
242
243    jobject config = env->CallStaticObjectMethod(
244                        fields.configClazz,
245                        fields.createConfigMethod,
246                        SkBitmap::kRGB_565_Config);
247
248    uint32_t width, height;
249    bool swapWidthAndHeight = false;
250    if (videoFrame->mRotationAngle == 90 || videoFrame->mRotationAngle == 270) {
251        width = videoFrame->mHeight;
252        height = videoFrame->mWidth;
253        swapWidthAndHeight = true;
254    } else {
255        width = videoFrame->mWidth;
256        height = videoFrame->mHeight;
257    }
258
259    jobject jBitmap = env->CallStaticObjectMethod(
260                            fields.bitmapClazz,
261                            fields.createBitmapMethod,
262                            width,
263                            height,
264                            config);
265    if (jBitmap == NULL) {
266        if (env->ExceptionCheck()) {
267            env->ExceptionClear();
268        }
269        ALOGE("getFrameAtTime: create Bitmap failed!");
270        return NULL;
271    }
272
273    SkBitmap *bitmap =
274            (SkBitmap *) env->GetLongField(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        uint32_t displayWidth = videoFrame->mDisplayWidth;
287        uint32_t displayHeight = videoFrame->mDisplayHeight;
288        if (swapWidthAndHeight) {
289            displayWidth = videoFrame->mDisplayHeight;
290            displayHeight = videoFrame->mDisplayWidth;
291        }
292        ALOGV("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    ALOGV("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        ALOGE("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        ALOGE("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    ALOGV("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        ALOGV("extractMetadata: Metadata is not found");
357        return NULL;
358    }
359    ALOGV("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    ALOGV("release");
366    Mutex::Autolock lock(sLock);
367    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
368    delete retriever;
369    setRetriever(env, thiz, (MediaMetadataRetriever*) 0);
370}
371
372static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz)
373{
374    ALOGV("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", "J");
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", "J");
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    ALOGV("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, retriever);
446}
447
448// JNI mapping between Java methods and native methods
449static JNINativeMethod nativeMethods[] = {
450        {
451            "_setDataSource",
452            "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
453            (void *)android_media_MediaMetadataRetriever_setDataSourceAndHeaders
454        },
455
456        {"setDataSource",   "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD},
457        {"_getFrameAtTime", "(JI)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
458        {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata},
459        {"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture},
460        {"release",         "()V", (void *)android_media_MediaMetadataRetriever_release},
461        {"native_finalize", "()V", (void *)android_media_MediaMetadataRetriever_native_finalize},
462        {"native_setup",    "()V", (void *)android_media_MediaMetadataRetriever_native_setup},
463        {"native_init",     "()V", (void *)android_media_MediaMetadataRetriever_native_init},
464};
465
466// This function only registers the native methods, and is called from
467// JNI_OnLoad in android_media_MediaPlayer.cpp
468int register_android_media_MediaMetadataRetriever(JNIEnv *env)
469{
470    return AndroidRuntime::registerNativeMethods
471        (env, kClassPathName, nativeMethods, NELEM(nativeMethods));
472}
473