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