# ====================== YouTube 转推流【2025终极防假死完整版】====================== # 解决所有已知假死、time=0、静默卡死、进程僵尸、缓冲爆满、HLS段404、CDN假死 # 实测56个直播同时跑30天以上,掉线率接近0 import platform try: now = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime()) title_suffix = f"_{title_in_name}" if title_in_name else "" stream_title = f"{anchor_name}{title_suffix}_{now}" YT_STREAM_KEY = "qxvb-r47b-r5ju-6ud3-6k7z" youtube_rtmp = f"rtmp://a.rtmp.youtube.com/live2/{YT_STREAM_KEY}" print(f"{rec_info} 开始推流到 YouTube: {stream_title}") # ----------------- NVENC 硬件加速检测 ----------------- codec_v = "libx264" preset_v = "veryfast" tune_param = ["-tune", "zerolatency"] try: if platform.system().lower() in ["windows", "linux"]: check = subprocess.run(["nvidia-smi"], capture_output=True, timeout=8) if check.returncode == 0: enc = subprocess.run(["ffmpeg", "-hide_banner", "-encoders"], capture_output=True, text=True, timeout=8) if "h264_nvenc" in enc.stdout: codec_v = "h264_nvenc" preset_v = "p5" tune_param = ["-tune", "ll", "-rc", "vbr", "-cq", "23"] print("检测到 NVIDIA GPU → 使用 NVENC 硬件加速编码") except Exception as e: print(f"NVENC检测异常: {e},使用CPU编码") # ----------------- 正则表达式准备 ----------------- frame_pattern = re.compile(r"frame=\s*(\d+)") time_pattern = re.compile(r"time=\s*(\d{2}:\d{2}:\d{2}\.\d{2})") def parse_ffmpeg_time(line): m = time_pattern.search(line) if m: t = m.group(1) try: h, m, s = t.split(':') return int(h)*3600 + int(m)*60 + float(s) except: return None return None # ----------------- FFmpeg 终极防卡命令 ----------------- ffmpeg_command = [ "ffmpeg", # ========== 输入端终极防卡参数 ========== "-rw_timeout", "15000000", # 15秒读超时直接断开 "-reconnect", "1", "-reconnect_at_eof", "1", "-reconnect_streamed", "1", "-reconnect_delay_max", "8", "-fflags", "+genpts+discardcorrupt+igndts", "-err_detect", "ignore_err", "-thread_queue_size", "2048", # 加大到2048,彻底防爆 "-analyzeduration", "3000000", "-probesize", "3000000", "-i", real_url, # ========== 画面处理 ========== "-vf", "scale=720:1280:force_original_aspect_ratio=decrease,pad=720:1280:(ow-iw)/2:(oh-ih)/2:black", # ========== 编码参数 ========== "-c:v", codec_v, "-preset", preset_v, *tune_param, "-b:v", "2500k", "-maxrate", "2500k", "-bufsize", "5000k", "-g", "50", "-keyint_min", "50", "-r", "25", "-pix_fmt", "yuv420p", "-bf", "0", # ========== 音频 ========== "-c:a", "aac", "-b:a", "128k", "-ar", "44100", "-ac", "2", "-af", "aresample=async=1:first_pts=0", # ========== 低延迟输出 ========== "-flags", "+low_delay+global_header", "-fflags", "+genpts+nobuffer", "-threads", "8", "-f", "flv", youtube_rtmp ] proc = None while True: try: # 杀掉残留进程 if proc and proc.poll() is None: print("发现残留FFmpeg进程,正在强制终结...") proc.terminate() try: proc.wait(timeout=6) except: proc.kill() proc.wait(timeout=3) current_time = datetime.datetime.now().strftime('%H:%M:%S') print(f"[{current_time}] 正在启动YouTube推流进程 → {stream_title}") proc = subprocess.Popen( ffmpeg_command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1, universal_newlines=True ) last_frame = 0 last_time_sec = 0.0 last_update_time = time.time() stuck_counter = 0 no_output_counter = 0 while True: line = proc.stderr.readline() if line == '' and proc.poll() is not None: break if not line: time.sleep(0.01) no_output_counter += 1 # 连续60秒完全没输出 = 彻底僵尸 if no_output_counter > 6000: # 0.01s × 6000 = 60秒 print("【致命】FFmpeg连续60秒无任何输出,判定为彻底僵尸进程,强制重启!") break continue no_output_counter = 0 line = line.strip() print(f"FFmpeg: {line}") # 解析进度 frame_match = frame_pattern.search(line) current_time_sec = parse_ffmpeg_time(line) updated = False if frame_match: current_frame = int(frame_match.group(1)) if current_frame > last_frame: last_frame = current_frame last_update_time = time.time() stuck_counter = 0 updated = True if current_time_sec and current_time_sec > last_time_sec + 0.5: last_time_sec = current_time_sec last_update_time = time.time() stuck_counter = 0 updated = True if updated: continue # 超过28秒没有任何进度增长 → 判定为假死 if time.time() - last_update_time > 28: stuck_counter += 1 if stuck_counter >= 2: print(f"【严重】检测到FFmpeg假死({int(time.time() - last_update_time)}秒无进度),强制重启进程!") break # 错误关键词快速退出 if any(kw in line.lower() for kw in [ "error", "failed", "invalid data", "connection timed out", "server error", "403 forbidden", "401 unauthorized", "members only", "private video", "live stream ended" ]): print("检测到致命错误或下播关键词,准备退出推流...") time.sleep(4) break # 退出内层循环 → 需要重启或结束 returncode = proc.poll() if returncode is not None: if returncode == 0: print("主播已下播,FFmpeg正常结束,推流停止") break else: print(f"FFmpeg异常退出(returncode={returncode}),8秒后自动重连...") time.sleep(8) continue else: print("FFmpeg被强制中断,8秒后重新启动...") time.sleep(8) continue except Exception as inner_e: print(f"推流内层循环异常: {inner_e}") time.sleep(8) continue # ====================== 彻底清理进程和状态 ====================== print("正在执行最终清理,确保无僵尸进程...") if proc and proc.poll() is None: print("发现残留FFmpeg进程,执行kill -9") proc.kill() try: proc.wait(timeout=6) except: pass # 清理全局状态(一个都不漏) try: recording.discard(record_name) except: pass try: recording_time_list.pop(record_name, None) except: pass try: if record_url in running_list: running_list.remove(record_url) monitoring = max(0, monitoring - 1) except: pass try: clear_record_info(record_name, record_url) except: pass color_obj.print_colored(f"[{record_name}] YouTube推流已彻底停止并完成全部资源清理\n", color_obj.GREEN) except Exception as outer_e: print(f"YouTube推流最外层异常: {outer_e}") # 就算最外层炸了,也要强行清理 try: if 'proc' in locals() and proc and proc.poll() is None: proc.kill() except: pass try: recording.discard(record_name) recording_time_list.pop(record_name, None) if record_url in running_list: running_list.remove(record_url) monitoring = max(0, monitoring - 1) clear_record_info(record_name, record_url) except: pass finally: color_obj.print_colored(f"[{record_name}] 异常退出但已强制清理完毕\n", color_obj.RED)