diff --git a/docs/en/guide/url-formats.md b/docs/en/guide/url-formats.md index 9da0d268..a5ba3228 100644 --- a/docs/en/guide/url-formats.md +++ b/docs/en/guide/url-formats.md @@ -12,7 +12,7 @@ When `r2h-token` (HTTP request authentication token) is configured, all URLs mus ## Multicast RTP to HTTP Unicast Stream ```url -http://server:port/rtp/multicast_address:port[?fcc=FCC_server:port][&fcc-type=protocol_type][&fec=FEC_port] +http://server:port/rtp/multicast_address:port[?fcc=FCC_server:port][&fcc-type=protocol_type][&fec=FEC_port][&r2h-ifname=network_interface][&r2h-ifname-fcc=fcc_network_interface] ``` **Examples**: @@ -22,6 +22,8 @@ http://192.168.1.1:5140/rtp/239.253.64.120:5140 http://192.168.1.1:5140/rtp/239.253.64.120:5140?fcc=10.255.14.152:15970 http://192.168.1.1:5140/rtp/239.253.64.120:5140?fcc=10.255.14.152:8027&fcc-type=huawei http://192.168.1.1:5140/rtp/239.81.0.195:4056?fec=4055 +http://192.168.1.1:5140/rtp/239.253.64.120:5140?r2h-ifname=eth1 +http://192.168.1.1:5140/rtp/239.253.64.120:5140?fcc=10.255.14.152:15970&r2h-ifname=eth0&r2h-ifname-fcc=eth1 ``` ### Parameters @@ -33,6 +35,8 @@ http://192.168.1.1:5140/rtp/239.81.0.195:4056?fec=4055 - `telecom`: Telecom/ZTE/Fiberhome FCC protocol (default) - `huawei`: Huawei FCC protocol - **fec** (optional): FEC (Forward Error Correction) port number, used to receive FEC redundant packets for packet loss recovery +- **r2h-ifname** (optional): Specify the network interface name to receive multicast streams (e.g., `eth0`, `eth1`). Takes priority over the global `upstream-interface-multicast` configuration +- **r2h-ifname-fcc** (optional): Specify the network interface name for FCC. Only effective when using FCC, takes priority over the global `upstream-interface-fcc` configuration ### Use Cases @@ -40,6 +44,8 @@ http://192.168.1.1:5140/rtp/239.81.0.195:4056?fec=4055 - Share IPTV streams across multiple devices on LAN - Enable millisecond-level channel switching with FCC - Improve playback stability with FEC packet loss recovery +- Use the `r2h-ifname` parameter to route different channels through different network interfaces (suitable for multi-NIC environments, such as receiving multicast streams from different operators on different interfaces) +- Use the `r2h-ifname-fcc` parameter to specify a separate network interface for FCC (separate multicast reception from FCC communication in multi-NIC environments) ## RTSP to HTTP @@ -61,8 +67,15 @@ http://192.168.1.1:5140/rtsp/iptv.example.com:554/channel1?tvdr=20240101120000GM # Custom time-shift parameter name + time offset http://192.168.1.1:5140/rtsp/iptv.example.com:554/channel1?seek=20240101120000&r2h-seek-name=seek&r2h-seek-offset=3600 + +# Specify network interface +http://192.168.1.1:5140/rtsp/iptv.example.com:554/channel1?r2h-ifname=eth1 ``` +### Parameters + +- **r2h-ifname** (optional): Specify the network interface name for RTSP connections. Takes priority over the global `upstream-interface-rtsp` configuration + ### Use Cases - Convert IPTV RTSP unicast streams to HTTP streams @@ -94,6 +107,9 @@ http://192.168.1.1:5140/http/upstream.example.com:8080/live/stream.m3u8 # Proxy HTTP request (port omitted, defaults to 80) http://192.168.1.1:5140/http/api.example.com/video?auth=xxx&quality=hd + +# Specify network interface +http://192.168.1.1:5140/http/upstream.example.com:8080/live/stream.m3u8?r2h-ifname=eth1 ``` ### Parameters @@ -101,6 +117,7 @@ http://192.168.1.1:5140/http/api.example.com/video?auth=xxx&quality=hd - **upstream_server**: Target HTTP server address - **port** (optional): Target server port, defaults to 80 - **path**: Request path, including query parameters +- **r2h-ifname** (optional): Specify the network interface name for HTTP connections. Takes priority over the global `upstream-interface-http` configuration ### Use Cases diff --git a/docs/guide/url-formats.md b/docs/guide/url-formats.md index f15aa7a2..b826f287 100644 --- a/docs/guide/url-formats.md +++ b/docs/guide/url-formats.md @@ -12,7 +12,7 @@ rtp2httpd 支持多种流媒体协议,通过不同的 URL 前缀进行区分 ## 组播 RTP 转 HTTP 单播流 ```url -http://服务器地址:端口/rtp/组播地址:端口[?fcc=FCC服务器:端口][&fcc-type=协议类型][&fec=FEC端口] +http://服务器地址:端口/rtp/组播地址:端口[?fcc=FCC服务器:端口][&fcc-type=协议类型][&fec=FEC端口][&r2h-ifname=网络接口][&r2h-ifname-fcc=FCC网络接口] ``` **示例**: @@ -22,6 +22,8 @@ http://192.168.1.1:5140/rtp/239.253.64.120:5140 http://192.168.1.1:5140/rtp/239.253.64.120:5140?fcc=10.255.14.152:15970 http://192.168.1.1:5140/rtp/239.253.64.120:5140?fcc=10.255.14.152:8027&fcc-type=huawei http://192.168.1.1:5140/rtp/239.81.0.195:4056?fec=4055 +http://192.168.1.1:5140/rtp/239.253.64.120:5140?r2h-ifname=eth1 +http://192.168.1.1:5140/rtp/239.253.64.120:5140?fcc=10.255.14.152:15970&r2h-ifname=eth0&r2h-ifname-fcc=eth1 ``` ### 参数说明 @@ -33,6 +35,8 @@ http://192.168.1.1:5140/rtp/239.81.0.195:4056?fec=4055 - `telecom`:电信/中兴/烽火 FCC 协议(默认) - `huawei`:华为 FCC 协议 - **fec**(可选):FEC 前向纠错端口号,用于接收 FEC 冗余数据包来恢复丢包 +- **r2h-ifname**(可选):指定用于接收组播流的网络接口名称(如 `eth0`, `eth1` 等)。优先级高于全局配置 `upstream-interface-multicast` +- **r2h-ifname-fcc**(可选):指定用于 FCC 的网络接口名称。仅在使用 FCC 时有效,优先级高于全局配置 `upstream-interface-fcc` ### 使用场景 @@ -40,6 +44,8 @@ http://192.168.1.1:5140/rtp/239.81.0.195:4056?fec=4055 - 在局域网内多设备共享 IPTV 流 - 配合 FCC 实现毫秒级换台 - 配合 FEC 实现丢包恢复,提高播放稳定性 +- 通过 `r2h-ifname` 参数指定不同频道使用不同网络接口(适用于多网卡环境,如不同接口接收不同运营商的组播流) +- 使用 `r2h-ifname-fcc` 参数为 FCC 指定独立的网络接口(在多网卡环境下分离组播接收和 FCC 通信) ## RTSP 转 HTTP @@ -61,8 +67,15 @@ http://192.168.1.1:5140/rtsp/iptv.example.com:554/channel1?tvdr=20240101120000GM # 自定义时移参数名 + 时间偏移 http://192.168.1.1:5140/rtsp/iptv.example.com:554/channel1?seek=20240101120000&r2h-seek-name=seek&r2h-seek-offset=3600 + +# 指定网络接口 +http://192.168.1.1:5140/rtsp/iptv.example.com:554/channel1?r2h-ifname=eth1 ``` +### 参数说明 + +- **r2h-ifname**(可选):指定用于 RTSP 连接的网络接口名称。优先级高于全局配置 `upstream-interface-rtsp` + ### 使用场景 - 将 IPTV RTSP 单播流转换为 HTTP 流 @@ -94,6 +107,9 @@ http://192.168.1.1:5140/http/upstream.example.com:8080/live/stream.m3u8 # 代理 HTTP 请求(省略端口,默认 80) http://192.168.1.1:5140/http/api.example.com/video?auth=xxx&quality=hd + +# 指定网络接口 +http://192.168.1.1:5140/http/upstream.example.com:8080/live/stream.m3u8?r2h-ifname=eth1 ``` ### 参数说明 @@ -101,6 +117,7 @@ http://192.168.1.1:5140/http/api.example.com/video?auth=xxx&quality=hd - **上游服务器**:目标 HTTP 服务器地址 - **端口**(可选):目标服务器端口,默认 80 - **路径**:请求路径,包括查询参数 +- **r2h-ifname**(可选):指定用于 HTTP 连接的网络接口名称。优先级高于全局配置 `upstream-interface-http` ### 使用场景 diff --git a/src/fcc.c b/src/fcc.c index a73eb996..e0de199d 100644 --- a/src/fcc.c +++ b/src/fcc.c @@ -366,7 +366,12 @@ int fcc_initialize_and_request(stream_context_t *ctx) { config.udp_rcvbuf_size, strerror(errno)); } - upstream_if = get_upstream_interface_for_fcc(); + /* Use per-service FCC interface if specified, otherwise use global config */ + if (service->ifname_fcc && service->ifname_fcc[0] != '\0') { + upstream_if = service->ifname_fcc; + } else { + upstream_if = get_upstream_interface_for_fcc(); + } bind_to_upstream_interface(fcc->fcc_sock, upstream_if); /* Bind to configured or ephemeral port */ diff --git a/src/http_proxy.c b/src/http_proxy.c index 0840f328..30dd1583 100644 --- a/src/http_proxy.c +++ b/src/http_proxy.c @@ -250,7 +250,7 @@ void http_proxy_set_request_headers(http_proxy_session_t *session, } } -int http_proxy_connect(http_proxy_session_t *session) { +int http_proxy_connect(http_proxy_session_t *session, service_t *service) { struct sockaddr_in server_addr; struct hostent *he; int connect_result; @@ -294,7 +294,12 @@ int http_proxy_connect(http_proxy_session_t *session) { } /* Bind to upstream interface if configured */ - upstream_if = get_upstream_interface_for_http(); + /* Use per-service interface if specified, otherwise use global config */ + if (service && service->ifname && service->ifname[0] != '\0') { + upstream_if = service->ifname; + } else { + upstream_if = get_upstream_interface_for_http(); + } bind_to_upstream_interface(session->socket, upstream_if); /* Connect to server (non-blocking) */ diff --git a/src/http_proxy.h b/src/http_proxy.h index 1b4641fb..daeb2a10 100644 --- a/src/http_proxy.h +++ b/src/http_proxy.h @@ -4,8 +4,9 @@ #include #include -/* Forward declaration */ +/* Forward declarations */ struct connection_s; +typedef struct service_s service_t; /* ========== HTTP PROXY BUFFER SIZE CONFIGURATION ========== */ @@ -163,9 +164,10 @@ void http_proxy_set_request_headers(http_proxy_session_t *session, /** * Connect to upstream HTTP server (non-blocking) * @param session HTTP proxy session (must have epoll_fd set) + * @param service Service structure containing interface configuration (can be NULL) * @return 0 on success (connection in progress), -1 on error */ -int http_proxy_connect(http_proxy_session_t *session); +int http_proxy_connect(http_proxy_session_t *session, service_t *service); /** * Handle socket events (readable/writable) for async I/O state machine diff --git a/src/multicast.c b/src/multicast.c index 721aaa51..dd178c12 100644 --- a/src/multicast.c +++ b/src/multicast.c @@ -151,7 +151,13 @@ static int prepare_mcast_group_req(service_t *service, struct group_req *gr, return -1; } - upstream_if = get_upstream_interface_for_multicast(); + /* Use per-service interface if specified, otherwise use global config */ + if (service->ifname && service->ifname[0] != '\0') { + upstream_if = service->ifname; + } else { + upstream_if = get_upstream_interface_for_multicast(); + } + if (upstream_if && upstream_if[0] != '\0') { gr->gr_interface = if_nametoindex(upstream_if); } @@ -240,8 +246,12 @@ static int join_mcast_group(service_t *service, int is_fec) { strerror(errno)); } - /* Determine which interface to use */ - upstream_if = get_upstream_interface_for_multicast(); + /* Use per-service interface if specified, otherwise use global config */ + if (service->ifname && service->ifname[0] != '\0') { + upstream_if = service->ifname; + } else { + upstream_if = get_upstream_interface_for_multicast(); + } bind_to_upstream_interface(sock, upstream_if); /* Prepare bind address with appropriate port */ diff --git a/src/rtp2httpd b/src/rtp2httpd new file mode 100755 index 00000000..7b1aa363 Binary files /dev/null and b/src/rtp2httpd differ diff --git a/src/rtsp.c b/src/rtsp.c index b971f975..59c3fc80 100644 --- a/src/rtsp.c +++ b/src/rtsp.c @@ -782,7 +782,12 @@ int rtsp_connect(rtsp_session_t *session) { return -1; } - upstream_if = get_upstream_interface_for_rtsp(); + /* Use per-service interface if specified, otherwise use global config */ + if (session->service && session->service->ifname && session->service->ifname[0] != '\0') { + upstream_if = session->service->ifname; + } else { + upstream_if = get_upstream_interface_for_rtsp(); + } bind_to_upstream_interface(session->socket, upstream_if); /* Connect to server (non-blocking) */ @@ -2449,7 +2454,12 @@ static int rtsp_setup_udp_sockets(rtsp_session_t *session) { logger(LOG_DEBUG, "RTSP: Setting up UDP sockets"); - upstream_if = get_upstream_interface_for_rtsp(); + /* Use per-service interface if specified, otherwise use global config */ + if (session->service && session->service->ifname && session->service->ifname[0] != '\0') { + upstream_if = session->service->ifname; + } else { + upstream_if = get_upstream_interface_for_rtsp(); + } session->local_rtp_port = 0; session->local_rtcp_port = 0; diff --git a/src/rtsp.h b/src/rtsp.h index 72e9f163..e91eba64 100644 --- a/src/rtsp.h +++ b/src/rtsp.h @@ -6,6 +6,9 @@ #include "stun.h" +/* Forward declarations */ +typedef struct service_s service_t; + #define RTSP_DISABLE_TCP_TRANSPORT 0 /* To debug UDP transport, set to 1 */ /* ========== RTSP BUFFER SIZE CONFIGURATION ========== */ @@ -111,6 +114,7 @@ typedef struct { int socket; /* TCP socket to RTSP server */ int epoll_fd; /* Epoll file descriptor for socket registration */ struct connection_s *conn; /* Connection pointer for fdmap registration */ + service_t *service; /* Service pointer for accessing per-service configuration */ rtsp_state_t state; /* Current RTSP state */ int status_index; /* Index in status_shared->clients array for state updates */ diff --git a/src/service.c b/src/service.c index 58ea724d..cbf0e102 100644 --- a/src/service.c +++ b/src/service.c @@ -29,6 +29,8 @@ struct rtp_url_components { fcc_type_t fcc_type; /* FCC protocol type */ int fcc_type_explicit; /* 1 if fcc-type was explicitly set via query param */ uint16_t fec_port; /* FEC multicast port (0 if not configured) */ + char ifname[IFNAMSIZ]; /* Network interface name (from ?r2h-ifname= query param) */ + char ifname_fcc[IFNAMSIZ]; /* Network interface name for FCC (from ?r2h-ifname-fcc= query param) */ }; /* Service lookup hashmap for O(1) service lookup by URL */ @@ -166,6 +168,26 @@ static int parse_rtp_url_components(char *url_part, } } } + + /* Parse r2h-ifname parameter from query string */ + char ifname_value[IFNAMSIZ]; + if (http_parse_query_param(query_start, "r2h-ifname", ifname_value, + sizeof(ifname_value)) == 0) { + if (ifname_value[0] != '\0') { + strncpy(components->ifname, ifname_value, IFNAMSIZ - 1); + components->ifname[IFNAMSIZ - 1] = '\0'; + } + } + + /* Parse r2h-ifname-fcc parameter from query string */ + char ifname_fcc_value[IFNAMSIZ]; + if (http_parse_query_param(query_start, "r2h-ifname-fcc", ifname_fcc_value, + sizeof(ifname_fcc_value)) == 0) { + if (ifname_fcc_value[0] != '\0') { + strncpy(components->ifname_fcc, ifname_fcc_value, IFNAMSIZ - 1); + components->ifname_fcc[IFNAMSIZ - 1] = '\0'; + } + } } /* Remove trailing slash from main part if present (e.g., @@ -777,9 +799,27 @@ service_t *service_create_from_http_url(const char *http_url) { /* Extract seek parameters from HTTP URL (same as RTSP) */ char *query_start = strchr(result->http_url, '?'); if (query_start) { + /* Extract r2h-ifname parameter */ + char ifname_value[IFNAMSIZ] = {0}; + http_parse_query_param(query_start + 1, "r2h-ifname", ifname_value, + sizeof(ifname_value)); + service_extract_seek_params(query_start, &result->seek_param_name, &result->seek_param_value, &result->seek_offset_seconds); + + /* Store interface name if specified */ + if (ifname_value[0] != '\0') { + result->ifname = strdup(ifname_value); + if (!result->ifname) { + logger(LOG_ERROR, "Failed to allocate memory for interface name"); + free(result->url); + free(result->http_url); + free(result); + return NULL; + } + logger(LOG_DEBUG, "HTTP proxy service will use interface: %s", result->ifname); + } } logger(LOG_DEBUG, "Created HTTP proxy service: %s -> %s", http_url, @@ -834,6 +874,7 @@ service_t *service_create_from_rtsp_url(const char *http_url) { char *seek_param_name = NULL; char *seek_param_value = NULL; int seek_offset_seconds = 0; + char ifname_value[IFNAMSIZ] = {0}; /* Validate input */ if (!http_url || strlen(http_url) >= sizeof(working_url)) { @@ -865,6 +906,10 @@ service_t *service_create_from_rtsp_url(const char *http_url) { /* Extract seek parameters from query string (modifies url_part in-place) */ query_start = strchr(url_part, '?'); if (query_start) { + /* Extract r2h-ifname parameter before processing seek params */ + http_parse_query_param(query_start + 1, "r2h-ifname", ifname_value, + sizeof(ifname_value)); + if (service_extract_seek_params(query_start, &seek_param_name, &seek_param_value, &seek_offset_seconds) < 0) { @@ -901,6 +946,18 @@ service_t *service_create_from_rtsp_url(const char *http_url) { seek_param_value = NULL; /* Transfer ownership */ result->seek_offset_seconds = seek_offset_seconds; + /* Store interface name if specified */ + if (ifname_value[0] != '\0') { + result->ifname = strdup(ifname_value); + if (!result->ifname) { + logger(LOG_ERROR, "Failed to allocate memory for interface name"); + goto cleanup; + } + logger(LOG_DEBUG, "RTSP service will use interface: %s", result->ifname); + } else { + result->ifname = NULL; + } + result->url = strdup(http_url); if (!result->url) { logger(LOG_ERROR, "Failed to allocate memory for HTTP URL"); @@ -1336,6 +1393,32 @@ service_t *service_create_from_rtp_url(const char *http_url) { if (fcc_res) freeaddrinfo(fcc_res); + /* Store interface name if specified */ + if (components.ifname[0] != '\0') { + result->ifname = strdup(components.ifname); + if (!result->ifname) { + logger(LOG_ERROR, "Failed to allocate memory for interface name"); + service_free(result); + return NULL; + } + logger(LOG_DEBUG, "RTP service will use interface: %s", result->ifname); + } else { + result->ifname = NULL; + } + + /* Store FCC interface name if specified */ + if (components.ifname_fcc[0] != '\0') { + result->ifname_fcc = strdup(components.ifname_fcc); + if (!result->ifname_fcc) { + logger(LOG_ERROR, "Failed to allocate memory for FCC interface name"); + service_free(result); + return NULL; + } + logger(LOG_DEBUG, "RTP service will use FCC interface: %s", result->ifname_fcc); + } else { + result->ifname_fcc = NULL; + } + /* Store original URL for reference */ result->url = strdup(http_url); if (!result->url) { @@ -1466,6 +1549,20 @@ service_t *service_clone(service_t *service) { } } + if (service->ifname) { + cloned->ifname = strdup(service->ifname); + if (!cloned->ifname) { + goto cleanup_error; + } + } + + if (service->ifname_fcc) { + cloned->ifname_fcc = strdup(service->ifname_fcc); + if (!cloned->ifname_fcc) { + goto cleanup_error; + } + } + /* Clone addrinfo structures */ if (service->addr) { cloned->addr = clone_addrinfo(service->addr); @@ -1544,6 +1641,16 @@ void service_free(service_t *service) { service->user_agent = NULL; } + if (service->ifname) { + free(service->ifname); + service->ifname = NULL; + } + + if (service->ifname_fcc) { + free(service->ifname_fcc); + service->ifname_fcc = NULL; + } + /* Free common fields */ if (service->url) { free(service->url); diff --git a/src/service.h b/src/service.h index 7502cd16..31d4b38a 100644 --- a/src/service.h +++ b/src/service.h @@ -63,6 +63,8 @@ typedef struct service_s { int seek_offset_seconds; /* Additional offset in seconds from r2h-seek-offset parameter */ char *user_agent; /* User-Agent header for timezone detection */ + char *ifname; /* Network interface name for multicast (from ?r2h-ifname= query param) */ + char *ifname_fcc; /* Network interface name for FCC (from ?r2h-ifname-fcc= query param) */ struct service_s *next; } service_t; diff --git a/src/stream.c b/src/stream.c index d18e73fc..b5bb313b 100644 --- a/src/stream.c +++ b/src/stream.c @@ -234,7 +234,7 @@ int stream_context_init_for_worker(stream_context_t *ctx, connection_t *conn, conn->http_req.x_forwarded_proto); /* Initiate connection */ - if (http_proxy_connect(&ctx->http_proxy) < 0) { + if (http_proxy_connect(&ctx->http_proxy, ctx->service) < 0) { logger(LOG_ERROR, "HTTP Proxy: Failed to initiate connection"); return -1; } @@ -268,6 +268,7 @@ int stream_context_init_for_worker(stream_context_t *ctx, connection_t *conn, ctx->rtsp.status_index = status_index; ctx->rtsp.epoll_fd = ctx->epoll_fd; ctx->rtsp.conn = conn; + ctx->rtsp.service = service; if (!service->rtsp_url) { logger(LOG_ERROR, "RTSP URL not found in service configuration"); return -1;