使用Flutter FFmpeg Kit合成音视频时出现exit code 1问题求助
问题描述
尝试将图片与音频文件合成为视频,用户可正常选择素材,但每次运行代码后FFmpeg返回exit code 1。已在AndroidManifest.xml中添加存储权限:
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
使用的Dart代码如下:
import 'dart:io'; import 'package:ffmpeg_kit_flutter/ffmpeg_kit.dart'; import 'package:ffmpeg_kit_flutter/return_code.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:image_picker/image_picker.dart'; import 'package:file_picker/file_picker.dart'; import 'package:path_provider/path_provider.dart'; Future<void> createVideoFromUserSelection() async { final ImagePicker _picker = ImagePicker(); final FilePickerResult? audioResult = await FilePicker.platform.pickFiles( type: FileType.audio, allowMultiple: false, ); final FilePickerResult? imageResult = await FilePicker.platform.pickFiles( type: FileType.image, allowMultiple: false, ); if (audioResult != null && imageResult != null) { final File audioFile = File(audioResult.files.single.path!); final File imageFile = File(imageResult.files.single.path!); final compressedImageBytes = await FlutterImageCompress.compressWithFile( imageFile.path, quality: 70, // Adjust the image quality as needed ); final compressedImageFile = File('${(await getTemporaryDirectory()).path}/temp_image.jpg'); await compressedImageFile.writeAsBytes(compressedImageBytes!.toList()); final outputDirectory = Directory( '/storage/emulated/0/Download'); // Specify the desired output directory final outputFileName = 'output_video.mp4'; // Specify the desired output file name final outputFile = File('${outputDirectory.path}/$outputFileName'); if (audioResult != null && imageResult != null) { final File audioFile = File(audioResult.files.single.path!); final File imageFile = File(imageResult.files.single.path!); // Verify audio file existence if (!await audioFile.exists()) { print("Audio file does not exist at path: ${audioFile.path}"); return; } // Verify image file existence if (!await imageFile.exists()) { print("Image file does not exist at path: ${imageFile.path}"); return; } } final arguments = [ '-loop', '1', '-i', compressedImageFile.path, '-i', audioFile.path, '-c:v', 'libx264', '-c:a', 'aac', outputFile.path, ]; await FFmpegKit.executeWithArguments(arguments).then((session) async { final output = await session.getLastReceivedStatistics(); print("FFmpeg session output:\n$output"); final returnCode = await session.getReturnCode(); print("Return code: $returnCode"); if (returnCode == ReturnCode.success) { print("Video creation completed"); } else { print("Video creation failed. FFmpeg exit code: $returnCode"); } }, onError: (error) { print("Video creation failed. Error: $error"); }); // Delete the temporary compressed image and audio files compressedImageFile.delete(); audioFile.delete(); } else { print("User canceled the selection."); } }
解决办法
1. 补充存储权限并动态申请
仅添加MANAGE_EXTERNAL_STORAGE权限不足以覆盖所有场景,需补充基础读写权限并在运行时动态申请:
- 在
AndroidManifest.xml中补充:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
- 使用
permission_handler包提前申请权限,通过后再执行合成逻辑:
import 'package:permission_handler/permission_handler.dart'; Future<bool> checkPermissions() async { final storageStatus = await Permission.storage.request(); final manageStorageStatus = await Permission.manageExternalStorage.request(); return storageStatus.isGranted && manageStorageStatus.isGranted; }
2. 修复FFmpeg命令参数
当前参数缺少视频时长匹配、兼容性配置,导致编码失败。修正后的参数数组:
final arguments = [ '-loop', '1', '-i', compressedImageFile.path, '-i', audioFile.path, '-c:v', 'libx264', '-pix_fmt', 'yuv420p', // 保证视频在多数播放器兼容 '-c:a', 'aac', '-b:a', '192k', // 明确音频比特率,避免编码错误 '-shortest', // 让视频时长跟随音频长度 outputFile.path, ];
3. 修正代码冗余与错误逻辑
- 删除内层重复定义的
audioFile、imageFile及冗余判断; - 移除删除用户原始音频文件的代码(这会误删用户素材),并给临时文件删除添加异常捕获:
try { await compressedImageFile.delete(); } catch (e) { print('Failed to delete temp image: $e'); } // 移除这行:audioFile.delete();
4. 获取完整FFmpeg错误日志
当前仅打印统计信息,无法定位具体错误。修改日志获取逻辑:
await FFmpegKit.executeWithArguments(arguments).then((session) async { final returnCode = await session.getReturnCode(); final fullLog = await session.getAllLogsAsString(); print("FFmpeg full log:\n$fullLog"); print("Return code: $returnCode"); if (returnCode == ReturnCode.success) { print("Video creation completed"); } else { print("Video creation failed. FFmpeg exit code: $returnCode"); } }, onError: (error) { print("Video creation failed. Error: $error"); });
通过完整日志可直接看到失败原因(如路径错误、编码格式不支持等)。
5. 确保输出目录合法可写
改用官方API获取公共下载目录,避免硬编码路径的兼容性问题:
final outputDirectory = await getExternalStoragePublicDirectory(DirectoryDownloads); await outputDirectory.create(recursive: true); // 确保目录存在 final outputFile = File('${outputDirectory.path}/$outputFileName');
内容的提问来源于stack exchange,提问作者Asad Kareem
相关产品推荐
相关产品推荐

