Android Q下使用FFmpeg通过MediaStore Uri转MP4为GIF失败求助
解决FFmpeg无法处理MediaStore Uri的问题
我来帮你搞定这个问题——你代码里的核心错误是把MediaStore的Uri直接转换成File对象,而MediaStore的Uri并不是真实的文件系统路径,尤其是在Android Q及以上的Scoped Storage机制下,FFmpeg根本无法识别这种"假路径",导致转换失败。
问题根源拆解
你写的这两行代码是关键错误:
val source = File(fileUri.toString()) val destination = File(newFileUri.toString())
MediaStore的Uri格式是content://media/external/video/media/xxxx,它是Content Provider的引用,不是硬盘上的文件路径,所以File对象指向的是一个不存在的文件,FFmpeg自然无法读取和写入。
可行解决方案:使用文件描述符传递给FFmpeg
FFmpeg支持通过Linux系统的/proc/self/fd/{fd}路径引用当前进程打开的文件描述符,我们可以通过ContentResolver获取MediaStore Uri对应的文件描述符,再传递给FFmpeg,这是适配所有Android版本(尤其是Q+)的最优方案。
修改后的完整代码
fun convertToGif(fileUri: Uri): Boolean { var result = false val contentResolver: ContentResolver = context.contentResolver val newFileName = fileUri.toString().substringAfterLast("/").substringBefore(".") + ".gif" // 1. 创建MediaStore中的GIF文件Uri val values = ContentValues().apply { put(MediaStore.Images.Media.DISPLAY_NAME, newFileName) put(MediaStore.Images.Media.MIME_TYPE, "image/gif") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { put(MediaStore.Images.Media.IS_PENDING, 1) } } val newFileUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) ?: return false // 插入失败直接返回 // 2. 使用try-with-resources确保文件描述符自动关闭 contentResolver.openFileDescriptor(fileUri, "r")?.use { inputFd -> contentResolver.openFileDescriptor(newFileUri, "w")?.use { outputFd -> // 3. 构造FFmpeg命令:使用文件描述符路径,同时添加GIF优化参数 // 参数说明:帧率10,缩放至320宽(高度按比例),使用lanczos算法保证缩放质量 val command = arrayOf( "-i", "/proc/self/fd/${inputFd.fd}", "-vf", "fps=10,scale=320:-1:flags=lanczos", "-c:v", "gif", "/proc/self/fd/${outputFd.fd}" ) // 4. 执行FFmpeg命令 result = when (FFmpeg.execute(command)) { Config.RETURN_CODE_SUCCESS -> { Log.i(Config.TAG, "GIF转换成功") // Q+需要将IS_PENDING设为0,让新创建的GIF文件在系统媒体库中可见 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { values.clear() values.put(MediaStore.Images.Media.IS_PENDING, 0) contentResolver.update(newFileUri, values, null, null) } true } Config.RETURN_CODE_CANCEL -> { Log.i(Config.TAG, "转换被用户取消") false } else -> { Log.e(Config.TAG, "转换失败,返回码:${FFmpeg.getLastReturnCode()}") Config.printLastCommandOutput(Log.ERROR) // 转换失败,删除创建的空GIF文件,避免产生垃圾 contentResolver.delete(newFileUri, null, null) false } } } } return result }
关键改进点说明
- 使用文件描述符:通过
/proc/self/fd/${fd}让FFmpeg直接访问ContentResolver打开的文件,完美绕过Uri转路径的问题 - 自动资源管理:用
use函数(try-with-resources)确保文件描述符自动关闭,避免资源泄漏 - GIF优化:添加了帧率、缩放参数,平衡GIF的体积和质量,你可以根据需求调整
fps和scale的值 - Q+适配:转换成功后更新
IS_PENDING状态,让新文件在媒体库中可见;失败时自动删除空文件
额外注意事项
- FFmpeg库版本:确保你使用的FFmpeg封装库支持文件描述符路径(比如主流的
com.arthenica:mobile-ffmpeg) - 权限适配:Android Q及以上不需要额外存储权限;Android P及以下需要申请
READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE - 错误调试:如果仍然失败,查看
Config.printLastCommandOutput()的日志,FFmpeg会输出具体的错误原因(比如输入格式不支持、参数错误等)
内容的提问来源于stack exchange,提问作者user3697853
相关产品推荐
相关产品推荐

