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