Android 10与11下Flutter FFmpeg存储路径兼容问题求助
Android 10/11存储路径适配问题:FFmpeg输出路径版本兼容故障排查
问题背景
我修改了FFmpeg的输出路径,指定为硬编码的应用专属外部存储路径:
String outputPath = '/storage/emulated/0/Android/data/com.example.videocutter/files/MyFolder/$formattedDateTime.mp4';
该实现在Android 10物理设备上运行正常,但在Android 11模拟器测试时抛出异常。
当改为公共外部存储路径后:
final String outputPath = '${directory.path}/download/$outputFileName';
Android 11的问题解决了,但Android 10上又出现类似权限/路径错误。需要分析版本差异根因,并给出通用解决方案。
原始代码
Future<void> trimVideos() async { try { final appDocDir = await getApplicationDocumentsDirectory(); String formattedDateTime = DateFormat('yyyyMMdd_HHmmss').format(DateTime.now()); //String outputPath = '${appDocDir.path}/$formattedDateTime.mp4'; String outputPath = '/storage/emulated/0/Android/data/com.example.videocutter/files/MyFolder/$formattedDateTime.mp4'; final String inputPath=widget.file.path; final Duration start = _controller.startTrim; final Duration end = _controller.endTrim; // Build the FFmpeg command for trimming with keyframe alignment final command = '-ss ${formatter(start)} -t ${formatter(end - start)} -noaccurate_seek -i $inputPath -codec copy -avoid_negative_ts 1 $outputPath'; // Execute FFmpeg command await FFmpegKit.execute(command).then((session) async { print('The command is executed: $command'); final returnCode = await session.getReturnCode(); if (ReturnCode.isSuccess(returnCode)) { print('Command is executed successfully'); setState(() { }); } else if (ReturnCode.isCancel(returnCode)) { print('Command is canceled'); } else { print('Error $returnCode'); final logs = await session.getLogsAsString(); print('Error details: $logs'); } }); } catch (e) { print('Error trimming video: $e'); } }
错误日志
Loaded ffmpeg-kit-flutter-android-https-x86-5.1.0. I/flutter (15789): The command is executed: -ss 00:00 -t 01:00 -noaccurate_seek -i /data/user/0/com.example.videocutter/cache/86eb14d4-041d-4c7a-a719-3394b318af56/Mercy - Badshah Feat. Lauren Gottlieb _ Official Music Video _ Latest Hit Song 2017.mp4 -codec copy -avoid_negative_ts 1 /storage/emulated/0/Android/data/com.example.videocutter/files/MyFolder/20231121_152801.mp4 I/flutter (15789): Error 1 I/flutter (15789): Error details: ffmpeg version n5.1.2 Copyright (c) 2000-2022 the FFmpeg developers I/flutter (15789): built with Android (7155654, based on r399163b1) clang version 11.0.5 (https://android.googlesource.com/toolchain/llvm-project 87f1315dfbea7c137aa2e6d362dbb457e388158d) I/flutter (15789): configuration: --cross-prefix=i686-linux-android- --sysroot=/files/android-sdk/ndk/22.1.7171670/toolchains/llvm/prebuilt/linux-x86_64/sysroot --prefix=/home/taner/Projects/ffmpeg-kit/prebuilt/android-x86/ffmpeg --pkg-config=/usr/bin/pkg-config --enable-version3 --arch=i686 --cpu=i686 --target-os=android --disable-neon --disable-asm --disable-inline-asm --ar=i686-linux-android-ar --cc=i686-linux-android24-clang --cxx=i686-linux-android24-clang++ --ranlib=i686-linux-android-ranlib --strip=i686-linux-android-strip --nm=i686-linux-android-nm --extra-libs='-L/home/taner/Projects/ffmpeg-kit/prebuilt/android-x86/cpu-features/lib -lndk_compat' --disable-autodetect --enable-cross-compile --enable-pic --enable-jni --enable-optimizations --enable-swscale --disable-sta
根因分析
Android 10(API 29)特性
- 分区存储默认启用,但支持通过
android:requestLegacyExternalStorage="true"在Manifest中兼容旧模式,允许直接访问硬编码路径。 - 应用专属外部存储(
/Android/data/包名/files/)属于应用私有空间,无需额外权限即可访问,因此硬编码路径能正常工作。 - 访问公共存储目录(如Download)需要申请
WRITE_EXTERNAL_STORAGE权限,否则会因权限不足报错。
Android 11(API 30)特性
- 分区存储强制启用,
requestLegacyExternalStorage不再生效,系统对路径权限校验更严格。 - 硬编码应用专属外部存储路径可能失效:FFmpeg作为Native进程,直接访问硬编码路径时,系统可能拒绝其权限(正确方式是通过Android API获取路径,而非硬编码)。
- 公共存储目录访问逻辑变更,直接通过路径写入需要符合分区存储规则,部分场景下需使用MediaStore API。
额外问题:路径含特殊字符未转义
从错误日志中的FFmpeg命令可见,输入路径包含空格,未用引号包裹,这会导致FFmpeg解析路径失败,也是触发错误的可能原因之一。
解决方案
方案1:使用应用专属外部存储(推荐,无需额外权限)
通过Flutter官方API获取路径,避免硬编码,确保跨版本兼容:
Future<void> trimVideos() async { try { final externalDir = await getExternalStorageDirectory(); // 创建自定义文件夹并确保存在 final myFolder = Directory('${externalDir.path}/MyFolder'); if (!await myFolder.exists()) { await myFolder.create(recursive: true); } String formattedDateTime = DateFormat('yyyyMMdd_HHmmss').format(DateTime.now()); String outputPath = '${myFolder.path}/$formattedDateTime.mp4'; final String inputPath = widget.file.path; final Duration start = _controller.startTrim; final Duration end = _controller.endTrim; // 路径含特殊字符需用引号包裹 final command = '-ss ${formatter(start)} -t ${formatter(end - start)} -noaccurate_seek -i "$inputPath" -codec copy -avoid_negative_ts 1 "$outputPath"'; await FFmpegKit.execute(command).then((session) async { // 后续逻辑保持不变... }); } catch (e) { print('Error trimming video: $e'); } }
- 该路径在Android 10/11均属于应用私有空间,无需申请存储权限。
- 通过
create(recursive: true)确保目标目录存在,避免FFmpeg因目录不存在报错。
方案2:输出到公共存储目录(如Download)
Android 10及以下
- 在
AndroidManifest.xml中声明权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <application android:requestLegacyExternalStorage="true">
- 动态申请存储权限:
if (await Permission.storage.request().isGranted) { // 执行写入逻辑 }
Android 11及以上
使用MediaStore API写入公共目录(推荐使用media_store插件简化操作),示例代码:
import 'package:media_store/media_store.dart'; Future<String> getPublicDownloadPath(String fileName) async { if (Platform.isAndroid) { final mediaStore = MediaStore(); final uri = await mediaStore.createFile( path: MediaStore.downloads, mimeType: 'video/mp4', displayName: fileName, ); return uri.path; } return ''; }
- 此方式无需申请
WRITE_EXTERNAL_STORAGE权限,符合Android 11+分区存储规则。
内容的提问来源于stack exchange,提问作者neelam anil
相关产品推荐
相关产品推荐

