diff --git a/docs/Deploy.md b/docs/Deploy.md index 605ef09..4929979 100644 --- a/docs/Deploy.md +++ b/docs/Deploy.md @@ -563,6 +563,8 @@ sudo ufw allow 8888/tcp sudo ufw allow 9999/tcp # 媒体服务 sudo ufw allow 40000:49999/udp +# 允许网段 +#sudo ufw allow from 192.168.1.0/24 to any # 删除端口 #sudo ufw delete allow 443/tcp diff --git a/docs/NetworkTopology.md b/docs/NetworkTopology.md new file mode 100644 index 0000000..cc06c16 --- /dev/null +++ b/docs/NetworkTopology.md @@ -0,0 +1,90 @@ +# 拓扑网络 + +多子网环境下的部署配置 + +## 两个子网 + +``` +# 配置`TURN`服务 +`coturn` + +#配置地址重写 +`ip-rewrite` +``` + +## 三个及其以上子网 + +### 配置端口转发 + +``` +# 配置转发:两个配置一个即可 +vim /etc/default/ufw +--- +DEFAULT_FORWARD_POLICY="ACCEPT" +--- +vim /etc/sysctl.conf | vim /etc/ufw/sysctl.conf +--- +net.ipv4.ip_forward=1 | net/ipv4/ip_forward=1 +--- +sysctl -p + +# *filter之前添加 +vim /etc/ufw/before.rules +--- +*nat +:PREROUTING ACCEPT [0:0] +:POSTROUTING ACCEPT [0:0] +-A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080 +-A POSTROUTING -j MASQUERADE +COMMIT +--- +ufw reload + +# 配置`TURN`服务 +`coturn` + +#配置地址重写 +`ip-rewrite` +``` + +## 端口转发 + +* DNAT: 目标IP转换 +* SNAT: 源IP转换 +* REDIRECT: 端口重定向 +* MASQUERADE:源地址动态伪装 + +``` +-A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080 +-A PREROUTING -d 192.168.1.100 -p tcp --dport 80 -j REDIRECT --to-port 8080 +-A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.2.100:8080 +-A PREROUTING -d 192.168.1.100 -p tcp --dport 80 -j DNAT --to-destination 192.168.2.100:8080 +-A POSTROUTING -p tcp --dport 80 -j SNAT --to-source 192.168.2.100 +-A POSTROUTING -s 192.168.1.0/24 -j SNAT --to-source 192.168.2.100 +-A POSTROUTING -j MASQUERADE +-A POSTROUTING -s 192.168.1.0/24 -j MASQUERADE +``` + +## iptables + +``` +# 查看nat +iptables -L -t nat +# 清理nat +iptables -F -t nat +``` + +### 四张表 + +* nat: NAT功能(端口映射、地址映射等等) +* raw: 优先级最高 +* filter:过滤功能 +* mangle:修改特定数据包 + +### 五条链 + +* INPUT: 通过路由表后目的地为本机 +* OUTPUT: 由本机产生向外转发 +* FORWARDING: 通过路由表后目的地不为本机 +* PREROUTING: 数据包进入路由表之前 +* POSTROUTING:发送到网卡接口之前 diff --git a/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/MediaService.java b/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/MediaService.java index 807cafd..0c129ba 100644 --- a/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/MediaService.java +++ b/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/MediaService.java @@ -155,11 +155,11 @@ public class MediaService extends Service { resources.getInteger(R.integer.imageQuantity), resources.getString(R.string.audioQuantity), resources.getString(R.string.videoQuantity), - resources.getBoolean(R.bool.broadcaster), resources.getInteger(R.integer.channelCount), resources.getInteger(R.integer.iFrameInterval), resources.getString(R.string.imagePath), resources.getString(R.string.videoPath), + resources.getString(R.string.videoFile), resources.getString(R.string.watermark), VideoSourceType.valueOf(resources.getString(R.string.videoSourceType)) ); @@ -237,7 +237,7 @@ public class MediaService extends Service { .setContentIntent(pendingIntent); final Notification notification = notificationBuilder.build(); this.startForeground((int) System.currentTimeMillis(), notification); - MediaManager.getInstance().initScreen(intent.getParcelableExtra("data")); + MediaManager.getInstance().initScreenCapturer(intent.getParcelableExtra("data")); } /** diff --git a/taoyao-client-android/taoyao/client/src/main/res/values/settings.xml b/taoyao-client-android/taoyao/client/src/main/res/values/settings.xml index 0eeab60..2dad546 100644 --- a/taoyao-client-android/taoyao/client/src/main/res/values/settings.xml +++ b/taoyao-client-android/taoyao/client/src/main/res/values/settings.xml @@ -34,8 +34,6 @@ fd-audio fd-video - - false 1 @@ -46,6 +44,8 @@ /taoyao /taoyao + + "'TAOYAO' yyyy-MM-dd HH:mm:ss" diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaManager.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaManager.java index 28278ac..cfaf7d7 100644 --- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaManager.java +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaManager.java @@ -6,7 +6,6 @@ import android.media.MediaCodecInfo; import android.media.MediaCodecList; import android.media.projection.MediaProjection; import android.os.Handler; -import android.speech.tts.TextToSpeech; import android.util.Log; import com.acgist.taoyao.media.client.PhotographClient; @@ -30,6 +29,7 @@ import org.webrtc.CapturerObserver; import org.webrtc.DefaultVideoDecoderFactory; import org.webrtc.DefaultVideoEncoderFactory; import org.webrtc.EglBase; +import org.webrtc.FileVideoCapturer; import org.webrtc.MediaConstraints; import org.webrtc.MediaStream; import org.webrtc.PeerConnectionFactory; @@ -44,9 +44,8 @@ import org.webrtc.VideoSource; import org.webrtc.VideoTrack; import org.webrtc.audio.JavaAudioDeviceModule; +import java.io.IOException; import java.util.Arrays; -import java.util.Locale; -import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -57,24 +56,22 @@ import java.util.stream.IntStream; */ public final class MediaManager { - private static final MediaManager INSTANCE = new MediaManager(); - - public static final MediaManager getInstance() { - return INSTANCE; - } - /** * 当前终端数量 */ private volatile int clientCount; /** - * 视频路径 + * 图片路径 */ private String imagePath; /** - * 图片路径 + * 视频路径 */ private String videoPath; + /** + * 分享视频文件路径 + */ + private String videoFile; /** * 图片质量 */ @@ -87,10 +84,6 @@ public final class MediaManager { * 视频质量 */ private String videoQuantity; - /** - * 语音播报 - */ - private boolean broadcaster; /** * 通道数量 */ @@ -103,16 +96,12 @@ public final class MediaManager { * 水印 */ private String watermark; - /** - * 视频来源类型 - */ - private VideoSourceType videoSourceType; /** * 信令 */ private ITaoyao taoyao; /** - * Handler + * MainHandler */ private Handler mainHandler; /** @@ -139,10 +128,6 @@ public final class MediaManager { * 音频来源 */ private AudioSource audioSource; - /** - * 视频捕获 - */ - private VideoCapturer videoCapturer; /** * 主码流视频来源 */ @@ -152,11 +137,15 @@ public final class MediaManager { */ private VideoSource shareVideoSource; /** - * 录像终端 + * 视频来源类型 */ - private RecordClient recordClient; + private VideoSourceType videoSourceType; /** - * 视频来源 + * 视频捕获 + */ + private VideoCapturer videoCapturer; + /** + * SurfaceTextureHelper */ private SurfaceTextureHelper surfaceTextureHelper; /** @@ -168,59 +157,67 @@ public final class MediaManager { */ private JavaAudioDeviceModule javaAudioDeviceModule; /** - * 语音播报 + * 录屏等待锁 */ - private TextToSpeech textToSpeech; + private final Object screenLock; + /** + * 录像终端 + */ + private RecordClient recordClient; /** * 视频处理 */ private VideoProcesser videoProcesser; - /** - * 录屏等待锁 - */ - private final Object screenLock = new Object(); static { -// // 设置采样 + // 设置采样 // WebRtcAudioUtils.setDefaultSampleRateHz(48000); -// // 噪声消除 + // 噪声消除 // WebRtcAudioUtils.setWebRtcBasedNoiseSuppressor(true); -// // 回声消除 + // 回声消除 // WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true); -// // 自动增益 + // 自动增益 // WebRtcAudioUtils.setWebRtcBasedAutomaticGainControl(true); -// // 支持的编码解码器 - final MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.ALL_CODECS); - for (MediaCodecInfo mediaCodecInfo : mediaCodecList.getCodecInfos()) { - // OMX.google = 软编 - // OMX.core = 硬编 - final String[] supportedTypes = mediaCodecInfo.getSupportedTypes(); - final String type = mediaCodecInfo.isEncoder() ? "编码器" : "解码器"; - Log.d(MediaManager.class.getSimpleName(), type + "名称:" + mediaCodecInfo.getName()); - Log.d(MediaManager.class.getSimpleName(), type + "类型:" + String.join(", ", supportedTypes)); - for (String supportType : supportedTypes) { - final MediaCodecInfo.CodecCapabilities codecCapabilities = mediaCodecInfo.getCapabilitiesForType(supportType); - Log.d(MediaManager.class.getSimpleName(), type + "支持的文件格式:" + codecCapabilities.getMimeType()); - // MediaCodecInfo.CodecCapabilities.COLOR_* - final int[] colorFormats = codecCapabilities.colorFormats; - Log.d(MediaManager.class.getSimpleName(), type + "支持的色彩格式:" + IntStream.of(colorFormats).boxed().map(String::valueOf).collect(Collectors.joining(", "))); - } - } + // 支持的编码解码器 + final MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.ALL_CODECS); + for (MediaCodecInfo mediaCodecInfo : mediaCodecList.getCodecInfos()) { + // OMX.core = 硬编 + // OMX.google = 软编 + final String[] supportedTypes = mediaCodecInfo.getSupportedTypes(); + final String type = mediaCodecInfo.isEncoder() ? "编码器" : "解码器"; + Log.d(MediaManager.class.getSimpleName(), type + "名称:" + mediaCodecInfo.getName()); + Log.d(MediaManager.class.getSimpleName(), type + "类型:" + String.join(", ", supportedTypes)); + for (String supportType : supportedTypes) { + final MediaCodecInfo.CodecCapabilities codecCapabilities = mediaCodecInfo.getCapabilitiesForType(supportType); + Log.d(MediaManager.class.getSimpleName(), type + "支持的文件格式:" + codecCapabilities.getMimeType()); + // MediaCodecInfo.CodecCapabilities.COLOR_* + final int[] colorFormats = codecCapabilities.colorFormats; + Log.d(MediaManager.class.getSimpleName(), type + "支持的色彩格式:" + IntStream.of(colorFormats).boxed().map(String::valueOf).collect(Collectors.joining(", "))); + } + } + } + + private static final MediaManager INSTANCE = new MediaManager(); + + public static final MediaManager getInstance() { + return INSTANCE; } private MediaManager() { this.clientCount = 0; + this.screenLock = new Object(); } /** - * @return 是否可用(所有配置加载完成) + * @return 是否可用 */ public boolean available() { return - this.taoyao != null && - this.context != null && - this.mainHandler != null && - this.mediaProperties != null; + this.taoyao != null && + this.context != null && + this.mainHandler != null && + this.mediaProperties != null && + this.webrtcProperties != null; } /** @@ -250,19 +247,19 @@ public final class MediaManager { * @param imageQuantity 图片质量 * @param audioQuantity 音频质量 * @param videoQuantity 视频质量 - * @param broadcaster 是否语音播报 * @param channelCount 音频通道数量 * @param iFrameInterval 关键帧频率 * @param imagePath 图片保存路径 * @param videoPath 视频保存路径 + * @param videoFile 分享视频文件路径 * @param watermark 水印信息 * @param videoSourceType 视频来源类型 */ public void initContext( Handler mainHandler, Context context, int imageQuantity, String audioQuantity, String videoQuantity, - boolean broadcaster, int channelCount, int iFrameInterval, - String imagePath, String videoPath, + int channelCount, int iFrameInterval, + String imagePath, String videoPath, String videoFile, String watermark, VideoSourceType videoSourceType ) { this.mainHandler = mainHandler; @@ -270,11 +267,11 @@ public final class MediaManager { this.imageQuantity = imageQuantity; this.audioQuantity = audioQuantity; this.videoQuantity = videoQuantity; - this.broadcaster = broadcaster; this.channelCount = channelCount; this.iFrameInterval = iFrameInterval; this.imagePath = imagePath; this.videoPath = videoPath; + this.videoFile = videoFile; this.watermark = watermark; this.videoSourceType = videoSourceType; synchronized (this) { @@ -292,18 +289,6 @@ public final class MediaManager { } } - public synchronized void broadcast(String text) { - if(!this.broadcaster) { - return; - } - if(this.textToSpeech == null) { - this.textToSpeech = new TextToSpeech(this.context, new MediaManager.TextToSpeechInitListener()); - } - this.textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, null, UUID.randomUUID().toString()); -// this.textToSpeech.stop(); -// this.textToSpeech.shutdown(); - } - /** * 新建终端 * @@ -333,6 +318,7 @@ public final class MediaManager { /** * 关闭一个终端 + * 所有终端关闭之后释放PeerConnectionFactory * * @return 剩余终端数量 */ @@ -341,7 +327,6 @@ public final class MediaManager { this.clientCount--; if (this.clientCount <= 0) { Log.i(MediaManager.class.getSimpleName(), "释放PeerConnectionFactory"); - // 注意顺序 this.stopVideoCapture(); this.closeMedia(); this.nativeStop(); @@ -351,6 +336,9 @@ public final class MediaManager { } } + /** + * 加载PeerConnectionFactory + */ private void initPeerConnectionFactory() { PeerConnectionFactory.initialize( PeerConnectionFactory.InitializationOptions.builder(this.context) @@ -360,14 +348,17 @@ public final class MediaManager { // .setEnableInternalTracer(true) .createInitializationOptions() ); - this.eglBase = EglBase.create(); + this.eglBase = EglBase.create(); this.eglContext = this.eglBase.getEglBaseContext(); } + /** + * 释放PeerConnectionFactory + */ private void stopPeerConnectionFactory() { if (this.eglBase != null) { this.eglBase.release(); - this.eglBase = null; + this.eglBase = null; this.eglContext = null; } PeerConnectionFactory.shutdownInternalTracer(); @@ -389,37 +380,41 @@ public final class MediaManager { // .setAudioEncoderFactoryFactory(new BuiltinAudioEncoderFactoryFactory()) // .setAudioDecoderFactoryFactory(new BuiltinAudioDecoderFactoryFactory()) .createPeerConnectionFactory(); - Arrays.stream(videoEncoderFactory.getSupportedCodecs()).forEach(v -> { + Arrays.stream(videoDecoderFactory.getSupportedCodecs()).forEach(v -> { Log.d(MediaManager.class.getSimpleName(), "支持的视频解码器:" + v.name); }); + Arrays.stream(videoEncoderFactory.getSupportedCodecs()).forEach(v -> { + Log.d(MediaManager.class.getSimpleName(), "支持的视频编码器:" + v.name); + }); this.initAudio(); this.initVideo(); this.initWatermark(); } + /** + * @return JavaAudioDeviceModule + */ private JavaAudioDeviceModule javaAudioDeviceModule() { + // 本地声音回调:只有本地音频而且建立媒体之后才有回调 +// WebRtcAudioRecord.setOnAudioSamplesReady(audioSamples -> {}); + // 配置音频 // final AudioAttributes audioAttributes = new AudioAttributes.Builder() // .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED) // .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) // .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) // .build(); -// WebRtcAudioRecord.setOnAudioSamplesReady(audioSamples -> { -// if(this.recordClient != null) { -// this.recordClient.onWebRtcAudioRecordSamplesReady(audioSamples); -// } -// }); final JavaAudioDeviceModule javaAudioDeviceModule = JavaAudioDeviceModule.builder(this.context) // .setSampleRate(48000) // .setSampleRate(mediaAudioProperties.getSampleRate()) // .setAudioFormat(AudioFormat.ENCODING_PCM_16BIT) // .setAudioSource(MediaRecorder.AudioSource.MIC) // .setAudioAttributes(audioAttributes) - // 超低延迟 // .setUseLowLatency() // .setUseStereoInput() // .setUseStereoOutput() - // 本地声音 -// .setSamplesReadyCallback() +// .setSamplesReadyCallback(audioSamples -> {}) +// .setUseHardwareNoiseSuppressor(true) +// .setUseHardwareAcousticEchoCanceler(true) .setAudioTrackErrorCallback(new JavaAudioDeviceModule.AudioTrackErrorCallback() { @Override public void onWebRtcAudioTrackInitError(String errorMessage) { @@ -468,11 +463,7 @@ public final class MediaManager { Log.i(MediaManager.class.getSimpleName(), "WebRTC本地音频录像结束"); } }) -// .setUseHardwareNoiseSuppressor(true) -// .setUseHardwareAcousticEchoCanceler(true) .createAudioDeviceModule(); -// javaAudioDeviceModule.setSpeakerMute(false); -// javaAudioDeviceModule.setMicrophoneMute(false); return javaAudioDeviceModule; } @@ -492,11 +483,11 @@ public final class MediaManager { // 加载视频 Log.i(MediaManager.class.getSimpleName(), "加载视频:" + this.videoSourceType); if (this.videoSourceType == VideoSourceType.FILE) { - this.initFile(); + this.initFileCapturer(); } else if (this.videoSourceType.isCamera()) { - this.initCamera(); + this.initCameraCapturer(); } else if (this.videoSourceType == VideoSourceType.SCREEN) { - this.initScreenPromise(); + this.initScreenCapturerPromise(); } else { // 其他来源 } @@ -505,15 +496,19 @@ public final class MediaManager { /** * 加载文件采集 */ - private void initFile() { - // 自己实现 -// new FileVideoCapturer(); + private void initFileCapturer() { + try { + this.videoCapturer = new FileVideoCapturer(this.videoFile); + } catch (IOException e) { + Log.e(MediaManager.class.getSimpleName(), "加载视频异常:" + this.videoFile, e); + } + this.initVideoSource(); } /** * 加载摄像头采集 */ - private void initCamera() { + private void initCameraCapturer() { final CameraEnumerator cameraEnumerator = new Camera2Enumerator(this.context); final String[] names = cameraEnumerator.getDeviceNames(); for (String name : names) { @@ -531,7 +526,7 @@ public final class MediaManager { /** * 加载屏幕采集 */ - private void initScreenPromise() { + private void initScreenCapturerPromise() { this.mainHandler.obtainMessage(Config.WHAT_SCREEN_CAPTURE).sendToTarget(); synchronized (this.screenLock) { try { @@ -547,7 +542,7 @@ public final class MediaManager { * * @param intent Intent */ - public void initScreen(Intent intent) { + public void initScreenCapturer(Intent intent) { this.videoCapturer = new ScreenCapturerAndroid(intent, new ScreenCallback()); this.initVideoSource(); synchronized (this.screenLock) { @@ -561,39 +556,54 @@ public final class MediaManager { private void initVideoSource() { // 加载视频 this.surfaceTextureHelper = SurfaceTextureHelper.create("MediaVideoThread", this.eglContext); - // 主码流 - this.mainVideoSource = this.peerConnectionFactory.createVideoSource(this.videoCapturer.isScreencast()); - // 次码流 - this.shareVideoSource = this.peerConnectionFactory.createVideoSource(this.videoCapturer.isScreencast()); + this.mainVideoSource = this.peerConnectionFactory.createVideoSource(this.videoCapturer.isScreencast()); + this.shareVideoSource = this.peerConnectionFactory.createVideoSource(this.videoCapturer.isScreencast()); + // 视频配置 final MediaVideoProperties mediaVideoProperties = this.mediaProperties.getVideo(); this.shareVideoSource.adaptOutputFormat(mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(), mediaVideoProperties.getFrameRate()); // 视频捕获 this.videoCapturer.initialize(this.surfaceTextureHelper, this.context, new VideoCapturerObserver()); } + /** + * 加载水印 + */ private void initWatermark() { - if(StringUtils.isNotEmpty(this.watermark)) { - final MediaVideoProperties mediaVideoProperties = this.mediaProperties.getVideos().get(this.videoQuantity); - if(this.videoProcesser == null) { - this.videoProcesser = new WatermarkProcesser(this.watermark, mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight()); - } else { - this.videoProcesser = new WatermarkProcesser(this.watermark, mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(), this.videoProcesser); - } + if(StringUtils.isEmpty(this.watermark)) { + return; + } + final MediaVideoProperties mediaVideoProperties = this.mediaProperties.getVideos().get(this.videoQuantity); + if(this.videoProcesser == null) { + this.videoProcesser = new WatermarkProcesser(this.watermark, mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight()); + } else { + this.videoProcesser = new WatermarkProcesser(this.watermark, mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(), this.videoProcesser); } } + /** + * 静音远程媒体 + */ public void muteAllRemote() { this.javaAudioDeviceModule.setSpeakerMute(true); } + /** + * 取消远程媒体静音 + */ public void unmuteAllRemote() { this.javaAudioDeviceModule.setSpeakerMute(false); } + /** + * 静音本地媒体 + */ public void muteAllLocal() { this.javaAudioDeviceModule.setMicrophoneMute(true); } + /** + * 取消本地媒体静音 + */ public void unmuteAllLocal() { this.javaAudioDeviceModule.setMicrophoneMute(false); } @@ -612,32 +622,55 @@ public final class MediaManager { } } + /** + * 更新音频配置 + * + * @param mediaAudioProperties 音频配置 + */ public void updateAudioConfig(MediaAudioProperties mediaAudioProperties) { this.mediaProperties.setAudio(mediaAudioProperties); this.updateAudioConfig(); } + /** + * 更新音频配置 + */ private void updateAudioConfig() { MediaAudioProperties mediaAudioProperties = this.mediaProperties.getAudio(); // TODO:调整音频 } + /** + * 更新视频配置 + * + * @param mediaVideoProperties 视频配置 + */ public void updateVideoConfig(MediaVideoProperties mediaVideoProperties) { this.mediaProperties.setVideo(mediaVideoProperties); this.updateVideoConfig(); } + /** + * 更新视频配置 + */ private void updateVideoConfig() { + // 更新视频采集 if(this.videoCapturer != null) { final MediaVideoProperties mediaVideoProperties = this.mediaProperties.getVideos().get(this.videoQuantity); this.videoCapturer.changeCaptureFormat(mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(), mediaVideoProperties.getFrameRate()); } + // 更新共享视频 if(this.shareVideoSource != null) { final MediaVideoProperties mediaVideoProperties = this.mediaProperties.getVideo(); this.shareVideoSource.adaptOutputFormat(mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(), mediaVideoProperties.getFrameRate()); } } + /** + * 更新WebRTC配置 + * + * @param webrtcProperties WebRTC配置 + */ public void updateWebrtcConfig(WebrtcProperties webrtcProperties) { this.webrtcProperties = webrtcProperties; } @@ -647,7 +680,7 @@ public final class MediaManager { * * @param videoSourceType 来源类型 */ - public void updateVideoSource(VideoSourceType videoSourceType) { + public void switchVideoSource(VideoSourceType videoSourceType) { if (this.videoSourceType == videoSourceType) { return; } @@ -668,11 +701,20 @@ public final class MediaManager { } }); } else { + // TODO:测试 // this.initVideo(); // 切换所有VideoTrack视频来源 } } + /** + * 新建本地媒体 + * + * @param audioProduce 是否生产音频 + * @param videoProduce 是否生产视频 + * + * @return 本地媒体 + */ public MediaStream buildLocalMediaStream(boolean audioProduce, boolean videoProduce) { final long id = Thread.currentThread().getId(); final MediaStream mediaStream = this.peerConnectionFactory.createLocalMediaStream("TaoyaoM" + id); @@ -687,12 +729,17 @@ public final class MediaManager { if(videoProduce) { final VideoTrack videoTrack = this.peerConnectionFactory.createVideoTrack("TaoyaoV" + id, this.shareVideoSource); videoTrack.setEnabled(true); - Log.i(MediaManager.class.getSimpleName(), "加载视频(次码流):" + videoTrack.id()); mediaStream.addTrack(videoTrack); + Log.i(MediaManager.class.getSimpleName(), "加载视频:" + videoTrack.id()); } return mediaStream; } + /** + * 新建媒体约束 + * + * @return 媒体约束 + */ public MediaConstraints buildMediaConstraints() { final MediaConstraints mediaConstraints = new MediaConstraints(); // ================ PC ================ // @@ -726,14 +773,21 @@ public final class MediaManager { return mediaConstraints; } + /** + * 开始采集 + */ private void startVideoCapture() { if(this.videoCapturer == null) { return; } final MediaVideoProperties mediaVideoProperties = this.mediaProperties.getVideos().get(this.videoQuantity); this.videoCapturer.startCapture(mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(), mediaVideoProperties.getFrameRate()); + Log.i(MediaManager.class.getSimpleName(), "开始视频采集:" + mediaVideoProperties.getWidth() + "*" + mediaVideoProperties.getHeight() + " - " + mediaVideoProperties.getFrameRate()); } + /** + * 关闭采集 + */ private void stopVideoCapture() { if(this.videoCapturer == null) { return; @@ -741,10 +795,15 @@ public final class MediaManager { try { this.videoCapturer.stopCapture(); } catch (InterruptedException e) { - Log.e(MediaManager.class.getSimpleName(), "关闭视频捕获异常", e); + Log.e(MediaManager.class.getSimpleName(), "关闭视频采集异常", e); } } + /** + * 开始拍照 + * + * @return 图片文件地址 + */ public String photograph() { synchronized (this) { final PhotographClient photographClient = new PhotographClient(this.imageQuantity, this.imagePath); @@ -758,6 +817,11 @@ public final class MediaManager { } } + /** + * 开始录像 + * + * @return 录像终端 + */ public RecordClient startRecord() { synchronized (this) { if(this.recordClient != null) { @@ -778,6 +842,11 @@ public final class MediaManager { } } + /** + * 结束录像 + * + * @return 视频文件地址 + */ public String stopRecord() { synchronized (this) { if(this.recordClient == null) { @@ -793,6 +862,8 @@ public final class MediaManager { } /** + * 预览视频 + * * @param flag Config.WHAT_* * @param videoTrack 视频媒体流Track * @@ -822,6 +893,9 @@ public final class MediaManager { return surfaceViewRenderer; } + /** + * 关闭媒体 + */ private void closeMedia() { if(this.audioSource != null) { this.audioSource.dispose(); @@ -864,7 +938,13 @@ public final class MediaManager { */ private class VideoCapturerObserver implements CapturerObserver { + /** + * 主码流观察者 + */ private CapturerObserver mainObserver; + /** + * 次码流观察者 + */ private CapturerObserver shareObserver; public VideoCapturerObserver() { @@ -963,18 +1043,6 @@ public final class MediaManager { } - private class TextToSpeechInitListener implements TextToSpeech.OnInitListener { - @Override - public void onInit(int status) { - Log.i(MediaManager.class.getSimpleName(), "加载语音播报:" + status); - if(status == TextToSpeech.SUCCESS) { - MediaManager.this.textToSpeech.setLanguage(Locale.CANADA); - MediaManager.this.textToSpeech.setPitch(1.0F); - MediaManager.this.textToSpeech.setSpeechRate(1.0F); - } - } - } - /** * 加载MediasoupClient */ diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/video/WatermarkProcesser.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/video/WatermarkProcesser.java index 7492813..92c2f35 100644 --- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/video/WatermarkProcesser.java +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/video/WatermarkProcesser.java @@ -16,6 +16,7 @@ import java.util.TimerTask; /** * 水印处理器 + * 只支持时间字符串水印 * * 性能优化: * 没有水印:20~25波动