事情是这样的,上周朋友发给我一个视频,足足365MB,他问我能不能帮忙压小点,要发群里,我说行啊,然后我试了试常见的工具,压是压了,但画质糊得跟马赛克似的,我想着,能不能用Go语言自己写个程序来搞定这件事?结果还真让我折腾出来了——从365MB压到25MB,画质肉眼基本无损。
你可能会问,Go语言不是做后端、做分布式系统的吗?跟视频压缩有什么关系?其实关系大了去了,Go的并发模型、内存管理、以及对FFmpeg这类底层工具的调用能力,让它处理这种重IO任务特别顺手,而且Go编译出来的二进制文件跑起来没有依赖,扔到服务器上就能跑,比Python写一堆依赖强多了。
那具体是怎么做到的?
先说说视频压缩的本质,视频其实就是一连串图片,每一帧都是一张图,如果直接存每一帧的原始数据,那文件当然大得吓人——你看一段1080p、30fps、时长10分钟的视频,原始大小差不多是 1920×1080×3(RGB)×30×600 ≈ 100GB,所以我们才会用编码器,比如H.264、H.265,用各种算法把帧之间的冗余信息去掉。
压缩的关键在于三件事:
-
编码器的选择
H.265(也叫HEVC)比H.264压缩效率高大概30%到50%,同样画质下,H.265的文件更小,但问题是很多浏览器不支持H.265,所以你要根据使用场景来权衡,我这次压的是准备发微信群的视频,微信本身会二次压缩,所以我选了H.265,压到25MB,微信再怎么压也糊不到哪里去。 -
比特率的控制
比特率决定了每秒视频的数据量,365MB到25MB,压缩比大约是14.6倍,按时长来算,如果视频是20分钟,比特率从原始的大约2.4Mbps降到170kbps左右。这时候如果直接用固定比特率,画面会崩。 所以我用了可变比特率(VBR)加上CRF(Constant Rate Factor)参数,让编码器在画面简单的地方省码率,复杂的地方多给码率。 -
分辨率和帧率
不是所有视频都需要4K 60fps,我朋友那个视频是1080p 30fps,我观察了一下内容——主要是他录的演讲PPT,动态不多,所以我把分辨率降到720p,帧率降到24fps,这一步直接砍掉了差不多一半的数据量。
然后是音频,很多人忽略音频,那365MB里面音频占了多少?如果是不压缩的PCM音频,码率是 44100×16×2 ≈ 1.4Mbps,我换成AAC编码,128kbps,压完大概占不到2MB,这一步锦上添花。
用Go写这个压缩程序的流程
我初始化了一个小项目,做这些事情:
package main
import (
"fmt"
"os/exec"
"path/filepath"
"runtime"
)
func compressVideo(inputPath string, outputPath string) error {
// 这里核心是调用 FFmpeg
args := []string{
"-i", inputPath,
"-c:v", "libx265", // 用 H.265 编码
"-crf", "28", // 控制质量,28 是个平衡点
"-preset", "medium", // 压缩速度和质量平衡
"-c:a", "aac",
"-b:a", "128k",
"-vf", "scale=1280:720", // 缩放到 720p
"-r", "24", // 降到 24 帧
outputPath,
}
cmd := exec.Command("ffmpeg", args...)
return cmd.Run()
}
你别看就这几行代码,里面的门道其实挺多的。
第一个坑是 -crf 28,CRF的范围是0到51,0是无损,51是最差,一般18到28是视觉无损到可接受的区间,我试了26、28、30,结果是28的时候文件大小刚好接近25MB,而且画面的文字边缘还能看清。 如果CRF到30,文字边缘就开始发虚了,PPT的视频最怕这个,因为观众要看字啊。
第二个坑是preset的选型,FFmpeg的preset有ultrafast、fast、medium、slow、veryslow等等,越慢压缩率越高,我是用medium,因为时间上比较合理——一个20分钟的视频,medium大概跑3到5分钟,veryslow要跑20分钟以上。压缩比只提升了不到10%,没必要。
第三个坑是并发处理,Go的优势在这,我朋友有好几个视频要压,我直接用goroutine并发跑:
func batchCompress(files []string) {
sem := make(chan struct{}, runtime.NumCPU())
for _, f := range files {
sem <- struct{}{}
go func(file string) {
defer func() { <-sem }()
compressVideo(file, filepath.Base(file)+"_compressed.mp4")
}(f)
}
// 等待所有任务完成
for i := 0; i < cap(sem); i++ {
sem <- struct{}{}
}
}
这样在4核机器上同时跑4个压缩任务,总时间只比压一个视频多一点。

还有几个进阶技巧
我后面又试了用 libvmaf 来评估压缩后的画质,VMAF是Netflix开源的一个视频质量评估模型,打分从0到100,越高越好,我原视频是原始录制,基本满分,压成25MB之后,VMAF得分大概在88左右。对于短视频分享来说,这个分数已经很能打了。 如果得分低于80,观众就会觉得哪不对劲。
如果视频里有静态场景(比如PPT的标题页),可以尝试 场景检测,在静态帧上分配更少的码率,这需要更复杂的逻辑——用 ffmpeg 的 scene 滤镜检测场景切换,然后在不同“片段”上用不同的CRF,我这次没用到,但如果你要追求极限压缩比,这是个方向。
最后说说文件格式
选MP4还是MKV?你要发微信群,MP4兼容性最好,我是先压成MKV(因为H.265在MKV里编码更快一些),然后用 ffmpeg -i input.mkv -c copy output.mp4 重新封装成MP4。这个过程不重新编码,所以质量不会损失,而且速度极快。
这样下来,就得到了一个25MB的MP4文件,朋友拿到手,看了一下,说:“诶,跟我原来的好像没什么区别啊。”那我就满意了。
所以你看,用Go写视频压缩不是个噱头。 它确实能work,而且能work得不错。
我从365MB压到25MB,压缩比差不多14倍,如果你手里的视频内容比较“静态”(比如讲课、会议记录),压缩比甚至可以到20倍,如果是动作片、游戏录像,那可能只能压到7到10倍。
想试试?先从装FFmpeg开始,然后写几行Go代码调用它,你也可以用 cgo 直接调libx265的C库,但没必要——命令行调用已经足够高效了,Go在这件事上的价值,不是替代FFmpeg,而是用它的并发和工程便利性,把“批量压缩不同参数组合挑最优结果”这类事情自动化。
就写到这里吧,有些参数你可能得自己调试几次——每个视频的“最优CRF”其实都不一样,跟内容复杂度有关,但这就是工程里那种真实的、带着颗粒感的过程,比那种一键压缩的软件有意思多了。
本文来自作者[kyadmin]投稿,不代表be365立场,如若转载,请注明出处:http://www.hljbesthome.com/jiankang/474.html
评论列表(4条)
我是be365的签约作者“kyadmin”!
希望本篇文章《365MB压缩成25MB视频文件?我用Go语言硬核实现了一把》能对你有所帮助!
本站[be365]内容主要涵盖:be365,be365网站,be365网址,ball365体育,Be365排名
本文概览:事情是这样的,上周朋友发给我一个视频,足足365MB,他问我能不能帮忙压小点,要发群里,我说行啊,然后我试了试常见的工具,压是压了,但画...