""" FFmpeg 工具模块 - 提供 FFmpeg 相关的工具函数,特别是硬件加速检测 优化多平台兼容性,支持渐进式降级和智能错误处理 """ import os import platform import subprocess import tempfile from typing import Dict, List, Optional, Tuple, Union from loguru import logger # 全局变量,存储检测到的硬件加速信息 _FFMPEG_HW_ACCEL_INFO = { "available": False, "type": None, "encoder": None, "hwaccel_args": [], "message": "", "is_dedicated_gpu": False, "fallback_available": False, # 是否有备用方案 "fallback_encoder": None, # 备用编码器 "platform": None, # 平台信息 "gpu_vendor": None, # GPU厂商 "tested_methods": [] # 已测试的方法 } # 硬件加速优先级配置(按平台和GPU类型) HWACCEL_PRIORITY = { "windows": { "nvidia": ["cuda", "nvenc", "d3d11va", "dxva2"], "amd": ["d3d11va", "dxva2", "amf"], # 不再完全禁用AMD "intel": ["qsv", "d3d11va", "dxva2"], "unknown": ["d3d11va", "dxva2"] }, "darwin": { "apple": ["videotoolbox"], "nvidia": ["cuda", "videotoolbox"], "amd": ["videotoolbox"], "intel": ["videotoolbox"], "unknown": ["videotoolbox"] }, "linux": { "nvidia": ["cuda", "nvenc", "vaapi"], "amd": ["vaapi", "amf"], "intel": ["qsv", "vaapi"], "unknown": ["vaapi"] } } # 编码器映射 ENCODER_MAPPING = { "cuda": "h264_nvenc", "nvenc": "h264_nvenc", "videotoolbox": "h264_videotoolbox", "qsv": "h264_qsv", "vaapi": "h264_vaapi", "amf": "h264_amf", "d3d11va": "libx264", # D3D11VA只用于解码 "dxva2": "libx264", # DXVA2只用于解码 "software": "libx264" } def get_null_input() -> str: """ 获取平台特定的空输入文件路径 Returns: str: 平台特定的空输入路径 """ system = platform.system().lower() if system == "windows": return "NUL" else: return "/dev/null" def create_test_video() -> str: """ 创建一个临时的测试视频文件,用于硬件加速测试 Returns: str: 临时测试视频文件路径 """ try: # 创建临时文件 temp_file = tempfile.NamedTemporaryFile(suffix='.mp4', delete=False) temp_path = temp_file.name temp_file.close() # 生成一个简单的测试视频(1秒,黑色画面) cmd = [ 'ffmpeg', '-y', '-f', 'lavfi', '-i', 'color=black:size=320x240:duration=1', '-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-t', '1', temp_path ] subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True) return temp_path except Exception as e: logger.debug(f"创建测试视频失败: {str(e)}") return get_null_input() def cleanup_test_video(path: str) -> None: """ 清理测试视频文件 Args: path: 测试视频文件路径 """ try: if path != get_null_input() and os.path.exists(path): os.unlink(path) except Exception as e: logger.debug(f"清理测试视频失败: {str(e)}") def check_ffmpeg_installation() -> bool: """ 检查ffmpeg是否已安装 Returns: bool: 如果安装则返回True,否则返回False """ try: # 在Windows系统上使用UTF-8编码 is_windows = os.name == 'nt' if is_windows: subprocess.run(['ffmpeg', '-version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8', check=True) else: subprocess.run(['ffmpeg', '-version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True) return True except (subprocess.SubprocessError, FileNotFoundError): logger.error("ffmpeg未安装或不在系统PATH中,请安装ffmpeg") return False def detect_gpu_vendor() -> str: """ 检测GPU厂商 Returns: str: GPU厂商 (nvidia, amd, intel, apple, unknown) """ system = platform.system().lower() try: if system == "windows": gpu_info = _get_windows_gpu_info().lower() if 'nvidia' in gpu_info or 'geforce' in gpu_info or 'quadro' in gpu_info: return "nvidia" elif 'amd' in gpu_info or 'radeon' in gpu_info: return "amd" elif 'intel' in gpu_info: return "intel" elif system == "darwin": # macOS上检查是否为Apple Silicon if platform.machine().lower() in ['arm64', 'aarch64']: return "apple" else: # Intel Mac,可能有独立显卡 gpu_info = _get_macos_gpu_info().lower() if 'nvidia' in gpu_info: return "nvidia" elif 'amd' in gpu_info or 'radeon' in gpu_info: return "amd" else: return "intel" elif system == "linux": gpu_info = _get_linux_gpu_info().lower() if 'nvidia' in gpu_info: return "nvidia" elif 'amd' in gpu_info or 'radeon' in gpu_info: return "amd" elif 'intel' in gpu_info: return "intel" except Exception as e: logger.debug(f"检测GPU厂商失败: {str(e)}") return "unknown" def test_hwaccel_method(method: str, test_input: str) -> bool: """ 测试特定的硬件加速方法 Args: method: 硬件加速方法名称 test_input: 测试输入文件路径 Returns: bool: 是否支持该方法 """ try: # 构建测试命令 cmd = ["ffmpeg", "-hide_banner", "-loglevel", "error"] # 添加硬件加速参数 if method == "cuda": cmd.extend(["-hwaccel", "cuda", "-hwaccel_output_format", "cuda"]) elif method == "nvenc": cmd.extend(["-hwaccel", "cuda"]) elif method == "videotoolbox": cmd.extend(["-hwaccel", "videotoolbox"]) elif method == "qsv": cmd.extend(["-hwaccel", "qsv"]) elif method == "vaapi": # 尝试找到VAAPI设备 render_device = _find_vaapi_device() if render_device: cmd.extend(["-hwaccel", "vaapi", "-vaapi_device", render_device]) else: cmd.extend(["-hwaccel", "vaapi"]) elif method == "d3d11va": cmd.extend(["-hwaccel", "d3d11va"]) elif method == "dxva2": cmd.extend(["-hwaccel", "dxva2"]) elif method == "amf": cmd.extend(["-hwaccel", "auto"]) # AMF通常通过auto检测 else: return False # 添加输入和输出 cmd.extend(["-i", test_input, "-f", "null", "-t", "0.1", "-"]) # 执行测试 result = subprocess.run( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=False, timeout=10 # 10秒超时 ) success = result.returncode == 0 if success: logger.debug(f"硬件加速方法 {method} 测试成功") else: logger.debug(f"硬件加速方法 {method} 测试失败: {result.stderr[:200]}") return success except subprocess.TimeoutExpired: logger.debug(f"硬件加速方法 {method} 测试超时") return False except Exception as e: logger.debug(f"硬件加速方法 {method} 测试异常: {str(e)}") return False def detect_hardware_acceleration() -> Dict[str, Union[bool, str, List[str], None]]: """ 检测系统可用的硬件加速器,使用渐进式检测和智能降级 Returns: Dict: 包含硬件加速信息的字典 """ global _FFMPEG_HW_ACCEL_INFO # 如果已经检测过,直接返回结果 if _FFMPEG_HW_ACCEL_INFO["type"] is not None: return _FFMPEG_HW_ACCEL_INFO # 检查ffmpeg是否已安装 if not check_ffmpeg_installation(): _FFMPEG_HW_ACCEL_INFO["message"] = "FFmpeg未安装或不在系统PATH中" return _FFMPEG_HW_ACCEL_INFO # 检测平台和GPU信息 system = platform.system().lower() gpu_vendor = detect_gpu_vendor() _FFMPEG_HW_ACCEL_INFO["platform"] = system _FFMPEG_HW_ACCEL_INFO["gpu_vendor"] = gpu_vendor logger.debug(f"检测硬件加速 - 平台: {system}, GPU厂商: {gpu_vendor}") # 获取FFmpeg支持的硬件加速器列表 try: hwaccels_cmd = subprocess.run( ['ffmpeg', '-hide_banner', '-hwaccels'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=False ) supported_hwaccels = hwaccels_cmd.stdout.lower() if hwaccels_cmd.returncode == 0 else "" logger.debug(f"FFmpeg支持的硬件加速器: {supported_hwaccels}") except Exception as e: logger.warning(f"获取FFmpeg硬件加速器列表失败: {str(e)}") supported_hwaccels = "" # 创建测试输入 test_input = create_test_video() try: # 根据平台和GPU厂商获取优先级列表 priority_list = HWACCEL_PRIORITY.get(system, {}).get(gpu_vendor, []) if not priority_list: priority_list = HWACCEL_PRIORITY.get(system, {}).get("unknown", []) logger.debug(f"硬件加速测试优先级: {priority_list}") # 按优先级测试硬件加速方法 for method in priority_list: # 检查FFmpeg是否支持该方法 if method not in supported_hwaccels and method != "nvenc": # nvenc可能不在hwaccels列表中 logger.debug(f"跳过不支持的硬件加速方法: {method}") continue _FFMPEG_HW_ACCEL_INFO["tested_methods"].append(method) if test_hwaccel_method(method, test_input): # 找到可用的硬件加速方法 _FFMPEG_HW_ACCEL_INFO["available"] = True _FFMPEG_HW_ACCEL_INFO["type"] = method _FFMPEG_HW_ACCEL_INFO["encoder"] = ENCODER_MAPPING.get(method, "libx264") # 构建硬件加速参数 if method == "cuda": _FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = ["-hwaccel", "cuda", "-hwaccel_output_format", "cuda"] elif method == "nvenc": _FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = ["-hwaccel", "cuda"] elif method == "videotoolbox": _FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = ["-hwaccel", "videotoolbox"] elif method == "qsv": _FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = ["-hwaccel", "qsv"] elif method == "vaapi": render_device = _find_vaapi_device() if render_device: _FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = ["-hwaccel", "vaapi", "-vaapi_device", render_device] else: _FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = ["-hwaccel", "vaapi"] elif method in ["d3d11va", "dxva2"]: _FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = ["-hwaccel", method] elif method == "amf": _FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = ["-hwaccel", "auto"] # 判断是否为独立GPU _FFMPEG_HW_ACCEL_INFO["is_dedicated_gpu"] = gpu_vendor in ["nvidia", "amd"] or (gpu_vendor == "intel" and "arc" in _get_gpu_info().lower()) _FFMPEG_HW_ACCEL_INFO["message"] = f"使用 {method} 硬件加速 ({gpu_vendor} GPU)" logger.debug(f"硬件加速检测成功: {method} ({gpu_vendor})") break # 如果没有找到硬件加速,设置软件编码作为备用 if not _FFMPEG_HW_ACCEL_INFO["available"]: _FFMPEG_HW_ACCEL_INFO["fallback_available"] = True _FFMPEG_HW_ACCEL_INFO["fallback_encoder"] = "libx264" _FFMPEG_HW_ACCEL_INFO["message"] = f"未找到可用的硬件加速,将使用软件编码 (平台: {system}, GPU: {gpu_vendor})" logger.debug("未检测到硬件加速,将使用软件编码") finally: # 清理测试文件 cleanup_test_video(test_input) return _FFMPEG_HW_ACCEL_INFO def _get_gpu_info() -> str: """ 获取GPU信息的统一接口 Returns: str: GPU信息字符串 """ system = platform.system().lower() if system == "windows": return _get_windows_gpu_info() elif system == "darwin": return _get_macos_gpu_info() elif system == "linux": return _get_linux_gpu_info() else: return "unknown" def _get_macos_gpu_info() -> str: """ 获取macOS系统的GPU信息 Returns: str: GPU信息字符串 """ try: # 使用system_profiler获取显卡信息 result = subprocess.run( ['system_profiler', 'SPDisplaysDataType'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=False ) if result.returncode == 0: return result.stdout # 备用方法:检查是否为Apple Silicon if platform.machine().lower() in ['arm64', 'aarch64']: return "Apple Silicon GPU" else: return "Intel Mac GPU" except Exception as e: logger.debug(f"获取macOS GPU信息失败: {str(e)}") return "unknown" def _find_vaapi_device() -> Optional[str]: """ 查找可用的VAAPI设备 Returns: Optional[str]: VAAPI设备路径,如果没有找到则返回None """ try: # 常见的VAAPI设备路径 possible_devices = [ "/dev/dri/renderD128", "/dev/dri/renderD129", "/dev/dri/card0", "/dev/dri/card1" ] for device in possible_devices: if os.path.exists(device): # 测试设备是否可用 test_cmd = subprocess.run( ["ffmpeg", "-hide_banner", "-loglevel", "error", "-hwaccel", "vaapi", "-vaapi_device", device, "-f", "lavfi", "-i", "color=black:size=64x64:duration=0.1", "-f", "null", "-"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False ) if test_cmd.returncode == 0: logger.debug(f"找到可用的VAAPI设备: {device}") return device logger.debug("未找到可用的VAAPI设备") return None except Exception as e: logger.debug(f"查找VAAPI设备失败: {str(e)}") return None def _detect_macos_acceleration(supported_hwaccels: str) -> None: """ 检测macOS系统的硬件加速 Args: supported_hwaccels: FFmpeg支持的硬件加速器列表 """ global _FFMPEG_HW_ACCEL_INFO if 'videotoolbox' in supported_hwaccels: # 测试videotoolbox try: test_cmd = subprocess.run( ["ffmpeg", "-hwaccel", "videotoolbox", "-i", "/dev/null", "-f", "null", "-"], stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True, check=False ) if test_cmd.returncode == 0: _FFMPEG_HW_ACCEL_INFO["available"] = True _FFMPEG_HW_ACCEL_INFO["type"] = "videotoolbox" _FFMPEG_HW_ACCEL_INFO["encoder"] = "h264_videotoolbox" _FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = ["-hwaccel", "videotoolbox"] # macOS的Metal GPU加速通常是集成GPU _FFMPEG_HW_ACCEL_INFO["is_dedicated_gpu"] = False return except Exception as e: logger.debug(f"测试videotoolbox失败: {str(e)}") _FFMPEG_HW_ACCEL_INFO["message"] = "macOS系统未检测到可用的videotoolbox硬件加速" def _detect_windows_acceleration(supported_hwaccels: str) -> None: """ 检测Windows系统的硬件加速 - 基于实际测试结果优化 重要发现:CUDA硬件解码在视频裁剪场景下会导致滤镜链错误, 因此优先使用纯NVENC编码器方案,既保证性能又确保兼容性。 Args: supported_hwaccels: FFmpeg支持的硬件加速器列表 """ global _FFMPEG_HW_ACCEL_INFO # 在Windows上,首先检查显卡信息 gpu_info = _get_windows_gpu_info() logger.debug(f"Windows GPU信息: {gpu_info}") # 检查是否为Intel集成显卡 is_intel_integrated = False if 'intel' in gpu_info.lower() and ('hd graphics' in gpu_info.lower() or 'uhd graphics' in gpu_info.lower()): logger.info("检测到Intel集成显卡") is_intel_integrated = True # 1. 优先检测NVIDIA硬件加速 - 基于实际测试的最佳方案 if 'nvidia' in gpu_info.lower() or 'geforce' in gpu_info.lower() or 'quadro' in gpu_info.lower(): logger.info("检测到NVIDIA显卡,开始测试硬件加速") # 检查NVENC编码器是否可用 try: encoders_cmd = subprocess.run( ["ffmpeg", "-hide_banner", "-encoders"], stderr=subprocess.PIPE, stdout=subprocess.PIPE, encoding='utf-8', text=True, check=False ) has_nvenc = "h264_nvenc" in encoders_cmd.stdout.lower() logger.debug(f"NVENC编码器检测结果: {'可用' if has_nvenc else '不可用'}") if has_nvenc: # 优先方案:纯NVENC编码器(测试证明最兼容) logger.debug("测试纯NVENC编码器(推荐方案,避免滤镜链问题)") test_cmd = subprocess.run([ "ffmpeg", "-hide_banner", "-loglevel", "error", "-f", "lavfi", "-i", "testsrc=duration=0.1:size=640x480:rate=30", "-c:v", "h264_nvenc", "-preset", "medium", "-cq", "23", "-pix_fmt", "yuv420p", "-f", "null", "-" ], stderr=subprocess.PIPE, stdout=subprocess.PIPE, encoding='utf-8', text=True, check=False) if test_cmd.returncode == 0: _FFMPEG_HW_ACCEL_INFO["available"] = True _FFMPEG_HW_ACCEL_INFO["type"] = "nvenc" # 使用nvenc类型标识纯编码器 _FFMPEG_HW_ACCEL_INFO["encoder"] = "h264_nvenc" _FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = [] # 不使用硬件解码参数 _FFMPEG_HW_ACCEL_INFO["is_dedicated_gpu"] = True _FFMPEG_HW_ACCEL_INFO["message"] = "纯NVENC编码器(最佳兼容性)" logger.info("✓ 纯NVENC编码器测试成功") return # 备用方案:如果需要的话,可以测试CUDA硬件解码(但不推荐用于视频裁剪) if 'cuda' in supported_hwaccels: logger.debug("测试CUDA硬件解码(仅用于非裁剪场景)") test_cmd = subprocess.run([ "ffmpeg", "-hide_banner", "-loglevel", "error", "-hwaccel", "cuda", "-hwaccel_output_format", "cuda", "-f", "lavfi", "-i", "testsrc=duration=0.1:size=640x480:rate=30", "-c:v", "h264_nvenc", "-preset", "medium", "-cq", "23", "-pix_fmt", "yuv420p", "-f", "null", "-" ], stderr=subprocess.PIPE, stdout=subprocess.PIPE, encoding='utf-8', text=True, check=False) if test_cmd.returncode == 0: _FFMPEG_HW_ACCEL_INFO["available"] = True _FFMPEG_HW_ACCEL_INFO["type"] = "cuda" # 保留cuda类型用于特殊场景 _FFMPEG_HW_ACCEL_INFO["encoder"] = "h264_nvenc" _FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = ["-hwaccel", "cuda", "-hwaccel_output_format", "cuda"] _FFMPEG_HW_ACCEL_INFO["is_dedicated_gpu"] = True _FFMPEG_HW_ACCEL_INFO["message"] = "CUDA+NVENC(限特殊场景使用)" _FFMPEG_HW_ACCEL_INFO["fallback_available"] = True _FFMPEG_HW_ACCEL_INFO["fallback_encoder"] = "h264_nvenc" logger.info("✓ CUDA+NVENC硬件加速测试成功(备用方案)") return except Exception as e: logger.debug(f"NVIDIA硬件加速测试失败: {str(e)}") # 2. 检测AMD硬件加速 if 'amd' in gpu_info.lower() or 'radeon' in gpu_info.lower(): logger.info("检测到AMD显卡,开始测试硬件加速") # 检查AMF编码器是否可用 try: encoders_cmd = subprocess.run( ["ffmpeg", "-hide_banner", "-encoders"], stderr=subprocess.PIPE, stdout=subprocess.PIPE, encoding='utf-8', text=True, check=False ) has_amf = "h264_amf" in encoders_cmd.stdout.lower() logger.debug(f"AMF编码器检测结果: {'可用' if has_amf else '不可用'}") if has_amf: # 测试AMF编码器 logger.debug("测试AMF编码器") test_cmd = subprocess.run([ "ffmpeg", "-hide_banner", "-loglevel", "error", "-f", "lavfi", "-i", "testsrc=duration=0.1:size=640x480:rate=30", "-c:v", "h264_amf", "-quality", "balanced", "-qp_i", "23", "-pix_fmt", "yuv420p", "-f", "null", "-" ], stderr=subprocess.PIPE, stdout=subprocess.PIPE, encoding='utf-8', text=True, check=False) if test_cmd.returncode == 0: _FFMPEG_HW_ACCEL_INFO["available"] = True _FFMPEG_HW_ACCEL_INFO["type"] = "amf" _FFMPEG_HW_ACCEL_INFO["encoder"] = "h264_amf" _FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = [] _FFMPEG_HW_ACCEL_INFO["is_dedicated_gpu"] = True _FFMPEG_HW_ACCEL_INFO["message"] = "AMD AMF编码器" logger.info("✓ AMD AMF编码器测试成功") return except Exception as e: logger.debug(f"AMD硬件加速测试失败: {str(e)}") # 3. 检测Intel硬件加速 if 'intel' in gpu_info.lower() and 'qsv' in supported_hwaccels: logger.info("检测到Intel显卡,开始测试硬件加速") try: encoders_cmd = subprocess.run( ["ffmpeg", "-hide_banner", "-encoders"], stderr=subprocess.PIPE, stdout=subprocess.PIPE, encoding='utf-8', text=True, check=False ) has_qsv = "h264_qsv" in encoders_cmd.stdout.lower() logger.debug(f"QSV编码器检测结果: {'可用' if has_qsv else '不可用'}") if has_qsv: # 测试QSV编码器 logger.debug("测试QSV编码器") test_cmd = subprocess.run([ "ffmpeg", "-hide_banner", "-loglevel", "error", "-f", "lavfi", "-i", "testsrc=duration=0.1:size=640x480:rate=30", "-c:v", "h264_qsv", "-preset", "medium", "-global_quality", "23", "-pix_fmt", "yuv420p", "-f", "null", "-" ], stderr=subprocess.PIPE, stdout=subprocess.PIPE, encoding='utf-8', text=True, check=False) if test_cmd.returncode == 0: _FFMPEG_HW_ACCEL_INFO["available"] = True _FFMPEG_HW_ACCEL_INFO["type"] = "qsv" _FFMPEG_HW_ACCEL_INFO["encoder"] = "h264_qsv" _FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = [] _FFMPEG_HW_ACCEL_INFO["is_dedicated_gpu"] = not is_intel_integrated _FFMPEG_HW_ACCEL_INFO["message"] = "Intel QSV编码器" logger.info("✓ Intel QSV编码器测试成功") return except Exception as e: logger.debug(f"Intel硬件加速测试失败: {str(e)}") # 4. 如果没有硬件编码器,使用软件编码 logger.info("未检测到可用的硬件编码器,使用软件编码") _FFMPEG_HW_ACCEL_INFO["available"] = False _FFMPEG_HW_ACCEL_INFO["type"] = "software" _FFMPEG_HW_ACCEL_INFO["encoder"] = "libx264" _FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = [] _FFMPEG_HW_ACCEL_INFO["is_dedicated_gpu"] = False _FFMPEG_HW_ACCEL_INFO["message"] = "使用软件编码" def _detect_linux_acceleration(supported_hwaccels: str) -> None: """ 检测Linux系统的硬件加速 Args: supported_hwaccels: FFmpeg支持的硬件加速器列表 """ global _FFMPEG_HW_ACCEL_INFO # 获取Linux显卡信息 gpu_info = _get_linux_gpu_info() is_nvidia = 'nvidia' in gpu_info.lower() is_intel = 'intel' in gpu_info.lower() is_amd = 'amd' in gpu_info.lower() or 'radeon' in gpu_info.lower() # 检测NVIDIA CUDA支持 if 'cuda' in supported_hwaccels and is_nvidia: try: test_cmd = subprocess.run( ["ffmpeg", "-hwaccel", "cuda", "-i", "/dev/null", "-f", "null", "-"], stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True, check=False ) if test_cmd.returncode == 0: _FFMPEG_HW_ACCEL_INFO["available"] = True _FFMPEG_HW_ACCEL_INFO["type"] = "cuda" _FFMPEG_HW_ACCEL_INFO["encoder"] = "h264_nvenc" _FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = ["-hwaccel", "cuda"] _FFMPEG_HW_ACCEL_INFO["is_dedicated_gpu"] = True return except Exception as e: logger.debug(f"测试CUDA失败: {str(e)}") # 检测VAAPI支持 if 'vaapi' in supported_hwaccels: # 检查是否存在渲染设备 render_devices = ['/dev/dri/renderD128', '/dev/dri/renderD129'] render_device = None for device in render_devices: if os.path.exists(device): render_device = device break if render_device: try: test_cmd = subprocess.run( ["ffmpeg", "-hwaccel", "vaapi", "-vaapi_device", render_device, "-i", "/dev/null", "-f", "null", "-"], stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True, check=False ) if test_cmd.returncode == 0: _FFMPEG_HW_ACCEL_INFO["available"] = True _FFMPEG_HW_ACCEL_INFO["type"] = "vaapi" _FFMPEG_HW_ACCEL_INFO["encoder"] = "h264_vaapi" _FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = ["-hwaccel", "vaapi", "-vaapi_device", render_device] # 根据显卡类型判断是否为独立显卡 _FFMPEG_HW_ACCEL_INFO["is_dedicated_gpu"] = is_nvidia or (is_amd and not is_intel) return except Exception as e: logger.debug(f"测试VAAPI失败: {str(e)}") # 检测Intel QSV支持 if 'qsv' in supported_hwaccels and is_intel: try: test_cmd = subprocess.run( ["ffmpeg", "-hwaccel", "qsv", "-i", "/dev/null", "-f", "null", "-"], stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True, check=False ) if test_cmd.returncode == 0: _FFMPEG_HW_ACCEL_INFO["available"] = True _FFMPEG_HW_ACCEL_INFO["type"] = "qsv" _FFMPEG_HW_ACCEL_INFO["encoder"] = "h264_qsv" _FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = ["-hwaccel", "qsv"] _FFMPEG_HW_ACCEL_INFO["is_dedicated_gpu"] = False # Intel QSV通常是集成GPU return except Exception as e: logger.debug(f"测试QSV失败: {str(e)}") _FFMPEG_HW_ACCEL_INFO["message"] = f"Linux系统未检测到可用的硬件加速,显卡信息: {gpu_info}" def _get_windows_gpu_info() -> str: """ 获取Windows系统的显卡信息 Returns: str: 显卡信息字符串 """ try: # 使用PowerShell获取更可靠的显卡信息,并使用UTF-8编码 gpu_info = subprocess.run( ['powershell', '-Command', "Get-WmiObject Win32_VideoController | Select-Object Name | Format-List"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8', text=True, check=False ) # 如果PowerShell失败,尝试使用wmic if not gpu_info.stdout.strip(): gpu_info = subprocess.run( ['wmic', 'path', 'win32_VideoController', 'get', 'name'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8', text=True, check=False ) # 记录详细的显卡信息以便调试 logger.debug(f"Windows显卡信息: {gpu_info.stdout}") return gpu_info.stdout except Exception as e: logger.warning(f"获取Windows显卡信息失败: {str(e)}") return "Unknown GPU" def _get_linux_gpu_info() -> str: """ 获取Linux系统的显卡信息 Returns: str: 显卡信息字符串 """ try: # 尝试使用lspci命令 gpu_info = subprocess.run( ['lspci', '-v', '-nn', '|', 'grep', '-i', 'vga\\|display'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True, check=False ) if gpu_info.stdout: return gpu_info.stdout # 如果lspci命令失败,尝试使用glxinfo gpu_info = subprocess.run( ['glxinfo', '|', 'grep', '-i', 'vendor\\|renderer'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True, check=False ) if gpu_info.stdout: return gpu_info.stdout return "Unknown GPU" except Exception as e: logger.warning(f"获取Linux显卡信息失败: {str(e)}") return "Unknown GPU" def get_ffmpeg_hwaccel_args() -> List[str]: """ 获取FFmpeg硬件加速参数 Returns: List[str]: FFmpeg硬件加速参数列表 """ # 如果还没有检测过,先进行检测 if _FFMPEG_HW_ACCEL_INFO["type"] is None: detect_hardware_acceleration() return _FFMPEG_HW_ACCEL_INFO["hwaccel_args"] def get_ffmpeg_hwaccel_type() -> Optional[str]: """ 获取FFmpeg硬件加速类型 Returns: Optional[str]: 硬件加速类型,如果不支持则返回None """ # 如果还没有检测过,先进行检测 if _FFMPEG_HW_ACCEL_INFO["type"] is None: detect_hardware_acceleration() return _FFMPEG_HW_ACCEL_INFO["type"] if _FFMPEG_HW_ACCEL_INFO["available"] else None def get_ffmpeg_hwaccel_encoder() -> Optional[str]: """ 获取FFmpeg硬件加速编码器 Returns: Optional[str]: 硬件加速编码器,如果不支持则返回None """ # 如果还没有检测过,先进行检测 if _FFMPEG_HW_ACCEL_INFO["type"] is None: detect_hardware_acceleration() return _FFMPEG_HW_ACCEL_INFO["encoder"] if _FFMPEG_HW_ACCEL_INFO["available"] else None def get_ffmpeg_hwaccel_info() -> Dict[str, Union[bool, str, List[str], None]]: """ 获取FFmpeg硬件加速信息 Returns: Dict: 包含硬件加速信息的字典 """ # 如果还没有检测过,先进行检测 if _FFMPEG_HW_ACCEL_INFO["type"] is None: detect_hardware_acceleration() return _FFMPEG_HW_ACCEL_INFO def is_ffmpeg_hwaccel_available() -> bool: """ 检查是否有可用的FFmpeg硬件加速 Returns: bool: 如果有可用的硬件加速则返回True,否则返回False """ # 如果还没有检测过,先进行检测 if _FFMPEG_HW_ACCEL_INFO["type"] is None: detect_hardware_acceleration() return _FFMPEG_HW_ACCEL_INFO["available"] def is_dedicated_gpu() -> bool: """ 检查是否使用独立显卡进行硬件加速 Returns: bool: 如果使用独立显卡则返回True,否则返回False """ # 如果还没有检测过,先进行检测 if _FFMPEG_HW_ACCEL_INFO["type"] is None: detect_hardware_acceleration() return _FFMPEG_HW_ACCEL_INFO["is_dedicated_gpu"] def get_optimal_ffmpeg_encoder() -> str: """ 获取最优的FFmpeg编码器 Returns: str: 编码器名称 """ # 如果还没有检测过,先进行检测 if _FFMPEG_HW_ACCEL_INFO["type"] is None: detect_hardware_acceleration() if _FFMPEG_HW_ACCEL_INFO["available"]: return _FFMPEG_HW_ACCEL_INFO["encoder"] elif _FFMPEG_HW_ACCEL_INFO["fallback_available"]: return _FFMPEG_HW_ACCEL_INFO["fallback_encoder"] else: return "libx264" # 默认软件编码器 def get_ffmpeg_command_with_hwaccel(input_path: str, output_path: str, **kwargs) -> List[str]: """ 生成带有硬件加速的FFmpeg命令 Args: input_path: 输入文件路径 output_path: 输出文件路径 **kwargs: 其他FFmpeg参数 Returns: List[str]: FFmpeg命令列表 """ # 如果还没有检测过,先进行检测 if _FFMPEG_HW_ACCEL_INFO["type"] is None: detect_hardware_acceleration() cmd = ["ffmpeg", "-y"] # 添加硬件加速参数 if _FFMPEG_HW_ACCEL_INFO["available"]: cmd.extend(_FFMPEG_HW_ACCEL_INFO["hwaccel_args"]) # 添加输入文件 cmd.extend(["-i", input_path]) # 添加编码器 encoder = get_optimal_ffmpeg_encoder() cmd.extend(["-c:v", encoder]) # 添加其他参数 for key, value in kwargs.items(): if key.startswith("_"): # 跳过内部参数 continue if isinstance(value, list): cmd.extend(value) else: cmd.extend([f"-{key}", str(value)]) # 添加输出文件 cmd.append(output_path) return cmd def test_ffmpeg_compatibility() -> Dict[str, any]: """ 测试FFmpeg兼容性并返回详细报告 Returns: Dict: 兼容性测试报告 """ report = { "ffmpeg_installed": False, "platform": platform.system().lower(), "gpu_vendor": "unknown", "hardware_acceleration": { "available": False, "type": None, "encoder": None, "tested_methods": [] }, "software_fallback": { "available": False, "encoder": "libx264" }, "recommendations": [] } # 检查FFmpeg安装 report["ffmpeg_installed"] = check_ffmpeg_installation() if not report["ffmpeg_installed"]: report["recommendations"].append("请安装FFmpeg并确保其在系统PATH中") return report # 检测硬件加速 hwaccel_info = detect_hardware_acceleration() report["gpu_vendor"] = hwaccel_info.get("gpu_vendor", "unknown") report["hardware_acceleration"]["available"] = hwaccel_info.get("available", False) report["hardware_acceleration"]["type"] = hwaccel_info.get("type") report["hardware_acceleration"]["encoder"] = hwaccel_info.get("encoder") report["hardware_acceleration"]["tested_methods"] = hwaccel_info.get("tested_methods", []) # 检查软件备用方案 report["software_fallback"]["available"] = hwaccel_info.get("fallback_available", True) report["software_fallback"]["encoder"] = hwaccel_info.get("fallback_encoder", "libx264") # 生成建议 if not report["hardware_acceleration"]["available"]: if report["gpu_vendor"] == "nvidia": report["recommendations"].append("建议安装NVIDIA驱动和CUDA工具包以启用硬件加速") elif report["gpu_vendor"] == "amd": report["recommendations"].append("AMD显卡硬件加速支持有限,建议使用软件编码") elif report["gpu_vendor"] == "intel": report["recommendations"].append("建议更新Intel显卡驱动以启用QSV硬件加速") else: report["recommendations"].append("未检测到支持的GPU,将使用软件编码") return report def force_software_encoding() -> None: """ 强制使用软件编码,禁用硬件加速 """ global _FFMPEG_HW_ACCEL_INFO _FFMPEG_HW_ACCEL_INFO.update({ "available": False, "type": "software", "encoder": "libx264", "hwaccel_args": [], "message": "强制使用软件编码", "is_dedicated_gpu": False, "fallback_available": True, "fallback_encoder": "libx264" }) logger.info("已强制切换到软件编码模式") def reset_hwaccel_detection() -> None: """ 重置硬件加速检测结果,强制重新检测 这在以下情况下很有用: 1. 驱动程序更新后 2. 系统配置改变后 3. 需要重新测试硬件加速时 """ global _FFMPEG_HW_ACCEL_INFO logger.info("🔄 重置硬件加速检测,将重新检测...") _FFMPEG_HW_ACCEL_INFO = { "available": False, "type": None, "encoder": None, "hwaccel_args": [], "message": "", "is_dedicated_gpu": False, "fallback_available": False, "fallback_encoder": None, "platform": None, "gpu_vendor": None, "tested_methods": [] } def test_nvenc_directly() -> bool: """ 直接测试NVENC编码器是否可用(无硬件解码) Returns: bool: NVENC是否可用 """ try: logger.info("🧪 直接测试NVENC编码器...") # 测试纯NVENC编码器 test_cmd = subprocess.run([ "ffmpeg", "-hide_banner", "-loglevel", "error", "-f", "lavfi", "-i", "testsrc=duration=1:size=640x480:rate=30", "-c:v", "h264_nvenc", "-preset", "fast", "-profile:v", "main", "-pix_fmt", "yuv420p", "-t", "1", "-f", "null", "-" ], stderr=subprocess.PIPE, stdout=subprocess.PIPE, encoding='utf-8', text=True, check=False) if test_cmd.returncode == 0: logger.info("✅ NVENC编码器测试成功!") return True else: logger.warning(f"❌ NVENC编码器测试失败: {test_cmd.stderr}") return False except Exception as e: logger.error(f"NVENC测试异常: {str(e)}") return False def force_use_nvenc_pure() -> None: """ 强制使用纯NVENC编码器模式 当自动检测失败但你确定NVENC可用时使用 """ global _FFMPEG_HW_ACCEL_INFO logger.info("🎯 强制启用纯NVENC编码器模式...") # 先测试NVENC是否真的可用 if test_nvenc_directly(): _FFMPEG_HW_ACCEL_INFO["available"] = True _FFMPEG_HW_ACCEL_INFO["type"] = "nvenc_pure" _FFMPEG_HW_ACCEL_INFO["encoder"] = "h264_nvenc" _FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = [] _FFMPEG_HW_ACCEL_INFO["is_dedicated_gpu"] = True _FFMPEG_HW_ACCEL_INFO["message"] = "强制启用纯NVENC编码器" logger.info("✅ 已强制启用纯NVENC编码器模式") else: logger.error("❌ NVENC编码器不可用,无法强制启用") def get_hwaccel_status() -> Dict[str, any]: """ 获取当前硬件加速状态的详细信息 Returns: Dict: 硬件加速状态信息 """ hwaccel_info = get_ffmpeg_hwaccel_info() status = { "available": hwaccel_info.get("available", False), "type": hwaccel_info.get("type", "software"), "encoder": hwaccel_info.get("encoder", "libx264"), "message": hwaccel_info.get("message", ""), "is_dedicated_gpu": hwaccel_info.get("is_dedicated_gpu", False), "platform": platform.system(), "gpu_vendor": detect_gpu_vendor(), "ffmpeg_available": check_ffmpeg_installation() } return status # 自动重置检测(在模块导入时执行) def _auto_reset_on_import(): """模块导入时自动重置硬件加速检测""" try: # 只在平台真正改变时才重置,而不是初始化时 current_platform = platform.system() cached_platform = _FFMPEG_HW_ACCEL_INFO.get("platform") # 只有当已经有缓存的平台信息,且平台改变了,才需要重置 if cached_platform is not None and cached_platform != current_platform: reset_hwaccel_detection() except Exception as e: logger.debug(f"自动重置检测失败: {str(e)}") # 执行自动重置 _auto_reset_on_import()