Safer ffmpeg kill, POST process/service actions, ARM encoder from hardware.env, filter_threads scaling

Made-with: Cursor
This commit is contained in:
eric
2026-03-28 12:03:13 -05:00
parent f4794e720a
commit 9b6570d06e
7 changed files with 204 additions and 45 deletions

View File

@@ -12,7 +12,7 @@ import subprocess
import sys
import time
from pathlib import Path
from typing import Any, Optional
from typing import Any, Iterable, Optional
BACKEND_STATE_VERSION = 1
@@ -49,10 +49,58 @@ async def run_pm2(cmd: str, timeout: float = 15.0) -> str:
return text
async def force_kill_ffmpeg(process_name: str) -> None:
"""尽量结束与本业务相关的 ffmpegLinux 精准 + 兜底Windows 与 Linux 兜底策略一致:结束 ffmpeg 进程)"""
def _linux_ffmpeg_pids_for_stack(process_name: str, project_root: Path | None) -> list[int]:
"""仅匹配与本项目直播栈相关的 ffmpeg命令行含 YouTube 推流、抖音拉流或项目路径),避免 killall 误伤整机其它 ffmpeg。"""
markers: list[str] = [
process_name,
"a.rtmp.youtube.com",
"rtmps://a.rtmp.youtube.com",
"live.douyin.com",
"pull-flv",
"douyincdn.com",
]
if project_root is not None:
try:
markers.append(str(project_root.resolve()))
except OSError:
pass
markers = [m for m in markers if m]
seen: set[int] = set()
proc_root = Path("/proc")
if not proc_root.is_dir():
return []
for entry in proc_root.iterdir():
if not entry.name.isdigit():
continue
try:
pid = int(entry.name)
except ValueError:
continue
cmdline = entry / "cmdline"
try:
raw = cmdline.read_bytes()
except OSError:
continue
if not raw or b"ffmpeg" not in raw.lower():
continue
cmd = raw.replace(b"\0", b" ").decode("utf-8", "replace")
if any(m in cmd for m in markers):
seen.add(pid)
return sorted(seen)
def _signal_pids(pids: Iterable[int], sig: int) -> None:
for pid in pids:
try:
os.kill(pid, sig)
except (ProcessLookupError, PermissionError):
pass
async def force_kill_ffmpeg(process_name: str, project_root: Path | None = None) -> None:
"""结束与本业务相关的 ffmpegLinux 按 /proc 命令行匹配 + 可选 pkill不再使用 killall -9 ffmpeg。"""
if sys.platform == "win32":
_ = process_name # 保留参数供将来按命令行筛选;当前与 Linux killall 行为对齐
_ = process_name
proc2 = await asyncio.create_subprocess_shell(
"taskkill /IM ffmpeg.exe /F /T 2>nul || exit /b 0",
stdout=asyncio.subprocess.DEVNULL,
@@ -61,17 +109,25 @@ async def force_kill_ffmpeg(process_name: str) -> None:
await proc2.wait()
return
for cmd in (
f"pkill -f 'ffmpeg.*{process_name}' || true",
"killall -9 ffmpeg 2>/dev/null || true",
):
proc = await asyncio.create_subprocess_shell(
cmd,
stdout=asyncio.subprocess.DEVNULL,
stderr=asyncio.subprocess.DEVNULL,
)
await proc.wait()
await asyncio.sleep(0.4)
def collect() -> list[int]:
return _linux_ffmpeg_pids_for_stack(process_name, project_root)
pids = await asyncio.to_thread(collect)
if pids:
await asyncio.to_thread(_signal_pids, pids, signal.SIGTERM)
await asyncio.sleep(0.6)
remaining = await asyncio.to_thread(collect)
if remaining:
await asyncio.to_thread(_signal_pids, remaining, signal.SIGKILL)
await asyncio.sleep(0.2)
safe = re.sub(r"[^a-zA-Z0-9_.-]", "", process_name) or "x"
proc = await asyncio.create_subprocess_shell(
f"pkill -f 'ffmpeg.*{safe}' || true",
stdout=asyncio.subprocess.DEVNULL,
stderr=asyncio.subprocess.DEVNULL,
)
await proc.wait()
class LocalProcessRegistry: