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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 22:44:52