mass test approaches

This commit is contained in:
henryruhs
2026-03-23 14:20:37 +01:00
parent 021b9a15f5
commit da74b85223
7 changed files with 729 additions and 19 deletions
+2 -3
View File
@@ -14,7 +14,7 @@ from facefusion.apis.endpoints.ping import websocket_ping
from facefusion.apis.endpoints.session import create_session, destroy_session, get_session, refresh_session
from facefusion.apis.endpoints.state import get_state, set_state
from facefusion import logger
from facefusion.apis.endpoints.stream import websocket_stream, websocket_stream_audio, websocket_stream_live, websocket_stream_mjpeg, websocket_stream_rtc, websocket_stream_whip, websocket_stream_whip_aio, websocket_stream_whip_dc, websocket_stream_whip_py
from facefusion.apis.endpoints.stream import websocket_stream, websocket_stream_audio, websocket_stream_live, websocket_stream_mjpeg, websocket_stream_rtc, websocket_stream_whip, websocket_stream_whip_dc, websocket_stream_whip_py
from facefusion.apis.middlewares.session import create_session_guard
@@ -87,8 +87,7 @@ def create_api() -> Starlette:
WebSocketRoute('/stream/whip-py', websocket_stream_whip_py, middleware = [ session_guard ]),
WebSocketRoute('/stream/whip-dc', websocket_stream_whip_dc, middleware = [ session_guard ]),
WebSocketRoute('/stream/live', websocket_stream_live, middleware = [ session_guard ]),
WebSocketRoute('/stream/whip-aio', websocket_stream_whip_aio, middleware = [ session_guard ]),
WebSocketRoute('/stream/rtc', websocket_stream_rtc, middleware = [ session_guard ]),
WebSocketRoute('/stream/rtc', websocket_stream_rtc, middleware = [ session_guard ]),
WebSocketRoute('/stream/mjpeg', websocket_stream_mjpeg, middleware = [ session_guard ]),
WebSocketRoute('/stream/audio', websocket_stream_audio, middleware = [ session_guard ])
]
+26 -1
View File
@@ -7,6 +7,7 @@ from collections import deque
from concurrent.futures import ThreadPoolExecutor
from typing import Deque, List
import av
import cv2
import numpy
from starlette.websockets import WebSocket
@@ -590,14 +591,18 @@ async def websocket_stream_whip_dc(websocket : WebSocket) -> None:
if source_paths:
from facefusion import whip_relay
from facefusion import rtc as rtc_audio
import socket as sock
stream_path = 'stream/' + session_id
rtp_port = whip_relay.create_session(stream_path)
rtp_port, audio_port = whip_relay.create_session(stream_path)
if not rtp_port:
logger.error('failed to create relay session', __name__)
await websocket.close()
return
audio_sock = sock.socket(sock.AF_INET, sock.SOCK_DGRAM) if audio_port else None
latest_frame_holder : list = [None]
whep_sent = False
lock = threading.Lock()
@@ -625,10 +630,30 @@ async def websocket_stream_whip_dc(websocket : WebSocket) -> None:
with lock:
latest_frame_holder[0] = frame
if data[:2] != JPEG_MAGIC and audio_sock and audio_port:
encoder = rtc_audio.get_opus_encoder()
pcm = numpy.frombuffer(data, dtype = numpy.int16).reshape(1, -1)
samples = pcm.shape[1] // (2 * 960) * 960 * 2
if samples > 0:
for offset in range(0, samples, 960 * 2):
chunk = pcm[:, offset:offset + 960 * 2]
if chunk.shape[1] == 960 * 2:
frame = av.AudioFrame.from_ndarray(chunk, format = 's16', layout = 'stereo')
frame.sample_rate = 48000
for packet in encoder.encode(frame):
audio_sock.sendto(bytes(packet), ('127.0.0.1', audio_port))
except Exception as exception:
logger.error(str(exception), __name__)
stop_event.set()
if audio_sock:
audio_sock.close()
loop = asyncio.get_running_loop()
await loop.run_in_executor(None, worker.join, 10)
return
+8 -5
View File
@@ -270,6 +270,7 @@ send_start_time : float = 0
opus_encoder : Optional[av.CodecContext] = None
audio_buffer : bytearray = bytearray()
audio_lock : threading.Lock = threading.Lock()
audio_pts : int = 0
OPUS_FRAME_SAMPLES : int = 960
@@ -320,6 +321,8 @@ def get_opus_encoder() -> av.CodecContext:
def send_audio(stream_path : str, pcm_data : bytes) -> None:
global audio_pts
session = sessions.get(stream_path)
if not session:
@@ -342,10 +345,11 @@ def send_audio(stream_path : str, pcm_data : bytes) -> None:
pcm = numpy.frombuffer(chunk, dtype = numpy.int16).reshape(1, -1)
frame = av.AudioFrame.from_ndarray(pcm, format = 's16', layout = 'stereo')
frame.sample_rate = 48000
frame.pts = None
frame.pts = audio_pts
for packet in encoder.encode(frame):
opus_data = bytes(packet)
buf = ctypes.create_string_buffer(opus_data)
for viewer in viewers:
if not viewer.get('connected'):
@@ -359,12 +363,11 @@ def send_audio(stream_path : str, pcm_data : bytes) -> None:
if not lib.rtcIsOpen(audio_track_id):
continue
elapsed = _time.monotonic() - send_start_time if send_start_time > 0 else 0
timestamp = int(elapsed * 48000) & 0xFFFFFFFF
buf = ctypes.create_string_buffer(opus_data)
lib.rtcSetTrackRtpTimestamp(audio_track_id, timestamp)
lib.rtcSetTrackRtpTimestamp(audio_track_id, audio_pts & 0xFFFFFFFF)
lib.rtcSendMessage(audio_track_id, buf, len(opus_data))
audio_pts += OPUS_FRAME_SAMPLES
h264_au_buffer : Dict[str, bytes] = {}
+7 -3
View File
@@ -88,12 +88,16 @@ def is_session_ready(stream_path : str) -> bool:
return False
def create_session(stream_path : str) -> int:
def create_session(stream_path : str) -> tuple:
try:
response = httpx.post('http://localhost:' + str(RELAY_PORT) + '/' + stream_path + '/create', timeout = 5)
if response.status_code == 200:
return int(response.text)
parts = response.text.split(',')
if len(parts) >= 2:
return int(parts[0]), int(parts[1])
return int(parts[0]), 0
except Exception:
pass
return 0
return 0, 0
+22 -7
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WHIP Stream Monitor</title>
<title>Video Stream</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { overflow: hidden; height: 100vh; }
@@ -107,7 +107,7 @@
<body>
<div class="layout">
<div class="sidebar">
<h2>WHIP Stream <span class="badge">LIVE</span></h2>
<h2>Video Stream <span class="badge">LIVE</span></h2>
<div class="section">
<div class="section-title"><span class="step-dot" id="dotSession">1</span> Session</div>
@@ -144,12 +144,11 @@
<label>Approach
<select id="streamMode">
<option value="whip-mediamtx">FFmpeg WHIP + MediaMTX</option>
<option value="whip-python">aiortc in-process WebRTC</option>
<option value="whip-python">aiortc WebRTC (no ext. deps)</option>
<option value="whip-datachannel">FFmpeg WHIP + libdatachannel relay</option>
<option value="ws-fmp4">FFmpeg fMP4 + WebSocket (MSE)</option>
<option value="datachannel-direct">libdatachannel direct</option>
<option value="whip-aiortc">aiortc WebRTC + audio (no ext. deps)</option>
<option value="ws-mjpeg">MJPEG over WebSocket (no deps)</option>
<option value="ws-mjpeg">MJPEG over WebSocket (no deps)</option>
</select>
</label>
</div>
@@ -324,8 +323,7 @@ var MODE_CONFIG = {
'whip-datachannel': { wsPath: '/stream/whip-dc', playback: 'whep' },
'ws-fmp4': { wsPath: '/stream/live', playback: 'mse' },
'datachannel-direct': { wsPath: '/stream/rtc', playback: 'whep' },
'whip-aiortc': { wsPath: '/stream/whip-aio', playback: 'whep' },
'ws-mjpeg': { wsPath: '/stream/mjpeg', playback: 'mjpeg' }
'ws-mjpeg': { wsPath: '/stream/mjpeg', playback: 'mjpeg' }
};
function getMode() {
@@ -1020,6 +1018,23 @@ async function connect() {
var mode = getMode();
framesSent = 0;
var outputVideo = document.getElementById('outputVideo');
outputVideo.srcObject = null;
outputVideo.removeAttribute('src');
outputVideo.load();
outputVideo.style.display = '';
var mjpegImg = outputVideo._mjpegImg;
if (mjpegImg) {
if (mjpegImg._prevUrl) URL.revokeObjectURL(mjpegImg._prevUrl);
mjpegImg.remove();
outputVideo._mjpegImg = null;
}
cleanupMse();
whepUrlFromServer = null;
var wsUrl = wsBase() + config.wsPath;
var protocols = ['access_token.' + accessToken];
var t0 = performance.now();
BIN
View File
Binary file not shown.
+664
View File
@@ -0,0 +1,664 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/time.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <rtc/rtc.h>
#define MAX_SESSIONS 16
#define MAX_VIEWERS 8
#define MAX_TRACKS 4
#define SDP_BUF_SIZE 16384
#define HTTP_BUF_SIZE 65536
typedef struct
{
int pc;
int tracks[MAX_TRACKS];
int track_count;
int audio_track;
int connected;
} Viewer;
typedef struct
{
char path[256];
int rtp_port;
int rtp_fd;
int audio_port;
int audio_fd;
Viewer viewers[MAX_VIEWERS];
int viewer_count;
int active;
uint32_t audio_pts;
pthread_mutex_t lock;
} Session;
typedef struct
{
Session *session;
int viewer_index;
int gathering_done;
pthread_mutex_t gather_lock;
pthread_cond_t gather_cond;
} ViewerCtx;
static Session sessions[MAX_SESSIONS];
static pthread_mutex_t sessions_lock = PTHREAD_MUTEX_INITIALIZER;
static volatile int running = 1;
static int next_rtp_port = 15000;
static Session *find_session(const char *path)
{
for (int i = 0; i < MAX_SESSIONS; i++)
{
if (sessions[i].active && strcmp(sessions[i].path, path) == 0)
{
return &sessions[i];
}
}
return NULL;
}
static double get_elapsed_seconds(struct timeval *start)
{
struct timeval now;
gettimeofday(&now, NULL);
return (now.tv_sec - start->tv_sec) + (now.tv_usec - start->tv_usec) / 1000000.0;
}
static void *rtp_receiver_thread(void *arg)
{
Session *session = (Session *)arg;
char buf[256 * 1024];
struct timeval start_time;
int started = 0;
while (running && session->active)
{
struct sockaddr_in from;
socklen_t fromlen = sizeof(from);
int n = recvfrom(session->rtp_fd, buf, sizeof(buf), 0, (struct sockaddr *)&from, &fromlen);
if (n <= 0)
{
continue;
}
if (!started)
{
gettimeofday(&start_time, NULL);
started = 1;
}
double elapsed = get_elapsed_seconds(&start_time);
uint32_t timestamp = (uint32_t)(elapsed * 90000.0);
pthread_mutex_lock(&session->lock);
for (int v = 0; v < session->viewer_count; v++)
{
Viewer *viewer = &session->viewers[v];
if (!viewer->connected)
{
continue;
}
for (int t = 0; t < viewer->track_count; t++)
{
if (!rtcIsOpen(viewer->tracks[t]))
{
continue;
}
rtcSetTrackRtpTimestamp(viewer->tracks[t], timestamp);
rtcSendMessage(viewer->tracks[t], buf, n);
}
}
pthread_mutex_unlock(&session->lock);
}
return NULL;
}
static void *audio_receiver_thread(void *arg)
{
Session *session = (Session *)arg;
char buf[4096];
while (running && session->active)
{
struct sockaddr_in from;
socklen_t fromlen = sizeof(from);
int n = recvfrom(session->audio_fd, buf, sizeof(buf), 0, (struct sockaddr *)&from, &fromlen);
if (n <= 0)
{
continue;
}
uint32_t ts = session->audio_pts;
session->audio_pts += 960;
pthread_mutex_lock(&session->lock);
for (int v = 0; v < session->viewer_count; v++)
{
Viewer *viewer = &session->viewers[v];
if (!viewer->connected || viewer->audio_track <= 0)
{
continue;
}
if (!rtcIsOpen(viewer->audio_track))
{
continue;
}
rtcSetTrackRtpTimestamp(viewer->audio_track, ts);
rtcSendMessage(viewer->audio_track, buf, n);
}
pthread_mutex_unlock(&session->lock);
}
return NULL;
}
static Session *create_session_slot(const char *path)
{
for (int i = 0; i < MAX_SESSIONS; i++)
{
if (!sessions[i].active)
{
memset(&sessions[i], 0, sizeof(Session));
strncpy(sessions[i].path, path, sizeof(sessions[i].path) - 1);
sessions[i].active = 1;
sessions[i].rtp_port = next_rtp_port++;
pthread_mutex_init(&sessions[i].lock, NULL);
int fd = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(sessions[i].rtp_port);
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
perror("bind rtp");
close(fd);
sessions[i].active = 0;
return NULL;
}
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
sessions[i].rtp_fd = fd;
sessions[i].audio_port = next_rtp_port++;
int afd = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in aaddr;
memset(&aaddr, 0, sizeof(aaddr));
aaddr.sin_family = AF_INET;
aaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
aaddr.sin_port = htons(sessions[i].audio_port);
bind(afd, (struct sockaddr *)&aaddr, sizeof(aaddr));
setsockopt(afd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
sessions[i].audio_fd = afd;
sessions[i].audio_pts = 0;
pthread_t tid;
pthread_create(&tid, NULL, rtp_receiver_thread, &sessions[i]);
pthread_detach(tid);
pthread_t atid;
pthread_create(&atid, NULL, audio_receiver_thread, &sessions[i]);
pthread_detach(atid);
return &sessions[i];
}
}
return NULL;
}
static ViewerCtx viewer_contexts[MAX_SESSIONS * MAX_VIEWERS];
static int viewer_ctx_count = 0;
static void on_viewer_state(int pc, rtcState state, void *ptr)
{
ViewerCtx *ctx = (ViewerCtx *)ptr;
if (state == RTC_CONNECTED && ctx->session)
{
pthread_mutex_lock(&ctx->session->lock);
if (ctx->viewer_index < ctx->session->viewer_count)
{
ctx->session->viewers[ctx->viewer_index].connected = 1;
}
pthread_mutex_unlock(&ctx->session->lock);
}
}
static void on_viewer_gathering(int pc, rtcGatheringState state, void *ptr)
{
ViewerCtx *ctx = (ViewerCtx *)ptr;
if (state == RTC_GATHERING_COMPLETE)
{
pthread_mutex_lock(&ctx->gather_lock);
ctx->gathering_done = 1;
pthread_cond_signal(&ctx->gather_cond);
pthread_mutex_unlock(&ctx->gather_lock);
}
}
static int create_viewer_pc(Session *session, const char *offer_sdp, char *answer_buf, int answer_size)
{
if (session->viewer_count >= MAX_VIEWERS)
{
return -1;
}
rtcConfiguration config;
memset(&config, 0, sizeof(config));
config.forceMediaTransport = true;
config.enableIceUdpMux = true;
int pc = rtcCreatePeerConnection(&config);
if (pc < 0)
{
return -1;
}
ViewerCtx *ctx = &viewer_contexts[viewer_ctx_count++];
ctx->session = session;
ctx->viewer_index = session->viewer_count;
ctx->gathering_done = 0;
pthread_mutex_init(&ctx->gather_lock, NULL);
pthread_cond_init(&ctx->gather_cond, NULL);
Viewer *viewer = &session->viewers[session->viewer_count];
viewer->pc = pc;
viewer->connected = 0;
viewer->track_count = 0;
rtcSetUserPointer(pc, ctx);
rtcSetGatheringStateChangeCallback(pc, on_viewer_gathering);
rtcSetStateChangeCallback(pc, on_viewer_state);
int video_track = rtcAddTrack(pc,
"m=video 9 UDP/TLS/RTP/SAVPF 96\r\n"
"a=rtpmap:96 VP8/90000\r\n"
"a=sendonly\r\na=mid:0\r\na=rtcp-mux\r\n");
if (video_track >= 0)
{
rtcPacketizerInit packetizer;
memset(&packetizer, 0, sizeof(packetizer));
packetizer.ssrc = 42;
packetizer.cname = "video";
packetizer.payloadType = 96;
packetizer.clockRate = 90000;
packetizer.maxFragmentSize = 1200;
rtcSetVP8Packetizer(video_track, &packetizer);
rtcChainRtcpSrReporter(video_track);
rtcChainRtcpNackResponder(video_track, 512);
viewer->tracks[viewer->track_count++] = video_track;
}
int audio_track = rtcAddTrack(pc,
"m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n"
"a=rtpmap:111 opus/48000/2\r\n"
"a=sendonly\r\na=mid:1\r\na=rtcp-mux\r\n");
if (audio_track >= 0)
{
rtcPacketizerInit audio_packetizer;
memset(&audio_packetizer, 0, sizeof(audio_packetizer));
audio_packetizer.ssrc = 43;
audio_packetizer.cname = "audio";
audio_packetizer.payloadType = 111;
audio_packetizer.clockRate = 48000;
rtcSetOpusPacketizer(audio_track, &audio_packetizer);
rtcChainRtcpSrReporter(audio_track);
viewer->audio_track = audio_track;
}
rtcSetRemoteDescription(pc, offer_sdp, "offer");
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += 5;
pthread_mutex_lock(&ctx->gather_lock);
while (!ctx->gathering_done)
{
if (pthread_cond_timedwait(&ctx->gather_cond, &ctx->gather_lock, &ts) != 0)
{
break;
}
}
pthread_mutex_unlock(&ctx->gather_lock);
int len = rtcGetLocalDescription(pc, answer_buf, answer_size);
if (len < 0)
{
return -1;
}
pthread_mutex_lock(&session->lock);
session->viewer_count++;
pthread_mutex_unlock(&session->lock);
return 0;
}
static void parse_http_request(const char *buf, int len, char *method, char *path, char *body, int *body_len)
{
method[0] = 0;
path[0] = 0;
body[0] = 0;
*body_len = 0;
sscanf(buf, "%15s %255s", method, path);
const char *body_start = strstr(buf, "\r\n\r\n");
if (body_start)
{
body_start += 4;
*body_len = len - (body_start - buf);
if (*body_len > 0)
{
memcpy(body, body_start, *body_len);
body[*body_len] = 0;
}
}
}
static void send_http_response(int fd, int status, const char *content_type, const char *body, int body_len)
{
char header[1024];
const char *status_text = status == 201 ? "Created" : status == 200 ? "OK" : "Not Found";
int hlen = snprintf(header, sizeof(header),
"HTTP/1.1 %d %s\r\n"
"Content-Type: %s\r\n"
"Content-Length: %d\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Access-Control-Allow-Methods: POST, DELETE, OPTIONS, GET\r\n"
"Access-Control-Allow-Headers: Content-Type\r\n"
"Connection: close\r\n"
"\r\n",
status, status_text, content_type, body_len);
write(fd, header, hlen);
if (body_len > 0)
{
write(fd, body, body_len);
}
}
static void handle_client(int client_fd)
{
char buf[HTTP_BUF_SIZE];
int total = 0;
int n;
while (total < HTTP_BUF_SIZE - 1)
{
n = read(client_fd, buf + total, HTTP_BUF_SIZE - 1 - total);
if (n <= 0)
{
break;
}
total += n;
if (strstr(buf, "\r\n\r\n"))
{
int content_length = 0;
char *cl = strstr(buf, "Content-Length:");
if (!cl)
{
cl = strstr(buf, "content-length:");
}
if (cl)
{
content_length = atoi(cl + 15);
}
char *body_start = strstr(buf, "\r\n\r\n") + 4;
int header_len = body_start - buf;
int body_so_far = total - header_len;
while (body_so_far < content_length && total < HTTP_BUF_SIZE - 1)
{
n = read(client_fd, buf + total, HTTP_BUF_SIZE - 1 - total);
if (n <= 0)
{
break;
}
total += n;
body_so_far = total - header_len;
}
break;
}
}
buf[total] = 0;
char method[16], path[256], body[SDP_BUF_SIZE];
int body_len;
parse_http_request(buf, total, method, path, body, &body_len);
if (strcmp(method, "OPTIONS") == 0)
{
send_http_response(client_fd, 200, "text/plain", "", 0);
close(client_fd);
return;
}
if (strcmp(method, "GET") == 0 && strcmp(path, "/health") == 0)
{
send_http_response(client_fd, 200, "text/plain", "ok", 2);
close(client_fd);
return;
}
if (strcmp(method, "POST") == 0 && strstr(path, "/create"))
{
char stream_path[256];
strncpy(stream_path, path + 1, sizeof(stream_path) - 1);
char *create_pos = strstr(stream_path, "/create");
if (create_pos)
{
*create_pos = 0;
}
pthread_mutex_lock(&sessions_lock);
Session *session = find_session(stream_path);
if (!session)
{
session = create_session_slot(stream_path);
}
pthread_mutex_unlock(&sessions_lock);
if (session)
{
char port_str[64];
snprintf(port_str, sizeof(port_str), "%d,%d", session->rtp_port, session->audio_port);
send_http_response(client_fd, 200, "text/plain", port_str, strlen(port_str));
}
else
{
send_http_response(client_fd, 500, "text/plain", "failed", 6);
}
close(client_fd);
return;
}
if (strcmp(method, "GET") == 0 && strncmp(path, "/session/", 9) == 0)
{
const char *check_path = path + 9;
pthread_mutex_lock(&sessions_lock);
Session *s = find_session(check_path);
pthread_mutex_unlock(&sessions_lock);
if (s)
{
send_http_response(client_fd, 200, "text/plain", "ok", 2);
}
else
{
send_http_response(client_fd, 404, "text/plain", "no", 2);
}
close(client_fd);
return;
}
if (strcmp(method, "POST") != 0 || !strstr(path, "/whep"))
{
send_http_response(client_fd, 404, "text/plain", "not found", 9);
close(client_fd);
return;
}
char stream_path[256];
char *whep_pos = strstr(path + 1, "/whep");
int plen = whep_pos - path - 1;
strncpy(stream_path, path + 1, plen);
stream_path[plen] = 0;
char answer[SDP_BUF_SIZE];
pthread_mutex_lock(&sessions_lock);
Session *session = find_session(stream_path);
pthread_mutex_unlock(&sessions_lock);
if (!session)
{
send_http_response(client_fd, 404, "text/plain", "no session", 10);
close(client_fd);
return;
}
int rc = create_viewer_pc(session, body, answer, SDP_BUF_SIZE);
if (rc < 0)
{
send_http_response(client_fd, 500, "text/plain", "failed", 6);
}
else
{
send_http_response(client_fd, 201, "application/sdp", answer, strlen(answer));
}
close(client_fd);
}
static void signal_handler(int sig)
{
running = 0;
}
int main(int argc, char *argv[])
{
int port = 8891;
if (argc > 1)
{
port = atoi(argv[1]);
}
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
rtcInitLogger(RTC_LOG_WARNING, NULL);
memset(sessions, 0, sizeof(sessions));
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0)
{
perror("socket");
return 1;
}
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
perror("bind");
close(server_fd);
return 1;
}
if (listen(server_fd, 16) < 0)
{
perror("listen");
close(server_fd);
return 1;
}
fprintf(stderr, "whip_relay listening on port %d\n", port);
while (running)
{
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd < 0)
{
continue;
}
handle_client(client_fd);
}
close(server_fd);
rtcCleanup();
return 0;
}