Commit 7a995230 by tanghuan

压缩视频时,根据视频文件大小设置压缩参数

1 parent 8fc77aab
...@@ -95,9 +95,9 @@ class CompressVideoHandler extends MessageHandler { ...@@ -95,9 +95,9 @@ class CompressVideoHandler extends MessageHandler {
throw Exception('参数错误'); throw Exception('参数错误');
} }
final quality = params['quality'] as String?; final quality = params['quality'] as String?;
if (quality == null || quality.isEmpty) { // if (quality == null || quality.isEmpty) {
throw Exception('参数错误'); // throw Exception('参数错误');
} // }
final resolution = params['resolution'] as double?; final resolution = params['resolution'] as double?;
if (resolution == null || resolution > 1 || resolution <= 0) { if (resolution == null || resolution > 1 || resolution <= 0) {
throw Exception('参数错误'); throw Exception('参数错误');
......
...@@ -85,7 +85,7 @@ class UploadFileHandler extends MessageHandler { ...@@ -85,7 +85,7 @@ class UploadFileHandler extends MessageHandler {
if (mimeType != 'video/mp4') { if (mimeType != 'video/mp4') {
success = await VideoUtil.convertToMp4(inputPath, outputPath); success = await VideoUtil.convertToMp4(inputPath, outputPath);
} else { } else {
success = await VideoUtil.compressVideo(inputPath, outputPath, 'low'); success = await VideoUtil.compressVideo(inputPath, outputPath, null);
} }
var endTime = DateTime.now(); var endTime = DateTime.now();
debugPrint('====================>压缩耗时:${endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch} 毫秒'); debugPrint('====================>压缩耗时:${endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch} 毫秒');
......
...@@ -181,7 +181,7 @@ class UploadStartHandler extends MessageHandler { ...@@ -181,7 +181,7 @@ class UploadStartHandler extends MessageHandler {
success = await VideoUtil.compressVideo( success = await VideoUtil.compressVideo(
inputPath, inputPath,
outputPath, outputPath,
'low', null,
onProgress: (progress) { onProgress: (progress) {
// progress 范围 0 ~ 100 // progress 范围 0 ~ 100
debugPrint('压缩进度: $_cmdUnique $progress%'); debugPrint('压缩进度: $_cmdUnique $progress%');
......
...@@ -8,7 +8,9 @@ import 'package:ffmpeg_kit_flutter_new/statistics.dart'; ...@@ -8,7 +8,9 @@ import 'package:ffmpeg_kit_flutter_new/statistics.dart';
class VideoUtil { class VideoUtil {
/// ///
/// 将视频格式转换为mp4 /// 将视频格式转换为mp4
/// 转码的同时,进行压缩 /// 转码的同时,根据文件大小自动选择压缩参数
/// iOS: h264_videotoolbox 硬件加速 + 按文件大小自动码率
/// Android: libx264 + 按文件大小自动CRF
/// [onProgress] 进度回调,值范围 0.0 ~ 1.0 /// [onProgress] 进度回调,值范围 0.0 ~ 1.0
/// ///
static Future<bool> convertToMp4( static Future<bool> convertToMp4(
...@@ -18,26 +20,29 @@ class VideoUtil { ...@@ -18,26 +20,29 @@ class VideoUtil {
}) async { }) async {
// 先获取视频总时长(微秒) // 先获取视频总时长(微秒)
final duration = await _getVideoDuration(inputPath); final duration = await _getVideoDuration(inputPath);
final fileSize = await File(inputPath).length();
String cmd; String cmd;
if (Platform.isIOS) { if (Platform.isIOS) {
final bitrate = _getBitrateByFileSize(fileSize);
cmd = '-i "$inputPath" ' cmd = '-i "$inputPath" '
'-c:v h264_videotoolbox ' // 启用 iOS 硬件加速 '-c:v h264_videotoolbox ' // 启用 iOS 硬件加速
'-b:v 1500k ' // 限制视频码率为 1.5Mbps (体积小,手机看足够) '-b:v ${bitrate}k ' // 根据文件大小自动计算码率
'-vf scale=1280:-2 ' // 缩放到 720p (保持比例) '-vf scale=1280:-2 ' // 缩放到 720p (保持比例)
'-c:a aac ' // 音频转为 AAC (兼容性最好) '-c:a aac ' // 音频转为 AAC (兼容性最好)
'-b:a 128k ' // 音频码率 '-b:a 128k ' // 音频码率
'"$outputPath"'; '"$outputPath"';
} else { } else {
cmd = '-i "$inputPath" ' // 指定输入文件路径 final crf = _getCrfByFileSize(fileSize);
cmd = '-i "$inputPath" '
'-c:v libx264 ' // 设置视频编码器为libx264(H.264) '-c:v libx264 ' // 设置视频编码器为libx264(H.264)
'-crf 28 ' // 设置恒定速率因子CRF为28(中等压缩质量) '-crf $crf ' // 根据文件大小自动计算CRF
'-c:a aac ' // 设置音频编码器为AAC '-c:a aac ' // 设置音频编码器为AAC
'-b:a 128k ' // 设置音频比特率为128kbps '-b:a 128k ' // 设置音频比特率为128kbps
'-preset fast ' '-preset fast '
'-threads 0 ' '-threads 0 '
'-strict experimental ' // 允许使用实验性编解码器功能 '-strict experimental ' // 允许使用实验性编解码器功能
'-movflags faststart ' // 优化MP4文件结构,使视频可以快速启动播放 '-movflags faststart ' // 优化MP4文件结构
'-f mp4 ' // 指定输出格式为MP4 '-f mp4 ' // 指定输出格式为MP4
'"$outputPath"'; // 指定输出文件路径 '"$outputPath"'; // 指定输出文件路径
} }
...@@ -65,12 +70,14 @@ class VideoUtil { ...@@ -65,12 +70,14 @@ class VideoUtil {
/// ///
/// 通过 ffmpeg 压缩视频 /// 通过 ffmpeg 压缩视频
/// [quality] 压缩质量,可选值: 'low' | 'middle' | 'high'
/// 未指定时根据文件大小自动计算CRF:文件越大压缩率越高,文件越小清晰度越高
/// [onProgress] 进度回调,值范围 0.0 ~ 1.0 /// [onProgress] 进度回调,值范围 0.0 ~ 1.0
/// ///
static Future<bool> compressVideo( static Future<bool> compressVideo(
String inputPath, String inputPath,
String outputPath, String outputPath,
String quality, { String? quality, {
void Function(int progress)? onProgress, void Function(int progress)? onProgress,
}) async { }) async {
final duration = await _getVideoDuration(inputPath); final duration = await _getVideoDuration(inputPath);
...@@ -80,18 +87,24 @@ class VideoUtil { ...@@ -80,18 +87,24 @@ class VideoUtil {
// 中等质量: CRF 23-26 // 中等质量: CRF 23-26
// 低质量: CRF 28-32 // 低质量: CRF 28-32
int crf; int crf;
switch (quality) { if (quality != null && quality.isNotEmpty) {
case 'low': switch (quality) {
crf = 32; case 'low':
break; crf = 32;
case 'middle': break;
crf = 26; case 'middle':
break; crf = 26;
case 'high': break;
crf = 20; case 'high':
break; crf = 20;
default: break;
throw Exception('参数错误'); default:
throw Exception('参数错误');
}
} else {
// 根据文件大小自动计算CRF
final fileSize = await File(inputPath).length();
crf = _getCrfByFileSize(fileSize);
} }
String cmd = '-i "$inputPath" ' // 输入文件 String cmd = '-i "$inputPath" ' // 输入文件
'-c:v libx264 ' // 视频编码器 '-c:v libx264 ' // 视频编码器
...@@ -125,6 +138,47 @@ class VideoUtil { ...@@ -125,6 +138,47 @@ class VideoUtil {
return completer.future; return completer.future;
} }
/// 根据文件大小自动计算视频码率(kbps)
/// 用于iOS h264_videotoolbox硬件加速模式(不支持CRF,需用码率控制质量)
/// 文件越大码率越低(压缩率越高,清晰度越低)
/// 文件越小码率越高(压缩率越低,清晰度越高)
static int _getBitrateByFileSize(int fileSizeBytes) {
final fileSizeMB = fileSizeBytes / (1024 * 1024);
if (fileSizeMB < 5) {
return 4000; // 小文件:高质量
} else if (fileSizeMB < 20) {
return 2500; // 中小文件:较高质量
} else if (fileSizeMB < 50) {
return 1500; // 中等文件:中等质量
} else if (fileSizeMB < 100) {
return 1000; // 较大文件:较低质量
} else if (fileSizeMB < 200) {
return 800; // 大文件:低质量
} else {
return 600; // 超大文件:最低质量
}
}
/// 根据文件大小自动计算CRF值
/// 文件越大CRF越高(压缩率越高,清晰度越低)
/// 文件越小CRF越低(压缩率越低,清晰度越高)
static int _getCrfByFileSize(int fileSizeBytes) {
final fileSizeMB = fileSizeBytes / (1024 * 1024);
if (fileSizeMB < 5) {
return 20; // 小文件:高质量
} else if (fileSizeMB < 20) {
return 23; // 中小文件:较高质量
} else if (fileSizeMB < 50) {
return 26; // 中等文件:中等质量
} else if (fileSizeMB < 100) {
return 28; // 较大文件:较低质量
} else if (fileSizeMB < 200) {
return 30; // 大文件:低质量
} else {
return 32; // 超大文件:最低质量
}
}
/// 获取视频总时长,返回毫秒 /// 获取视频总时长,返回毫秒
static Future<int> _getVideoDuration(String videoPath) async { static Future<int> _getVideoDuration(String videoPath) async {
final session = await FFmpegKit.execute( final session = await FFmpegKit.execute(
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!