# windows_utils.py import os import sys import platform import subprocess import ctypes import traceback from log import logger class WindowsSystemUtils: """Windows系统工具类""" @staticmethod def is_windows(): """检查是否是Windows系统""" return os.name == 'nt' or sys.platform.startswith('win') @staticmethod def get_windows_version(): """获取Windows版本信息""" if not WindowsSystemUtils.is_windows(): return None version_info = { 'system': platform.system(), 'release': platform.release(), 'version': platform.version(), 'machine': platform.machine(), 'processor': platform.processor(), 'architecture': platform.architecture()[0] } # 获取详细版本信息 try: # 使用ctypes获取Windows版本 class OSVERSIONINFOEX(ctypes.Structure): _fields_ = [ ('dwOSVersionInfoSize', ctypes.c_ulong), ('dwMajorVersion', ctypes.c_ulong), ('dwMinorVersion', ctypes.c_ulong), ('dwBuildNumber', ctypes.c_ulong), ('dwPlatformId', ctypes.c_ulong), ('szCSDVersion', ctypes.c_wchar * 128), ('wServicePackMajor', ctypes.c_ushort), ('wServicePackMinor', ctypes.c_ushort), ('wSuiteMask', ctypes.c_ushort), ('wProductType', ctypes.c_byte), ('wReserved', ctypes.c_byte) ] os_version = OSVERSIONINFOEX() os_version.dwOSVersionInfoSize = ctypes.sizeof(OSVERSIONINFOEX) if ctypes.windll.Ntdll.RtlGetVersion(ctypes.byref(os_version)) == 0: version_info['major_version'] = os_version.dwMajorVersion version_info['minor_version'] = os_version.dwMinorVersion version_info['build_number'] = os_version.dwBuildNumber version_info['service_pack'] = os_version.wServicePackMajor # 转换为可读版本名称 if version_info['major_version'] == 10: version_info['name'] = f"Windows 10/11 (Build {version_info['build_number']})" elif version_info['major_version'] == 6: if version_info['minor_version'] == 3: version_info['name'] = "Windows 8.1" elif version_info['minor_version'] == 2: version_info['name'] = "Windows 8" elif version_info['minor_version'] == 1: version_info['name'] = "Windows 7" elif version_info['minor_version'] == 0: version_info['name'] = "Windows Vista" elif version_info['major_version'] == 5: if version_info['minor_version'] == 2: version_info['name'] = "Windows XP Professional x64" elif version_info['minor_version'] == 1: version_info['name'] = "Windows XP" except Exception as e: logger.warning(f"获取Windows版本信息失败: {str(e)}") return version_info @staticmethod def check_ffmpeg_installation(): """检查FFmpeg安装""" try: # Windows上使用where命令查找ffmpeg result = subprocess.run( 'where ffmpeg', shell=True, capture_output=True, text=True, timeout=5 ) if result.returncode == 0: ffmpeg_path = result.stdout.strip().split('\n')[0] # 获取版本信息 version_result = subprocess.run( ['ffmpeg', '-version'], capture_output=True, text=True, timeout=5 ) version_info = {} if version_result.returncode == 0: lines = version_result.stdout.split('\n') for line in lines: if 'version' in line.lower(): version_info['version'] = line.strip() break return { 'installed': True, 'path': ffmpeg_path, 'version': version_info.get('version', 'unknown'), 'details': lines[0] if lines else 'unknown' } except subprocess.TimeoutExpired: return {'installed': False, 'error': 'Timeout checking ffmpeg'} except FileNotFoundError: pass except Exception as e: logger.warning(f"检查FFmpeg失败: {str(e)}") return {'installed': False, 'version': 'not found'} @staticmethod def check_rtmp_server_accessibility(rtmp_url): """检查RTMP服务器可达性""" if not rtmp_url.startswith('rtmp://'): return {'accessible': False, 'error': 'Invalid RTMP URL format'} try: # 提取主机和端口 parts = rtmp_url.replace('rtmp://', '').split('/')[0].split(':') host = parts[0] port = int(parts[1]) if len(parts) > 1 else 1935 # Windows网络连接检查 import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(5) result = sock.connect_ex((host, port)) sock.close() if result == 0: return {'accessible': True, 'host': host, 'port': port} else: # Windows错误代码含义 error_map = { 10061: 'Connection refused (服务器拒绝连接)', 10060: 'Connection timed out (连接超时)', 10013: 'Permission denied (权限被拒绝)', 10048: 'Address already in use (地址已被使用)', 10049: 'Cannot assign requested address (无法分配请求的地址)', 10050: 'Network is down (网络断开)', 10051: 'Network is unreachable (网络不可达)', } error_msg = error_map.get(result, f'Unknown error code: {result}') return { 'accessible': False, 'host': host, 'port': port, 'error_code': result, 'error': error_msg } except Exception as e: return {'accessible': False, 'error': str(e)} @staticmethod def optimize_windows_for_streaming(): """优化Windows系统设置以支持推流""" optimizations = {} # 1. 检查电源设置 try: # 使用powercfg检查当前电源方案 result = subprocess.run( 'powercfg /getactivescheme', shell=True, capture_output=True, text=True ) if result.returncode == 0: optimizations['power_scheme'] = result.stdout.strip() else: optimizations['power_scheme'] = 'Cannot check power scheme' # 建议使用高性能电源方案 optimizations['power_recommendation'] = ( 'For best streaming performance, use High Performance power plan. ' 'Run: powercfg /setactive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' ) except Exception as e: optimizations['power_scheme'] = f'Error checking power: {str(e)}' # 2. 防火墙建议 optimizations['firewall'] = 'Consider adding firewall rules for RTMP ports (1935, 1936)' # 3. 网络优化建议 optimizations['network_optimizations'] = [ 'Increase TCP buffer size: netsh int tcp set global autotuninglevel=normal', 'Disable TCP auto-tuning (if unstable): netsh int tcp set global autotuninglevel=disabled', 'Enable TCP fast open: netsh int tcp set global fastopen=enabled', 'Set TCP keepalive: netsh int tcp set global keepalivetime=30000' ] # 4. 显卡设置建议 optimizations['gpu_recommendations'] = [ 'Update graphics drivers to latest version', 'In NVIDIA Control Panel: Set Power Management Mode to "Prefer Maximum Performance"', 'In Windows Graphics Settings: Add ffmpeg.exe and set to "High Performance"' ] return optimizations @staticmethod def create_windows_firewall_rule(port, name="RTMP Streaming"): """创建Windows防火墙规则""" commands = [ f'netsh advfirewall firewall add rule name="{name}" dir=in action=allow protocol=TCP localport={port}', f'netsh advfirewall firewall add rule name="{name} UDP" dir=in action=allow protocol=UDP localport={port}' ] results = [] for cmd in commands: try: # Windows上以管理员权限运行 import ctypes is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 if not is_admin: results.append({ 'command': cmd, 'success': False, 'error': '需要管理员权限运行此命令' }) continue result = subprocess.run(cmd, shell=True, capture_output=True, text=True) results.append({ 'command': cmd, 'success': result.returncode == 0, 'output': result.stdout, 'error': result.stderr }) except Exception as e: results.append({ 'command': cmd, 'success': False, 'error': str(e) }) return results @staticmethod def get_system_resources(): """获取Windows系统资源 - 修复版""" import psutil resources = { 'cpu_percent': psutil.cpu_percent(interval=0.1), 'cpu_count': psutil.cpu_count(), 'cpu_freq': getattr(psutil.cpu_freq(), 'current', 0) if psutil.cpu_freq() else 0, 'memory_percent': psutil.virtual_memory().percent, 'memory_used_gb': psutil.virtual_memory().used / (1024 ** 3), 'memory_total_gb': psutil.virtual_memory().total / (1024 ** 3), 'process_count': len(psutil.pids()), } try: # 磁盘IO(可能在某些系统上不可用) disk_io = psutil.disk_io_counters() if disk_io: resources['disk_io'] = { 'read_bytes': disk_io.read_bytes, 'write_bytes': disk_io.write_bytes, 'read_count': disk_io.read_count, 'write_count': disk_io.write_count } except: resources['disk_io'] = {'available': False} try: # 网络IO net_io = psutil.net_io_counters() resources['network_io'] = { 'bytes_sent': net_io.bytes_sent, 'bytes_recv': net_io.bytes_recv, 'packets_sent': net_io.packets_sent, 'packets_recv': net_io.packets_recv } except: resources['network_io'] = {'available': False} # GPU信息 - 多种方法获取 resources['gpu_info'] = WindowsSystemUtils._get_gpu_info() return resources @staticmethod def _get_gpu_info(): """获取GPU信息 - 支持多种方法""" gpu_info = { 'method': 'none', 'gpus': [] } # 方法2: 使用torch获取GPU信息 (如果可用) try: import torch if torch.cuda.is_available(): for i in range(torch.cuda.device_count()): gpu_data = { 'name': torch.cuda.get_device_name(i), 'total_memory_mb': torch.cuda.get_device_properties(i).total_memory / (1024 ** 2), 'method': 'torch' } gpu_info['gpus'].append(gpu_data) if gpu_info['gpus']: gpu_info['method'] = 'torch' return gpu_info except ImportError: logger.debug("torch未安装,跳过PyTorch GPU检测") except Exception as e: logger.debug(f"PyTorch GPU检测失败: {str(e)}") # 方法3: 使用dxdiag命令获取GPU信息 (Windows原生) try: # 运行dxdiag并保存到临时文件 import tempfile temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.txt') temp_file.close() result = subprocess.run( f'dxdiag /t {temp_file.name}', shell=True, capture_output=True, text=True, timeout=10 ) if result.returncode == 0: with open(temp_file.name, 'r', encoding='utf-8', errors='ignore') as f: dxdiag_content = f.read() # 解析dxdiag输出 import re # 查找显示设备部分 display_sections = re.split(r'Display Devices\n-+', dxdiag_content) if len(display_sections) > 1: for section in display_sections[1:]: # 提取显卡名称 name_match = re.search(r'Card name:\s*(.+)', section) # 提取显存 memory_match = re.search(r'Display Memory:\s*(\d+)', section) # 提取驱动版本 driver_match = re.search(r'Driver Version:\s*(.+)', section) gpu_data = { 'name': name_match.group(1).strip() if name_match else 'Unknown GPU', 'display_memory_mb': int(memory_match.group(1)) if memory_match else 0, 'driver_version': driver_match.group(1).strip() if driver_match else 'Unknown', 'method': 'dxdiag' } gpu_info['gpus'].append(gpu_data) if gpu_info['gpus']: gpu_info['method'] = 'dxdiag' # 清理临时文件 try: os.unlink(temp_file.name) except: pass except Exception as e: logger.debug(f"dxdiag GPU检测失败: {str(e)}") # 方法4: 使用Windows注册表 (高级方法) try: import winreg # 打开显卡注册表键 reg_path = r"SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}" with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, reg_path) as key: i = 0 while True: try: subkey_name = winreg.EnumKey(key, i) subkey_path = f"{reg_path}\\{subkey_name}" with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, subkey_path) as subkey: try: driver_desc = winreg.QueryValueEx(subkey, "DriverDesc")[0] # 跳过基本显示适配器 if "basic display" not in driver_desc.lower(): gpu_data = { 'name': driver_desc, 'method': 'registry' } gpu_info['gpus'].append(gpu_data) except WindowsError: pass i += 1 except WindowsError: break if gpu_info['gpus']: gpu_info['method'] = 'registry' except Exception as e: logger.debug(f"注册表GPU检测失败: {str(e)}") return gpu_info @staticmethod def test_ffmpeg_streaming(): """测试FFmpeg推流功能""" test_results = [] # 测试1: 基本的FFmpeg功能 try: result = subprocess.run( ['ffmpeg', '-version'], capture_output=True, text=True, timeout=5 ) test_results.append({ 'test': 'ffmpeg_version', 'success': result.returncode == 0, 'output': result.stdout[:100] if result.stdout else 'No output' }) except Exception as e: test_results.append({ 'test': 'ffmpeg_version', 'success': False, 'error': str(e) }) # 测试2: 编码器支持 # 测试3: 生成测试视频并推流到null (本地测试) try: # 创建一个简单的测试命令 test_command = [ 'ffmpeg', '-f', 'lavfi', '-i', 'testsrc=duration=2:size=640x480:rate=30', '-c:v', 'libx264', '-t', '1', # 只运行1秒 '-f', 'null', # 输出到null '-' ] result = subprocess.run( test_command, capture_output=True, text=True, timeout=10 ) test_results.append({ 'test': 'ffmpeg_basic_encode', 'success': result.returncode == 0, 'output': 'Success' if result.returncode == 0 else result.stderr[:200] }) except Exception as e: test_results.append({ 'test': 'ffmpeg_basic_encode', 'success': False, 'error': str(e) }) return test_results # 系统检测和配置 def detect_and_configure_windows(): """检测并配置Windows系统""" if not WindowsSystemUtils.is_windows(): logger.info("非Windows系统,跳过Windows特定配置") return None logger.info("检测到Windows系统,进行系统检测和配置...") config_result = { 'system_info': {}, 'ffmpeg_info': {}, 'gpu_info': {}, 'optimizations': {}, 'resources': {}, 'ffmpeg_tests': [], 'status': 'unknown', 'issues': [], 'recommendations': [] } try: # 1. 获取系统信息 system_info = WindowsSystemUtils.get_windows_version() config_result['system_info'] = system_info logger.info(f"Windows系统: {system_info.get('name', 'Unknown')}") # 2. 检查FFmpeg ffmpeg_info = WindowsSystemUtils.check_ffmpeg_installation() config_result['ffmpeg_info'] = ffmpeg_info if not ffmpeg_info['installed']: config_result['issues'].append("FFmpeg未安装") config_result['recommendations'].append({ 'priority': 'critical', 'action': '安装FFmpeg', 'details': '从 https://github.com/BtbN/FFmpeg-Builds/releases 下载 ffmpeg-master-latest-win64-gpl.zip,解压并添加bin目录到PATH' }) logger.error("FFmpeg未安装!") else: logger.info(f"FFmpeg版本: {ffmpeg_info['version']}") logger.info(f"FFmpeg路径: {ffmpeg_info.get('path', 'unknown')}") # 运行FFmpeg测试 logger.info("运行FFmpeg功能测试...") ffmpeg_tests = WindowsSystemUtils.test_ffmpeg_streaming() config_result['ffmpeg_tests'] = ffmpeg_tests # 检查测试结果 failed_tests = [t for t in ffmpeg_tests if not t.get('success', False)] if failed_tests: config_result['issues'].append(f"FFmpeg测试失败 ({len(failed_tests)}个)") for test in failed_tests: logger.warning(f"FFmpeg测试失败: {test.get('test')}") else: logger.info(f"FFmpeg功能测试成功!") # 3. 获取GPU信息 gpu_info = WindowsSystemUtils._get_gpu_info() config_result['gpu_info'] = gpu_info if gpu_info['gpus']: logger.info(f"检测到 {len(gpu_info['gpus'])} 个GPU:") for i, gpu in enumerate(gpu_info['gpus']): logger.info(f" GPU{i}: {gpu.get('name', 'Unknown')}") if gpu.get('adapter_ram_mb', 0) > 0: logger.info(f" 显存: {gpu['adapter_ram_mb']} MB") else: logger.warning("未检测到GPU信息") config_result['issues'].append("未检测到GPU信息") # 4. 系统优化建议 optimizations = WindowsSystemUtils.optimize_windows_for_streaming() config_result['optimizations'] = optimizations # 输出优化建议 logger.info("系统优化建议:") for key, value in optimizations.items(): if isinstance(value, list): logger.info(f" {key}:") for item in value: logger.info(f" - {item}") else: logger.info(f" {key}: {value}") # 5. 获取系统资源 resources = WindowsSystemUtils.get_system_resources() config_result['resources'] = resources logger.info(f"系统资源: CPU {resources['cpu_percent']}%, 内存 {resources['memory_percent']}%") # 检查资源问题 if resources['cpu_percent'] > 85: config_result['issues'].append(f"CPU使用率过高: {resources['cpu_percent']}%") config_result['recommendations'].append({ 'priority': 'high', 'action': '降低CPU使用率', 'details': '关闭不必要的程序,减少后台进程' }) if resources['memory_percent'] > 90: config_result['issues'].append(f"内存使用率过高: {resources['memory_percent']}%") config_result['recommendations'].append({ 'priority': 'high', 'action': '释放内存', 'details': '关闭内存占用大的程序,重启系统' }) # 6. 生成网络测试建议 config_result['recommendations'].append({ 'priority': 'medium', 'action': '测试RTMP服务器连接', 'details': '运行: python -c "import socket; sock=socket.socket(); sock.settimeout(5); print(\'OK\' if sock.connect_ex((\'your-server\', 1935))==0 else \'FAIL\')"' }) # 7. 推流配置建议 config_result['recommendations'].append({ 'priority': 'medium', 'action': '使用软件编码', 'details': 'Windows上建议使用libx264而非硬件编码,更稳定' }) config_result['recommendations'].append({ 'priority': 'low', 'action': '调整推流参数', 'details': '尝试: -preset ultrafast -tune zerolatency -b:v 1500k -maxrate 1500k -bufsize 3000k' }) # 设置状态 if config_result['issues']: config_result['status'] = 'warning' logger.warning(f"发现 {len(config_result['issues'])} 个问题") else: config_result['status'] = 'ready' logger.info("Windows系统检测完成,状态正常") except Exception as e: logger.error(f"Windows系统检测异常: {str(e)}") logger.error(traceback.format_exc()) config_result['status'] = 'error' config_result['error'] = str(e) return config_result # Windows快速诊断函数 def quick_windows_diagnosis(): """快速Windows诊断""" logger.info("开始Windows快速诊断...") results = { 'ffmpeg': WindowsSystemUtils.check_ffmpeg_installation(), 'system': WindowsSystemUtils.get_windows_version(), 'gpu': WindowsSystemUtils._get_gpu_info(), 'timestamp': time.strftime("%Y-%m-%d %H:%M:%S") } # 快速资源检查 try: import psutil results['resources'] = { 'cpu': psutil.cpu_percent(), 'memory': psutil.virtual_memory().percent } except: results['resources'] = {'error': 'Failed to get resources'} # 输出摘要 print("\n" + "=" * 60) print("Windows快速诊断结果") print("=" * 60) print(f"\n系统: {results['system'].get('name', 'Unknown')}") print(f"时间: {results['timestamp']}") if results['ffmpeg']['installed']: print(f"✓ FFmpeg: {results['ffmpeg']['version']}") else: print("✗ FFmpeg: 未安装") gpu_count = len(results['gpu'].get('gpus', [])) if gpu_count > 0: print(f"✓ GPU: {gpu_count}个检测到") for i, gpu in enumerate(results['gpu']['gpus'][:2]): # 只显示前2个 print(f" {gpu.get('name', 'Unknown GPU')}") else: print("⚠ GPU: 未检测到") if 'resources' in results and 'error' not in results['resources']: print(f"系统负载: CPU {results['resources']['cpu']}%, 内存 {results['resources']['memory']}%") print("\n" + "=" * 60) return results