mirror of
https://github.com/truewhile/MeBox.git
synced 2026-11-12 19:34:24 +08:00
103 lines
3.4 KiB
Go
103 lines
3.4 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"strings"
|
||
|
||
"github.com/truewhile/MeBox/internal/model"
|
||
)
|
||
|
||
// 蓝光原盘(m2ts,40 Mbps 级别)在两核小机器上重编码只有 0.1~0.2 倍速,HLS
|
||
// 永远追不上播放,播放器等不到切片就报「转码失败」。当源视频本身就是浏览器
|
||
// 能解的 H.264 / 8bit / 4:2:0,且用户选的是「原画」档位(不改分辨率)时,
|
||
// 这里改走只换封装(-c:v copy)的直通路径:视频不重编码,音频转 AAC,
|
||
// CPU 占用接近于零,起播时间从几十秒降到几秒。
|
||
//
|
||
// 直通只对「原画」生效。1080P/720P/480P 档位意味着要改分辨率,必须重新编码;
|
||
// 那类请求仍然按原来的 libx264 路径处理(慢,但码率可控)。
|
||
|
||
// remuxEncoderLabel 是直通任务在日志与任务面板里的标签。
|
||
const remuxEncoderLabel = "remux"
|
||
|
||
// remuxEligible 判断这次请求能否复制视频流而不是重编码。
|
||
func (t *TranscoderService) remuxEligible(ctx context.Context, m *model.Media, quality *LocalHLSQuality, subtitleStream int) bool {
|
||
if t == nil || t.repo == nil || m == nil {
|
||
return false
|
||
}
|
||
// 烧录图形字幕要把字幕叠加进画面,必须解码+重编码。
|
||
if subtitleStream >= 0 {
|
||
return false
|
||
}
|
||
if !remuxAppliesToQuality(quality) {
|
||
return false
|
||
}
|
||
probe, err := t.repo.MediaProbe.Get(ctx, m.ID)
|
||
if err != nil || probe == nil {
|
||
// 没有探测缓存就无法确认档次/像素格式,保守走重编码。
|
||
return false
|
||
}
|
||
return remuxCopyableHeight(probe) > 0
|
||
}
|
||
|
||
// remuxAppliesToQuality 只放行「原画」档位(Height 为 0,即不缩放)。
|
||
// quality 为 nil 表示调用方没有指定档位(走全局 max_height),同样不直通,
|
||
// 否则会绕过配置的分辨率上限给出一个远超预期的码流。
|
||
func remuxAppliesToQuality(quality *LocalHLSQuality) bool {
|
||
return quality != nil && quality.Height <= 0
|
||
}
|
||
|
||
// remuxCopyableHeight 在源视频可直接复制进 HLS 时返回它的高度,否则返回 0。
|
||
func remuxCopyableHeight(probe *model.MediaProbe) int {
|
||
if probe == nil || probe.Height <= 0 {
|
||
return 0
|
||
}
|
||
if !strings.EqualFold(strings.TrimSpace(probe.VideoCodec), "h264") {
|
||
return 0
|
||
}
|
||
if !browserSafeH264Payload(probe.Payload) {
|
||
return 0
|
||
}
|
||
return probe.Height
|
||
}
|
||
|
||
// probePayloadStreams 只解析落库 payload 里与直通判断有关的字段。
|
||
type probePayloadStreams struct {
|
||
Streams []struct {
|
||
Type string `json:"type"`
|
||
Profile string `json:"profile"`
|
||
PixFmt string `json:"pix_fmt"`
|
||
} `json:"streams"`
|
||
}
|
||
|
||
// browserSafeH264Payload 只放行 8bit 4:2:0 的 Baseline/Main/High。
|
||
// Hi10P(High 10)和 4:2:2 母带浏览器解不出来,复制过去只会黑屏或无声。
|
||
func browserSafeH264Payload(payload string) bool {
|
||
payload = strings.TrimSpace(payload)
|
||
if payload == "" {
|
||
// 老数据没有 payload:已经有 codec + 分辨率,按 8bit 常规情况处理。
|
||
return true
|
||
}
|
||
var parsed probePayloadStreams
|
||
if err := json.Unmarshal([]byte(payload), &parsed); err != nil {
|
||
return false
|
||
}
|
||
for _, stream := range parsed.Streams {
|
||
if !strings.EqualFold(strings.TrimSpace(stream.Type), "video") {
|
||
continue
|
||
}
|
||
switch strings.ToLower(strings.TrimSpace(stream.Profile)) {
|
||
case "", "constrained baseline", "baseline", "main", "high":
|
||
default:
|
||
return false
|
||
}
|
||
switch strings.ToLower(strings.TrimSpace(stream.PixFmt)) {
|
||
case "", "yuv420p", "yuvj420p":
|
||
default:
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
return true
|
||
}
|