0, "allocations" => []]; } $state = json_decode(file_get_contents($coreFile), true); return is_array($state) ? $state : ["cursor" => 0, "allocations" => []]; } function saveCoreState(array $state): void { global $coreFile; file_put_contents($coreFile, json_encode($state, JSON_PRETTY_PRINT)); } /* --------------------------------------------------------- NUMA + SMT TOPOLOGY (SOURCE OF TRUTH) --------------------------------------------------------- */ function getNumaTopology(): array { $nodes = []; /* discover NUMA nodes */ foreach (glob('/sys/devices/system/node/node*') as $nodePath) { $node = (int)str_replace('node', '', basename($nodePath)); $nodes[$node] = []; } /* map cpu -> node -> core_id */ foreach (glob('/sys/devices/system/cpu/cpu[0-9]*') as $cpuPath) { $cpu = (int)str_replace('cpu', '', basename($cpuPath)); $topo = "$cpuPath/topology"; if (!is_dir($topo)) { continue; } $coreId = (int)trim(file_get_contents("$topo/core_id")); $node = null; foreach (glob("$cpuPath/node*") as $n) { $node = (int)str_replace('node', '', basename($n)); break; } if ($node === null) { continue; } $nodes[$node][$coreId][] = $cpu; } /* normalize ordering */ ksort($nodes); foreach ($nodes as &$cores) { ksort($cores); foreach ($cores as &$threads) { sort($threads); // primary thread first } } return $nodes; } /* --------------------------------------------------------- BUILD GLOBAL ROUND-ROBIN PLAN EVEN CPUs FIRST → ODD CPUs --------------------------------------------------------- */ function buildAllocationPlan(array $nodes): array { $even = []; $odd = []; foreach ($nodes as $node => $cores) { foreach ($cores as $coreId => $threads) { foreach ($threads as $cpu) { $entry = [ "node" => $node, "cpu" => $cpu ]; if (($cpu % 2) === 0) { $even[] = $entry; } else { $odd[] = $entry; } } } } /* deterministic ordering */ usort($even, fn($a, $b) => $a["cpu"] <=> $b["cpu"]); usort($odd, fn($a, $b) => $a["cpu"] <=> $b["cpu"]); return array_merge($even, $odd); } /* --------------------------------------------------------- ALLOCATION API --------------------------------------------------------- */ function allocateCore(int $serviceId): array { $state = loadCoreState(); if (isset($state["allocations"][$serviceId])) { return $state["allocations"][$serviceId]; } $nodes = getNumaTopology(); $plan = buildAllocationPlan($nodes); if (empty($plan)) { return ["node" => 0, "cpu" => 0]; } $slot = $plan[$state["cursor"] % count($plan)]; $state["cursor"]++; $state["allocations"][$serviceId] = $slot; saveCoreState($state); return $slot; } function freeCore(int $serviceId): void { $state = loadCoreState(); if (isset($state["allocations"][$serviceId])) { unset($state["allocations"][$serviceId]); saveCoreState($state); } } function getServiceCore(int $serviceId): ?array { $state = loadCoreState(); return $state["allocations"][$serviceId] ?? null; } $jsonFile = __DIR__ . "/input.json"; if (!file_exists($jsonFile)) { file_put_contents($jsonFile, json_encode([])); } $data = json_decode(file_get_contents($jsonFile), true); /* Fix old entries missing service_name or volume */ foreach ($data as $k => $d) { if (!isset($d["service_name"])) $data[$k]["service_name"] = ""; if (!isset($d["volume"])) $data[$k]["volume"] = "0"; } file_put_contents($jsonFile, json_encode($data, JSON_PRETTY_PRINT)); /* ---------------- ADD NEW ---------------- */ if ($_SERVER["REQUEST_METHOD"] === "POST" && $_POST["action"] === "add") { $new = [ "id" => time(), "service_name" => $_POST["service_name"], "input_udp" => $_POST["input_udp"], "output_udp" => $_POST["output_udp"], "video_format" => $_POST["video_format"], "audio_format" => $_POST["audio_format"], "resolution" => $_POST["resolution"], "video_bitrate" => $_POST["video_bitrate"], "audio_bitrate" => $_POST["audio_bitrate"], "volume" => $_POST["volume"], "service" => $_POST["service"] ]; $data[] = $new; file_put_contents($jsonFile, json_encode($data, JSON_PRETTY_PRINT)); $alloc = getServiceCore($new["id"]); if ($alloc === null) { $alloc = allocateCore($new["id"]); } $core = (int)$alloc["cpu"]; $node = (int)$alloc["node"]; $ffmpeg = 'numactl --cpunodebind=' . $node . ' --membind=' . $node . ' taskset -c ' . $core . ' ffmpeg -hide_banner -loglevel info -thread_queue_size 65536 -fflags +genpts+discardcorrupt+nobuffer -readrate 1.0' . ' -i "udp://@' . $new["input_udp"] . '?fifo_size=100000000&buffer_size=100000000&overrun_nonfatal=1"' . ' -vf "yadif=mode=0:deint=0,scale=' . $new["resolution"] . ',format=yuv420p" ' . ' -c:v ' . $new["video_format"] . ' -flags -ildct-ilme -threads 1 -g 10 -bf 0 ' . ' -b:v ' . $new["video_bitrate"] . 'k -minrate ' . max(0, $new["video_bitrate"] - 500) . 'k -maxrate ' . ($new["video_bitrate"] + 500) . 'k -bufsize 1835k ' . ' -c:a ' . $new["audio_format"] . ' -b:a ' . $new["audio_bitrate"] . 'k -ar 48000 -ac 2 -af "volume=' . $new["volume"] . 'dB,aresample=async=1:first_pts=0" ' . ' -metadata service_provider="ShreeBhattJI" '; if ($new["service_name"] !== "") { $ffmpeg .= '-metadata service_name="' . $new["service_name"] . '" '; } $ffmpeg .= ' -pcr_period 20 -f mpegts "udp://' . $new["output_udp"] . '?pkt_size=1316&bitrate=4500000&flush_packets=1"'; if ($new["service_name"] !== "") $ffmpeg .= '-metadata service_name="' . $new["service_name"] . '"'; $ffmpeg .= ' -f mpegts "udp://@' . $new["output_udp"] . '?pkt_size=1316&bitrate=4500000"'; file_put_contents("/var/www/encoder/{$new["id"]}.sh", $ffmpeg); if ($new["service"] === "enable") { exec("sudo systemctl enable encoder@{$new["id"]}"); exec("sudo systemctl restart encoder@{$new["id"]}"); } echo "OK"; exit; } /* ---------------- DELETE ---------------- */ if ($_SERVER["REQUEST_METHOD"] === "POST" && $_POST["action"] === "delete") { $id = intval($_POST["id"]); $newData = []; foreach ($data as $row) { if ($row["id"] != $id) $newData[] = $row; } file_put_contents($jsonFile, json_encode($newData, JSON_PRETTY_PRINT)); exec("sudo systemctl stop encoder@$id"); exec("sudo systemctl disable encoder@$id"); freeCore($id); if (file_exists("/var/www/encoder/$id.sh")) unlink("/var/www/encoder/$id.sh"); echo "OK"; exit; } /* ---------------- EDIT ---------------- */ if ($_SERVER["REQUEST_METHOD"] === "POST" && $_POST["action"] === "edit") { $id = intval($_POST["id"]); $newData = []; foreach ($data as $row) { if ($row["id"] == $id) { $row = [ "id" => $id, "service_name" => $_POST["service_name"], "input_udp" => $_POST["input_udp"], "output_udp" => $_POST["output_udp"], "video_format" => $_POST["video_format"], "audio_format" => $_POST["audio_format"], "resolution" => $_POST["resolution"], "video_bitrate" => $_POST["video_bitrate"], "audio_bitrate" => $_POST["audio_bitrate"], "volume" => $_POST["volume"], "service" => $_POST["service"] ]; $new = $row; $alloc = getServiceCore($id); if ($alloc === null) { $alloc = allocateCore($id); } $core = (int)$alloc["cpu"]; $node = (int)$alloc["node"]; $ffmpeg = 'numactl --cpunodebind=' . $node . ' --membind=' . $node . ' taskset -c ' . $core . ' ffmpeg -hide_banner -loglevel info -thread_queue_size 65536 -fflags +genpts+discardcorrupt+nobuffer -readrate 1.0' . ' -i "udp://@' . $new["input_udp"] . '?fifo_size=100000000&buffer_size=100000000&overrun_nonfatal=1"' . ' -vf "yadif=mode=0:deint=0,scale=' . $new["resolution"] . ',format=yuv420p" ' . ' -c:v ' . $new["video_format"] . ' -flags -ildct-ilme -threads 1 -g 10 -bf 0 ' . ' -b:v ' . $new["video_bitrate"] . 'k -minrate ' . max(0, $new["video_bitrate"] - 500) . 'k -maxrate ' . ($new["video_bitrate"] + 500) . 'k -bufsize 1835k ' . ' -c:a ' . $new["audio_format"] . ' -b:a ' . $new["audio_bitrate"] . 'k -ar 48000 -ac 2 -af "volume=' . $new["volume"] . 'dB,aresample=async=1:first_pts=0" ' . ' -metadata service_provider="ShreeBhattJI" '; if ($new["service_name"] !== "") { $ffmpeg .= '-metadata service_name="' . $new["service_name"] . '" '; } $ffmpeg .= ' -pcr_period 20 -f mpegts "udp://' . $new["output_udp"] . '?pkt_size=1316&bitrate=4500000&flush_packets=1"'; file_put_contents("/var/www/encoder/$id.sh", $ffmpeg); if ($new["service"] === "enable") { exec("sudo systemctl enable encoder@$id"); exec("sudo systemctl restart encoder@$id"); } else { exec("sudo systemctl stop encoder@$id"); exec("sudo systemctl disable encoder@$id"); } } $newData[] = $row; } file_put_contents($jsonFile, json_encode($newData, JSON_PRETTY_PRINT)); echo "OK"; exit; } /* ---------------- RESTART ---------------- */ if ($_SERVER["REQUEST_METHOD"] === "POST" && $_POST["action"] === "restart") { $id = intval($_POST["id"]); exec("sudo systemctl restart encoder@$id"); echo "OK"; exit; } ?>

Service List

ID Service Name Input Output Video Audio Resolution V-Bitrate A-Bitrate Volume (dB) Status Actions
dB