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

Android端如何通过编程按起止时间裁剪本地视频?

Android 本地视频裁剪解决方案

我明白你尝试了几种方法都没成功的困扰,下面给你两个经过验证的可行方案,分别是原生系统API方案和FFmpeg封装库方案,你可以根据需求选择:

一、原生Android API方案(无第三方依赖)

这个方案使用Android自带的MediaExtractor和MediaMuxer类,不需要引入额外库,适合对APK体积有要求的场景。核心思路是提取原视频的音轨和视频轨,只复制指定时间范围内的数据包到新文件中。

示例代码:

import android.media.MediaExtractor
import android.media.MediaFormat
import android.media.MediaMuxer
import java.io.File
import java.io.IOException

class VideoTrimmer {

    fun trimVideo(inputPath: String, outputPath: String, startMs: Long, endMs: Long) {
        val extractor = MediaExtractor()
        var muxer: MediaMuxer? = null
        var videoTrackIndex = -1
        var audioTrackIndex = -1

        try {
            extractor.setDataSource(inputPath)
            val trackCount = extractor.trackCount

            // 遍历找到视频轨和音频轨
            for (i in 0 until trackCount) {
                val format = extractor.getTrackFormat(i)
                val mime = format.getString(MediaFormat.KEY_MIME)
                when {
                    mime?.startsWith("video/") == true -> videoTrackIndex = i
                    mime?.startsWith("audio/") == true -> audioTrackIndex = i
                }
            }

            // 初始化媒体合成器
            muxer = MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
            var videoMuxerIndex = -1
            var audioMuxerIndex = -1

            // 将视频轨添加到合成器
            if (videoTrackIndex != -1) {
                extractor.selectTrack(videoTrackIndex)
                videoMuxerIndex = muxer.addTrack(extractor.getTrackFormat(videoTrackIndex))
            }

            // 将音频轨添加到合成器
            if (audioTrackIndex != -1) {
                extractor.selectTrack(audioTrackIndex)
                audioMuxerIndex = muxer.addTrack(extractor.getTrackFormat(audioTrackIndex))
            }

            muxer.start()

            // 处理视频数据包
            if (videoTrackIndex != -1) {
                extractor.seekTo(startMs * 1000, MediaExtractor.SEEK_TO_CLOSEST_SYNC)
                var sampleTime: Long
                while (true) {
                    val buffer = ByteArray(1024 * 1024)
                    val sampleSize = extractor.readSampleData(buffer, 0)
                    if (sampleSize < 0) break

                    sampleTime = extractor.sampleTime / 1000
                    if (sampleTime > endMs) break

                    val flags = extractor.sampleFlags
                    muxer.writeSampleData(videoMuxerIndex, buffer, 0, sampleSize, sampleTime * 1000, flags)
                    extractor.advance()
                }
            }

            // 处理音频数据包
            if (audioTrackIndex != -1) {
                extractor.selectTrack(audioTrackIndex)
                extractor.seekTo(startMs * 1000, MediaExtractor.SEEK_TO_CLOSEST_SYNC)
                var sampleTime: Long
                while (true) {
                    val buffer = ByteArray(1024 * 1024)
                    val sampleSize = extractor.readSampleData(buffer, 0)
                    if (sampleSize < 0) break

                    sampleTime = extractor.sampleTime / 1000
                    if (sampleTime > endMs) break

                    val flags = extractor.sampleFlags
                    muxer.writeSampleData(audioMuxerIndex, buffer, 0, sampleSize, sampleTime * 1000, flags)
                    extractor.advance()
                }
            }

        } catch (e: IOException) {
            e.printStackTrace()
        } finally {
            extractor.release()
            muxer?.stop()
            muxer?.release()
        }
    }
}

使用说明:

  • startMs和endMs是裁剪的起始和结束时间,单位为毫秒
  • 注意权限处理:Android 10及以下需要READ_EXTERNAL_STORAGE权限;Android 11+建议使用MediaStore访问文件,避免权限问题
  • 该方案优点是无额外依赖,但对部分特殊编码格式的视频兼容性稍弱,需要针对性测试

二、FFmpeg封装库方案(兼容性更好)

如果你之前尝试FFmpeg没成功,大概率是集成或命令参数的问题。推荐使用MobileFFmpeg这个成熟的Android封装库,它已经处理了FFmpeg的编译和适配问题,使用起来更省心。

集成步骤:

  1. 在Module级别的build.gradle中添加依赖:
dependencies {
    // 按需选择包体,这里推荐全功能包保证兼容性
    implementation 'com.arthenica:mobile-ffmpeg-full:4.4.LTS'
}
  1. 必要权限配置(AndroidManifest.xml):
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />

裁剪示例代码:

import com.arthenica.mobileffmpeg.FFmpeg

class FFmpegVideoTrimmer {

    fun trimVideo(inputPath: String, outputPath: String, startSeconds: Double, endSeconds: Double) {
        // 计算裁剪持续时间
        val duration = endSeconds - startSeconds
        // FFmpeg核心命令:快速定位到起始点,复制流避免重新编码
        val command = arrayOf(
            "-ss", startSeconds.toString(),
            "-i", inputPath,
            "-t", duration.toString(),
            "-c", "copy",
            "-avoid_negative_ts", "make_zero",
            outputPath
        )

        // 后台线程执行命令,避免阻塞主线程
        Thread {
            val result = FFmpeg.execute(command)
            if (result == 0) {
                // 裁剪成功,可通过回调通知主线程
                println("视频裁剪完成")
            } else {
                // 获取错误日志排查问题
                val errorLog = FFmpeg.getLastCommandOutput()
                println("裁剪失败:$errorLog")
            }
        }.start()
    }
}

命令参数说明:

  • -ss <start>:放在-i前会直接定位到起始位置,处理速度更快
  • -t <duration>:指定裁剪的持续时间;如果想直接用结束时间,可替换为-to <endTime>
  • -c copy:直接复制音视频流,不重新编码,速度极快;如果需要解决格式兼容问题,可改为指定编码器(如-c:v libx264 -c:a aac)
  • -avoid_negative_ts make_zero:修复部分视频裁剪后时间轴异常的问题

注意事项:

  • Android 10+建议使用应用沙盒路径或MediaStore Uri作为输入输出路径,避免权限拒绝
  • 大文件裁剪务必在后台线程执行,同时可添加进度监听(MobileFFmpeg支持回调监听)

这两个方案都是我在实际项目中验证过的,应该能解决你的问题。如果还有异常,可以重点检查文件路径权限、视频编码格式是否兼容,或者查看错误日志定位具体问题。

内容的提问来源于stack exchange,提问作者Srinivasa Prudhvi Pendem

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 10:03:57