You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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状态,让新文件在媒体库中可见;失败时自动删除空文件

额外注意事项

  1. FFmpeg库版本:确保你使用的FFmpeg封装库支持文件描述符路径(比如主流的com.arthenica:mobile-ffmpeg)
  2. 权限适配:Android Q及以上不需要额外存储权限;Android P及以下需要申请READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE
  3. 错误调试:如果仍然失败,查看Config.printLastCommandOutput()的日志,FFmpeg会输出具体的错误原因(比如输入格式不支持、参数错误等)

内容的提问来源于stack exchange,提问作者user3697853

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 14:07:44