Skip to content
Toggle navigation
Toggle navigation
This project
Loading...
Sign in
ethan
/
appframe
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
Commit 7a995230
authored
2026-04-27 11:49:16 +0800
by
tanghuan
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
压缩视频时,根据视频文件大小设置压缩参数
1 parent
8fc77aab
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
65 additions
and
11 deletions
lib/data/repositories/message/compress_handler.dart
lib/data/repositories/message/upload_file.dart
lib/data/repositories/message/upload_start_handler.dart
lib/utils/video_util.dart
lib/data/repositories/message/compress_handler.dart
View file @
7a99523
...
@@ -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
(
'参数错误'
);
...
...
lib/data/repositories/message/upload_file.dart
View file @
7a99523
...
@@ -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}
毫秒'
);
...
...
lib/data/repositories/message/upload_start_handler.dart
View file @
7a99523
...
@@ -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
%'
);
...
...
lib/utils/video_util.dart
View file @
7a99523
...
@@ -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,6 +87,7 @@ class VideoUtil {
...
@@ -80,6 +87,7 @@ class VideoUtil {
// 中等质量: CRF 23-26
// 中等质量: CRF 23-26
// 低质量: CRF 28-32
// 低质量: CRF 28-32
int
crf
;
int
crf
;
if
(
quality
!=
null
&&
quality
.
isNotEmpty
)
{
switch
(
quality
)
{
switch
(
quality
)
{
case
'low'
:
case
'low'
:
crf
=
32
;
crf
=
32
;
...
@@ -93,6 +101,11 @@ class VideoUtil {
...
@@ -93,6 +101,11 @@ class VideoUtil {
default
:
default
:
throw
Exception
(
'参数错误'
);
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 '
// 视频编码器
'-crf
$crf
'
// 恒定速率因子(质量控制)
'-crf
$crf
'
// 恒定速率因子(质量控制)
...
@@ -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
(
...
...
Write
Preview
Styling with
Markdown
is supported
Attach a file
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to post a comment