android_media_MediaMetadataRetriever.cpp revision 0e4b535b439f2c8bd4f7ba820f81bf74336bae76
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
26// Please do not enable "USE_PRIVATE_NATIVE_BITMAP_CONSTUCTOR"
27// This mode will be removed and it is kept here for
28// convenient comparsion just in case. Will be removed soon.
29// Tests show that this mode is also ~30ms slower,
30// when rotation is involved.
31#define USE_PRIVATE_NATIVE_BITMAP_CONSTUCTOR 0
32
33#if (!USE_PRIVATE_NATIVE_BITMAP_CONSTUCTOR)
34#include <core/SkCanvas.h>
35#include <core/SkDevice.h>
36#include <core/SkScalar.h>
37#endif
38
39#include <media/mediametadataretriever.h>
40#include <private/media/VideoFrame.h>
41
42#include "jni.h"
43#include "JNIHelp.h"
44#include "android_runtime/AndroidRuntime.h"
45
46
47using namespace android;
48
49struct fields_t {
50    jfieldID context;
51    jclass bitmapClazz;
52#if USE_PRIVATE_NATIVE_BITMAP_CONSTUCTOR
53    jmethodID bitmapConstructor;
54    jmethodID createBitmapRotationMethod;
55#else
56    jfieldID nativeBitmap;
57    jmethodID createBitmapMethod;
58    jclass configClazz;
59    jmethodID createConfigMethod;
60#endif
61};
62
63static fields_t fields;
64static Mutex sLock;
65static const char* const kClassPathName = "android/media/MediaMetadataRetriever";
66
67static void process_media_retriever_call(JNIEnv *env, status_t opStatus, const char* exception, const char *message)
68{
69    if (opStatus == (status_t) INVALID_OPERATION) {
70        jniThrowException(env, "java/lang/IllegalStateException", NULL);
71    } else if (opStatus != (status_t) OK) {
72        if (strlen(message) > 230) {
73            // If the message is too long, don't bother displaying the status code.
74            jniThrowException( env, exception, message);
75        } else {
76            char msg[256];
77            // Append the status code to the message.
78            sprintf(msg, "%s: status = 0x%X", message, opStatus);
79            jniThrowException( env, exception, msg);
80        }
81    }
82}
83
84static MediaMetadataRetriever* getRetriever(JNIEnv* env, jobject thiz)
85{
86    // No lock is needed, since it is called internally by other methods that are protected
87    MediaMetadataRetriever* retriever = (MediaMetadataRetriever*) env->GetIntField(thiz, fields.context);
88    return retriever;
89}
90
91static void setRetriever(JNIEnv* env, jobject thiz, int retriever)
92{
93    // No lock is needed, since it is called internally by other methods that are protected
94    MediaMetadataRetriever *old = (MediaMetadataRetriever*) env->GetIntField(thiz, fields.context);
95    env->SetIntField(thiz, fields.context, retriever);
96}
97
98static void android_media_MediaMetadataRetriever_setDataSource(JNIEnv *env, jobject thiz, jstring path)
99{
100    LOGV("setDataSource");
101    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
102    if (retriever == 0) {
103        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
104        return;
105    }
106    if (!path) {
107        jniThrowException(env, "java/lang/IllegalArgumentException", "Null pointer");
108        return;
109    }
110
111    const char *pathStr = env->GetStringUTFChars(path, NULL);
112    if (!pathStr) {  // OutOfMemoryError exception already thrown
113        return;
114    }
115
116    // Don't let somebody trick us in to reading some random block of memory
117    if (strncmp("mem://", pathStr, 6) == 0) {
118        jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid pathname");
119        return;
120    }
121
122    process_media_retriever_call(env, retriever->setDataSource(pathStr), "java/lang/RuntimeException", "setDataSource failed");
123    env->ReleaseStringUTFChars(path, pathStr);
124}
125
126static void android_media_MediaMetadataRetriever_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
127{
128    LOGV("setDataSource");
129    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
130    if (retriever == 0) {
131        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
132        return;
133    }
134    if (!fileDescriptor) {
135        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
136        return;
137    }
138    int fd = getParcelFileDescriptorFD(env, fileDescriptor);
139    if (offset < 0 || length < 0 || fd < 0) {
140        if (offset < 0) {
141            LOGE("negative offset (%lld)", offset);
142        }
143        if (length < 0) {
144            LOGE("negative length (%lld)", length);
145        }
146        if (fd < 0) {
147            LOGE("invalid file descriptor");
148        }
149        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
150        return;
151    }
152    process_media_retriever_call(env, retriever->setDataSource(fd, offset, length), "java/lang/RuntimeException", "setDataSource failed");
153}
154
155static void android_media_MediaMetadataRetriever_setMode(JNIEnv *env, jobject thiz, jint mode)
156{
157    LOGV("setMode");
158    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
159    if (retriever == 0) {
160        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
161        return;
162    }
163    process_media_retriever_call(env, retriever->setMode(mode), "java/lang/RuntimeException", "setMode failed");
164}
165
166static jobject android_media_MediaMetadataRetriever_getFrameAtTime(JNIEnv *env, jobject thiz, jlong timeUs, jint option)
167{
168    LOGV("getFrameAtTime: %lld us option: %d", timeUs, option);
169    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
170    if (retriever == 0) {
171        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
172        return NULL;
173    }
174
175    // Call native method to retrieve a video frame
176    VideoFrame *videoFrame = NULL;
177    sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option);
178    if (frameMemory != 0) {  // cast the shared structure to a VideoFrame object
179        videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
180    }
181    if (videoFrame == NULL) {
182        LOGE("getFrameAtTime: videoFrame is a NULL pointer");
183        return NULL;
184    }
185
186    LOGV("Dimension = %dx%d and bytes = %d",
187            videoFrame->mDisplayWidth,
188            videoFrame->mDisplayHeight,
189            videoFrame->mSize);
190
191#if USE_PRIVATE_NATIVE_BITMAP_CONSTUCTOR
192    jobject matrix = NULL;
193    if (videoFrame->mRotationAngle != 0) {
194        LOGD("Create a rotation matrix: %d degrees", videoFrame->mRotationAngle);
195        jclass matrixClazz = env->FindClass("android/graphics/Matrix");
196        if (matrixClazz == NULL) {
197            jniThrowException(env, "java/lang/RuntimeException",
198                "Can't find android/graphics/Matrix");
199            return NULL;
200        }
201        jmethodID matrixConstructor =
202            env->GetMethodID(matrixClazz, "<init>", "()V");
203        if (matrixConstructor == NULL) {
204            jniThrowException(env, "java/lang/RuntimeException",
205                "Can't find Matrix constructor");
206            return NULL;
207        }
208        matrix =
209            env->NewObject(matrixClazz, matrixConstructor);
210        if (matrix == NULL) {
211            LOGE("Could not create a Matrix object");
212            return NULL;
213        }
214
215        LOGV("Rotate the matrix: %d degrees", videoFrame->mRotationAngle);
216        jmethodID setRotateMethod =
217                env->GetMethodID(matrixClazz, "setRotate", "(F)V");
218        if (setRotateMethod == NULL) {
219            jniThrowException(env, "java/lang/RuntimeException",
220                "Can't find Matrix setRotate method");
221            return NULL;
222        }
223        env->CallVoidMethod(matrix, setRotateMethod, 1.0 * videoFrame->mRotationAngle);
224        env->DeleteLocalRef(matrixClazz);
225    }
226
227    // Create a SkBitmap to hold the pixels
228    SkBitmap *bitmap = new SkBitmap();
229    if (bitmap == NULL) {
230        LOGE("getFrameAtTime: cannot instantiate a SkBitmap object.");
231        return NULL;
232    }
233    bitmap->setConfig(SkBitmap::kRGB_565_Config, videoFrame->mDisplayWidth, videoFrame->mDisplayHeight);
234    if (!bitmap->allocPixels()) {
235        delete bitmap;
236        LOGE("failed to allocate pixel buffer");
237        return NULL;
238    }
239    memcpy((uint8_t*)bitmap->getPixels(), (uint8_t*)videoFrame + sizeof(VideoFrame), videoFrame->mSize);
240
241    // Since internally SkBitmap uses reference count to manage the reference to
242    // its pixels, it is important that the pixels (along with SkBitmap) be
243    // available after creating the Bitmap is returned to Java app.
244    jobject jSrcBitmap = env->NewObject(fields.bitmapClazz,
245            fields.bitmapConstructor, (int) bitmap, NULL, true, NULL, -1);
246
247    jobject jBitmap = env->CallStaticObjectMethod(
248                fields.bitmapClazz,
249                fields.createBitmapRotationMethod,
250                jSrcBitmap,                     // source Bitmap
251                0,                              // x
252                0,                              // y
253                videoFrame->mDisplayWidth,      // width
254                videoFrame->mDisplayHeight,     // height
255                matrix,                         // transform matrix
256                false);                         // filter
257
258#else
259
260    jobject config = env->CallStaticObjectMethod(
261                        fields.configClazz,
262                        fields.createConfigMethod,
263                        SkBitmap::kRGB_565_Config);
264
265    jobject jBitmap = env->CallStaticObjectMethod(
266                            fields.bitmapClazz,
267                            fields.createBitmapMethod,
268                            videoFrame->mDisplayWidth,
269                            videoFrame->mDisplayHeight,
270                            config);
271    SkBitmap *bitmap =
272            (SkBitmap *) env->GetIntField(jBitmap, fields.nativeBitmap);
273
274    bitmap->lockPixels();
275
276    memcpy((uint8_t*)bitmap->getPixels(),
277            (uint8_t*)videoFrame + sizeof(VideoFrame), videoFrame->mSize);
278
279    bitmap->unlockPixels();
280
281    if (videoFrame->mRotationAngle != 0) {
282        SkDevice device(*bitmap);
283        SkCanvas canvas(&device);
284        canvas.rotate((SkScalar) (videoFrame->mRotationAngle * 1.0));
285        canvas.drawBitmap(*bitmap, 0, 0);
286    }
287#endif
288    LOGV("Return a new bitmap constructed with the rotation matrix");
289    return jBitmap;
290}
291
292static jbyteArray android_media_MediaMetadataRetriever_extractAlbumArt(JNIEnv *env, jobject thiz)
293{
294    LOGV("extractAlbumArt");
295    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
296    if (retriever == 0) {
297        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
298        return NULL;
299    }
300    MediaAlbumArt* mediaAlbumArt = NULL;
301    sp<IMemory> albumArtMemory = retriever->extractAlbumArt();
302    if (albumArtMemory != 0) {  // cast the shared structure to a MediaAlbumArt object
303        mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->pointer());
304    }
305    if (mediaAlbumArt == NULL) {
306        LOGE("extractAlbumArt: Call to extractAlbumArt failed.");
307        return NULL;
308    }
309
310    unsigned int len = mediaAlbumArt->mSize;
311    char* data = (char*) mediaAlbumArt + sizeof(MediaAlbumArt);
312    jbyteArray array = env->NewByteArray(len);
313    if (!array) {  // OutOfMemoryError exception has already been thrown.
314        LOGE("extractAlbumArt: OutOfMemoryError is thrown.");
315    } else {
316        jbyte* bytes = env->GetByteArrayElements(array, NULL);
317        if (bytes != NULL) {
318            memcpy(bytes, data, len);
319            env->ReleaseByteArrayElements(array, bytes, 0);
320        }
321    }
322
323    // No need to delete mediaAlbumArt here
324    return array;
325}
326
327static jobject android_media_MediaMetadataRetriever_extractMetadata(JNIEnv *env, jobject thiz, jint keyCode)
328{
329    LOGV("extractMetadata");
330    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
331    if (retriever == 0) {
332        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
333        return NULL;
334    }
335    const char* value = retriever->extractMetadata(keyCode);
336    if (!value) {
337        LOGV("extractMetadata: Metadata is not found");
338        return NULL;
339    }
340    LOGV("extractMetadata: value (%s) for keyCode(%d)", value, keyCode);
341    return env->NewStringUTF(value);
342}
343
344static void android_media_MediaMetadataRetriever_release(JNIEnv *env, jobject thiz)
345{
346    LOGV("release");
347    Mutex::Autolock lock(sLock);
348    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
349    delete retriever;
350    setRetriever(env, thiz, 0);
351}
352
353static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz)
354{
355    LOGV("native_finalize");
356    // No lock is needed, since android_media_MediaMetadataRetriever_release() is protected
357    android_media_MediaMetadataRetriever_release(env, thiz);
358}
359
360// This function gets a field ID, which in turn causes class initialization.
361// It is called from a static block in MediaMetadataRetriever, which won't run until the
362// first time an instance of this class is used.
363static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env)
364{
365    jclass clazz = env->FindClass(kClassPathName);
366    if (clazz == NULL) {
367        jniThrowException(env, "java/lang/RuntimeException", "Can't find android/media/MediaMetadataRetriever");
368        return;
369    }
370
371    fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
372    if (fields.context == NULL) {
373        jniThrowException(env, "java/lang/RuntimeException", "Can't find MediaMetadataRetriever.mNativeContext");
374        return;
375    }
376
377    fields.bitmapClazz = env->FindClass("android/graphics/Bitmap");
378    if (fields.bitmapClazz == NULL) {
379        jniThrowException(env, "java/lang/RuntimeException", "Can't find android/graphics/Bitmap");
380        return;
381    }
382#if USE_PRIVATE_NATIVE_BITMAP_CONSTUCTOR
383    fields.bitmapConstructor = env->GetMethodID(fields.bitmapClazz, "<init>", "(I[BZ[BI)V");
384    if (fields.bitmapConstructor == NULL) {
385        jniThrowException(env, "java/lang/RuntimeException", "Can't find Bitmap constructor");
386        return;
387    }
388    fields.createBitmapRotationMethod =
389            env->GetStaticMethodID(fields.bitmapClazz, "createBitmap",
390                    "(Landroid/graphics/Bitmap;IIIILandroid/graphics/Matrix;Z)"
391                    "Landroid/graphics/Bitmap;");
392    if (fields.createBitmapRotationMethod == NULL) {
393        jniThrowException(env, "java/lang/RuntimeException",
394                "Can't find Bitmap.createBitmap method");
395        return;
396    }
397#else
398    fields.createBitmapMethod =
399            env->GetStaticMethodID(fields.bitmapClazz, "createBitmap",
400                    "(IILandroid/graphics/Bitmap$Config;)"
401                    "Landroid/graphics/Bitmap;");
402    if (fields.createBitmapMethod == NULL) {
403        jniThrowException(env, "java/lang/RuntimeException",
404                "Can't find Bitmap.createBitmap(int, int, Config)  method");
405        return;
406    }
407    fields.nativeBitmap = env->GetFieldID(fields.bitmapClazz, "mNativeBitmap", "I");
408    if (fields.nativeBitmap == NULL) {
409        jniThrowException(env, "java/lang/RuntimeException",
410                "Can't find Bitmap.mNativeBitmap field");
411    }
412
413    fields.configClazz = env->FindClass("android/graphics/Bitmap$Config");
414    if (fields.configClazz == NULL) {
415        jniThrowException(env, "java/lang/RuntimeException",
416                               "Can't find Bitmap$Config class");
417        return;
418    }
419    fields.createConfigMethod =
420            env->GetStaticMethodID(fields.configClazz, "nativeToConfig",
421                    "(I)Landroid/graphics/Bitmap$Config;");
422    if (fields.createConfigMethod == NULL) {
423        jniThrowException(env, "java/lang/RuntimeException",
424                "Can't find Bitmap$Config.nativeToConfig(int)  method");
425        return;
426    }
427#endif
428}
429
430static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz)
431{
432    LOGV("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        {"setDataSource",   "(Ljava/lang/String;)V", (void *)android_media_MediaMetadataRetriever_setDataSource},
444        {"setDataSource",   "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD},
445        {"setMode",         "(I)V", (void *)android_media_MediaMetadataRetriever_setMode},
446        {"_getFrameAtTime", "(JI)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
447        {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata},
448        {"extractAlbumArt", "()[B", (void *)android_media_MediaMetadataRetriever_extractAlbumArt},
449        {"release",         "()V", (void *)android_media_MediaMetadataRetriever_release},
450        {"native_finalize", "()V", (void *)android_media_MediaMetadataRetriever_native_finalize},
451        {"native_setup",    "()V", (void *)android_media_MediaMetadataRetriever_native_setup},
452        {"native_init",     "()V", (void *)android_media_MediaMetadataRetriever_native_init},
453};
454
455// This function only registers the native methods, and is called from
456// JNI_OnLoad in android_media_MediaPlayer.cpp
457int register_android_media_MediaMetadataRetriever(JNIEnv *env)
458{
459    return AndroidRuntime::registerNativeMethods
460        (env, kClassPathName, nativeMethods, NELEM(nativeMethods));
461}
462