1. 项目概述一个为移动端而生的视频压缩库如果你做过移动端应用开发尤其是涉及用户上传视频的功能大概率遇到过这个头疼的问题用户手机里拍的一段十几秒、几十秒的视频动辄几十兆甚至上百兆直接上传服务器不仅耗时耗流量还可能因为文件过大被服务端拒绝。在后台偷偷压缩吧要么压缩效果惨不忍睹要么压缩过程卡死主线程用户体验直接降到冰点。这就是ModelTC/LightCompress这个项目诞生的背景它瞄准的就是移动端Android iOS视频压缩这个高频且痛苦的场景。简单来说LightCompress是一个轻量级、高性能的视频压缩库。它的“轻量”体现在库本身的体积和内存占用上而“高性能”则意味着它能在保证可观压缩比和画质的前提下实现飞快的压缩速度。最吸引人的是它把复杂的视频编解码参数和流程封装成了极其简单的 API开发者可能只需要几行代码就能集成一个稳定可靠的视频压缩功能把用户上传的“庞然大物”变成适合网络传输的“小精灵”。我最初接触它是在一个社交类 App 的项目里用户需要上传短视频进行分享。我们尝试过调用系统MediaCodec自己写也试过一些其他开源方案不是太慢就是压缩后画质损失严重或者内存溢出崩溃。直到用了LightCompress才算是找到了一个在压缩质量、速度和稳定性上比较均衡的解决方案。它不是一个万能的、参数可精细调控到极致的专业工具而是一个为移动端应用场景深度优化的“开箱即用”型组件这正是其核心价值所在。2. 核心设计思路与架构解析2.1 为什么移动端视频压缩是个难题在深入LightCompress之前得先明白移动端视频压缩的挑战在哪这能帮你理解它设计上的取舍。首先资源限制严苛。移动设备的 CPU 算力、内存大小、电池续航都无法与桌面端相比。一个全高清1080p视频的压缩过程是计算密集型任务如果处理不当很容易导致手机发烫、应用卡顿甚至被系统杀死。其次用户体验要求高。压缩过程必须在后台进行不能阻塞用户交互。用户希望点击“上传”后很快就能看到结果而不是盯着一个转圈圈的进度条等上半天。这就要求压缩算法不仅要快还要能方便地实现异步处理和进度回调。再者效果需要平衡。压缩的本质是在文件大小、视频质量和压缩速度之间做权衡。无脑追求高压缩比画质可能变得模糊、出现色块过分追求画质文件体积又下不来。对于大多数社交、工具类应用需要的是一种“感知上还不错”的平衡文件显著变小但肉眼看起来画质下降不明显。LightCompress的设计正是围绕解决这三个核心挑战展开的。它没有试图去实现一个参数繁多的全能编码器而是基于大量实践预设了几套针对不同场景如聊天发送、动态发布、资料备份的优化参数组合让开发者可以像选择“画质优先”或“速度优先”模式一样简单调用。2.2 核心架构在 FFmpeg 与系统 MediaCodec 之间的抉择视频压缩的核心是编码器。在移动端主要有两条技术路径一是使用跨平台的 FFmpeg 库二是调用 Android 的MediaCodec或 iOS 的VideoToolbox等系统原生 API。FFmpeg 路径的优势是控制力极强兼容性极好你可以调整几乎所有编码参数。但劣势也很明显首先FFmpeg 库本身比较大集成后会显著增加 App 的安装包体积其次其纯软件编码的实现方式在移动设备上可能效率不如硬件编码更耗电。系统 MediaCodec/VideoToolbox 路径的优势是直接调用设备上的硬件编码器效率高、速度快、功耗低。这是移动设备为视频录制和播放设计的专用电路性能是它的强项。但劣势是不同厂商、不同系统版本的设备其硬件编码器的支持情况和表现可能存在差异参数调优的通用性稍差。LightCompress的选择非常明确在 Android 端优先使用MediaCodec在 iOS 端使用VideoToolbox。这是一个务实且高性能的选择。它放弃了 FFmpeg 带来的极致参数可控性换来了更小的二进制依赖、更快的编码速度和更低的功耗这完全契合移动端应用的核心诉求。它的架构可以理解为一个统一的、简洁的 API 层底下根据平台分流到各自最优的系统硬件编码器实现。同时它封装了视频解析、解码、滤镜如缩放、裁剪、编码、封装等一系列复杂流程对外只暴露几个关键配置项如目标分辨率、码率、帧率等。注意这种依赖系统硬件编码器的设计意味着压缩效果在一定程度上会受到设备本身的影响。不过现代中高端手机的硬件编码器质量已经相当不错对于常规的压缩需求其输出的一致性是可以接受的。3. 功能特性与核心参数详解3.1 主要功能特性一览LightCompress提供的功能非常聚焦都是围绕“压缩”这个核心任务展开的视频压缩核心功能支持设置输出视频的分辨率、视频码率、帧率、关键帧间隔等。视频裁剪可以在压缩前或压缩过程中按比例或指定尺寸裁剪视频画面。视频旋转自动检测或手动指定输出视频的旋转角度修正手机录制视频的方向问题。进度监听提供压缩进度的回调方便在 UI 上展示进度条。异步执行所有压缩任务默认在后台线程执行不阻塞主线程。多任务管理支持配置同时进行的压缩任务数量避免过多任务耗尽系统资源。3.2 关键配置参数解析与实战建议调用LightCompress时你需要理解并设置几个关键参数它们直接决定了输出视频的“体积、质量、速度”三角关系。1. 分辨率 (resolution)这是影响文件大小的首要因素。LightCompress通常允许你设置一个目标分辨率如 720p库会自动将原视频缩放至该分辨率。如果原视频分辨率已经低于目标值则保持原样。实战建议对于社交分享720p (1280x720) 是一个甜点分辨率在手机屏幕上观看清晰度足够文件体积比 1080p 小很多。如果是头像视频或小图预览可以考虑 480p 甚至更低。2. 视频码率 (videoBitrate)码率是每秒视频数据量单位通常是 Mbps 或 Kbps。它是影响画质和体积最直接的参数。LightCompress允许你设置一个目标码率。如何设定一个合理的码率这里有个经验公式可参考推荐码率 (Kbps) ≈ 目标分辨率宽度 * 目标分辨率高度 * 帧率 * 运动因子 * 0.07。运动因子对于谈话类、静态场景多的视频取 0.5-1.0对于一般运动、游戏画面取 1.0-1.5对于高速运动、复杂场景取 1.5-2.0。例如目标为 720p (1280x720)30fps一般运动场景运动因子1.01280 * 720 * 30 * 1.0 * 0.07 ≈ 1935 Kbps (约 1.9 Mbps)。LightCompress也提供了一些预设的码率等级如LOW,MEDIUM,HIGH内部就是基于类似逻辑计算的初学者可以直接使用。实战建议不要盲目追求低码率。过低的码率会导致严重的压缩瑕疵如色块、模糊。可以先使用库推荐的MEDIUM等级根据实际输出效果微调。3. 帧率 (frameRate)即每秒多少帧画面。降低帧率可以直接减少数据量。人眼对帧率的敏感度低于分辨率和画质。实战建议如果原视频是 30fps输出设置为 24fps 或 25fps在大多数情况下视觉流畅度感知差异不大但能有效减少体积。对于非游戏、非高速运动视频降至 20fps 也是可接受的选项。4. 关键帧间隔 (keyFrameInterval)也叫 GOP 长度。两个关键帧I帧之间的间隔。关键帧是完整编码的画面间隔内的帧P帧/B帧只记录与关键帧的差异。增大间隔可以减少关键帧数量压缩率更高但拖动进度条时定位可能会稍慢。实战建议对于需要快速预览拖拽的视频可以设置为 2-3 秒例如30fps 下设置interval60表示每60帧一个关键帧即2秒。对于纯顺序播放、不常拖动的视频可以设置到 5-10 秒以获取更好的压缩率。5. 音频配置视频压缩通常也包含音频轨道的重编码。可以配置音频的码率、采样率等。实战建议对于语音为主的视频如教程、聊天音频码率 64kbps 的单声道Mono就足够了。对于有背景音乐的视频可以提升到 128kbps 的立体声Stereo。过高的音频码率对整体体验提升有限但会增加文件大小。下面是一个参数选择的速查表你可以根据你的应用场景快速定位应用场景推荐分辨率推荐视频码率推荐帧率核心目标即时通讯/聊天发送480p 或 原视频缩小50%LOW (约500-800kbps)≤ 24fps极限体积最小化保证可看清社交动态/短视频分享720pMEDIUM (约1.5-2.5Mbps)24-30fps平衡画质与体积主流选择用户资料/作品展示1080p (如果原视频够高)HIGH (约3-5Mbps)原帧率或30fps画质优先文件稍大也可接受应用内预览/缩略图固定宽度如320px非常低 (200-400kbps)15fps 或更低快速加载画质可妥协4. Android 平台集成与核心代码剖析4.1 环境集成与依赖添加在 Android 项目中集成LightCompress非常 straightforward。它已经发布在 Maven Central 上。在你的模块级build.gradle.kts(或build.gradle) 文件中添加依赖dependencies { implementation(com.github.model-tc:light-compress:最新版本号) // 请替换为GitHub releases页的最新版本 }或者如果你仍在使用 Groovy DSLdependencies { implementation com.github.model-tc:light-compress:最新版本号 }添加依赖后同步项目即可。库内部已经处理了MediaCodec等原生依赖你无需额外配置。4.2 核心 API 使用与实战示例LightCompress的核心类是LightCompressor。压缩一个视频通常包含以下步骤创建压缩配置对象 (Compression)这是核心定义所有输出参数。执行压缩调用compress()方法传入源文件路径、目标文件路径和配置。处理结果通过回调接收成功、失败或进度更新。下面是一个完整的 Kotlin 示例展示了如何压缩一个视频并显示进度import com.abedelazizshe.lightcompressor.LightCompressor import com.abedelazizshe.lightcompressor.Compression import com.abedelazizshe.lightcompressor.CompressionListener import com.abedelazizshe.lightcompressor.VideoQuality // 1. 定义源文件和目标文件 val sourceUri: Uri ... // 从相册或相机获取的 Uri val sourcePath getRealPathFromUri(context, sourceUri) // 需要将 Uri 转换为实际文件路径 val destinationPath File(context.cacheDir, compressed_${System.currentTimeMillis()}.mp4).absolutePath // 2. 创建压缩配置 val compression Compression( // 视频质量预设内部会计算对应的码率 videoQuality VideoQuality.MEDIUM, // 是否禁止音频true则输出静音视频 isMinBitRateEnabled false, // 目标视频宽度高度会按比例自动计算 videoWidth 720, // 目标帧率设为0则保持原帧率 frameRate 25, // 关键帧间隔秒 keyFrameInterval 2f, // 视频比特率单位Kbps如果设置了videoQuality此参数可能被覆盖 videoBitrate 1500, ) // 3. 执行压缩 LightCompressor().compress( srcPath sourcePath, destPath destinationPath, listener object : CompressionListener { override fun onStart() { // 压缩开始可以显示进度对话框 runOnUiThread { showProgressDialog(压缩中...) } } override fun onSuccess() { // 压缩成功destinationPath 就是压缩后的文件 runOnUiThread { dismissProgressDialog() uploadVideo(destinationPath) // 执行上传等后续操作 } } override fun onFailure(failureMessage: String) { // 压缩失败 runOnUiThread { dismissProgressDialog() Toast.makeText(context, 压缩失败: $failureMessage, Toast.LENGTH_LONG).show() } } override fun onProgress(percent: Float) { // 进度更新percent 范围 0-100 runOnUiThread { updateProgressDialog(percent.toInt()) } } }, compression compression )代码要点解析VideoQuality.MEDIUM这是一个非常实用的预设。如果你对码率没概念直接用LOW/MEDIUM/HIGH是最省事且效果不错的方式。videoWidth 720设置了目标宽度为 720 像素高度会根据原视频宽高比自动计算防止画面变形。frameRate 25将帧率设置为 25fps这是一个在体积和流畅度之间很好的平衡点也是 PAL 制式的标准帧率。keyFrameInterval 2f每2秒一个关键帧兼顾压缩率和拖拽体验。监听器在主线程回调onStart,onSuccess,onFailure,onProgress这些回调默认不在主线程如果你要更新 UI必须用runOnUiThread或Handler切回主线程。4.3 高级功能视频裁剪与旋转除了压缩LightCompress也支持在压缩流程中嵌入裁剪和旋转操作。裁剪示例假设你想将视频裁剪为 1:1 的正方形适用于某些社交平台的头像或封面。val compression Compression( videoQuality VideoQuality.MEDIUM, videoWidth 720, // 启用裁剪并设置裁剪模式 videoBitrate 1500, ).setTrim(enable true, startMs 0, endMs 10000) // 可选裁剪前10秒 .setVideoScale(enable true, width 720, height 720) // 关键强制输出为720x720 // 注意setVideoScale 会强制拉伸到指定尺寸可能变形。 // 更常见的做法是使用“裁剪”模式但库的裁剪API可能需要指定矩形区域。 // 这里假设库提供了setCrop方法请查阅最新API文档 // .setCrop(enable true, x 0, y 0, width targetWidth, height targetHeight)旋转示例很多手机录制的视频带有旋转元数据Rotation Metadata播放器能正确识别但有些处理流程会丢失这个信息。LightCompress可以强制修正旋转。val compression Compression( videoQuality VideoQuality.MEDIUM, videoWidth 720, ).setFixedRotation(rotation 90) // 将视频顺时针旋转90度实操心得关于裁剪一个更通用的实践是先获取原视频的宽高计算出一个位于画面中心的、最大可能的正方形或其他比例区域然后将这个区域的坐标和尺寸传递给裁剪函数。LightCompress可能不直接提供高级裁剪逻辑你可能需要自己计算这些参数。5. iOS 平台集成与 Swift 实战5.1 使用 CocoaPods 集成对于 iOS 项目使用 CocoaPods 是集成LightCompress最方便的方式。在你的Podfile中添加pod LightCompressor然后运行pod install。库内部基于VideoToolbox和AVFoundation框架无需额外链接系统库。5.2 Swift 代码示例与最佳实践iOS 端的 API 设计与 Android 端理念一致但具体用法遵循 Swift 风格。import LightCompressor import AVFoundation class VideoCompressor { func compressVideo(sourceURL: URL, destinationURL: URL) { // 1. 创建压缩配置 let compression Compression( quality: .medium, // 质量预设 videoSize: CGSize(width: 720, height: 1280), // 目标尺寸 (注意是 CGSize) // 也可以只设置宽度高度自动计算: videoWidth: 720 frameRate: 25, videoBitrate: 1500000, // 单位比特每秒 (bps)这里是1.5Mbps keyFrameInterval: 2 // 单位秒 ) // 2. 创建压缩器实例 let lightCompressor LightCompressor() // 3. 执行压缩 lightCompressor.compressVideo( source: sourceURL, destination: destinationURL, configuration: compression, progressQueue: .main, // 进度回调所在的队列 progressHandler: { progress in // progress 是一个 Float (0.0 ~ 1.0) print(压缩进度: \(progress * 100)%) self.updateProgressView(Float(progress)) }, completion: { result in DispatchQueue.main.async { switch result { case .onSuccess(let path, let sizeBefore, let sizeAfter): let reduction Double(sizeBefore - sizeAfter) / Double(sizeBefore) * 100 print(压缩成功路径: \(path)) print(原始大小: \(sizeBefore) bytes, 压缩后: \(sizeAfter) bytes, 减少了 \(String(format: %.1f, reduction))%) // 处理压缩后的视频 self.handleCompressedVideo(at: destinationURL) case .onStart: print(压缩任务开始) case .onFailure(let error): print(压缩失败: \(error.localizedDescription)) // 处理错误 } } } ) } private func updateProgressView(_ progress: Float) { // 更新UI进度条 } private func handleCompressedVideo(at url: URL) { // 上传或预览压缩后的视频 } }iOS 端特别注意尺寸参数iOS 的videoSize是CGSize类型需要同时指定宽高。如果你只想设定宽度让高度自适应可能需要先读取原视频尺寸然后计算等比高度。或者查看库是否提供了videoWidth这样的便捷参数。码率单位注意videoBitrate的单位是bps (bits per second)而不是 Android 端常用的 Kbps。1.5 Mbps 应写为1500000。内存管理视频压缩是内存密集型操作。确保在合适的时机如视图控制器销毁时取消未完成的压缩任务并妥善处理输入/输出文件的 URL避免内存泄漏。后台任务如果压缩任务可能耗时很长需要考虑应用进入后台的情况。虽然LightCompressor本身在后台线程运行但你可能需要向系统申请后台任务执行时间 (BGTask)以确保压缩能在应用退到后台后继续完成。6. 性能优化与避坑指南6.1 内存与性能优化策略即使LightCompress底层用了硬件编码处理大视频时仍需要注意资源管理。限制并发任务不要同时发起太多压缩任务。虽然库可能内部有队列但最佳实践是由应用层控制一个全局的、有限大小的任务队列例如最多同时进行2个压缩任务。过多的并发任务会争抢有限的硬件编码器资源和内存导致所有任务都变慢甚至引发 OOM内存溢出崩溃。及时释放资源压缩完成后确保对源文件和目标文件的引用被及时释放特别是那些存储在临时目录的大文件。对于失败的任务也要清理产生的中间文件。使用适当的视频尺寸在压缩前先判断原视频尺寸。如果用户上传的是一个 4K 视频而你最终只需要 720p那么先让库将其下采样到 720p 再进行编码是合理的。避免用高分辨率进行高码率编码再缩小那样效率极低。利用isMinBitRateEnabled参数这个参数设为true时库会尝试启用一个最低比特率模式。在某些场景下如原视频本身码率就很低、画面简单这可以防止编码器输出比原文件还大的“负压缩”情况。但副作用是可能在某些复杂场景下画质损失更明显。建议根据实际测试决定是否开启。6.2 常见问题与排查实录在实际集成中你可能会遇到以下问题问题1压缩后的视频体积反而变大了原因分析这是“负压缩”现象。通常发生在原视频已经是低分辨率、低码率比如已经用某个高效率编码器压缩过而你的压缩配置如码率设置得比原视频还高。解决方案先获取原视频的码率和分辨率信息。如果原视频码率已经很低例如低于500kbps且分辨率符合要求可以考虑跳过压缩直接使用原视频。启用isMinBitRateEnabled参数如果库支持防止输出码率过高。更激进地降低目标码率或分辨率。问题2压缩进度卡在某个百分比不动最后失败原因分析可能遇到了视频文件中无法解码的损坏帧或者设备硬件编码器对该视频的某些特性如特定的色彩格式、编码Profile支持不佳。解决方案在onFailure回调中检查错误信息。LightCompress通常会返回一个错误描述。尝试用其他播放器或工具先修复一下原视频文件。考虑使用一个更“宽容”的配置比如降低目标分辨率或帧率有时能绕过编码器的某些限制。在代码中添加更完善的异常捕获和日志记录定位卡住的具体位置。问题3压缩后的视频没有声音原因分析压缩配置中可能禁用了音频或者源视频的音频编码格式不被输出容器支持。解决方案检查Compression配置中是否有disableAudio或类似参数确保其值为false。LightCompress默认会保留并重新编码音频。如果原视频音频是特殊编码如某些手机录制的 HE-AAC确保库的版本支持该格式的音频流转码。可以尝试输出一个只有视频的版本和一个音视频都有的版本来对比测试。问题4在部分低端或老旧机型上压缩崩溃原因分析硬件编码器在不同芯片如高通、联发科、麒麟和不同系统版本上支持的特性有差异。某些过于激进或特殊的编码参数可能在特定设备上不被支持。解决方案降级配置在低端设备上自动使用更保守的配置如使用VideoQuality.LOW降低分辨率到 480p。异常回退实现一个安全模式。当使用优选配置压缩失败时捕获异常并自动用一套最基础、兼容性最广的配置如 H.264 Baseline Profile, 低码率重试一次。设备检测可以根据设备型号或 API Level 来动态选择配置策略。问题5如何准确获取原视频信息在决定压缩参数前了解原视频的“底子”很重要。你可以使用 Android 的MediaMetadataRetriever或 iOS 的AVAsset来获取信息。Android 示例 (Kotlin):fun getVideoInfo(filePath: String): PairInt, Int? { val retriever MediaMetadataRetriever() return try { retriever.setDataSource(filePath) val width retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toIntOrNull() val height retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toIntOrNull() val rotation retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION)?.toIntOrNull() // 注意有些视频的宽高元数据是旋转后的需要根据rotation矫正 if (width ! null height ! null) { if (rotation 90 || rotation 270) { // 如果视频被旋转了90或270度实际显示的宽高应该互换 Pair(height, width) } else { Pair(width, height) } } else { null } } catch (e: Exception) { e.printStackTrace() null } finally { retriever.release() } }获取到原始宽高和旋转信息后你就能智能地决定目标分辨率避免不必要的放大或错误的裁剪。7. 进阶应用与场景扩展7.1 实现“智能压缩”策略一个成熟的应用不会对所有视频“一刀切”地用同一套参数压缩。可以设计一个智能策略根据原视频质量分级读取原视频的码率、分辨率、时长。如果原视频码率已经很低如 1 Mbps且分辨率适中如 1080p可以轻度压缩甚至跳过压缩。如果原视频是 4K 高码率则采用较强的压缩降低分辨率到 1080p 或 720p使用中等码率。根据网络环境调整结合设备当前的网络类型Wi-Fi/4G/5G。在 Wi-Fi 环境下可以适当放宽码率限制提供更好的画质。在蜂窝网络下则采用更激进的压缩策略为用户节省流量。根据用户选择提供“高质量上传”耗流量和“节省流量上传”两个选项将选择权交给用户。7.2 与上传模块的协同压缩不是终点上传才是。需要设计好压缩与上传的流水线。串行 vs 并行对于多个视频是压缩完一个上传一个串行还是压缩和上传各自独立的流水线并行通常串行更简单资源占用可控并行效率更高但需要更复杂的任务调度和错误处理。任务持久化对于可能耗时很长的任务如压缩并上传多个大视频要考虑应用被杀死的情况。可以将待压缩的任务队列、进行中的任务状态持久化到数据库或本地文件应用重启后能恢复。上传前的最后检查压缩完成后在上传前最后检查一下输出文件文件是否存在、大小是否合理避免负压缩、是否可以正常打开。这一步可以拦截掉大部分有问题的任务避免无意义的网络请求。7.3 监控与数据统计为了持续优化体验可以收集一些匿名数据压缩耗时分布记录不同分辨率、时长的视频压缩所需时间用于评估性能。压缩比分布统计原始大小与压缩后大小的比例了解压缩效果。失败率与错误类型监控压缩失败的情况分析是哪些原因导致的文件损坏、参数不支持、内存不足等。 这些数据可以帮助你调整默认的压缩参数或者针对特定机型做兼容性优化。LightCompress作为一个工具库解决了视频压缩的核心技术问题。但要把它用好融入到真实的产品流程中还需要开发者在上层根据自身业务逻辑构建起任务管理、策略选择、错误处理、用户体验这一整套体系。它提供了一块高性能的“砖”而你需要用它来建造稳固的“墙”。