android_media_MediaMetadataRetriever.cpp revision 11eab056dd0133a390169d3581edf3eef26d6a54
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 jobject android_media_MediaMetadataRetriever_getFrameAtTime(JNIEnv *env, jobject thiz, jlong timeUs, jint option)
156{
157    LOGV("getFrameAtTime: %lld us option: %d", timeUs, option);
158    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
159    if (retriever == 0) {
160        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
161        return NULL;
162    }
163
164    // Call native method to retrieve a video frame
165    VideoFrame *videoFrame = NULL;
166    sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option);
167    if (frameMemory != 0) {  // cast the shared structure to a VideoFrame object
168        videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
169    }
170    if (videoFrame == NULL) {
171        LOGE("getFrameAtTime: videoFrame is a NULL pointer");
172        return NULL;
173    }
174
175    LOGV("Dimension = %dx%d and bytes = %d",
176            videoFrame->mDisplayWidth,
177            videoFrame->mDisplayHeight,
178            videoFrame->mSize);
179
180#if USE_PRIVATE_NATIVE_BITMAP_CONSTUCTOR
181    jobject matrix = NULL;
182    if (videoFrame->mRotationAngle != 0) {
183        LOGD("Create a rotation matrix: %d degrees", videoFrame->mRotationAngle);
184        jclass matrixClazz = env->FindClass("android/graphics/Matrix");
185        if (matrixClazz == NULL) {
186            jniThrowException(env, "java/lang/RuntimeException",
187                "Can't find android/graphics/Matrix");
188            return NULL;
189        }
190        jmethodID matrixConstructor =
191            env->GetMethodID(matrixClazz, "<init>", "()V");
192        if (matrixConstructor == NULL) {
193            jniThrowException(env, "java/lang/RuntimeException",
194                "Can't find Matrix constructor");
195            return NULL;
196        }
197        matrix =
198            env->NewObject(matrixClazz, matrixConstructor);
199        if (matrix == NULL) {
200            LOGE("Could not create a Matrix object");
201            return NULL;
202        }
203
204        LOGV("Rotate the matrix: %d degrees", videoFrame->mRotationAngle);
205        jmethodID setRotateMethod =
206                env->GetMethodID(matrixClazz, "setRotate", "(F)V");
207        if (setRotateMethod == NULL) {
208            jniThrowException(env, "java/lang/RuntimeException",
209                "Can't find Matrix setRotate method");
210            return NULL;
211        }
212        env->CallVoidMethod(matrix, setRotateMethod, 1.0 * videoFrame->mRotationAngle);
213        env->DeleteLocalRef(matrixClazz);
214    }
215
216    // Create a SkBitmap to hold the pixels
217    SkBitmap *bitmap = new SkBitmap();
218    if (bitmap == NULL) {
219        LOGE("getFrameAtTime: cannot instantiate a SkBitmap object.");
220        return NULL;
221    }
222    bitmap->setConfig(SkBitmap::kRGB_565_Config, videoFrame->mDisplayWidth, videoFrame->mDisplayHeight);
223    if (!bitmap->allocPixels()) {
224        delete bitmap;
225        LOGE("failed to allocate pixel buffer");
226        return NULL;
227    }
228    memcpy((uint8_t*)bitmap->getPixels(), (uint8_t*)videoFrame + sizeof(VideoFrame), videoFrame->mSize);
229
230    // Since internally SkBitmap uses reference count to manage the reference to
231    // its pixels, it is important that the pixels (along with SkBitmap) be
232    // available after creating the Bitmap is returned to Java app.
233    jobject jSrcBitmap = env->NewObject(fields.bitmapClazz,
234            fields.bitmapConstructor, (int) bitmap, NULL, true, NULL, -1);
235
236    jobject jBitmap = env->CallStaticObjectMethod(
237                fields.bitmapClazz,
238                fields.createBitmapRotationMethod,
239                jSrcBitmap,                     // source Bitmap
240                0,                              // x
241                0,                              // y
242                videoFrame->mDisplayWidth,      // width
243                videoFrame->mDisplayHeight,     // height
244                matrix,                         // transform matrix
245                false);                         // filter
246
247#else
248
249    jobject config = env->CallStaticObjectMethod(
250                        fields.configClazz,
251                        fields.createConfigMethod,
252                        SkBitmap::kRGB_565_Config);
253
254    jobject jBitmap = env->CallStaticObjectMethod(
255                            fields.bitmapClazz,
256                            fields.createBitmapMethod,
257                            videoFrame->mDisplayWidth,
258                            videoFrame->mDisplayHeight,
259                            config);
260    SkBitmap *bitmap =
261            (SkBitmap *) env->GetIntField(jBitmap, fields.nativeBitmap);
262
263    bitmap->lockPixels();
264
265    memcpy((uint8_t*)bitmap->getPixels(),
266            (uint8_t*)videoFrame + sizeof(VideoFrame), videoFrame->mSize);
267
268    bitmap->unlockPixels();
269
270    if (videoFrame->mRotationAngle != 0) {
271        SkDevice device(*bitmap);
272        SkCanvas canvas(&device);
273        canvas.rotate((SkScalar) (videoFrame->mRotationAngle * 1.0));
274        canvas.drawBitmap(*bitmap, 0, 0);
275    }
276#endif
277    LOGV("Return a new bitmap constructed with the rotation matrix");
278    return jBitmap;
279}
280
281static jbyteArray android_media_MediaMetadataRetriever_getEmbeddedPicture(
282        JNIEnv *env, jobject thiz, jint pictureType)
283{
284    LOGV("getEmbeddedPicture: %d", pictureType);
285    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
286    if (retriever == 0) {
287        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
288        return NULL;
289    }
290    MediaAlbumArt* mediaAlbumArt = NULL;
291
292    // FIXME:
293    // Use pictureType to retrieve the intended embedded picture and also change
294    // the method name to getEmbeddedPicture().
295    sp<IMemory> albumArtMemory = retriever->extractAlbumArt();
296    if (albumArtMemory != 0) {  // cast the shared structure to a MediaAlbumArt object
297        mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->pointer());
298    }
299    if (mediaAlbumArt == NULL) {
300        LOGE("getEmbeddedPicture: Call to getEmbeddedPicture failed.");
301        return NULL;
302    }
303
304    unsigned int len = mediaAlbumArt->mSize;
305    char* data = (char*) mediaAlbumArt + sizeof(MediaAlbumArt);
306    jbyteArray array = env->NewByteArray(len);
307    if (!array) {  // OutOfMemoryError exception has already been thrown.
308        LOGE("getEmbeddedPicture: OutOfMemoryError is thrown.");
309    } else {
310        jbyte* bytes = env->GetByteArrayElements(array, NULL);
311        if (bytes != NULL) {
312            memcpy(bytes, data, len);
313            env->ReleaseByteArrayElements(array, bytes, 0);
314        }
315    }
316
317    // No need to delete mediaAlbumArt here
318    return array;
319}
320
321static jobject android_media_MediaMetadataRetriever_extractMetadata(JNIEnv *env, jobject thiz, jint keyCode)
322{
323    LOGV("extractMetadata");
324    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
325    if (retriever == 0) {
326        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
327        return NULL;
328    }
329    const char* value = retriever->extractMetadata(keyCode);
330    if (!value) {
331        LOGV("extractMetadata: Metadata is not found");
332        return NULL;
333    }
334    LOGV("extractMetadata: value (%s) for keyCode(%d)", value, keyCode);
335    return env->NewStringUTF(value);
336}
337
338static void android_media_MediaMetadataRetriever_release(JNIEnv *env, jobject thiz)
339{
340    LOGV("release");
341    Mutex::Autolock lock(sLock);
342    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
343    delete retriever;
344    setRetriever(env, thiz, 0);
345}
346
347static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz)
348{
349    LOGV("native_finalize");
350    // No lock is needed, since android_media_MediaMetadataRetriever_release() is protected
351    android_media_MediaMetadataRetriever_release(env, thiz);
352}
353
354// This function gets a field ID, which in turn causes class initialization.
355// It is called from a static block in MediaMetadataRetriever, which won't run until the
356// first time an instance of this class is used.
357static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env)
358{
359    jclass clazz = env->FindClass(kClassPathName);
360    if (clazz == NULL) {
361        jniThrowException(env, "java/lang/RuntimeException", "Can't find android/media/MediaMetadataRetriever");
362        return;
363    }
364
365    fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
366    if (fields.context == NULL) {
367        jniThrowException(env, "java/lang/RuntimeException", "Can't find MediaMetadataRetriever.mNativeContext");
368        return;
369    }
370
371    fields.bitmapClazz = env->FindClass("android/graphics/Bitmap");
372    if (fields.bitmapClazz == NULL) {
373        jniThrowException(env, "java/lang/RuntimeException", "Can't find android/graphics/Bitmap");
374        return;
375    }
376#if USE_PRIVATE_NATIVE_BITMAP_CONSTUCTOR
377    fields.bitmapConstructor = env->GetMethodID(fields.bitmapClazz, "<init>", "(I[BZ[BI)V");
378    if (fields.bitmapConstructor == NULL) {
379        jniThrowException(env, "java/lang/RuntimeException", "Can't find Bitmap constructor");
380        return;
381    }
382    fields.createBitmapRotationMethod =
383            env->GetStaticMethodID(fields.bitmapClazz, "createBitmap",
384                    "(Landroid/graphics/Bitmap;IIIILandroid/graphics/Matrix;Z)"
385                    "Landroid/graphics/Bitmap;");
386    if (fields.createBitmapRotationMethod == NULL) {
387        jniThrowException(env, "java/lang/RuntimeException",
388                "Can't find Bitmap.createBitmap method");
389        return;
390    }
391#else
392    fields.createBitmapMethod =
393            env->GetStaticMethodID(fields.bitmapClazz, "createBitmap",
394                    "(IILandroid/graphics/Bitmap$Config;)"
395                    "Landroid/graphics/Bitmap;");
396    if (fields.createBitmapMethod == NULL) {
397        jniThrowException(env, "java/lang/RuntimeException",
398                "Can't find Bitmap.createBitmap(int, int, Config)  method");
399        return;
400    }
401    fields.nativeBitmap = env->GetFieldID(fields.bitmapClazz, "mNativeBitmap", "I");
402    if (fields.nativeBitmap == NULL) {
403        jniThrowException(env, "java/lang/RuntimeException",
404                "Can't find Bitmap.mNativeBitmap field");
405    }
406
407    fields.configClazz = env->FindClass("android/graphics/Bitmap$Config");
408    if (fields.configClazz == NULL) {
409        jniThrowException(env, "java/lang/RuntimeException",
410                               "Can't find Bitmap$Config class");
411        return;
412    }
413    fields.createConfigMethod =
414            env->GetStaticMethodID(fields.configClazz, "nativeToConfig",
415                    "(I)Landroid/graphics/Bitmap$Config;");
416    if (fields.createConfigMethod == NULL) {
417        jniThrowException(env, "java/lang/RuntimeException",
418                "Can't find Bitmap$Config.nativeToConfig(int)  method");
419        return;
420    }
421#endif
422}
423
424static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz)
425{
426    LOGV("native_setup");
427    MediaMetadataRetriever* retriever = new MediaMetadataRetriever();
428    if (retriever == 0) {
429        jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
430        return;
431    }
432    setRetriever(env, thiz, (int)retriever);
433}
434
435// JNI mapping between Java methods and native methods
436static JNINativeMethod nativeMethods[] = {
437        {"setDataSource",   "(Ljava/lang/String;)V", (void *)android_media_MediaMetadataRetriever_setDataSource},
438        {"setDataSource",   "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD},
439        {"_getFrameAtTime", "(JI)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
440        {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata},
441        {"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture},
442        {"release",         "()V", (void *)android_media_MediaMetadataRetriever_release},
443        {"native_finalize", "()V", (void *)android_media_MediaMetadataRetriever_native_finalize},
444        {"native_setup",    "()V", (void *)android_media_MediaMetadataRetriever_native_setup},
445        {"native_init",     "()V", (void *)android_media_MediaMetadataRetriever_native_init},
446};
447
448// This function only registers the native methods, and is called from
449// JNI_OnLoad in android_media_MediaPlayer.cpp
450int register_android_media_MediaMetadataRetriever(JNIEnv *env)
451{
452    return AndroidRuntime::registerNativeMethods
453        (env, kClassPathName, nativeMethods, NELEM(nativeMethods));
454}
455