Merge "media: Add scaled video thumbnail extractor api." into oc-mr1-dev
diff --git a/api/current.txt b/api/current.txt
index 3b868ad..e299720 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -22742,6 +22742,7 @@
     method public android.graphics.Bitmap getFrameAtTime(long, int);
     method public android.graphics.Bitmap getFrameAtTime(long);
     method public android.graphics.Bitmap getFrameAtTime();
+    method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int);
     method public void release();
     method public void setDataSource(java.lang.String) throws java.lang.IllegalArgumentException;
     method public void setDataSource(java.lang.String, java.util.Map<java.lang.String, java.lang.String>) throws java.lang.IllegalArgumentException;
diff --git a/api/system-current.txt b/api/system-current.txt
index fc1b6ed..1b292cc 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -24686,6 +24686,7 @@
     method public android.graphics.Bitmap getFrameAtTime(long, int);
     method public android.graphics.Bitmap getFrameAtTime(long);
     method public android.graphics.Bitmap getFrameAtTime();
+    method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int);
     method public void release();
     method public void setDataSource(java.lang.String) throws java.lang.IllegalArgumentException;
     method public void setDataSource(java.lang.String, java.util.Map<java.lang.String, java.lang.String>) throws java.lang.IllegalArgumentException;
diff --git a/api/test-current.txt b/api/test-current.txt
index f50ef55..7aeb52e 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -22878,6 +22878,7 @@
     method public android.graphics.Bitmap getFrameAtTime(long, int);
     method public android.graphics.Bitmap getFrameAtTime(long);
     method public android.graphics.Bitmap getFrameAtTime();
+    method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int);
     method public void release();
     method public void setDataSource(java.lang.String) throws java.lang.IllegalArgumentException;
     method public void setDataSource(java.lang.String, java.util.Map<java.lang.String, java.lang.String>) throws java.lang.IllegalArgumentException;
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 7dd70d4..6664456 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -246,7 +246,7 @@
      * {@link #OPTION_CLOSEST} often has larger performance overhead compared
      * to the other options if there is no sync frame located at timeUs.
      *
-     * @return A Bitmap containing a representative video frame, which 
+     * @return A Bitmap containing a representative video frame, which
      *         can be null, if such a frame cannot be retrieved.
      */
     public Bitmap getFrameAtTime(long timeUs, int option) {
@@ -255,7 +255,58 @@
             throw new IllegalArgumentException("Unsupported option: " + option);
         }
 
-        return _getFrameAtTime(timeUs, option);
+        return _getFrameAtTime(timeUs, option, -1 /*dst_width*/, -1 /*dst_height*/);
+    }
+
+    /**
+     * Retrieve a video frame near a given timestamp scaled to a desired size.
+     * Call this method after setDataSource(). This method finds a representative
+     * frame close to the given time position by considering the given option
+     * if possible, and returns it as a bitmap with same aspect ratio as the source
+     * while scaling it so that it fits into the desired size of dst_width by dst_height.
+     * This is useful for generating a thumbnail for an input data source or just to
+     * obtain a scaled frame at the given time position.
+     *
+     * @param timeUs The time position in microseconds where the frame will be retrieved.
+     * When retrieving the frame at the given time position, there is no
+     * guarantee that the data source has a frame located at the position.
+     * When this happens, a frame nearby will be returned. If timeUs is
+     * negative, time position and option will ignored, and any frame
+     * that the implementation considers as representative may be returned.
+     *
+     * @param option a hint on how the frame is found. Use
+     * {@link #OPTION_PREVIOUS_SYNC} if one wants to retrieve a sync frame
+     * that has a timestamp earlier than or the same as timeUs. Use
+     * {@link #OPTION_NEXT_SYNC} if one wants to retrieve a sync frame
+     * that has a timestamp later than or the same as timeUs. Use
+     * {@link #OPTION_CLOSEST_SYNC} if one wants to retrieve a sync frame
+     * that has a timestamp closest to or the same as timeUs. Use
+     * {@link #OPTION_CLOSEST} if one wants to retrieve a frame that may
+     * or may not be a sync frame but is closest to or the same as timeUs.
+     * {@link #OPTION_CLOSEST} often has larger performance overhead compared
+     * to the other options if there is no sync frame located at timeUs.
+     *
+     * @param dst_width expected output bitmap width
+     * @param dst_height expected output bitmap height
+     * @return A Bitmap of size not larger than dst_width by dst_height containing a
+     *         scaled video frame, which can be null, if such a frame cannot be retrieved.
+     * @throws IllegalArgumentException if passed in invalid option or width by height
+     *         is less than or equal to 0.
+     */
+    public Bitmap getScaledFrameAtTime(
+            long timeUs, int option, int dst_width, int dst_height) {
+        if (option < OPTION_PREVIOUS_SYNC ||
+            option > OPTION_CLOSEST) {
+            throw new IllegalArgumentException("Unsupported option: " + option);
+        }
+        if (dst_width <= 0) {
+            throw new IllegalArgumentException("Invalid width: " + dst_width);
+        }
+        if (dst_height <= 0) {
+            throw new IllegalArgumentException("Invalid height: " + dst_height);
+        }
+
+        return _getFrameAtTime(timeUs, option, dst_width, dst_height);
     }
 
     /**
@@ -273,8 +324,8 @@
      * negative, time position and option will ignored, and any frame
      * that the implementation considers as representative may be returned.
      *
-     * @return A Bitmap containing a representative video frame, which
-     *         can be null, if such a frame cannot be retrieved.
+     * @return A Bitmap of size dst_widthxdst_height containing a representative
+     *         video frame, which can be null, if such a frame cannot be retrieved.
      *
      * @see #getFrameAtTime(long, int)
      */
@@ -297,17 +348,16 @@
      * @see #getFrameAtTime(long, int)
      */
     public Bitmap getFrameAtTime() {
-        return getFrameAtTime(-1, OPTION_CLOSEST_SYNC);
+        return _getFrameAtTime(-1, OPTION_CLOSEST_SYNC, -1 /*dst_width*/, -1 /*dst_height*/);
     }
 
-    private native Bitmap _getFrameAtTime(long timeUs, int option);
+    private native Bitmap _getFrameAtTime(long timeUs, int option, int width, int height);
 
-    
     /**
      * Call this method after setDataSource(). This method finds the optional
      * graphic or album/cover art associated associated with the data source. If
      * there are more than one pictures, (any) one of them is returned.
-     * 
+     *
      * @return null if no such graphic is found.
      */
     public byte[] getEmbeddedPicture() {
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index 71f3856..4659ae1 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -244,9 +244,11 @@
     }
 }
 
-static jobject android_media_MediaMetadataRetriever_getFrameAtTime(JNIEnv *env, jobject thiz, jlong timeUs, jint option)
+static jobject android_media_MediaMetadataRetriever_getFrameAtTime(
+        JNIEnv *env, jobject thiz, jlong timeUs, jint option, jint dst_width, jint dst_height)
 {
-    ALOGV("getFrameAtTime: %lld us option: %d", (long long)timeUs, option);
+    ALOGV("getFrameAtTime: %lld us option: %d dst width: %d heigh: %d",
+            (long long)timeUs, option, dst_width, dst_height);
     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
     if (retriever == 0) {
         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
@@ -274,15 +276,19 @@
                         fields.createConfigMethod,
                         GraphicsJNI::colorTypeToLegacyBitmapConfig(kRGB_565_SkColorType));
 
-    uint32_t width, height;
+    uint32_t width, height, displayWidth, displayHeight;
     bool swapWidthAndHeight = false;
     if (videoFrame->mRotationAngle == 90 || videoFrame->mRotationAngle == 270) {
         width = videoFrame->mHeight;
         height = videoFrame->mWidth;
         swapWidthAndHeight = true;
+        displayWidth = videoFrame->mDisplayHeight;
+        displayHeight = videoFrame->mDisplayWidth;
     } else {
         width = videoFrame->mWidth;
         height = videoFrame->mHeight;
+        displayWidth = videoFrame->mDisplayWidth;
+        displayHeight = videoFrame->mDisplayHeight;
     }
 
     jobject jBitmap = env->CallStaticObjectMethod(
@@ -308,22 +314,26 @@
            videoFrame->mHeight,
            videoFrame->mRotationAngle);
 
-    if (videoFrame->mDisplayWidth  != videoFrame->mWidth ||
-        videoFrame->mDisplayHeight != videoFrame->mHeight) {
-        uint32_t displayWidth = videoFrame->mDisplayWidth;
-        uint32_t displayHeight = videoFrame->mDisplayHeight;
-        if (swapWidthAndHeight) {
-            displayWidth = videoFrame->mDisplayHeight;
-            displayHeight = videoFrame->mDisplayWidth;
-        }
+    if (dst_width <= 0 || dst_height <= 0) {
+        dst_width = displayWidth;
+        dst_height = displayHeight;
+    } else {
+        float factor = std::min((float)dst_width / (float)displayWidth,
+                (float)dst_height / (float)displayHeight);
+        dst_width = std::round(displayWidth * factor);
+        dst_height = std::round(displayHeight * factor);
+    }
+
+    if ((uint32_t)dst_width != videoFrame->mWidth ||
+        (uint32_t)dst_height != videoFrame->mHeight) {
         ALOGV("Bitmap dimension is scaled from %dx%d to %dx%d",
-                width, height, displayWidth, displayHeight);
+                width, height, dst_width, dst_height);
         jobject scaledBitmap = env->CallStaticObjectMethod(fields.bitmapClazz,
-                                    fields.createScaledBitmapMethod,
-                                    jBitmap,
-                                    displayWidth,
-                                    displayHeight,
-                                    true);
+                                fields.createScaledBitmapMethod,
+                                jBitmap,
+                                dst_width,
+                                dst_height,
+                                true);
         return scaledBitmap;
     }
 
@@ -474,7 +484,7 @@
 
         {"setDataSource",   "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD},
         {"_setDataSource",   "(Landroid/media/MediaDataSource;)V", (void *)android_media_MediaMetadataRetriever_setDataSourceCallback},
-        {"_getFrameAtTime", "(JI)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
+        {"_getFrameAtTime", "(JIII)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
         {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata},
         {"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture},
         {"release",         "()V", (void *)android_media_MediaMetadataRetriever_release},