Avoid transcoding in MediaPlayers
Test: Manual
Bug: 158466177
Change-Id: I71776e1e81002b1505f5094c92fdd10706d1268e
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 70c924a..bbafc7b 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -40,9 +40,16 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
+import android.app.AppGlobals;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.res.AssetFileDescriptor;
+import android.net.Uri;
import android.provider.DocumentsContract.Document;
+import android.provider.MediaStore;
import android.system.ErrnoException;
import android.system.Os;
import android.system.StructStat;
@@ -118,6 +125,7 @@
// non-final so it can be toggled by Robolectric's ShadowFileUtils
private static boolean sEnableCopyOptimizations = true;
+ private static volatile int sMediaProviderAppId = -1;
private static final long COPY_CHECKPOINT_BYTES = 524288;
@@ -1425,6 +1433,54 @@
}
/** {@hide} */
+ public static FileDescriptor convertToModernFd(FileDescriptor fd) {
+ try {
+ Context context = AppGlobals.getInitialApplication();
+ if (UserHandle.getAppId(Process.myUid()) == getMediaProviderAppId(context)) {
+ // Never convert modern fd for MediaProvider, because this requires
+ // MediaStore#scanFile and can cause infinite loops when MediaProvider scans
+ return null;
+ }
+ File realFile = ParcelFileDescriptor.getFile(fd);
+ Log.i(TAG, "Changing to modern format dataSource for: " + realFile);
+ ContentResolver resolver = context.getContentResolver();
+
+ Uri uri = MediaStore.scanFile(resolver, realFile);
+ if (uri != null) {
+ Bundle opts = new Bundle();
+ // TODO(b/158465539): Use API constant
+ opts.putBoolean("android.provider.extra.ACCEPT_ORIGINAL_MEDIA_FORMAT", true);
+ AssetFileDescriptor afd = resolver.openTypedAssetFileDescriptor(uri, "*/*", opts);
+ Log.i(TAG, "Changed to modern format dataSource for: " + realFile);
+ return afd.getFileDescriptor();
+ } else {
+ Log.i(TAG, "Failed to change to modern format dataSource for: " + realFile);
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to change to modern format dataSource");
+ }
+ return null;
+ }
+
+ private static int getMediaProviderAppId(Context context) {
+ if (sMediaProviderAppId != -1) {
+ return sMediaProviderAppId;
+ }
+
+ PackageManager pm = context.getPackageManager();
+ ProviderInfo provider = context.getPackageManager().resolveContentProvider(
+ MediaStore.AUTHORITY, PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_SYSTEM_ONLY);
+ if (provider == null) {
+ return -1;
+ }
+
+ sMediaProviderAppId = UserHandle.getAppId(provider.applicationInfo.uid);
+ return sMediaProviderAppId;
+ }
+
+ /** {@hide} */
@VisibleForTesting
public static class MemoryPipe extends Thread implements AutoCloseable {
private final FileDescriptor[] pipe;
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index f9cbdd4..44890be 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -31,6 +31,8 @@
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.os.FileUtils;
+import android.os.SystemProperties;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -1523,6 +1525,11 @@
if (fileDescriptor == null) {
throw new NullPointerException("fileDescriptor cannot be null");
}
+ boolean optimize = SystemProperties.getBoolean("fuse.sys.transcode_exif_optimize", false);
+ FileDescriptor modernFd = optimize ? FileUtils.convertToModernFd(fileDescriptor) : null;
+ if (modernFd != null) {
+ fileDescriptor = modernFd;
+ }
mAssetInputStream = null;
mFilename = null;
@@ -2533,11 +2540,20 @@
private void initForFilename(String filename) throws IOException {
FileInputStream in = null;
+ FileInputStream legacyInputStream = null;
mAssetInputStream = null;
mFilename = filename;
mIsInputStream = false;
try {
in = new FileInputStream(filename);
+ boolean optimize = SystemProperties.getBoolean("fuse.sys.transcode_exif_optimize",
+ false);
+ FileDescriptor modernFd = optimize ? FileUtils.convertToModernFd(in.getFD()) : null;
+ if (modernFd != null) {
+ legacyInputStream = in;
+ in = new FileInputStream(modernFd);
+ }
+
if (isSeekableFD(in.getFD())) {
mSeekableFileDescriptor = in.getFD();
} else {
@@ -2546,6 +2562,7 @@
loadAttributes(in);
} finally {
closeQuietly(in);
+ closeQuietly(legacyInputStream);
}
}
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 835a709..ca8b9b9 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -30,7 +30,10 @@
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
+import android.os.Bundle;
+import android.os.FileUtils;
import android.os.IBinder;
+import android.os.SystemProperties;
import android.text.TextUtils;
import java.io.FileDescriptor;
@@ -48,7 +51,6 @@
* frame and meta data from an input media file.
*/
public class MediaMetadataRetriever implements AutoCloseable {
-
// borrowed from ExoPlayer
private static final String[] STANDARD_GENRES = new String[] {
// These are the official ID3v1 genres.
@@ -296,7 +298,19 @@
* non-negative.
* @throws IllegalArgumentException if the arguments are invalid
*/
- public native void setDataSource(FileDescriptor fd, long offset, long length)
+ public void setDataSource(FileDescriptor fd, long offset, long length)
+ throws IllegalArgumentException {
+ boolean optimize = SystemProperties.getBoolean("fuse.sys.transcode_retriever_optimize",
+ false);
+ FileDescriptor modernFd = optimize ? FileUtils.convertToModernFd(fd) : null;
+ if (modernFd == null) {
+ _setDataSource(fd, offset, length);
+ } else {
+ _setDataSource(modernFd, offset, length);
+ }
+ }
+
+ private native void _setDataSource(FileDescriptor fd, long offset, long length)
throws IllegalArgumentException;
/**
@@ -340,7 +354,12 @@
try {
ContentResolver resolver = context.getContentResolver();
try {
- fd = resolver.openAssetFileDescriptor(uri, "r");
+ boolean optimize =
+ SystemProperties.getBoolean("fuse.sys.transcode_retriever_optimize", false);
+ Bundle opts = new Bundle();
+ opts.putBoolean("android.provider.extra.ACCEPT_ORIGINAL_MEDIA_FORMAT", true);
+ fd = optimize ? resolver.openTypedAssetFileDescriptor(uri, "*/*", opts)
+ : resolver.openAssetFileDescriptor(uri, "r");
} catch(FileNotFoundException e) {
throw new IllegalArgumentException("could not access " + uri);
}
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 851c1ec..47d276a 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -31,6 +31,8 @@
import android.media.SubtitleController.Anchor;
import android.media.SubtitleTrack.RenderingWidget;
import android.net.Uri;
+import android.os.Bundle;
+import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -1104,7 +1106,13 @@
}
private boolean attemptDataSource(ContentResolver resolver, Uri uri) {
- try (AssetFileDescriptor afd = resolver.openAssetFileDescriptor(uri, "r")) {
+ boolean optimize = SystemProperties.getBoolean("fuse.sys.transcode_player_optimize",
+ false);
+ Bundle opts = new Bundle();
+ opts.putBoolean("android.provider.extra.ACCEPT_ORIGINAL_MEDIA_FORMAT", true);
+ try (AssetFileDescriptor afd = optimize
+ ? resolver.openTypedAssetFileDescriptor(uri, "*/*", opts)
+ : resolver.openAssetFileDescriptor(uri, "r")) {
setDataSource(afd);
return true;
} catch (NullPointerException | SecurityException | IOException ex) {
@@ -1245,7 +1253,13 @@
*/
public void setDataSource(FileDescriptor fd, long offset, long length)
throws IOException, IllegalArgumentException, IllegalStateException {
- _setDataSource(fd, offset, length);
+ boolean optimize = SystemProperties.getBoolean("fuse.sys.transcode_player_optimize", false);
+ FileDescriptor modernFd = optimize ? FileUtils.convertToModernFd(fd) : null;
+ if (modernFd == null) {
+ _setDataSource(fd, offset, length);
+ } else {
+ _setDataSource(modernFd, offset, length);
+ }
}
private native void _setDataSource(FileDescriptor fd, long offset, long length)
@@ -2899,8 +2913,13 @@
AssetFileDescriptor fd = null;
try {
+ boolean optimize = SystemProperties.getBoolean("fuse.sys.transcode_player_optimize",
+ false);
ContentResolver resolver = context.getContentResolver();
- fd = resolver.openAssetFileDescriptor(uri, "r");
+ Bundle opts = new Bundle();
+ opts.putBoolean("android.provider.extra.ACCEPT_ORIGINAL_MEDIA_FORMAT", true);
+ fd = optimize ? resolver.openTypedAssetFileDescriptor(uri, "*/*", opts)
+ : resolver.openAssetFileDescriptor(uri, "r");
if (fd == null) {
return;
}
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index 6fbd29c..126897a 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -704,7 +704,7 @@
(void *)android_media_MediaMetadataRetriever_setDataSourceAndHeaders
},
- {"setDataSource", "(Ljava/io/FileDescriptor;JJ)V",
+ {"_setDataSource", "(Ljava/io/FileDescriptor;JJ)V",
(void *)android_media_MediaMetadataRetriever_setDataSourceFD},
{"_setDataSource", "(Landroid/media/MediaDataSource;)V",
(void *)android_media_MediaMetadataRetriever_setDataSourceCallback},