Android MediaPlayer调用setDataSource时出现“Failed to convert to modern format file descriptor”异常求助
我之前也碰到过这个一模一样的错误,当时折腾了好一阵才搞定,先帮你分析下问题,再给几个可行的解决方案:
首先先把你遇到的错误信息贴出来方便对照:
Failed to convert to modern format file descriptor
java.io.IOException: Input file descriptor already original
at android.provider.MediaStore.getOriginalMediaFormatFileDescriptor(MediaStore.java:943)
at android.os.FileUtils.convertToModernFd(FileUtils.java:1470)
at android.media.MediaPlayer.setDataSource(MediaPlayer.java:1271)
at android.media.MediaPlayer.setDataSource(MediaPlayer.java:1254)
错误原因分析
这个异常主要出现在Android 10(API 29)及以上版本,核心原因是系统引入了媒体格式适配机制:
- 当你直接用
FileInputStream获取的FileDescriptor传入MediaPlayer时,系统会尝试将其转换为“现代格式FD”,但如果这个FD没有关联正确的媒体元数据,或者已经是原始状态,就会触发转换失败。 - 你尝试的
Uri.fromFile()方式,在Android 10+因为Scoped Storage(分区存储)的限制,这种File Uri无法被MediaPlayer正确识别,同样会触发类似的底层异常。
解决方案
方案一:用ContentResolver + FileProvider获取适配的FD(推荐,全版本兼容)
如果你的音频文件在应用私有目录之外,或者需要适配Android 10+的分区存储,这是最稳妥的方式。通过FileProvider生成合法的Content Uri,再用ContentResolver获取可被MediaPlayer识别的FD:
if (myAudio.exists()) { mp.reset(); // 生成FileProvider的Content Uri,替换成你的应用包名 Uri contentUri = FileProvider.getUriForFile(getContext(), "com.your.app.id.fileprovider", myAudio); ParcelFileDescriptor pfd = null; try { pfd = getContext().getContentResolver().openFileDescriptor(contentUri, "r"); if (pfd != null) { mp.setDataSource(pfd.getFileDescriptor()); mp.setOnErrorListener(this); mp.setOnPreparedListener(this); mp.prepareAsync(); btnPlay.setImageResource(R.drawable.pause); } } catch (IOException e) { e.printStackTrace(); btnPlay.setImageResource(R.drawable.play); errorGot += 1; } finally { if (pfd != null) { try { pfd.close(); } catch (IOException e) { e.printStackTrace(); } } } } else { btnPlay.setImageResource(R.drawable.play); errorGot += 1; }
注意:需要先配置FileProvider:
- 在
AndroidManifest.xml中添加Provider声明:
<provider android:name="androidx.core.content.FileProvider" android:authorities="com.your.app.id.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
- 在
res/xml目录下创建file_paths.xml,定义允许共享的文件路径:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 根据你的文件位置选择对应标签,比如外部存储根目录 --> <external-path name="external_files" path="."/> <!-- 或者应用私有目录 --> <files-path name="internal_files" path="."/> </paths>
方案二:直接使用文件绝对路径(仅适应用私有目录文件)
如果你的音频文件存储在应用私有目录(比如getFilesDir()、getCacheDir()),可以直接用文件路径调用setDataSource,绕开FD和Uri的适配问题:
if (myAudio.exists()) { mp.reset(); try { mp.setDataSource(myAudio.getAbsolutePath()); mp.setOnErrorListener(this); mp.setOnPreparedListener(this); mp.prepareAsync(); btnPlay.setImageResource(R.drawable.pause); } catch (IOException e) { e.printStackTrace(); btnPlay.setImageResource(R.drawable.play); errorGot += 1; } } else { btnPlay.setImageResource(R.drawable.play); errorGot += 1; }
这个方法简单直接,而且不会触发分区存储的权限问题,适合私有目录内的媒体文件。
方案三:延迟关闭FileInputStream(不推荐,兼容性差)
你之前的代码里提前关闭了FileInputStream,可能导致FD被系统提前回收,无法完成格式转换。可以尝试把流的关闭操作放到onPrepared回调里:
if (myAudio.exists()) { mp.reset(); FileInputStream is = null; try { is = new FileInputStream(myAudio); FileDescriptor fd = is.getFD(); mp.setDataSource(fd); mp.setOnErrorListener(this); mp.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { btnPlay.setImageResource(R.drawable.pause); // 播放完成或释放时再关闭流 if (is != null) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } }); mp.prepareAsync(); } catch (IOException e) { e.printStackTrace(); btnPlay.setImageResource(R.drawable.play); errorGot += 1; // 异常时也要关闭流 if (is != null) { try { is.close(); } catch (IOException ex) { ex.printStackTrace(); } } } } else { btnPlay.setImageResource(R.drawable.play); errorGot += 1; }
不过这个方法在不同Android版本上表现不稳定,不建议作为长期方案。
总结
优先选择方案一或方案二,根据你的文件存储位置来决定:如果是外部公共目录用方案一,私有目录用方案二,这样既能解决当前的FD转换错误,也能适配新的Android版本规范。
内容的提问来源于stack exchange,提问作者wdyz

