1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.messaging.util; 18 19 import android.content.Context; 20 import android.text.format.Formatter; 21 22 import com.google.common.base.Stopwatch; 23 24 import java.io.File; 25 import java.util.concurrent.TimeUnit; 26 27 /** 28 * Compresses a GIF so it can be sent via MMS. 29 * <p> 30 * The entry point lives in its own class, we can defer loading the native GIF transcoding library 31 * into memory until we actually need it. 32 */ 33 public class GifTranscoder { 34 private static final String TAG = LogUtil.BUGLE_TAG; 35 36 private static int MIN_HEIGHT = 100; 37 private static int MIN_WIDTH = 100; 38 39 static { 40 System.loadLibrary("giftranscode"); 41 } 42 transcode(Context context, String filePath, String outFilePath)43 public static boolean transcode(Context context, String filePath, String outFilePath) { 44 if (!isEnabled()) { 45 return false; 46 } 47 final long inputSize = new File(filePath).length(); 48 Stopwatch stopwatch = Stopwatch.createStarted(); 49 final boolean success = transcodeInternal(filePath, outFilePath); 50 stopwatch.stop(); 51 final long elapsedMs = stopwatch.elapsed(TimeUnit.MILLISECONDS); 52 final long outputSize = new File(outFilePath).length(); 53 final float compression = (inputSize > 0) ? ((float) outputSize / inputSize) : 0; 54 55 if (success) { 56 LogUtil.i(TAG, String.format("Resized GIF (%s) in %d ms, %s => %s (%.0f%%)", 57 LogUtil.sanitizePII(filePath), 58 elapsedMs, 59 Formatter.formatShortFileSize(context, inputSize), 60 Formatter.formatShortFileSize(context, outputSize), 61 compression * 100.0f)); 62 } 63 return success; 64 } 65 transcodeInternal(String filePath, String outFilePath)66 private static native boolean transcodeInternal(String filePath, String outFilePath); 67 68 /** 69 * Estimates the size of a GIF transcoded from a GIF with the specified size. 70 */ estimateFileSizeAfterTranscode(long fileSize)71 public static long estimateFileSizeAfterTranscode(long fileSize) { 72 // I tested transcoding on ~70 GIFs and found that the transcoded files are in general 73 // about 25-35% the size of the original. This compression ratio is very consistent for the 74 // class of GIFs we care about most: those converted from video clips and 1-3 MB in size. 75 return (long) (fileSize * 0.35f); 76 } 77 canBeTranscoded(int width, int height)78 public static boolean canBeTranscoded(int width, int height) { 79 if (!isEnabled()) { 80 return false; 81 } 82 return width >= MIN_WIDTH && height >= MIN_HEIGHT; 83 } 84 isEnabled()85 private static boolean isEnabled() { 86 final boolean enabled = BugleGservices.get().getBoolean( 87 BugleGservicesKeys.ENABLE_GIF_TRANSCODING, 88 BugleGservicesKeys.ENABLE_GIF_TRANSCODING_DEFAULT); 89 if (!enabled) { 90 LogUtil.w(TAG, "GIF transcoding is disabled"); 91 } 92 return enabled; 93 } 94 } 95