提示

返回博客列表

一个 4GB 的视频怎么压到 200MB 还看不出区别?视频压缩的实战参数指南

一个 4GB 的视频怎么压到 200MB 还看不出区别?视频压缩的实战参数指南

你录了一个小时的屏幕操作教程,源文件 4GB。想发给同事,微信提示"文件过大";想传到网盘,上传要半小时;想存本地,硬盘快红了。怎么办?压缩。但压缩这件事水很深——CRF 设多少合适?2-Pass 到底什么时候用?preset 选 fast 还是 slow?压完之后画质损失了多少?这篇文章不堆理论公式,直接给不同场景下的实战参数组合,你拿过去改个文件名就能用。附带我压了上百个视频总结出的"体积/画质平衡表"。

TL;DR:日常压缩首选 CRF 模式——设一个质量值(如 23),FFmpeg 自动分配码率,简单省心。需要精确控制体积用 2-Pass。H.264 CRF 推荐:存档级 18、日常 23、分享 28、预览 35。H.265 在同等画质下 CRF 可以比 H.264 高 4-6(体积小 30-40%)。preset 选 slow 比 medium 多压 10-15% 但编码时间翻倍,日常用 medium 够了。

目录

一、压缩之前:先搞清楚你为什么要压

视频压缩不是目的,解决问题才是。不同目的对应完全不同的参数选择:

目的 核心需求 推荐方案 典型压缩比
存档收藏 画质优先,体积次要 CRF 18 + slow 30-50%
日常使用 画质和体积平衡 CRF 23 + medium 50-70%
分享发送 体积优先,画质可接受 CRF 28 + fast 70-85%
预览缩略图 能看清就行 CRF 35 + ultrafast 90-95%
微信发送 必须 < 25MB(或 100MB) 2-Pass 目标体积 视情况

一个常见的错误:拿到视频就 -crf 18 往上怼,压完之后体积几乎没变,白等半天。如果原视频本身就是高压缩比的(比如 YouTube 下载的 VP9 视频),再压的空间很小。 压缩前先用 ffprobe 看一下原始码率:

ffprobe -v error -show_entries format=bit_rate -of default=noprint_wrappers=1:nokey=1 input.mp4

如果原始码率已经很低(比如 1080P 只有 2Mbps),那压缩空间确实有限——这种情况下与其压视频,不如直接接受原文件大小。

二、CRF 模式:设一个值,剩下交给编码器

2.1 CRF 的工作原理(用人话说)

CRF(Constant Rate Factor)的核心理念:你告诉我"要多好的画质",我帮你分配刚好够用的码率。

CRF 工作流程:
1. 你把 CRF 设为 23
2. 编码器逐帧分析画面复杂度
3. 简单的帧(纯色背景)→ 分配低码率
4. 复杂的帧(快速运动、细节丰富)→ 分配高码率
5. 最终:所有帧的"视觉质量"一致

这就是为什么 CRF 模式下你不能直接控制文件大小——复杂视频自然需要更多数据,简单视频自然体积小。不同视频用同一个 CRF 值,出来的体积可能差好几倍。

2.2 H.264 CRF 值速查

CRF 画质描述 1080P 典型码率 适用场景
0 无损(巨大) 不适用 中间处理、存档母带
18 接近无损 8-15 Mbps 本地存档、需要二次编辑
20 极佳 5-10 Mbps 高清收藏
23 默认,很好 2.5-5 Mbps 日常使用推荐
26 不错 1.5-3 Mbps 云存储、NAS 归档
28 可接受 1-2 Mbps 微信/邮件分享
32 明显压缩感 500k-1 Mbps 预览、缩略图
35 较差 300-500 kbps 仅用于识别内容
51 最差 极低 不推荐

2.3 实战命令

# 日常压缩(推荐)
ffmpeg -i input.mp4 -c:v libx264 -preset medium -crf 23 \
  -c:a aac -b:a 128k output.mp4

# 存档级(画质优先)
ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 18 \
  -c:a aac -b:a 192k output.mp4

# 分享级(体积优先)
ffmpeg -i input.mp4 -c:v libx264 -preset fast -crf 28 \
  -c:a aac -b:a 96k output.mp4

# 同时缩放分辨率(4K → 1080P,进一步减体积)
ffmpeg -i input.mp4 -c:v libx264 -preset medium -crf 23 \
  -vf "scale=1920:-2" -c:a aac -b:a 128k output.mp4

关于音频的说明:很多人只调视频参数,忘了音频。一个 AAC 128kbps 的音频流在 1 小时视频中约占 56MB——不小。如果原视频的音频已经是低码率,用 -c:a copy 直接复制,别重新编码。

三、2-Pass:精确控制体积的唯一方式

3.1 什么时候必须用 2-Pass

CRF 的问题是:你不知道压出来多大。以下场景必须用 2-Pass:

- 微信发送:视频必须 < 100MB(或 < 25MB)
- 上传到有限制的平台:比如某些 CMS 限制 50MB
- 批量处理:100 个视频,需要总大小控制在 10GB 以内
- 流媒体:需要生成固定码率的 ABR 档位

3.2 2-Pass 的原理

第一遍(Pass 1):编码器快速扫描整个视频,记录每一帧的复杂度
  → 输出一个统计文件(.log),包含"哪里复杂、哪里简单"

第二遍(Pass 2):根据第一遍的统计信息,合理分配码率
  → 复杂的帧多给码率,简单的帧少给
  → 在给定总体积下,实现最好的画质

3.3 实战命令

# 目标体积 50MB,视频时长 600 秒
# 计算视频码率:
#   总码率 = 50MB × 8 × 1024 / 600s ≈ 682 kbps
#   视频码率 = 682 - 128(音频) = 554 kbps

# Pass 1
ffmpeg -i input.mp4 -c:v libx264 -preset medium \
  -b:v 554k -pass 1 -an -f mp4 NUL

# Pass 2
ffmpeg -i input.mp4 -c:v libx264 -preset medium \
  -b:v 554k -pass 2 -c:a aac -b:a 128k output.mp4

3.4 自动计算目标码率的脚本

import subprocess
import json
import os

def compress_to_target_size(input_path, output_path, target_mb):
    """将视频压缩到指定体积"""
    # 获取视频时长
    cmd = ['ffprobe', '-v', 'error', '-show_entries',
           'format=duration', '-of', 'json', input_path]
    result = subprocess.run(cmd, capture_output=True, text=True)
    duration = float(json.loads(result.stdout)['format']['duration'])

    # 音频码率
    audio_bitrate = 128  # kbps

    # 计算视频码率
    # 目标体积 (bits) = 总码率 × 时长
    # 总码率 = 目标体积 / 时长
    total_bitrate = (target_mb * 8 * 1024) / duration  # kbps
    video_bitrate = max(100, int(total_bitrate - audio_bitrate))

    print(f"视频时长: {duration:.0f}s")
    print(f"目标体积: {target_mb}MB")
    print(f"视频码率: {video_bitrate} kbps")

    # 如果计算出的码率比原视频还高,直接用 CRF
    if video_bitrate > 5000:
        print("目标体积足够大,改用 CRF 23")
        subprocess.run([
            'ffmpeg', '-i', input_path,
            '-c:v', 'libx264', '-preset', 'medium', '-crf', '23',
            '-c:a', 'aac', '-b:a', f'{audio_bitrate}k',
            output_path, '-y'
        ], check=True)
        return

    # Pass 1
    subprocess.run([
        'ffmpeg', '-i', input_path,
        '-c:v', 'libx264', '-preset', 'medium',
        '-b:v', f'{video_bitrate}k',
        '-pass', '1', '-an',
        '-f', 'mp4', 'NUL' if os.name == 'nt' else '/dev/null',
        '-y'
    ], check=True)

    # Pass 2
    subprocess.run([
        'ffmpeg', '-i', input_path,
        '-c:v', 'libx264', '-preset', 'medium',
        '-b:v', f'{video_bitrate}k',
        '-pass', '2',
        '-c:a', 'aac', '-b:a', f'{audio_bitrate}k',
        output_path, '-y'
    ], check=True)

    # 清理临时文件
    for f in ['ffmpeg2pass-0.log', 'ffmpeg2pass-0.log.mbtree']:
        if os.path.exists(f):
            os.remove(f)

    actual_size = os.path.getsize(output_path) / 1024 / 1024
    print(f"实际体积: {actual_size:.1f}MB")


if __name__ == '__main__':
    compress_to_target_size('input.mp4', 'output.mp4', target_mb=50)

四、preset 选择:时间换空间的交易

4.1 preset 的本质

preset 控制的是编码器在"寻找最优压缩方式"上花多少时间。它不改变画质(CRF 相同,画质基本一致),只改变压缩效率和编码速度。

ultrafast → superfast → veryfast → faster → fast → medium → slow → slower → veryslow

速度快 ←————————————————————————————→ 压缩率高
体积大 ←————————————————————————————→ 体积小

4.2 实测数据(1080P 10分钟视频,CRF 23)

preset 编码时间 输出体积 相对 medium 的压缩率
ultrafast 18s 215 MB -18%(更大)
veryfast 35s 198 MB -9%
fast 52s 189 MB -4%
medium 68s 182 MB 基准
slow 132s 168 MB +8%
slower 280s 157 MB +14%
veryslow 610s 148 MB +19%

结论:从 medium 到 slow,体积减少 8%,时间翻倍——性价比不错。但从 slow 到 veryslow,体积再减 11%,时间翻了 4.6 倍——得不偿失。日常用 medium,重要的存档用 slow,足够了。

4.3 一个反直觉的事实

很多教程说"preset 只影响编码速度不影响画质"——这个说法不准确。正确的表述是:相同 CRF 下,不同 preset 的画质基本一致,但 slower preset 能用更低的码率达到相同画质。

举个例子:CRF 23 + mediumCRF 25 + slow,出来的体积可能差不多,画质也差不多。因为 slow 在压缩效率上的提升,相当于把 CRF 调低了 2 左右。

五、实战场景速查表

场景 命令 预期效果
屏幕录制教程 -c:v libx264 -preset medium -crf 26 -c:a aac -b:a 96k 4GB → 200MB
手机拍摄视频 -c:v libx264 -preset slow -crf 23 -c:a aac -b:a 128k 1GB → 300MB
游戏录屏 -c:v libx264 -preset fast -crf 22 -c:a aac -b:a 192k 需要高码率保留运动细节
动画/二次元 -c:v libx264 -preset medium -crf 26 -tune animation 动画压缩效率极高
PPT 录屏 -c:v libx264 -preset ultrafast -crf 30 静态画面,极致压缩
存档电影 -c:v libx265 -preset slow -crf 22 -c:a copy H.265 节省 30% 体积
微信发视频 用 2-Pass,目标 25MB 精确控制
4K → 1080P -vf scale=1920:-2 -c:v libx264 -crf 23 体积直接降 4 倍

六、H.264 vs H.265:什么时候值得升级

6.1 实际压缩效率对比

我拿同一个 4K 视频(10分钟,原始 8GB)做了测试:

编码 CRF 体积 编码时间 VMAF
H.264 23 520 MB 68s 92.1
H.265 23 310 MB 245s 93.5
H.265 28 195 MB 230s 91.8

结论: - H.265 CRF 28 的体积(195MB)和 H.264 CRF 23(520MB)画质接近,但体积只有 37% - H.265 的代价是编码时间长 3-4 倍

6.2 决策建议

用 H.264 的场景:
  ✅ 需要快速压缩(中等 CPU)
  ✅ 要发给别人(兼容性最好,所有设备都能播)
  ✅ 视频本身不大(< 500MB),压缩空间有限

用 H.265 的场景:
  ✅ 存档收藏(压一次,看很多次)
  ✅ 4K/HDR 视频(H.265 对高分辨率优势更大)
  ✅ 大量视频需要批量压缩(虽然慢,但省的空间值回时间)
  ✅ 自己用 Jellyfin 看(服务端转码,客户端兼容性不是问题)

6.3 H.265 命令

# H.265 软件编码(慢但效果好)
ffmpeg -i input.mp4 -c:v libx265 -preset medium -crf 28 \
  -c:a aac -b:a 128k output.mp4

# H.265 硬件加速编码(Intel QSV,快但同码率画质略差)
ffmpeg -i input.mp4 -c:v hevc_qsv -preset medium -global_quality 25 \
  -c:a aac -b:a 128k output.mp4

注意:H.265 的 CRF 值和 H.264 的不是一个尺度。H.265 的 CRF 28 画质约等于 H.264 的 CRF 23。换算关系大概是 H.265 CRF = H.264 CRF + 4~6。

七、批量压缩脚本

#!/usr/bin/env python3
"""批量压缩目录下所有视频"""

import os
import subprocess
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor


def compress_video(input_path, output_path, crf=23, preset='medium'):
    """压缩单个视频"""
    cmd = [
        'ffmpeg', '-i', str(input_path),
        '-c:v', 'libx264', '-preset', preset, '-crf', str(crf),
        '-c:a', 'aac', '-b:a', '128k',
        '-movflags', '+faststart',  # 网页渐进式加载
        str(output_path), '-y'
    ]

    try:
        subprocess.run(cmd, capture_output=True, check=True, timeout=3600)

        in_size = os.path.getsize(input_path) / 1024 / 1024
        out_size = os.path.getsize(output_path) / 1024 / 1024
        ratio = (1 - out_size / in_size) * 100 if in_size else 0

        return {
            'file': input_path.name,
            'in_mb': in_size,
            'out_mb': out_size,
            'saved': ratio,
            'success': True,
        }
    except Exception as e:
        return {'file': input_path.name, 'success': False, 'error': str(e)}


def batch_compress(input_dir, output_dir, crf=23, preset='medium',
                   workers=2, skip_existing=True):
    """批量压缩"""
    os.makedirs(output_dir, exist_ok=True)
    video_exts = {'.mp4', '.mkv', '.mov', '.avi', '.flv', '.webm', '.ts'}

    tasks = []
    for filepath in Path(input_dir).iterdir():
        if filepath.suffix.lower() in video_exts:
            out_path = Path(output_dir) / f'{filepath.stem}_compressed.mp4'
            if skip_existing and out_path.exists():
                print(f"跳过(已存在): {filepath.name}")
                continue
            tasks.append((filepath, out_path))

    print(f"待压缩: {len(tasks)} 个视频")
    results = []

    with ThreadPoolExecutor(max_workers=workers) as executor:
        futures = {}
        for in_path, out_path in tasks:
            future = executor.submit(
                compress_video, in_path, out_path, crf, preset
            )
            futures[future] = in_path.name

        for i, future in enumerate(futures):
            result = future.result()
            results.append(result)

            name = futures[future]
            if result['success']:
                print(f"[{i+1}/{len(tasks)}] {name}: "
                      f"{result['in_mb']:.0f}MB → {result['out_mb']:.0f}MB "
                      f"({result['saved']:.0f}%)")
            else:
                print(f"[{i+1}/{len(tasks)}] {name}: 失败 - {result['error']}")

    # 汇总
    success = [r for r in results if r['success']]
    if success:
        total_in = sum(r['in_mb'] for r in success)
        total_out = sum(r['out_mb'] for r in success)
        total_saved = (1 - total_out / total_in) * 100
        print(f"\n总计: {total_in:.0f}MB → {total_out:.0f}MB "
              f"(节省 {total_saved:.0f}%)")

    return results


if __name__ == '__main__':
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('input_dir')
    parser.add_argument('-o', '--output-dir', default='./compressed')
    parser.add_argument('--crf', type=int, default=23)
    parser.add_argument('--preset', default='medium')
    parser.add_argument('-w', '--workers', type=int, default=2)
    args = parser.parse_args()

    batch_compress(args.input_dir, args.output_dir,
                   args.crf, args.preset, args.workers)

八、压缩后别忘了验画质

压完之后怎么看画质损失了多少?快速方法:

# 用 VMAF 对比原片和压缩版
ffmpeg -i original.mp4 -i compressed.mp4 \
  -filter_complex "libvmaf=log_path=vmaf.json:log_fmt=json" \
  -f null /dev/null

# 查看分数
cat vmaf.json | grep VMAF_score

更直观的方式:截取同一帧对比

# 截取原片第 60 秒
ffmpeg -i original.mp4 -ss 60 -vframes 1 original_frame.png

# 截取压缩版第 60 秒
ffmpeg -i compressed.mp4 -ss 60 -vframes 1 compressed_frame.png

# 两张图放一起对比,差别一目了然

更多画质评估的内容见 同样是 1080P,哪个画质更好?用 VMAF、PSNR、SSIM 给视频打分

九、合规与温馨提示


压缩这件事,我用了三年才搞明白:90% 的情况用 -crf 23 -preset medium 就够了。 剩下的 10%——需要精确控制体积、需要极致压缩比、需要保留 HDR 元数据——才需要深入研究参数。别一上来就追求"最优参数",先把 CRF 23 跑起来,看看结果,不满意再微调。工具是为你服务的,不是你为工具服务。

本文由 VidDown 技术博客原创发布。VidDown 下载器内置了视频转码压缩功能——下载完成后可以直接选择压缩画质和格式,不需要手动敲 FFmpeg 命令。访问 VidDown 了解更多。

想亲手试试?用 VidDown 一键解析下载

粘贴视频链接即可解析,多平台支持、网页端即用;下载桌面客户端解锁海外平台本地解析,开通会员更享不限次下载。

顶部