android_media_MediaMetadataRetriever.cpp revision 0041b5c561a48ed8f63c4fe8ae3bff5196f68d0f
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    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)", offset);
150        }
151        if (length < 0) {
152            ALOGE("negative length (%lld)", 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    size_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
266    SkBitmap *bitmap =
267            (SkBitmap *) env->GetIntField(jBitmap, fields.nativeBitmap);
268
269    bitmap->lockPixels();
270    rotate((uint16_t*)bitmap->getPixels(),
271           (uint16_t*)((char*)videoFrame + sizeof(VideoFrame)),
272           videoFrame->mWidth,
273           videoFrame->mHeight,
274           videoFrame->mRotationAngle);
275    bitmap->unlockPixels();
276
277    if (videoFrame->mDisplayWidth  != videoFrame->mWidth ||
278        videoFrame->mDisplayHeight != videoFrame->mHeight) {
279        size_t displayWidth = videoFrame->mDisplayWidth;
280        size_t displayHeight = videoFrame->mDisplayHeight;
281        if (swapWidthAndHeight) {
282            displayWidth = videoFrame->mDisplayHeight;
283            displayHeight = videoFrame->mDisplayWidth;
284        }
285        ALOGV("Bitmap dimension is scaled from %dx%d to %dx%d",
286                width, height, displayWidth, displayHeight);
287        jobject scaledBitmap = env->CallStaticObjectMethod(fields.bitmapClazz,
288                                    fields.createScaledBitmapMethod,
289                                    jBitmap,
290                                    displayWidth,
291                                    displayHeight,
292                                    true);
293        return scaledBitmap;
294    }
295
296    return jBitmap;
297}
298
299static jbyteArray android_media_MediaMetadataRetriever_getEmbeddedPicture(
300        JNIEnv *env, jobject thiz, jint pictureType)
301{
302    ALOGV("getEmbeddedPicture: %d", pictureType);
303    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
304    if (retriever == 0) {
305        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
306        return NULL;
307    }
308    MediaAlbumArt* mediaAlbumArt = NULL;
309
310    // FIXME:
311    // Use pictureType to retrieve the intended embedded picture and also change
312    // the method name to getEmbeddedPicture().
313    sp<IMemory> albumArtMemory = retriever->extractAlbumArt();
314    if (albumArtMemory != 0) {  // cast the shared structure to a MediaAlbumArt object
315        mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->pointer());
316    }
317    if (mediaAlbumArt == NULL) {
318        ALOGE("getEmbeddedPicture: Call to getEmbeddedPicture failed.");
319        return NULL;
320    }
321
322    unsigned int len = mediaAlbumArt->mSize;
323    char* data = (char*) mediaAlbumArt + sizeof(MediaAlbumArt);
324    jbyteArray array = env->NewByteArray(len);
325    if (!array) {  // OutOfMemoryError exception has already been thrown.
326        ALOGE("getEmbeddedPicture: OutOfMemoryError is thrown.");
327    } else {
328        jbyte* bytes = env->GetByteArrayElements(array, NULL);
329        if (bytes != NULL) {
330            memcpy(bytes, data, len);
331            env->ReleaseByteArrayElements(array, bytes, 0);
332        }
333    }
334
335    // No need to delete mediaAlbumArt here
336    return array;
337}
338
339static jobject android_media_MediaMetadataRetriever_extractMetadata(JNIEnv *env, jobject thiz, jint keyCode)
340{
341    ALOGV("extractMetadata");
342    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
343    if (retriever == 0) {
344        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
345        return NULL;
346    }
347    const char* value = retriever->extractMetadata(keyCode);
348    if (!value) {
349        ALOGV("extractMetadata: Metadata is not found");
350        return NULL;
351    }
352    ALOGV("extractMetadata: value (%s) for keyCode(%d)", value, keyCode);
353    return env->NewStringUTF(value);
354}
355
356static void android_media_MediaMetadataRetriever_release(JNIEnv *env, jobject thiz)
357{
358    ALOGV("release");
359    Mutex::Autolock lock(sLock);
360    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
361    delete retriever;
362    setRetriever(env, thiz, 0);
363}
364
365static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz)
366{
367    ALOGV("native_finalize");
368    // No lock is needed, since android_media_MediaMetadataRetriever_release() is protected
369    android_media_MediaMetadataRetriever_release(env, thiz);
370}
371
372// This function gets a field ID, which in turn causes class initialization.
373// It is called from a static block in MediaMetadataRetriever, which won't run until the
374// first time an instance of this class is used.
375static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env)
376{
377    jclass clazz = env->FindClass(kClassPathName);
378    if (clazz == NULL) {
379        return;
380    }
381
382    fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
383    if (fields.context == NULL) {
384        return;
385    }
386
387    jclass bitmapClazz = env->FindClass("android/graphics/Bitmap");
388    if (bitmapClazz == NULL) {
389        return;
390    }
391    fields.bitmapClazz = (jclass) env->NewGlobalRef(bitmapClazz);
392    if (fields.bitmapClazz == NULL) {
393        return;
394    }
395    fields.createBitmapMethod =
396            env->GetStaticMethodID(fields.bitmapClazz, "createBitmap",
397                    "(IILandroid/graphics/Bitmap$Config;)"
398                    "Landroid/graphics/Bitmap;");
399    if (fields.createBitmapMethod == NULL) {
400        return;
401    }
402    fields.createScaledBitmapMethod =
403            env->GetStaticMethodID(fields.bitmapClazz, "createScaledBitmap",
404                    "(Landroid/graphics/Bitmap;IIZ)"
405                    "Landroid/graphics/Bitmap;");
406    if (fields.createScaledBitmapMethod == NULL) {
407        return;
408    }
409    fields.nativeBitmap = env->GetFieldID(fields.bitmapClazz, "mNativeBitmap", "I");
410    if (fields.nativeBitmap == NULL) {
411        return;
412    }
413
414    jclass configClazz = env->FindClass("android/graphics/Bitmap$Config");
415    if (configClazz == NULL) {
416        return;
417    }
418    fields.configClazz = (jclass) env->NewGlobalRef(configClazz);
419    if (fields.configClazz == NULL) {
420        return;
421    }
422    fields.createConfigMethod =
423            env->GetStaticMethodID(fields.configClazz, "nativeToConfig",
424                    "(I)Landroid/graphics/Bitmap$Config;");
425    if (fields.createConfigMethod == NULL) {
426        return;
427    }
428}
429
430static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz)
431{
432    ALOGV("native_setup");
433    MediaMetadataRetriever* retriever = new MediaMetadataRetriever();
434    if (retriever == 0) {
435        jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
436        return;
437    }
438    setRetriever(env, thiz, (int)retriever);
439}
440
441// JNI mapping between Java methods and native methods
442static JNINativeMethod nativeMethods[] = {
443        {
444            "_setDataSource",
445            "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
446            (void *)android_media_MediaMetadataRetriever_setDataSourceAndHeaders
447        },
448
449        {"setDataSource",   "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD},
450        {"_getFrameAtTime", "(JI)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
451        {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata},
452        {"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture},
453        {"release",         "()V", (void *)android_media_MediaMetadataRetriever_release},
454        {"native_finalize", "()V", (void *)android_media_MediaMetadataRetriever_native_finalize},
455        {"native_setup",    "()V", (void *)android_media_MediaMetadataRetriever_native_setup},
456        {"native_init",     "()V", (void *)android_media_MediaMetadataRetriever_native_init},
457};
458
459// This function only registers the native methods, and is called from
460// JNI_OnLoad in android_media_MediaPlayer.cpp
461int register_android_media_MediaMetadataRetriever(JNIEnv *env)
462{
463    return AndroidRuntime::registerNativeMethods
464        (env, kClassPathName, nativeMethods, NELEM(nativeMethods));
465}
466