音视频-色域

发布于:2025-09-15 ⋅ 阅读:(19) ⋅ 点赞:(0)

音视频-色域

1. 色域相关

1.1 python测试视频生成

import os
import subprocess
import argparse
import json

# 色域配置
COLOR_PROFILES = {
    "bt601": {
        "ffmpeg": {
            "colorspace": "5",
            "color_primaries": "5",
            "color_trc": "6",  # smpte170m
        },
        "x264-params": "colorprim=bt601:transfer=smpte170m:colormatrix=bt601",
        "x265-params": "colorprim=bt601:transfer=smpte170m:colormatrix=bt601",
    },
    "bt709": {
        "ffmpeg": {
            "colorspace": "1",
            "color_primaries": "1",
            "color_trc": "1",
        },
        "x264-params": "colorprim=bt709:transfer=bt709:colormatrix=bt709",
        "x265-params": "colorprim=bt709:transfer=bt709:colormatrix=bt709",
    },
    "bt2020": {
        "ffmpeg": {
            "colorspace": "9",
            "color_primaries": "9",
            "color_trc": "16",  # smpte2084 (PQ)
        },
        "x265-params": "colorprim=bt2020:transfer=smpte2084:colormatrix=bt2020nc",
    },
}

# HDR 配置
HDR_PROFILES = {
    "hdr10": {
        "x265": (
            ":master-display="
            "G(13250,34500)B(7500,3000)R(34000,16000)"
            "WP(15635,16450)L(10000000,1)"
            ":max-cll=1000,400"
        )
    },
    "hlg": {
        "x265": ":transfer=arib-std-b67"  # Hybrid Log-Gamma
    },
    # Dolby Vision 需要二次封装
}

def probe_video(video_path):
    """使用 ffprobe 获取视频流信息"""
    if not os.path.exists(video_path):
        return {"error": "文件不存在"}

    cmd = [
        "ffprobe",
        "-v", "quiet",
        "-print_format", "json",
        "-show_streams",
        "-show_format",
        video_path
    ]
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        return {"error": result.stderr}

    info = json.loads(result.stdout)
    video_stream = next((s for s in info.get("streams", []) if s.get("codec_type") == "video"), None)
    if not video_stream:
        return {"error": "未找到视频流"}

    fps = eval(video_stream.get("r_frame_rate", "0/1"))
    return {
        "codec": video_stream.get("codec_name"),
        "pix_fmt": video_stream.get("pix_fmt"),
        "width": video_stream.get("width"),
        "height": video_stream.get("height"),
        "color_range": video_stream.get("color_range", "unknown"),
        "color_space": video_stream.get("color_space", "unknown"),
        "color_primaries": video_stream.get("color_primaries", "unknown"),
        "color_trc": video_stream.get("color_transfer", "unknown"),
        "fps": fps,
    }

def generate_videos(profile, color_range, bit_depth, out_dir, hdr="off"):
    os.makedirs(out_dir, exist_ok=True)

    profiles = [profile] if profile != "all" else list(COLOR_PROFILES.keys())
    ranges = [color_range] if color_range != "all" else ["tv", "pc"]
    bits = [bit_depth] if bit_depth != "all" else ["8bit", "10bit"]

    if hdr == "all":
        hdrs = ["off"] + list(HDR_PROFILES.keys())
    else:
        hdrs = [hdr]

    results = []

    for p in profiles:
        for r in ranges:
            for b in bits:
                for h in hdrs:
                    ffmpeg_args = []

                    # 选择像素格式与编码器
                    if b == "8bit":
                        pix_fmt = "yuv420p"
                        encoder = "libx264" if p != "bt2020" else "libx265"
                    else:  # 10bit
                        pix_fmt = "yuv420p10le"
                        encoder = "libx265"

                    # 输出文件名
                    filename = f"{p}_{r}_{b}"
                    if h != "off":
                        filename += f"_{h}"
                    filename += "_1080p.mp4"
                    filepath = os.path.join(out_dir, filename)

                    # 色彩参数
                    ffmpeg_args += [
                        "-pix_fmt", pix_fmt,
                        "-colorspace", COLOR_PROFILES[p]["ffmpeg"]["colorspace"],
                        "-color_primaries", COLOR_PROFILES[p]["ffmpeg"]["color_primaries"],
                        "-color_trc", COLOR_PROFILES[p]["ffmpeg"]["color_trc"],
                        "-color_range", "1" if r == "tv" else "2",
                    ]

                    # 编码器私有参数
                    if encoder == "libx264":
                        ffmpeg_args += ["-x264-params", COLOR_PROFILES[p]["x264-params"]]
                    else:
                        x265_params = COLOR_PROFILES[p]["x265-params"]
                        if h in HDR_PROFILES and "x265" in HDR_PROFILES[h]:
                            x265_params += HDR_PROFILES[h]["x265"]
                        ffmpeg_args += ["-x265-params", x265_params]

                    # 构建命令
                    ffmpeg_cmd = [
                        "ffmpeg",
                        "-f", "lavfi",
                        "-i", "testsrc=duration=10:size=1920x1080:rate=25",
                        "-c:v", encoder,
                        "-crf", "23",
                        "-preset", "medium",
                        *ffmpeg_args,
                        filepath,
                    ]

                    print(f"\n生成视频: {filepath}")
                    try:
                        subprocess.run(ffmpeg_cmd, check=True, capture_output=True)
                        # probe 生成的视频
                        info = probe_video(filepath)
                        results.append((True, filepath, info))
                        print("视频流信息:", info)
                    except subprocess.CalledProcessError as e:
                        results.append((False, filepath, {"error": e.stderr.decode()}))
                        print(f"生成失败: {filepath}\n", e.stderr.decode())

    return results


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="生成不同色域/位深/HDR 的视频测试文件")
    parser.add_argument("--profile", default="all", choices=["all"] + list(COLOR_PROFILES.keys()),
                        help="色域 (bt601, bt709, bt2020, all)")
    parser.add_argument("--range", default="all", choices=["all", "tv", "pc"],
                        help="范围 (tv, pc, all)")
    parser.add_argument("--bit", default="all", choices=["all", "8bit", "10bit"],
                        help="位深 (8bit, 10bit, all)")
    parser.add_argument("--hdr", default="off", choices=["off", "all"] + list(HDR_PROFILES.keys()),
                        help="HDR 设置 (off, hdr10, hlg, all)")
    parser.add_argument("--out", default="output_videos", help="输出目录")

    args = parser.parse_args()

    results = generate_videos(args.profile, args.range, args.bit, args.out, args.hdr)

    print("\n=== 生成结果汇总 ===")
    for success, filepath, info in results:
        status = "成功" if success else "失败"
        print(f"{status}: {filepath}")
        print(f"  信息: {info}")

1.2 测试视频生成

  • BT.601 TV 范围
ffmpeg -f lavfi -i testsrc=duration=10:size=1920x1080:rate=25 -pix_fmt yuv420p -colorspace 5 -color_primaries 5 -color_trc 2 -color_range 1 -c:v libx264 -x264-params colorprim=bt601:transfer=bt709:colormatrix=bt601 -crf 23 -preset medium bt601_tv_1080p.mp4
  • BT.601 PC 范围
ffmpeg -f lavfi -i testsrc=duration=10:size=1920x1080:rate=25 -pix_fmt yuv420p -colorspace 5 -color_primaries 5 -color_trc 2 -color_range 2 -c:v libx264 -x264-params colorprim=bt601:transfer=bt709:colormatrix=bt601 -crf 23 -preset medium bt601_pc_1080p.mp4
  • BT.709 TV 范围
ffmpeg -f lavfi -i testsrc=duration=10:size=1920x1080:rate=25 -pix_fmt yuv420p -colorspace 1 -color_primaries 1 -color_trc 1 -color_range 1 -c:v libx264 -x264-params colorprim=bt709:transfer=bt709:colormatrix=bt709 -crf 23 -preset medium bt709_tv_1080p.mp4
  • BT.709 PC 范围
ffmpeg -f lavfi -i testsrc=duration=10:size=1920x1080:rate=25 -pix_fmt yuv420p -colorspace 1 -color_primaries 1 -color_trc 1 -color_range 2 -c:v libx264 -x264-params colorprim=bt709:transfer=bt709:colormatrix=bt709 -crf 23 -preset medium bt709_pc_1080p.mp4
  • BT.2020 TV 范围
ffmpeg -f lavfi -i testsrc=duration=10:size=1920x1080:rate=25 -pix_fmt yuv420p -colorspace 9 -color_primaries 9 -color_trc 16 -color_range 1 -c:v libx265 -x265-params colorprim=bt2020:transfer=smpte2084:colormatrix=bt2020nc -crf 23 -preset medium bt2020_tv_1080p.mp4
  • BT.2020 PC 范围
ffmpeg -f lavfi -i testsrc=duration=10:size=1920x1080:rate=25 -pix_fmt yuv420p -colorspace 9 -color_primaries 9 -color_trc 16 -color_range 2 -c:v libx265 -x265-params colorprim=bt2020:transfer=smpte2084:colormatrix=bt2020nc -crf 23 -preset medium bt2020_pc_1080p.mp4
  • BT.601 TV 范围(10-bit)
ffmpeg -f lavfi -i testsrc=duration=10:size=1920x1080:rate=25 -pix_fmt yuv420p10le -colorspace 5 -color_primaries 5 -color_trc 2 -color_range 1 -c:v libx265 -x265-params colorprim=bt601:transfer=bt709:colormatrix=bt601 -crf 23 -preset medium bt601_tv_10bit_1080p.mp4
  • BT.601 PC 范围(10-bit)
ffmpeg -f lavfi -i testsrc=duration=10:size=1920x1080:rate=25 -pix_fmt yuv420p10le -colorspace 5 -color_primaries 5 -color_trc 2 -color_range 2 -c:v libx265 -x265-params colorprim=bt601:transfer=bt709:colormatrix=bt601 -crf 23 -preset medium bt601_pc_10bit_1080p.mp4
  • BT.709 TV 范围(10-bit)
ffmpeg -f lavfi -i testsrc=duration=10:size=1920x1080:rate=25 -pix_fmt yuv420p10le -colorspace 1 -color_primaries 1 -color_trc 1 -color_range 1 -c:v libx265 -x265-params colorprim=bt709:transfer=bt709:colormatrix=bt709 -crf 23 -preset medium bt709_tv_10bit_1080p.mp4
  • BT.709 PC 范围(10-bit)
ffmpeg -f lavfi -i testsrc=duration=10:size=1920x1080:rate=25 -pix_fmt yuv420p10le -colorspace 1 -color_primaries 1 -color_trc 1 -color_range 2 -c:v libx265 -x265-params colorprim=bt709:transfer=bt709:colormatrix=bt709 -crf 23 -preset medium bt709_pc_10bit_1080p.mp4
  • BT.2020 TV 范围(10-bit)
ffmpeg -f lavfi -i testsrc=duration=10:size=1920x1080:rate=25 -pix_fmt yuv420p10le -colorspace 9 -color_primaries 9 -color_trc 16 -color_range 1 -c:v libx265 -x265-params colorprim=bt2020:transfer=smpte2084:colormatrix=bt2020nc -crf 23 -preset medium bt2020_tv_10bit_1080p.mp4
  • BT.2020 PC 范围(10-bit)
ffmpeg -f lavfi -i testsrc=duration=10:size=1920x1080:rate=25 -pix_fmt yuv420p10le -colorspace 9 -color_primaries 9 -color_trc 16 -color_range 2 -c:v libx265 -x265-params colorprim=bt2020:transfer=smpte2084:colormatrix=bt2020nc -crf 23 -preset medium bt2020_pc_10bit_1080p.mp4