使用FFmpeg在Android上实现视频时长剪裁
前言
裁剪视频片段是一个常见的音视频剪辑需求,使用FFmpeg命令可以很方便地剪裁指定开始时间和结束时间的视频片段。本篇文章在Android上使用FFmpeg实现视频时长剪裁功能。本文是Android实现FFmpeg命令行功能——在你的手机上使用命令行剪辑视频的下集。
FFmpeg命令裁剪视频片段
FFmpeg裁剪视频片段的命令如下:
ffmpeg -ss 00:02:00 -t 00:03:00 -i input.mp4 output.mp4
上面的命令表示截取视频input.mp4从00:02:00开始,时长为00:03:00的片段输出到文件output.mp4中。-ss后面的参数表示开始时间,-t后面的参数表示片段长度,-i后面的参数是输入文件。在大多数情况下,FFmpeg命令是无法直接精确定位到用户指定的开始时间的,而是定位到开始时间点前的靠近位置,从这个靠近位置开始解码视频到用户指定的开始时间,靠近位置和开始时间之间的多余视频片段会被舍弃,然后从用户指定的开始时间点继续解码得到用户指定时长的视频片段,重新编码该段视频输出到用户指定的输出文件中。上面的FFmpeg命令是可以比较精确地截取用户需要的视频片段的,只是重新编码视频这一操作会比较耗时。
在对精度没有那么高要求的情况下,下面的FFmpeg命令可以更快地裁剪视频片段。
ffmpeg -ss 00:02:00 -t 00:03:00 -i input.mp4 -c copy output.mp4
和第一条FFmpeg命令相比,该条命令多了-c copy。-c copy表示不进行重编码,而是进行流复制,这样可以省下编码视频的时间,缺点是裁剪的定位时间并不精准,主要原因是FFmpeg定位点和用户指定的开始时间之间的额外视频片段会被保留下来。
以上两种FFmpeg命令就可以满足大多数情况下裁剪视频片段的需求了。在Android实现FFmpeg命令行功能——在你的手机上使用命令行剪辑视频文章中,我们已经将FFmpeg命令行的实现代码编译成动态库引入我们的项目中,因此我们的项目是可以直接支持FFmpeg命令行的执行的。只要用户确定输出输出文件、开始时间和结束时间、精确裁剪还是快速裁剪,我们就可以使用FFmpeg命令行实现视频时长剪裁,接下来的任务开发一个视频时长剪裁的UI界面,让用户输入需要的信息。
使用Jetpack Compose开发视频时长剪裁的界面
我们设计的视频时长剪裁的界面包括一个视频播放器,供用户浏览视频,两个带有小球的滑块条,供用户左右移动小球选择开始时间和结束时间、精确裁剪还是快速裁剪的选择按钮、最后还需要用户输入目标文件名。
视频播放器我们使用的是Android官方推出的ExoPlayer播放器。注意原来的旧ExoPlayer项目已经被废弃了,ExoPlayer现在作为Android最新推出的媒体库AndroidX Media的一部分Media3 ExoPlayer继续更新和维护。根据Android官方的最新文档,我们在build.gradle.kts文件引入Media3 ExoPlayer。
implementation("androidx.media3:media3-exoplayer:1.5.1")
implementation("androidx.media3:media3-exoplayer-dash:1.5.1")
implementation("androidx.media3:media3-ui:1.5.1")
初始化一个exoPlayer对象。
exoPlayer = ExoPlayer.Builder(context).build().apply {
val mediaItem = MediaItem.fromUri(currentVideoUri!!)
setMediaItem(mediaItem)
prepare()
}
在页面上显示播放器。我们是在Compose函数中使用一个PlayerView对象。
var playerView: PlayerView? = null
...
AndroidView(
modifier = Modifier
.fillMaxWidth()
.height(200.dp),
factory = { ctx ->
playerView = PlayerView(ctx).apply {
player = viewModel.getExoPlayer()
useController = true // Important: Enable the default controls
}
playerView!!
}
)
ExoPlayer是一个功能非常完备的播放器,自带视频播放控件,使用它我们就不用自己去实现和播放视频相关的功能了。
接下来我们需要绘制两个滑块条供用户选择开始时间和结束时间。Jetpack Compose本身就提供了可以实现滑块条功能的基础组件Slider。
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Slider(
value: Float,
onValueChange: (Float) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
@IntRange(from = 0) steps: Int = 0,
onValueChangeFinished: (() -> Unit)? = null,
colors: SliderColors = SliderDefaults.colors(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
)
value是当前滑块条的数值,和滑块的位置对应。onValueChange是用户拖动滑块时的响应事件。valueRange是滑块条的数值的范围。steps是滑块条范围内离散值数量,例如steps=100,valueRange= 0f..1f,则0f..1f这个范围会被均分为100份,其中1份是滑块移动的最小单位。
Slider(
value = viewModel.startTime,
onValueChange = { newStartTime ->
viewModel.updateStartTimeAndSeek(newStartTime)
},
valueRange = 0f..1f,
steps = 100,
modifier = Modifier.fillMaxWidth()
)
Slider(
value = viewModel.endTime,
onValueChange = { newEndTime ->
viewModel.updateEndTimeAndSeek(newEndTime)
},
valueRange = 0f..1f,
steps = 100,
modifier = Modifier.fillMaxWidth()
)
用户在页面上拖动滑块来选择剪裁视频片段的开始时间(结束时间),同时视频播放器也会跳转到用户选定的开始时间(结束时间)。
我们使用kotlin里面的枚举类来表示精确裁剪和快速裁剪两种模式。提供两个按钮供用户选择精确裁剪和快速裁剪两种模式。
enum class CroppingMode {
Quick,
Precise
}
...
// Toggle Buttons for Cropping Mode
ToggleButton(
checked = viewModel.croppingMode == VideoClippingViewModel.CroppingMode.Quick, // Access from ViewModel
onCheckedChange = { viewModel.croppingMode = VideoClippingViewModel.CroppingMode.Quick }, // Set in ViewModel
modifier = Modifier.weight(1f)
) {
Text("Quick Crop")
}
Spacer(modifier = Modifier.width(8.dp))
ToggleButton(
checked = viewModel.croppingMode == VideoClippingViewModel.CroppingMode.Precise, // Access from ViewModel
onCheckedChange = { viewModel.croppingMode = VideoClippingViewModel.CroppingMode.Precise }, // Set in ViewModel
modifier = Modifier.weight(1f)
) {
Text("Precise Crop")
}
这样用户就可以选择剪裁模式了。
完成上面的开发,我们就得到了一个实现视频时长剪裁的界面。
最后我们还需要用户启动视频时长剪裁任务前输入目标文件名,可以设置一个弹窗供用户输入文件名信息。
AlertDialog(
onDismissRequest = { viewModel.showFileNameDialog = false }, // Set in ViewModel
title = { Text(context.getString(R.string.file_title)) },
text = {
OutlinedTextField(
value = viewModel.outputFileName, // Access from ViewModel
onValueChange = { viewModel.outputFileName = it }, // Set in ViewModel
label = { Text(context.getString(R.string.file_title)) }
)
},
confirmButton = {
Button(onClick = {
if (viewModel.outputFileName.isNotBlank()) {
val startTimeSeconds = viewModel.startTime * duration / 1000
val endTimeSeconds = viewModel.endTime * duration / 1000
Log.d(TAG,
"Start Time: $startTimeSeconds seconds, End Time: $endTimeSeconds seconds, " +
"Output File: ${viewModel.outputFileName}, Mode: ${viewModel.croppingMode}"
)
// Use viewModel.outputFileName in your video clipping logic
startTask(
activity,
file,
nextDestination,
viewModel
)
viewModel.showFileNameDialog = false // Close the dialog
viewModel.outputFileName = "" // Reset for next use (Optional)
}
}) {
Text(context.getString(R.string.determine))
}
},
dismissButton = {
Button(onClick = { viewModel.showFileNameDialog = false }) {
Text(context.getString(R.string.cancel))
}
}
)
用户点击“视频剪裁”会跳出一个弹窗让用户输入文件名。
用户选择好开始时间、结束时间、剪裁模式,点击“视频剪裁”,输入文件名,点击确定就可以开启一个视频时长剪裁任务了。具体来说我们根据用户输入的信息生成一个FFmpeg命令,然后在后台执行这个命令。
val cmd_str=
if(viewModel.croppingMode==VideoClippingViewModel.CroppingMode.Quick){
"ffmpeg -ss ${TextsUtils.millisecondsToString(start_time)} -t ${TextsUtils.millisecondsToString(new_duration)} -i ${file.path} -c copy ${target_path}"
}
else{
"ffmpeg -ss ${TextsUtils.millisecondsToString(start_time)} -t ${TextsUtils.millisecondsToString(new_duration)} -i ${file.path} ${target_path}"
}
总结
这样,我们就开发了一个具有视频时长剪裁功能的Android应用。在我自己的x86 Android10虚拟机和arm64-v8a Android14的真机上都可以正常运行。裁剪视频的精度和速度还是可以的。下次打算使用FFmpeg在Android上实现视频格式转换功能。 大家可以在GitHub上看见本项目的源码并下载编译好的的安卓应用。
如果不方便访问GitHub可以通过下面的链接下载编译好的的安卓应用。
音视频编辑器下载
有什么问题或者建议可以在评论区、私信或者邮箱联系我。 码字和写代码不易,希望大家能喜欢和关注我的项目,谢谢。
参考
1.ffmpeg Documentation(FFmpeg命令行的官方文档)
2.FFmpeg 实用命令
3.AndroidX Media
4.Composables(Compose组件信息合集网站)