Files
acfunlive-remake/encode_test.go
T
tamakyi a891094f29 合并默认改用 HEVC、码率按「源片 + 500」自动算;手动合并自动匹配同名弹幕
两件事:

1) 合成码率与编码器
- `mergeBitrate` 填 0(默认)时不再是 ICQ 质量模式,改成「源片视频码率 + 500 Kbps」:
  烧进去的文字和头像都是高频细节,和源片同码率会糊,所以留一点余量;探测不到源片
  码率时才退回质量模式(QSV 用 ICQ 22、软编用 CRF 18),下限 800 Kbps。
- 编码器默认换成硬件 HEVC:候选顺序 hevc_qsv → h264_qsv → libx264,QSV 可用性按
  编码器分别探测(原来只探 h264_qsv)。HEVC 在 mp4 里打 `hvc1` tag,Apple 系
  (QuickTime / Safari / iPhone)才认 —— ffmpeg 默认写的是 hev1。
- 日志现在会打印编码器优先顺序和实际用的码率。

  实测(90 秒真实素材,源片 3217 kbps,密集弹幕):日志「按源片 3217 kbps + 弹幕
  余量 500 kbps 取 3717 kbps」,成片 codec=hevc、tag=hvc1、实际 3.71 Mbps。
  之前同样是这份素材:h264 + ICQ22 会飙到 7386 kbps(79 MB),改成自动码率后
  43.7 MB,体积小了 45% 画质还更稳。

2) 手动合并自动匹配弹幕
- 新增 `GET /api/merge/danmu?video=…`:按视频名找同目录下的同名弹幕,依次试
  `.jsonl`、`.ass`、`.nick.ass`;同目录没有就拿文件名去录播目录再找一次。
- 页面在「手填/粘贴视频后失焦」「多选弹窗选完」「从文件列表多选后」「点预览或
  开始合并时弹幕框还空着」这几种情况下都会自动匹配并提示;弹幕框里已经有内容时
  绝不动它,找不到也保持为空让用户自己选。
- 填回框里的写法与浏览弹窗一致:录播/成片目录下的只写文件名,别处的写绝对路径。

测试:新增 encode_test.go(编码器顺序、参数与 hvc1、自动码率、ffmpeg 输出解析)
和 TestFindDanmuForVideo(7 个子用例:优先级、录播目录回退、绝对路径、找不到返回空);
仓库外的 verify_danmu_match.py 4 组断言全过,verify_pick.py / verify_settings.py 重跑
无回归;go test ./...、gofmt、go vet 干净。
2026-09-25 19:23:44 +08:00

105 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"strconv"
"strings"
"testing"
)
// 编码器候选顺序:硬件 HEVC 最省 → 硬件 H.264 → CPU 软编
func TestEncoderCandidatesOrder(t *testing.T) {
cases := []struct {
hevc, h264 bool
want []string
}{
{true, true, []string{"hevc_qsv", "h264_qsv", "libx264"}},
{false, true, []string{"h264_qsv", "libx264"}},
{true, false, []string{"hevc_qsv", "libx264"}},
{false, false, []string{"libx264"}},
}
for _, c := range cases {
got := encoderCandidates(c.hevc, c.h264)
if strings.Join(got, ",") != strings.Join(c.want, ",") {
t.Errorf("hevc=%v h264=%v 应为 %v,实际 %v", c.hevc, c.h264, c.want, got)
}
}
// 候选列表必须能真的跑出结果,最后一个永远是纯 CPU 的 libx264
last := encoderCandidates(true, true)
if last[len(last)-1] != "libx264" {
t.Errorf("最后一个候选必须是 libx264,实际 %s", last[len(last)-1])
}
}
// 编码器参数:固定码率 / 质量模式,HEVC 必须带 hvc1 tag(Apple 系才认)
func TestEncoderOpts(t *testing.T) {
join := func(ss []string) string { return strings.Join(ss, " ") }
cases := []struct {
encoder string
bitrate int
want []string
}{
{"hevc_qsv", 4000, []string{"-b:v", "4000k", "-tag:v", "hvc1"}},
{"h264_qsv", 4000, []string{"-b:v", "4000k"}},
{"libx264", 4000, []string{"-b:v", "4000k", "-preset", "medium"}},
{"hevc_qsv", 0, []string{"-global_quality", "22", "-preset", "medium", "-tag:v", "hvc1"}},
{"libx265", 0, []string{"-crf", "18", "-preset", "medium", "-tag:v", "hvc1"}},
}
for _, c := range cases {
got := encoderOpts(c.encoder, c.bitrate)
if join(got) != join(c.want) {
t.Errorf("%s @ %d 应为 %v,实际 %v", c.encoder, c.bitrate, c.want, got)
}
}
}
// 「合成码率」填 0 时的算法:源片码率 + 弹幕余量,探测不到就交回 0(走质量模式)
func TestAutoMergeBitrate(t *testing.T) {
cases := []struct {
source int
want int
}{
{3197, 3697}, // 用户那场录播的源片码率
{0, 0}, // 探测不到 → 质量模式
{-1, 0},
{100, 800}, // 低于下限时抬到下限
{3000, 3500}, // 正常情况就是 +500
}
for _, c := range cases {
if got := autoMergeBitrate(c.source); got != c.want {
t.Errorf("源码率 %d 应得 %d,实际 %d", c.source, c.want, got)
}
}
}
// ffmpeg 输出的解析:分辨率靠「逗号 + 数字x数字」,码率靠视频流那一行的 kb/s,
// 视频流没写码率时退回容器总码率
func TestProbeStreamRegex(t *testing.T) {
sizeLine := " Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709, progressive), 1920x1080 [SAR 1:1 DAR 16:9], 3217 kb/s, 23.98 fps"
m := videoSizeRe.FindStringSubmatch(sizeLine)
if len(m) != 3 {
t.Fatalf("没解析出分辨率:%s", sizeLine)
}
w, _ := strconv.Atoi(m[1])
h, _ := strconv.Atoi(m[2])
if w != 1920 || h != 1080 {
t.Errorf("分辨率解析成 %dx%d", w, h)
}
// 视频流那一行里的码率(不是编码器名里的 0x…)
if b := videoBitrateRe.FindStringSubmatch(sizeLine); len(b) != 2 || b[1] != "3217" {
t.Errorf("视频流码率没解析对:%v", b)
}
portrait := " Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, smpte170m/unknown/unknown, progressive), 720x1280 [SAR 1:1 DAR 9:16], 1570 kb/s, 10 fps"
m = videoSizeRe.FindStringSubmatch(portrait)
if len(m) != 3 || m[1] != "720" || m[2] != "1280" {
t.Errorf("竖屏分辨率没解析对:%v", m)
}
// 视频流那行没写码率时用容器总码率兜底
durLine := " Duration: 00:02:55.07, start: 0.000000, bitrate: 1767 kb/s"
if m := containerBitrateRe.FindStringSubmatch(durLine); len(m) != 2 || m[1] != "1767" {
t.Errorf("容器码率没解析对:%v", m)
}
}