Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7d127140 authored by Chong Zhang's avatar Chong Zhang
Browse files

Use heif embedded thumbnail if available

Use the heif embedded thumbnail as long as it's not
too small to avoid decoding the full image and downscale.

bug: 74395267
Test: CTS MediaMetadataRetriever test; manual test of thumbnail
extraction by browsing new folders containing heif files in
Downloads app.
Change-Id: I5b2be0521452376153a773cb7671fefe7f9bc439
parent d1366f60
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -566,6 +566,25 @@ public class MediaMetadataRetriever
        return getImageAtIndexInternal(imageIndex, params);
    }

    /**
     * @hide
     *
     * This method retrieves the thumbnail image for a still image if it's available.
     * It should only be called after {@link #setDataSource}.
     *
     * @param imageIndex 0-based index of the image, negative value indicates primary image.
     * @param params BitmapParams that controls the returned bitmap config (such as pixel formats).
     * @param targetSize intended size of one edge (wdith or height) of the thumbnail,
     *                   this is a heuristic for the framework to decide whether the embedded
     *                   thumbnail should be used.
     * @param maxPixels maximum pixels of thumbnail, this is a heuristic for the frameowrk to
     *                  decide whehther the embedded thumnbail (or a downscaled version of it)
     *                  should be used.
     * @return the retrieved thumbnail, or null if no suitable thumbnail is available.
     */
    public native @Nullable Bitmap getThumbnailImageAtIndex(
            int imageIndex, @NonNull BitmapParams params, int targetSize, int maxPixels);

    /**
     * This method is similar to {@link #getImageAtIndex(int, BitmapParams)} except that
     * the default for {@link BitmapParams} will be used.
+30 −4
Original line number Diff line number Diff line
@@ -92,10 +92,14 @@ public class ThumbnailUtils {
        SizedThumbnailBitmap sizedThumbnailBitmap = new SizedThumbnailBitmap();
        Bitmap bitmap = null;
        MediaFileType fileType = MediaFile.getFileType(filePath);
        if (fileType != null && (fileType.fileType == MediaFile.FILE_TYPE_JPEG
                || MediaFile.isRawImageFileType(fileType.fileType))) {
        if (fileType != null) {
            if (fileType.fileType == MediaFile.FILE_TYPE_JPEG
                    || MediaFile.isRawImageFileType(fileType.fileType)) {
                createThumbnailFromEXIF(filePath, targetSize, maxPixels, sizedThumbnailBitmap);
                bitmap = sizedThumbnailBitmap.mBitmap;
            } else if (fileType.fileType == MediaFile.FILE_TYPE_HEIF) {
                bitmap = createThumbnailFromMetadataRetriever(filePath, targetSize, maxPixels);
            }
        }

        if (bitmap == null) {
@@ -519,4 +523,26 @@ public class ThumbnailUtils {
            sizedThumbBitmap.mBitmap = BitmapFactory.decodeFile(filePath, fullOptions);
        }
    }

    private static Bitmap createThumbnailFromMetadataRetriever(
            String filePath, int targetSize, int maxPixels) {
        if (filePath == null) {
            return null;
        }
        Bitmap thumbnail = null;
        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
        try {
            retriever.setDataSource(filePath);
            MediaMetadataRetriever.BitmapParams params = new MediaMetadataRetriever.BitmapParams();
            params.setPreferredConfig(Bitmap.Config.ARGB_8888);
            thumbnail = retriever.getThumbnailImageAtIndex(-1, params, targetSize, maxPixels);
        } catch (RuntimeException ex) {
            // Assume this is a corrupt video file.
        } finally {
            if (retriever != null) {
                retriever.release();
            }
        }
        return thumbnail;
    }
}
+65 −3
Original line number Diff line number Diff line
@@ -263,7 +263,9 @@ static void rotate(T *dst, const T *src, size_t width, size_t height, int angle)
static jobject getBitmapFromVideoFrame(
        JNIEnv *env, VideoFrame *videoFrame, jint dst_width, jint dst_height,
        SkColorType outColorType) {
    ALOGV("getBitmapFromVideoFrame: dimension = %dx%d and bytes = %d",
    ALOGV("getBitmapFromVideoFrame: dimension = %dx%d, displaySize = %dx%d, bytes = %d",
            videoFrame->mWidth,
            videoFrame->mHeight,
            videoFrame->mDisplayWidth,
            videoFrame->mDisplayHeight,
            videoFrame->mSize);
@@ -330,8 +332,7 @@ static jobject getBitmapFromVideoFrame(
        dst_height = std::round(displayHeight * factor);
    }

    if ((uint32_t)dst_width != videoFrame->mWidth ||
        (uint32_t)dst_height != videoFrame->mHeight) {
    if ((uint32_t)dst_width != width || (uint32_t)dst_height != height) {
        ALOGV("Bitmap dimension is scaled from %dx%d to %dx%d",
                width, height, dst_width, dst_height);
        jobject scaledBitmap = env->CallStaticObjectMethod(fields.bitmapClazz,
@@ -433,6 +434,61 @@ static jobject android_media_MediaMetadataRetriever_getImageAtIndex(
    return getBitmapFromVideoFrame(env, videoFrame, -1, -1, outColorType);
}

static jobject android_media_MediaMetadataRetriever_getThumbnailImageAtIndex(
        JNIEnv *env, jobject thiz, jint index, jobject params, jint targetSize, jint maxPixels)
{
    ALOGV("getThumbnailImageAtIndex: index %d", index);

    sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz);
    if (retriever == 0) {
        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
        return NULL;
    }

    int colorFormat = getColorFormat(env, params);
    jint dst_width = -1, dst_height = -1;

    // Call native method to retrieve an image
    VideoFrame *videoFrame = NULL;
    sp<IMemory> frameMemory = retriever->getImageAtIndex(
            index, colorFormat, true /*metaOnly*/, true /*thumbnail*/);
    if (frameMemory != 0) {
        videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
        int32_t thumbWidth = videoFrame->mWidth;
        int32_t thumbHeight = videoFrame->mHeight;
        videoFrame = NULL;
        int64_t thumbPixels = thumbWidth * thumbHeight;

        // Here we try to use the included thumbnail if it's not too shabby.
        // If this fails ThumbnailUtils would have to decode the full image and
        // downscale which could take long.
        if (thumbWidth >= targetSize || thumbHeight >= targetSize
                || thumbPixels * 6 >= maxPixels) {
            frameMemory = retriever->getImageAtIndex(
                    index, colorFormat, false /*metaOnly*/, true /*thumbnail*/);
            videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());

            if (thumbPixels > maxPixels) {
                int downscale = ceil(sqrt(thumbPixels / (float)maxPixels));
                dst_width = thumbWidth / downscale;
                dst_height = thumbHeight /downscale;
            }
        }
    }
    if (videoFrame == NULL) {
        ALOGV("getThumbnailImageAtIndex: no suitable thumbnails available");
        return NULL;
    }

    // Ignore rotation for thumbnail extraction to be consistent with
    // thumbnails extracted by BitmapFactory APIs.
    videoFrame->mRotationAngle = 0;

    SkColorType outColorType = setOutColorType(env, colorFormat, params);

    return getBitmapFromVideoFrame(env, videoFrame, dst_width, dst_height, outColorType);
}

static jobject android_media_MediaMetadataRetriever_getFrameAtIndex(
        JNIEnv *env, jobject thiz, jint frameIndex, jint numFrames, jobject params)
{
@@ -663,6 +719,12 @@ static const JNINativeMethod nativeMethods[] = {
            (void *)android_media_MediaMetadataRetriever_getImageAtIndex
        },

        {
            "getThumbnailImageAtIndex",
            "(ILandroid/media/MediaMetadataRetriever$BitmapParams;II)Landroid/graphics/Bitmap;",
            (void *)android_media_MediaMetadataRetriever_getThumbnailImageAtIndex
        },

        {
            "_getFrameAtIndex",
            "(IILandroid/media/MediaMetadataRetriever$BitmapParams;)Ljava/util/List;",