[+] 分辨率调整

This commit is contained in:
acgist
2023-02-14 20:42:02 +08:00
parent 778b933000
commit 948b628937
14 changed files with 266 additions and 128 deletions

View File

@@ -8,7 +8,6 @@
|:--|:--|:--| |:--|:--|:--|
|taoyao-client-web|终端示例|Web终端示例| |taoyao-client-web|终端示例|Web终端示例|
|taoyao-client-android|终端示例|安卓终端示例| |taoyao-client-android|终端示例|安卓终端示例|
|taoyao-client-desktop|终端示例|桌面终端示例|
|taoyao-media-server|媒体服务|Mediasoup媒体服务| |taoyao-media-server|媒体服务|Mediasoup媒体服务|
|taoyao-signal-server|信令服务|信令业务逻辑| |taoyao-signal-server|信令服务|信令业务逻辑|

View File

@@ -336,7 +336,7 @@ firewall-cmd --zone=public --add-port=9999/tcp --permanent
# 信令服务WebSocket # 信令服务WebSocket
firewall-cmd --zone=public --add-port=8888/tcp --permanent firewall-cmd --zone=public --add-port=8888/tcp --permanent
# 媒体服务(控制):只暴露给信令服务 # 媒体服务(控制):只暴露给信令服务
firewall-cmd --permanent --add-rich-rule="rule family="ipv4" source address="192.168.1.0/24" port protocol="tcp" port="4443" accept" firewall-cmd --zone=public --add-rich-rule="rule family="ipv4" source address="192.168.1.0/24" port protocol="tcp" port="4443" accept" --permanent
# 媒体服务(数据) # 媒体服务(数据)
firewall-cmd --zone=public --add-port=40000-49999/udp --permanent firewall-cmd --zone=public --add-port=40000-49999/udp --permanent
@@ -345,7 +345,7 @@ firewall-cmd --list-ports
# 删除端口 # 删除端口
#firewall-cmd --zone=public --remove-port=8443/tcp --permanent #firewall-cmd --zone=public --remove-port=8443/tcp --permanent
firewall-cmd --permanent --remove-rich-rule="rule family="ipv4" source address="192.168.1.0/24" port protocol="tcp" port="4443" accept" #firewall-cmd --zone=public --remove-rich-rule="rule family="ipv4" source address="192.168.1.0/24" port protocol="tcp" port="4443" accept" --permanent
#firewall-cmd --zone=public --remove-port=9999/tcp --permanent #firewall-cmd --zone=public --remove-port=9999/tcp --permanent
#firewall-cmd --zone=public --remove-port=8888/tcp --permanent #firewall-cmd --zone=public --remove-port=8888/tcp --permanent
#firewall-cmd --zone=public --remove-port=40000-49999/udp --permanent #firewall-cmd --zone=public --remove-port=40000-49999/udp --permanent

View File

@@ -31,26 +31,31 @@ public class MediaProperties {
*/ */
@Schema(title = "视频配置", description = "视频配置") @Schema(title = "视频配置", description = "视频配置")
private MediaVideoProperties video; private MediaVideoProperties video;
/**
* 4K视频
*/
@Schema(title = "4K视频", description = "4K视频")
private MediaVideoProperties udVideo;
/**
* 2K视频
*/
@Schema(title = "2K视频", description = "2K视频")
private MediaVideoProperties qdVideo;
/** /**
* 超清视频 * 超清视频
*/ */
@Schema(title = "超清视频", description = "超清视频") @Schema(title = "超清视频", description = "超清视频")
private MediaVideoProperties mostVideo; private MediaVideoProperties fdVideo;
/** /**
* 高清视频 * 高清视频
*/ */
@Schema(title = "高清视频", description = "高清视频") @Schema(title = "高清视频", description = "高清视频")
private MediaVideoProperties highVideo; private MediaVideoProperties hdVideo;
/** /**
* 标清视频 * 标清视频
*/ */
@Schema(title = "标清视频", description = "标清视频") @Schema(title = "标清视频", description = "标清视频")
private MediaVideoProperties normVideo; private MediaVideoProperties sdVideo;
/**
* 流畅视频
*/
@Schema(title = "流畅视频", description = "流畅视频")
private MediaVideoProperties flowVideo;
/** /**
* 媒体服务配置 * 媒体服务配置
*/ */

View File

@@ -58,7 +58,7 @@ public class MediaVideoProperties {
/** /**
* 分辨率(画面大小) * 分辨率(画面大小)
*/ */
@Schema(title = "分辨率", description = "分辨率影响画面大小", example = "1920*1080|1280*720|480*360") @Schema(title = "分辨率", description = "分辨率影响画面大小", example = "1920*1080|1280*720")
private String resolution; private String resolution;
/** /**
* 宽度 * 宽度

View File

@@ -21,11 +21,11 @@ public class WebrtcProperties {
* STUN服务器 * STUN服务器
*/ */
@Schema(title = "STUN服务器", description = "STUN服务器") @Schema(title = "STUN服务器", description = "STUN服务器")
private String[] stun; private WebrtcStunProperties[] stun;
/** /**
* TURN服务器 * TURN服务器
*/ */
@Schema(title = "TURN服务器", description = "TURN服务器") @Schema(title = "TURN服务器", description = "TURN服务器")
private String[] turn; private WebrtcTurnProperties[] turn;
} }

View File

@@ -0,0 +1,36 @@
package com.acgist.taoyao.boot.property;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* WebRTC STUN配置
*
* @author acgist
*/
@Getter
@Setter
@Schema(title = "WebRTC STUN配置", description = "WebRTC STUN配置")
public class WebrtcStunProperties {
/**
* 主机
*/
@Schema(title = "主机", description = "主机")
protected String host;
/**
* 端口
*/
@Schema(title = "端口", description = "端口")
protected Integer port;
/**
* @return 完整地址
*/
@Schema(title = "完整地址", description = "完整地址")
public String getAddress() {
return "stun://" + this.host + ":" + this.port;
}
}

View File

@@ -0,0 +1,36 @@
package com.acgist.taoyao.boot.property;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* WebRTC TURN配置
*
* @author acgist
*/
@Getter
@Setter
@Schema(title = "WebRTC TURN配置", description = "WebRTC TURN配置")
public class WebrtcTurnProperties extends WebrtcStunProperties {
/**
* 帐号
*/
@Schema(title = "帐号", description = "帐号")
private String username;
/**
* 密码
*/
@Schema(title = "密码", description = "密码")
private String password;
/**
* @return 完整地址
*/
@Schema(title = "完整地址", description = "完整地址")
public String getAddress() {
return "turn://" + this.host + ":" + this.port;
}
}

View File

@@ -0,0 +1,91 @@
package com.acgist.taoyao.boot.utils;
import java.net.http.HttpClient;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509TrustManager;
import lombok.extern.slf4j.Slf4j;
/**
* HTTP工具
*
* @author acgist
*/
@Slf4j
public final class HTTPUtils {
private HTTPUtils() {
}
/**
* @return HTTPClient
*/
public static final HttpClient newClient() {
return HttpClient
.newBuilder()
.sslContext(buildSSLContext())
.build();
}
/**
* SSLContext
*
* @return {@link SSLContext}
*/
private static final SSLContext buildSSLContext() {
try {
// SSL协议SSL、SSLv2、SSLv3、TLS、TLSv1、TLSv1.1、TLSv1.2、TLSv1.3
final SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, new X509TrustManager[] { TaoyaoTrustManager.INSTANCE }, new SecureRandom());
return sslContext;
} catch (KeyManagementException | NoSuchAlgorithmException e) {
log.error("新建SSLContext异常", e);
}
try {
return SSLContext.getDefault();
} catch (NoSuchAlgorithmException e) {
log.error("新建SSLContext异常", e);
}
return null;
}
/**
* 证书验证
*
* @author acgist
*/
public static class TaoyaoTrustManager implements X509TrustManager {
private static final TaoyaoTrustManager INSTANCE = new TaoyaoTrustManager();
private TaoyaoTrustManager() {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
if(chain == null) {
throw new CertificateException("证书验证失败");
}
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
if(chain == null) {
throw new CertificateException("证书验证失败");
}
}
}
}

View File

@@ -1,8 +0,0 @@
taoyao:
script:
media-reboot: pm2 restart taoyao-media-server
media-shutdown: pm2 stop taoyao-media-server
system-reboot: shutdown -s -t 0
system-shutdown: shutdown -r -t 0
platform-reboot: net stop taoyao-signal-server & net start taoyao-signal-server
platform-shutdown: net stop taoyao-signal-server

View File

@@ -20,7 +20,7 @@ server:
# context-path: /taoyao # context-path: /taoyao
spring: spring:
profiles: profiles:
active: local active: dev
application: application:
name: taoyao-signal-server name: taoyao-signal-server
servlet: servlet:
@@ -68,30 +68,36 @@ taoyao:
bitrate: 1200 bitrate: 1200
frame-rate: 24 frame-rate: 24
resolution: 1920*1080 resolution: 1920*1080
# 超清视频 # 4KUD=UHD=4K
most-video: ud-video:
format: H264
bitrate: 1600
frame-rate: 30
resolution: 4096*2160
# 2KQD=QHD=2K
qd-video:
format: H264
bitrate: 1600
frame-rate: 30
resolution: 2560*1440
# 超清视频FD=FHD=1080P
fd-video:
format: H264 format: H264
bitrate: 1200 bitrate: 1200
frame-rate: 24 frame-rate: 24
resolution: 1920*1080 resolution: 1920*1080
# 高清视频 # 高清视频HD=720P
high-video: hd-video:
format: H264 format: H264
bitrate: 1000 bitrate: 1000
frame-rate: 18 frame-rate: 18
resolution: 1280*720 resolution: 1280*720
# 标清视频 # 标清视频SD=480P
norm-video: sd-video:
format: H264 format: H264
bitrate: 800 bitrate: 800
frame-rate: 16 frame-rate: 16
resolution: 720*480 resolution: 720*480
# 流畅视频
flow-video:
format: H264
bitrate: 600
frame-rate: 16
resolution: 640*480
# 媒体服务配置 # 媒体服务配置
media-server-list: media-server-list:
- name: media-local-a - name: media-local-a
@@ -120,22 +126,26 @@ taoyao:
thread-min: 4 thread-min: 4
thread-max: 128 thread-max: 128
thread-name-prefix: ${spring.application.name}-signal- thread-name-prefix: ${spring.application.name}-signal-
keep-alive-time: 60 keep-alive-time: 60000
buffer-size: 2048 buffer-size: 2048
# WebRTC配置 # WebRTC配置没有P2P所以不会用到
webrtc: webrtc:
# STUN服务 # STUN服务
stun: stun:
- stun:stun1.l.google.com:19302 - host: stun1.l.google.com
- stun:stun2.l.google.com:19302 port: 19302
- stun:stun3.l.google.com:19302 - host: stun2.l.google.com
- stun:stun4.l.google.com:19302 port: 19302
# TURN服务 # TURN服务需要自己搭建coturn
turn: turn:
- turn:192.168.8.110:3478 - host: 192.168.8.110
- turn:192.168.8.111:3478 port: 3478
- turn:192.168.8.112:3478 username: taoyao
- turn:192.168.8.113:3478 password: taoyao
- host: 192.168.8.111
port: 3478
username: taoyao
password: taoyao
# 安全配置 # 安全配置
security: security:
enabled: true enabled: true
@@ -145,6 +155,7 @@ taoyao:
password: taoyao password: taoyao
# 定时任务 # 定时任务
scheduled: scheduled:
media: 0 * * * * ?
client: 0 * * * * ? client: 0 * * * * ?
# 脚本配置 # 脚本配置
script: script:

View File

@@ -16,7 +16,7 @@ import com.acgist.taoyao.signal.event.client.ClientCloseEvent;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
/** /**
* 会话管理 * 终端管理
* *
* @author acgist * @author acgist
*/ */
@@ -30,7 +30,7 @@ public class ClientManager {
private ApplicationContext applicationContext; private ApplicationContext applicationContext;
/** /**
* 会话列表 * 终端列表
*/ */
private List<Client> clients = new CopyOnWriteArrayList<>(); private List<Client> clients = new CopyOnWriteArrayList<>();
@@ -40,6 +40,8 @@ public class ClientManager {
} }
/** /**
* 终端打开加入管理
*
* @param client 终端 * @param client 终端
*/ */
public void open(Client client) { public void open(Client client) {
@@ -118,7 +120,7 @@ public class ClientManager {
/** /**
* @param sn 终端标识 * @param sn 终端标识
* *
* @return 终端会话 * @return 终端列表
*/ */
public List<Client> clients(String sn) { public List<Client> clients(String sn) {
return this.clients().stream() return this.clients().stream()
@@ -127,7 +129,7 @@ public class ClientManager {
} }
/** /**
* @return 所有终端会话 * @return 所有终端列表
*/ */
public List<Client> clients() { public List<Client> clients() {
return this.clients.stream() return this.clients.stream()
@@ -148,7 +150,7 @@ public class ClientManager {
/** /**
* @param sn 终端标识 * @param sn 终端标识
* *
* @return 终端状态 * @return 终端状态列表
*/ */
public List<ClientStatus> status(String sn) { public List<ClientStatus> status(String sn) {
return this.clients(sn).stream() return this.clients(sn).stream()
@@ -157,7 +159,7 @@ public class ClientManager {
} }
/** /**
* @return 所有终端状态 * @return 所有终端状态列表
*/ */
public List<ClientStatus> status() { public List<ClientStatus> status() {
return this.clients().stream() return this.clients().stream()
@@ -168,7 +170,7 @@ public class ClientManager {
/** /**
* 发送消息 * 发送消息
* *
* @param instance 会话实例 * @param instance 终端实例
* @param message 消息 * @param message 消息
*/ */
public void send(AutoCloseable instance, Message message) { public void send(AutoCloseable instance, Message message) {
@@ -181,9 +183,9 @@ public class ClientManager {
} }
/** /**
* 关闭会话 * 关闭终端
* *
* @param instance 会话实例 * @param instance 终端实例
*/ */
public void close(AutoCloseable instance) { public void close(AutoCloseable instance) {
final Client client = this.client(instance); final Client client = this.client(instance);
@@ -195,7 +197,7 @@ public class ClientManager {
instance.close(); instance.close();
} }
} catch (Exception e) { } catch (Exception e) {
log.error("关闭会话异常", e); log.error("关闭终端异常", e);
} finally { } finally {
if(client != null) { if(client != null) {
// 移除管理 // 移除管理
@@ -207,15 +209,15 @@ public class ClientManager {
} }
/** /**
* 定时关闭超时会话 * 定时关闭超时终端
*/ */
private void closeTimeout() { private void closeTimeout() {
log.debug("定时关闭超时会话"); log.debug("定时关闭超时终端");
this.clients.stream() this.clients.stream()
.filter(v -> !v.authorized()) .filter(v -> !v.authorized())
.filter(v -> v.timeout(this.taoyaoProperties.getTimeout())) .filter(v -> v.timeout(this.taoyaoProperties.getTimeout()))
.forEach(v -> { .forEach(v -> {
log.debug("关闭超时会话{}", v); log.debug("关闭超时终端{}", v);
this.close(v); this.close(v);
}); });
} }

View File

@@ -67,7 +67,7 @@ public class SocketSignal {
this.socketProperties.getThreadMin(), this.socketProperties.getThreadMin(),
this.socketProperties.getThreadMax(), this.socketProperties.getThreadMax(),
this.socketProperties.getKeepAliveTime(), this.socketProperties.getKeepAliveTime(),
TimeUnit.SECONDS, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(this.socketProperties.getQueueSize()), new LinkedBlockingQueue<>(this.socketProperties.getQueueSize()),
this.newThreadFactory() this.newThreadFactory()
); );

View File

@@ -1,24 +1,18 @@
package com.acgist.taoyao.signal.media; package com.acgist.taoyao.signal.media;
import java.net.URI; import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.WebSocket; import java.net.http.WebSocket;
import java.net.http.WebSocket.Listener; import java.net.http.WebSocket.Listener;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage; import java.util.concurrent.CompletionStage;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext; import java.util.concurrent.TimeoutException;
import javax.net.ssl.X509TrustManager;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -33,6 +27,7 @@ import com.acgist.taoyao.boot.model.MessageCode;
import com.acgist.taoyao.boot.model.MessageCodeException; import com.acgist.taoyao.boot.model.MessageCodeException;
import com.acgist.taoyao.boot.property.MediaServerProperties; import com.acgist.taoyao.boot.property.MediaServerProperties;
import com.acgist.taoyao.boot.property.TaoyaoProperties; import com.acgist.taoyao.boot.property.TaoyaoProperties;
import com.acgist.taoyao.boot.utils.HTTPUtils;
import com.acgist.taoyao.boot.utils.JSONUtils; import com.acgist.taoyao.boot.utils.JSONUtils;
import com.acgist.taoyao.signal.protocol.Protocol; import com.acgist.taoyao.signal.protocol.Protocol;
import com.acgist.taoyao.signal.protocol.ProtocolManager; import com.acgist.taoyao.signal.protocol.ProtocolManager;
@@ -104,6 +99,19 @@ public class MediaClient {
public String name() { public String name() {
return this.name; return this.name;
} }
/**
* 心跳
*/
public void heartbeat() {
final CompletableFuture<WebSocket> future = this.webSocket.sendPing(ByteBuffer.allocate(0));
try {
log.debug("心跳:{}", this.name);
future.get(this.taoyaoProperties.getTimeout(), TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
log.error("心跳异常:{}", this.name, e);
}
}
/** /**
* 连接WebSocket通道 * 连接WebSocket通道
@@ -112,14 +120,12 @@ public class MediaClient {
final URI uri = URI.create(this.mediaServerProperties.getAddress()); final URI uri = URI.create(this.mediaServerProperties.getAddress());
log.info("连接媒体服务:{}", uri); log.info("连接媒体服务:{}", uri);
try { try {
HttpClient final WebSocket webSocket = HTTPUtils.newClient()
.newBuilder()
.sslContext(buildSSLContext())
.build()
.newWebSocketBuilder() .newWebSocketBuilder()
.connectTimeout(Duration.ofMillis(this.taoyaoProperties.getTimeout())) .connectTimeout(Duration.ofMillis(this.taoyaoProperties.getTimeout()))
.buildAsync(uri, new MessageListener()) .buildAsync(uri, new MessageListener())
.get(); .get();
log.info("连接媒体服务成功:{}", webSocket);
} catch (InterruptedException | ExecutionException e) { } catch (InterruptedException | ExecutionException e) {
log.error("连接媒体服务异常:{}", uri, e); log.error("连接媒体服务异常:{}", uri, e);
this.taskScheduler.schedule( this.taskScheduler.schedule(
@@ -304,59 +310,4 @@ public class MediaClient {
} }
/**
* SSLContext
*
* @return {@link SSLContext}
*/
private static final SSLContext buildSSLContext() {
try {
// SSL协议SSL、SSLv2、SSLv3、TLS、TLSv1、TLSv1.1、TLSv1.2、TLSv1.3
final SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, new X509TrustManager[] { TaoyaoTrustManager.INSTANCE }, new SecureRandom());
return sslContext;
} catch (KeyManagementException | NoSuchAlgorithmException e) {
log.error("新建SSLContext异常", e);
}
try {
return SSLContext.getDefault();
} catch (NoSuchAlgorithmException e) {
log.error("新建SSLContext异常", e);
}
return null;
}
/**
* 证书验证
*
* @author acgist
*/
public static class TaoyaoTrustManager implements X509TrustManager {
private static final TaoyaoTrustManager INSTANCE = new TaoyaoTrustManager();
private TaoyaoTrustManager() {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
if(chain == null) {
throw new CertificateException("证书验证失败");
}
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
if(chain == null) {
throw new CertificateException("证书验证失败");
}
}
}
} }

View File

@@ -5,6 +5,7 @@ import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.Scheduled;
import com.acgist.taoyao.boot.annotation.Manager; import com.acgist.taoyao.boot.annotation.Manager;
import com.acgist.taoyao.boot.property.MediaProperties; import com.acgist.taoyao.boot.property.MediaProperties;
@@ -30,6 +31,11 @@ public class MediaClientManager {
*/ */
private Map<String, MediaClient> clientMap = new ConcurrentHashMap<>(); private Map<String, MediaClient> clientMap = new ConcurrentHashMap<>();
@Scheduled(cron = "${taoyao.scheduled.media:0 * * * * ?}")
public void scheduled() {
this.heartbeat();
}
/** /**
* 加载媒体服务终端 * 加载媒体服务终端
*/ */
@@ -52,5 +58,14 @@ public class MediaClientManager {
public MediaClient mediaClient(String name) { public MediaClient mediaClient(String name) {
return this.clientMap.get(name); return this.clientMap.get(name);
} }
/**
* 心跳
*/
private void heartbeat() {
this.clientMap.forEach((k, v) -> {
v.heartbeat();
});
}
} }