File size: 6,167 Bytes
d646dd7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
"""编码器选择单测(v1.0.9)—— 假探针,不跑真实 ffmpeg。

覆盖:auto 三档顺序(nvenc → x265 达标 → x264)、x265 门槛判定、
显式指定(跳门槛/失败回退)、None 兜底、进程内缓存、参数映射。

用法:
    venv/bin/python tools/tests/test_encoder_select.py
"""
import os
import sys

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))

import core.encoder_probe as ep
from config import settings

FAILS = []


def check(cond, msg):
    if cond:
        print(f"  PASS: {msg}")
    else:
        print(f"  FAIL: {msg}")
        FAILS.append(msg)


PASS_PROBE = lambda *a, **k: {"per_stream_fps": [55.0, 55.0],
                              "min_fps": 55.0, "passed": True,
                              "threshold_fps": 45.0, "streams": 2,
                              "frames": 45, "elapsed_s": 1.0}
LOW_PROBE = lambda *a, **k: {"per_stream_fps": [20.0, 20.0],
                             "min_fps": 20.0, "passed": False,
                             "threshold_fps": 45.0, "streams": 2,
                             "frames": 45, "elapsed_s": 2.3}


def main():
    # 环境:单个假 ffmpeg 二进制,支持全部编码器
    ep._FFMPEG_CACHE = ["/fake/ffmpeg"]
    ep._ENCODER_CACHE[("/fake/ffmpeg", "libx264")] = True
    ep._ENCODER_CACHE[("/fake/ffmpeg", "libx265")] = True
    ep._ENCODER_CACHE[("/fake/ffmpeg", "hevc_nvenc")] = True

    # 1. auto:nvenc 探针可用 → 首选 nvenc
    ep._SELECT_CACHE.clear()
    ep.probe_nvenc = lambda *a, **k: True
    c = ep.select_encoder("auto", 1280, 960, 30, 2)
    check(c is not None and c.encoder == "hevc_nvenc",
          f"auto 链首选 hevc_nvenc(得到 {c and c.encoder})")
    check(c is not None and c.codec == "HEVC" and c.args[-1] == "hvc1",
          "nvenc 选择带 HEVC/hvc1")
    check(c is not None and c.args == ep.encoder_args("hevc_nvenc"),
          "nvenc 参数映射一致")

    # 2. nvenc 失败 → x265 达标 → libx265
    ep._SELECT_CACHE.clear()
    ep.probe_nvenc = lambda *a, **k: False
    ep.probe_x265 = PASS_PROBE
    c = ep.select_encoder("auto", 1280, 960, 30, 2)
    check(c is not None and c.encoder == "libx265"
          and c.probe["min_fps"] == 55.0,
          "nvenc 失败且 x265 达标 → libx265")
    check(c is not None and c.crf == settings.RECORD_VIDEO_CRF,
          "x265 crf=RECORD_VIDEO_CRF")

    # 3. x265 不达标 → x264 兜底
    ep._SELECT_CACHE.clear()
    ep.probe_x265 = LOW_PROBE
    c = ep.select_encoder("auto", 1280, 960, 30, 2)
    check(c is not None and c.encoder == "libx264"
          and c.crf == settings.RECORD_VIDEO_X264_CRF,
          "x265 不达标 → x264 兜底(CRF 回退档)")

    # 4. 显式 x265:跳过速度探针(探针被调用即失败)
    ep._SELECT_CACHE.clear()
    def boom(*a, **k):
        raise AssertionError("probe should not be called")
    ep.probe_x265 = boom
    ep.probe_nvenc = boom
    try:
        c = ep.select_encoder("x265", 1280, 960, 30, 2)
        ok = c is not None and c.encoder == "libx265"
    except AssertionError:
        ok = False
    check(ok, "显式 x265 跳过速度门槛(不调探针)")

    # 5. 显式 nvenc 失败 → auto 链回退(此处 x265 达标)
    ep._SELECT_CACHE.clear()
    ep.probe_nvenc = lambda *a, **k: False
    ep.probe_x265 = PASS_PROBE
    c = ep.select_encoder("nvenc", 1280, 960, 30, 2)
    check(c is not None and c.encoder == "libx265",
          "显式 nvenc 失败 → auto 链回退")

    # 6. 显式 x264:不调任何探针
    ep._SELECT_CACHE.clear()
    calls = {"n": 0}
    ep.probe_nvenc = lambda *a, **k: calls.__setitem__("n", calls["n"] + 1) or True
    ep.probe_x265 = lambda *a, **k: calls.__setitem__("n", calls["n"] + 1) or PASS_PROBE()
    c = ep.select_encoder("x264", 1280, 960, 30, 2)
    check(c is not None and c.encoder == "libx264" and calls["n"] == 0,
          "显式 x264 跳过探针直接采用")

    # 7. 支持表缺 libx264 → None(全链失败)
    ep._SELECT_CACHE.clear()
    ep._ENCODER_CACHE[("/fake/ffmpeg", "libx264")] = False
    ep.probe_nvenc = lambda *a, **k: False
    ep.probe_x265 = LOW_PROBE
    c = ep.select_encoder("auto", 1280, 960, 30, 2)
    check(c is None, "无任何可用编码器 → None")
    ep._ENCODER_CACHE[("/fake/ffmpeg", "libx264")] = True

    # 8. 显式 x265 但二进制不支持 → auto 链回退 + 日志播报
    ep._SELECT_CACHE.clear()
    ep._ENCODER_CACHE[("/fake/ffmpeg", "libx265")] = False
    ep.probe_nvenc = lambda *a, **k: False
    ep.probe_x265 = PASS_PROBE
    logs = []
    c = ep.select_encoder("x265", 1280, 960, 30, 2, log=logs.append)
    check(c is not None and c.encoder == "libx264"
          and any("回退" in l for l in logs),
          "显式 x265 不支持 → auto 回退且播报原因")
    ep._ENCODER_CACHE[("/fake/ffmpeg", "libx265")] = True

    # 9. 进程内缓存:同参第二次不重探(返回同一对象)
    ep._SELECT_CACHE.clear()
    calls = {"n": 0}
    ep.probe_nvenc = lambda *a, **k: calls.__setitem__("n", calls["n"] + 1) or True
    c1 = ep.select_encoder("auto", 640, 480, 30, 1)
    n1 = calls["n"]
    c2 = ep.select_encoder("auto", 640, 480, 30, 1)
    check(c1 is c2 and calls["n"] == n1, "进程内缓存:同参不重探")

    # 10. 无 ffmpeg 二进制 → None
    ep._FFMPEG_CACHE = []
    ep._SELECT_CACHE.clear()
    check(ep.select_encoder("auto", 640, 480, 30, 1) is None,
          "无 ffmpeg → None")

    # 11. 参数映射表(x264 用回退档、x265/nvenc 用 HEVC 档)
    check(ep.encoder_args("libx265")[-1] == "hvc1", "libx265 带 hvc1")
    check(ep.encoder_args("libx265", 28)[1] == "28", "libx265 crf 透传")
    check("-cq" in ep.encoder_args("hevc_nvenc"), "nvenc 用 -cq")
    check(ep.encoder_args("libx264")[-2:] ==
          ["-crf", str(settings.RECORD_VIDEO_X264_CRF)],
          "libx264 用回退 CRF")

    print()
    if FAILS:
        print(f"FAIL: {len(FAILS)} 项未通过")
        return 1
    print("PASS: encoder_select 单测全部通过")
    return 0


if __name__ == "__main__":
    sys.exit(main())