You've already forked FrameTour-RenderWorker
feat(video): 添加硬件加速支持
- 定义硬件加速类型常量(none、qsv、cuda) - 配置QSV和CUDA编码参数及预设 - 在WorkerConfig中添加硬件加速配置选项 - 实现基于硬件加速类型的编码参数动态获取 - 添加FFmpeg硬件加速解码和滤镜参数 - 检测并报告系统硬件加速支持信息 - 在API客户端中上报硬件加速配置和支持状态
This commit is contained in:
128
handlers/base.py
128
handlers/base.py
@@ -19,6 +19,10 @@ from domain.task import Task
|
||||
from domain.result import TaskResult, ErrorCode
|
||||
from domain.config import WorkerConfig
|
||||
from services import storage
|
||||
from constant import (
|
||||
HW_ACCEL_NONE, HW_ACCEL_QSV, HW_ACCEL_CUDA,
|
||||
VIDEO_ENCODE_PARAMS, VIDEO_ENCODE_PARAMS_QSV, VIDEO_ENCODE_PARAMS_CUDA
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from services.api_client import APIClientV2
|
||||
@@ -26,15 +30,94 @@ if TYPE_CHECKING:
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# v2 统一视频编码参数(来自集成文档)
|
||||
VIDEO_ENCODE_ARGS = [
|
||||
'-c:v', 'libx264',
|
||||
'-preset', 'medium',
|
||||
'-profile:v', 'main',
|
||||
'-level', '4.0',
|
||||
'-crf', '23',
|
||||
'-pix_fmt', 'yuv420p',
|
||||
]
|
||||
def get_video_encode_args(hw_accel: str = HW_ACCEL_NONE) -> List[str]:
|
||||
"""
|
||||
根据硬件加速配置获取视频编码参数
|
||||
|
||||
Args:
|
||||
hw_accel: 硬件加速类型 (none, qsv, cuda)
|
||||
|
||||
Returns:
|
||||
FFmpeg 视频编码参数列表
|
||||
"""
|
||||
if hw_accel == HW_ACCEL_QSV:
|
||||
params = VIDEO_ENCODE_PARAMS_QSV
|
||||
return [
|
||||
'-c:v', params['codec'],
|
||||
'-preset', params['preset'],
|
||||
'-profile:v', params['profile'],
|
||||
'-level', params['level'],
|
||||
'-global_quality', params['global_quality'],
|
||||
'-look_ahead', params['look_ahead'],
|
||||
]
|
||||
elif hw_accel == HW_ACCEL_CUDA:
|
||||
params = VIDEO_ENCODE_PARAMS_CUDA
|
||||
return [
|
||||
'-c:v', params['codec'],
|
||||
'-preset', params['preset'],
|
||||
'-profile:v', params['profile'],
|
||||
'-level', params['level'],
|
||||
'-rc', params['rc'],
|
||||
'-cq', params['cq'],
|
||||
'-b:v', '0', # 配合 vbr 模式使用 cq
|
||||
]
|
||||
else:
|
||||
# 软件编码(默认)
|
||||
params = VIDEO_ENCODE_PARAMS
|
||||
return [
|
||||
'-c:v', params['codec'],
|
||||
'-preset', params['preset'],
|
||||
'-profile:v', params['profile'],
|
||||
'-level', params['level'],
|
||||
'-crf', params['crf'],
|
||||
'-pix_fmt', params['pix_fmt'],
|
||||
]
|
||||
|
||||
|
||||
def get_hwaccel_decode_args(hw_accel: str = HW_ACCEL_NONE) -> List[str]:
|
||||
"""
|
||||
获取硬件加速解码参数(输入文件之前使用)
|
||||
|
||||
Args:
|
||||
hw_accel: 硬件加速类型 (none, qsv, cuda)
|
||||
|
||||
Returns:
|
||||
FFmpeg 硬件加速解码参数列表
|
||||
"""
|
||||
if hw_accel == HW_ACCEL_CUDA:
|
||||
# CUDA 硬件加速解码
|
||||
# 注意:使用 cuda 作为 hwaccel,但输出到系统内存以便 CPU 滤镜处理
|
||||
return ['-hwaccel', 'cuda', '-hwaccel_output_format', 'cuda']
|
||||
elif hw_accel == HW_ACCEL_QSV:
|
||||
# QSV 硬件加速解码
|
||||
return ['-hwaccel', 'qsv', '-hwaccel_output_format', 'qsv']
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
def get_hwaccel_filter_prefix(hw_accel: str = HW_ACCEL_NONE) -> str:
|
||||
"""
|
||||
获取硬件加速滤镜前缀(用于 hwdownload 从 GPU 到 CPU)
|
||||
|
||||
注意:由于大多数复杂滤镜(如 lut3d, overlay, crop 等)不支持硬件表面,
|
||||
我们需要在滤镜链开始时将硬件表面下载到系统内存。
|
||||
|
||||
Args:
|
||||
hw_accel: 硬件加速类型
|
||||
|
||||
Returns:
|
||||
需要添加到滤镜链开头的 hwdownload 滤镜字符串
|
||||
"""
|
||||
if hw_accel == HW_ACCEL_CUDA:
|
||||
return 'hwdownload,format=nv12,'
|
||||
elif hw_accel == HW_ACCEL_QSV:
|
||||
return 'hwdownload,format=nv12,'
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
# v2 统一视频编码参数(兼容旧代码,使用软件编码)
|
||||
VIDEO_ENCODE_ARGS = get_video_encode_args(HW_ACCEL_NONE)
|
||||
|
||||
# v2 统一音频编码参数
|
||||
AUDIO_ENCODE_ARGS = [
|
||||
@@ -178,6 +261,33 @@ class BaseHandler(TaskHandler, ABC):
|
||||
self.config = config
|
||||
self.api_client = api_client
|
||||
|
||||
def get_video_encode_args(self) -> List[str]:
|
||||
"""
|
||||
获取当前配置的视频编码参数
|
||||
|
||||
Returns:
|
||||
FFmpeg 视频编码参数列表
|
||||
"""
|
||||
return get_video_encode_args(self.config.hw_accel)
|
||||
|
||||
def get_hwaccel_decode_args(self) -> List[str]:
|
||||
"""
|
||||
获取硬件加速解码参数(在输入文件之前使用)
|
||||
|
||||
Returns:
|
||||
FFmpeg 硬件加速解码参数列表
|
||||
"""
|
||||
return get_hwaccel_decode_args(self.config.hw_accel)
|
||||
|
||||
def get_hwaccel_filter_prefix(self) -> str:
|
||||
"""
|
||||
获取硬件加速滤镜前缀
|
||||
|
||||
Returns:
|
||||
需要添加到滤镜链开头的 hwdownload 滤镜字符串
|
||||
"""
|
||||
return get_hwaccel_filter_prefix(self.config.hw_accel)
|
||||
|
||||
def before_handle(self, task: Task) -> None:
|
||||
"""处理前钩子"""
|
||||
logger.debug(f"[task:{task.task_id}] Before handle: {task.task_type.value}")
|
||||
|
||||
@@ -10,7 +10,7 @@ import os
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
|
||||
from handlers.base import BaseHandler, VIDEO_ENCODE_ARGS
|
||||
from handlers.base import BaseHandler
|
||||
from domain.task import Task, TaskType, TransitionConfig, TRANSITION_TYPES
|
||||
from domain.result import TaskResult, ErrorCode
|
||||
|
||||
@@ -235,8 +235,8 @@ class ComposeTransitionHandler(BaseHandler):
|
||||
'-map', '[outv]',
|
||||
]
|
||||
|
||||
# 编码参数(与片段视频一致)
|
||||
cmd.extend(VIDEO_ENCODE_ARGS)
|
||||
# 编码参数(根据硬件加速配置动态获取)
|
||||
cmd.extend(self.get_video_encode_args())
|
||||
|
||||
# 帧率
|
||||
fps = output_spec.fps
|
||||
|
||||
@@ -10,7 +10,7 @@ import os
|
||||
import logging
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from handlers.base import BaseHandler, VIDEO_ENCODE_ARGS
|
||||
from handlers.base import BaseHandler
|
||||
from domain.task import Task, TaskType, RenderSpec, OutputSpec, Effect
|
||||
from domain.result import TaskResult, ErrorCode
|
||||
|
||||
@@ -170,6 +170,11 @@ class RenderSegmentVideoHandler(BaseHandler):
|
||||
"""
|
||||
cmd = ['ffmpeg', '-y', '-hide_banner']
|
||||
|
||||
# 硬件加速解码参数(在输入文件之前)
|
||||
hwaccel_args = self.get_hwaccel_decode_args()
|
||||
if hwaccel_args:
|
||||
cmd.extend(hwaccel_args)
|
||||
|
||||
# 输入文件
|
||||
cmd.extend(['-i', input_file])
|
||||
|
||||
@@ -196,8 +201,8 @@ class RenderSegmentVideoHandler(BaseHandler):
|
||||
elif filters:
|
||||
cmd.extend(['-vf', filters])
|
||||
|
||||
# 编码参数(v2 统一参数)
|
||||
cmd.extend(VIDEO_ENCODE_ARGS)
|
||||
# 编码参数(根据硬件加速配置动态获取)
|
||||
cmd.extend(self.get_video_encode_args())
|
||||
|
||||
# 帧率
|
||||
fps = output_spec.fps
|
||||
@@ -253,6 +258,12 @@ class RenderSegmentVideoHandler(BaseHandler):
|
||||
effects = render_spec.get_effects()
|
||||
has_camera_shot = any(e.effect_type == 'cameraShot' for e in effects)
|
||||
|
||||
# 硬件加速时需要先 hwdownload(将 GPU 表面下载到系统内存)
|
||||
hwaccel_prefix = self.get_hwaccel_filter_prefix()
|
||||
if hwaccel_prefix:
|
||||
# 去掉末尾的逗号,作为第一个滤镜
|
||||
filters.append(hwaccel_prefix.rstrip(','))
|
||||
|
||||
# 1. 变速处理
|
||||
speed = float(render_spec.speed) if render_spec.speed else 1.0
|
||||
if speed != 1.0 and speed > 0:
|
||||
@@ -304,7 +315,8 @@ class RenderSegmentVideoHandler(BaseHandler):
|
||||
fps=fps,
|
||||
has_overlay=has_overlay,
|
||||
overlap_head_ms=overlap_head_ms,
|
||||
overlap_tail_ms=overlap_tail_ms
|
||||
overlap_tail_ms=overlap_tail_ms,
|
||||
use_hwdownload=bool(hwaccel_prefix)
|
||||
)
|
||||
|
||||
# 6. 帧冻结(tpad)- 用于转场 overlap 区域
|
||||
@@ -337,7 +349,8 @@ class RenderSegmentVideoHandler(BaseHandler):
|
||||
fps: int,
|
||||
has_overlay: bool = False,
|
||||
overlap_head_ms: int = 0,
|
||||
overlap_tail_ms: int = 0
|
||||
overlap_tail_ms: int = 0,
|
||||
use_hwdownload: bool = False
|
||||
) -> str:
|
||||
"""
|
||||
构建包含特效的 filter_complex 滤镜图
|
||||
@@ -351,6 +364,7 @@ class RenderSegmentVideoHandler(BaseHandler):
|
||||
has_overlay: 是否有叠加层
|
||||
overlap_head_ms: 头部 overlap 时长
|
||||
overlap_tail_ms: 尾部 overlap 时长
|
||||
use_hwdownload: 是否使用了硬件加速解码(已在 base_filters 中包含 hwdownload)
|
||||
|
||||
Returns:
|
||||
filter_complex 格式的滤镜字符串
|
||||
|
||||
Reference in New Issue
Block a user