using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using Vinno.FIS.TRTCClient.Common.Enum; using Vinno.FIS.TRTCClient.Common.Log; using Vinno.FIS.TRTCClient.Common.Pipe; using Vinno.vCloud.FIS.CrossPlatform.Common; using Vinno.vCloud.FIS.CrossPlatform.Common.Enum; using Vinno.vCloud.FIS.CrossPlatform.Common.Helper; using Vinno.vCloud.FIS.CrossPlatform.Common.LiveVideo; using Vinno.vCloud.FIS.CrossPlatform.Common.LiveVideo.Interface; namespace Vinno.vCloud.FIS.CrossPlatform.Windows.LiveVideo.RTC { public class RtcMultiPusherV2 : PusherBase, ILiveVideoPusherV2 { private readonly ConcurrentDictionary _pushers; private readonly ConcurrentDictionary _liveStatus; public event EventHandler ChannelStateChanged; public RtcMultiPusherV2() { _pushers = new ConcurrentDictionary(); _liveStatus = new ConcurrentDictionary(); } public bool StartPusher(IExtendedData pushParams, IEnumerable deviceInfos) { var rtcParams = pushParams as RtcExtendedData; if (rtcParams == null || rtcParams.AppId == 0) { CrossPlatformHelper.Instance.LogWriter?.WriteLineError("RTC Multi Extended Data Is Null!"); return false; } var userInfos = rtcParams.UserInfos; for (int i = 0; i < userInfos.Count(); i++) { var deviceInfo = deviceInfos.FirstOrDefault(c => c.Category == userInfos[i].Category); if (deviceInfo != null) { var device = (CPVideoDeviceOutputInfo)deviceInfo.Clone(); bool isMute = true; string micId = ""; if (i == 0) { isMute = rtcParams.IsMute; micId = rtcParams.MicDeviceId; } var pusher = new TRTCClientV2(device, userInfos[i].Width, userInfos[i].Height, rtcParams.AppId, userInfos[i].UserId, rtcParams.RoomId, userInfos[i].UserSign, userInfos[i].VideoFps, userInfos[i].VideoBitrate, userInfos[i].MinVideoBitrate, isMute, micId); if (_pushers.TryAdd(userInfos[i].Category, pusher)) { pusher.ImageFrameReceived += OnImageFrameReceived; pusher.FirstFrameReceived += OnFirstFrameReceived; pusher.Start(); if (LiveVideoStatusChecker.Instance.IsRealTimeCapturing && userInfos[i].Category == EnumLiveChannelCategory.Main)//只有RTCMultiPusherV2需要开启预览,才能拿到图,其余只要开启Capturer就可以拿到图, { pusher.StartPreview(); } } else { pusher.Dispose(); } } else { CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"Rtc Multi PusherV2 Can not find {deviceInfo.Category} User Info!"); } } return true; } public bool StopPusher() { foreach (var pusher in _pushers.Values) { pusher.ImageFrameReceived -= OnImageFrameReceived; pusher.FirstFrameReceived -= OnFirstFrameReceived; pusher.Stop(); } _pushers.Clear(); _liveStatus.Clear(); return true; } public void SetMute(bool isMute) { if (_pushers.Count > 0) { _pushers[0].SetMute(isMute); } } public void SwitchMic(string micId) { if (_pushers.Count > 0) { _pushers[0].SwitchMic(micId); } } public override void StartPreview(EnumLiveChannelCategory category) { if (_pushers.TryGetValue(category, out var client)) { client.StartPreview(); } base.StartPreview(category); } public override void StopPreview() { foreach (var item in _pushers.Values) { if (item.Category == EnumLiveChannelCategory.Main && LiveVideoStatusChecker.Instance.IsRealTimeCapturing) { continue; } item.StopPreview(); } base.StopPreview(); } private void OnImageFrameReceived(object sender, ImageFrameData e) { var category = (EnumLiveChannelCategory)sender; if (category == EnumLiveChannelCategory.Main) { RainbowImageDetector.Instance.AddImage(e); LiveVideoStatusChecker.Instance.AddImage(e); } CopyPreviewData(category, e); } private void OnFirstFrameReceived(object sender, EnumLiveChannelCategory e) { if (!_liveStatus.TryGetValue(e, out var states)) { if (_liveStatus.TryAdd(e, EnumLiveStates.TerminalIsPushing)) { ChannelStateChanged?.Invoke(this, new ChannelStateEventArgsV2(e, EnumDeviceLiveState.Pushing)); } } } public bool StartSpeedTest(uint appId, string userId, string userSign) { TRTCPusher pusher = null; try { pusher = new TRTCPusher(); return pusher.StartSpeedTest(appId, userId, userSign); } catch (Exception ex) { CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"Rtc Single PusherV2 StartSpeedTest Error:{ex}"); return false; } finally { if (pusher != null) { pusher.StopSpeedTest(); pusher = null; } } } public override void DoDispose() { StopPusher(); _pushers.Clear(); base.DoDispose(); } private class TRTCClientV2 : IDisposable { public event EventHandler ImageFrameReceived; public event EventHandler FirstFrameReceived; private TRTCRoomInfo _roomInfo; private int _processId; private ImageFrameData _imageFrameData; private EnumLiveChannelCategory _category; private ProcessKeepHelper _processKeeper; private PipeClient _messageClient; private PipeServer _messageServer; private PipeServer _videoPipeServer; private PipeClient _videoPipeClient; private bool _isPreviewing; private TerminalImageSenderV2 _terminalSender; private bool _isConnectedWithClient; private readonly object _previewLocker = new object(); private int _terminalWidth; private int _terminalHeight; private readonly ManualResetEvent _handlingImageEvent = new ManualResetEvent(true); public EnumLiveChannelCategory Category => _category; public TRTCClientV2(CPVideoDeviceOutputInfo deviceInfo, int outputWidth, int outputHeight, int appid, string userId, int roomId, string userSign, int fps, int bitrate, int minBitrate, bool isMute, string micId) { _category = deviceInfo.Category; _imageFrameData = new ImageFrameData(0, 0); _roomInfo = new TRTCRoomInfo(); _roomInfo.AppId = (uint)appid; _roomInfo.UserSig = userSign; _roomInfo.UserId = userId; _roomInfo.RoomId = (uint)roomId; _roomInfo.OutputWidth = outputWidth; _roomInfo.OutputHeight = outputHeight; _roomInfo.DeviceWidth = deviceInfo.OutputWidth; _roomInfo.DeviceHeight = deviceInfo.OutputHeight; _roomInfo.VideoFps = (uint)fps; _roomInfo.VideoBitrate = (uint)bitrate; _roomInfo.MinVideoBitrate = (uint)minBitrate; _roomInfo.DeviceId = deviceInfo.VideoDeviceId; _roomInfo.IsSonopost = CommonParameter.Instance.IsSonopost; _roomInfo.MicDeviceId = micId; _roomInfo.IsMute = isMute; _roomInfo.IsOldServerMode = false; if (_category == EnumLiveChannelCategory.Main && !_roomInfo.IsSonopost) { _roomInfo.IsUSMachineImage = true; } _roomInfo.IsForLive = true; switch (_category) { case EnumLiveChannelCategory.Main: _roomInfo.Category = EnumLiveChannelCategory.Main; _roomInfo.IsFillMode = false; break; case EnumLiveChannelCategory.Auxiliary1: _roomInfo.Category = EnumLiveChannelCategory.Auxiliary1; _roomInfo.IsFillMode = true; break; case EnumLiveChannelCategory.Auxiliary2: _roomInfo.Category = EnumLiveChannelCategory.Auxiliary2; _roomInfo.IsFillMode = true; break; case EnumLiveChannelCategory.Auxiliary3: _roomInfo.Category = EnumLiveChannelCategory.Auxiliary3; _roomInfo.IsFillMode = true; break; } } public void Start() { var trtcClientApp = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Vinno.FIS.TRTCClient.exe"); var logPath = FISWin.FISLogPath; if (logPath.EndsWith("\\")) { logPath = logPath.Substring(0, logPath.Length - 1); } var argument = $"pid=\"{Process.GetCurrentProcess().Id}\" lpt=\"{logPath}\" {_roomInfo}"; _processId = ProcessHelper.StartProcess(trtcClientApp, argument); if (_processId == 0) { CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"RtcMultiPusherV2 Start Process Error,UserId:{_roomInfo.UserId}"); return; } CrossPlatformHelper.Instance.LogWriter?.WriteLineInfo($"RtcMultiPusherV2 TRTCClient Start,RoomId:{_roomInfo.RoomId},ProcessId:{_processId}"); StartMessagePipe(); StartVideoPipeServer(); if (_category == EnumLiveChannelCategory.Main && !_roomInfo.IsSonopost) { StartVideoPipeClient(); StartTerminalSender(); } _processKeeper = new ProcessKeepHelper(_processId); _processKeeper.StartKeepProcess(() => { StopMessagePipe(); StopVideoPipeServer(); StopVideoPipeClient(); StopTerminalSender(); var processId = ProcessHelper.StartProcess(trtcClientApp, argument); _processId = processId; StartMessagePipe(); StartVideoPipeServer(); if (_category == EnumLiveChannelCategory.Main && !_roomInfo.IsSonopost) { StartVideoPipeClient(); StartTerminalSender(); } return processId; }); } private void StartTerminalSender() { if (_category == EnumLiveChannelCategory.Main && !_roomInfo.IsSonopost) { if (_roomInfo.OutputWidth <= 640 && _roomInfo.OutputHeight <= 480) { _terminalWidth = 640; _terminalHeight = 480; _terminalSender = new Terminal640X480SenderV2(_roomInfo.VideoDeviceId, _roomInfo.OutputWidth, _roomInfo.OutputHeight); } else if (_roomInfo.OutputWidth <= 960 && _roomInfo.OutputHeight <= 544) { _terminalWidth = 960; _terminalHeight = 544; _terminalSender = new Terminal960X540SenderV2(_roomInfo.VideoDeviceId, _roomInfo.OutputWidth, _roomInfo.OutputHeight); } else if (_roomInfo.OutputWidth <= 960 && _roomInfo.OutputHeight <= 720) { _terminalWidth = 960; _terminalHeight = 720; _terminalSender = new Terminal960X720SenderV2(_roomInfo.VideoDeviceId, _roomInfo.OutputWidth, _roomInfo.OutputHeight); } else if (_roomInfo.OutputWidth <= 1280 && _roomInfo.OutputHeight <= 720) { _terminalWidth = 1280; _terminalHeight = 720; _terminalSender = new Terminal1280X720SenderV2(_roomInfo.VideoDeviceId, _roomInfo.OutputWidth, _roomInfo.OutputHeight); } else { _terminalWidth = 1920; _terminalHeight = 1080; _terminalSender = new Terminal1920X1080SenderV2(_roomInfo.VideoDeviceId, _roomInfo.OutputWidth, _roomInfo.OutputHeight); } _terminalSender.VideoFrameDataReceived += OnVideoFrameDataReceived; _terminalSender.Start(); } } private void StartVideoPipeClient() { _videoPipeClient = new PipeClient($"{_processId}_TerminalImage"); _videoPipeClient.LogMsgThrow += OnLogMsgThrow; CrossPlatformHelper.Instance.LogWriter?.WriteLineInfo($"RtcMultiPusherV2 Create Local Video Pipe Client :{_videoPipeClient.PipeName}"); _videoPipeClient.Start(); } private void OnLogMsgThrow(object sender, LogEventArgs e) { if (e == null) { return; } switch (e.LogType) { case DeviceLogCategory.Error: CrossPlatformHelper.Instance.LogWriter?.WriteLineError(e.Msg); break; case DeviceLogCategory.Warn: CrossPlatformHelper.Instance.LogWriter?.WriteLineWarn(e.Msg); break; case DeviceLogCategory.Verb: CrossPlatformHelper.Instance.LogWriter?.WriteLineVerbose(e.Msg); break; case DeviceLogCategory.Info: default: CrossPlatformHelper.Instance.LogWriter?.WriteLineInfo(e.Msg); break; } } private void StartVideoPipeServer() { _videoPipeServer = new PipeServer($"{_processId}_LocalVideo"); _videoPipeServer.DataReceived += OnVideoDataReceived; _videoPipeServer.LogMsgThrow += OnLogMsgThrow; CrossPlatformHelper.Instance.LogWriter?.WriteLineInfo($"RtcMultiPusherV2Create Local Video Pipe Server :{_videoPipeServer.PipeName}"); _videoPipeServer.StartAndReceive(); } public void Stop() { CrossPlatformHelper.Instance.LogWriter?.WriteLineInfo($"RtcMultiPusherV2 TRTCClient Stop"); _processKeeper?.StopKeepProcess(); SendExitMessage(); StopPreview(); StopVideoPipeServer(); StopVideoPipeClient(); StopMessagePipe(); StopTerminalSender(); var success = ProcessHelper.FinishProcess(_processId); if (!success) { CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"RtcMultiPusherV2 Finish Process Error,UserId:{_roomInfo.UserId}"); } } private void StopTerminalSender() { if (_terminalSender != null) { _terminalSender.VideoFrameDataReceived -= OnVideoFrameDataReceived; _terminalSender.Stop(); _terminalSender = null; }; } private void OnVideoFrameDataReceived(object sender, CPVideoFrameData e) { if (e == null) { return; } _videoPipeClient?.SendBytes(e.Data); } private void SendExitMessage() { CrossPlatformHelper.Instance.LogWriter?.WriteLineInfo($"RtcMultiPusherV2 MessageClient Send Exit"); var pipeMessageData = new PipeMessageData(PipeMessage.Exit, new byte[0]); _messageClient?.SendBytes(pipeMessageData.ToBytes()); Thread.Sleep(50); } public void StartMessagePipe() { _messageClient = new PipeClient($"{_processId}_FIS2TRTCClientMessage"); _messageClient.LogMsgThrow += OnLogMsgThrow; _messageClient.Start(); _messageServer = new PipeServer($"{_processId}_TRTCClient2FISMessage"); _messageServer.DataReceived += OnMessageDataReceived; _messageServer.LogMsgThrow += OnLogMsgThrow; _messageServer.StartAndReceive(); } private void OnMessageDataReceived(object sender, byte[] e) { if (e == null) { return; } var pipeMessageData = PipeMessageData.FromBytes(e); CrossPlatformHelper.Instance.LogWriter?.WriteLineInfo($"RtcMultiPusherV2 Message Pipe Server Receive PipeMessage{pipeMessageData?.PipeMessage}"); if (pipeMessageData.PipeMessage == PipeMessage.ImageSizeChanged) { var pipeImageSizeData = PipeImageSizeData.FromBytes(pipeMessageData.MessageData); if (_imageFrameData.Width != pipeImageSizeData.Width || _imageFrameData.Height != pipeImageSizeData.Height) { _imageFrameData.Resize(pipeImageSizeData.Width, pipeImageSizeData.Height); } } else if (pipeMessageData.PipeMessage == PipeMessage.BootUp) { lock (_previewLocker) { _isConnectedWithClient = true; if (_roomInfo.IsUSMachineImage && _roomInfo.IsForLive) { CrossPlatformHelper.Instance.LogWriter?.WriteLineInfo($"RtcMultiPusherV2 MessageClient Send ImageSizeChanged,Width:{_terminalWidth},Height:{_terminalHeight}"); var pipeImageSizeData = new PipeMessageData(PipeMessage.ImageSizeChanged, new PipeImageSizeData(_terminalWidth, _terminalHeight).ToBytes()); _messageClient?.SendBytes(pipeImageSizeData.ToBytes()); } if (_isPreviewing) { CrossPlatformHelper.Instance.LogWriter?.WriteLineInfo($"RtcMultiPusherV2 MessageClient Send StartPreview"); var startPreviewMessage = new PipeMessageData(PipeMessage.StartPreview, new byte[0]); _messageClient?.SendBytes(startPreviewMessage.ToBytes()); } else { CrossPlatformHelper.Instance.LogWriter?.WriteLineInfo($"RtcMultiPusherV2 MessageClient Send StopPreview"); var stopPreviewMessage = new PipeMessageData(PipeMessage.StopPreview, new byte[0]); _messageClient?.SendBytes(stopPreviewMessage.ToBytes()); } } } else if (pipeMessageData.PipeMessage == PipeMessage.FirstFrameReceived) { FirstFrameReceived?.Invoke(this, _category); } } private void StopVideoPipeServer() { if (_videoPipeServer != null) { CrossPlatformHelper.Instance.LogWriter?.WriteLineInfo($"RtcMultiPusherV2 Close Pipe Server Video{_videoPipeServer.PipeName}"); _videoPipeServer.DataReceived -= OnVideoDataReceived; _videoPipeServer.Dispose(); _videoPipeServer.LogMsgThrow -= OnLogMsgThrow; _videoPipeServer = null; } } private void StopVideoPipeClient() { if (_videoPipeClient != null) { CrossPlatformHelper.Instance.LogWriter?.WriteLineInfo($"RtcMultiPusherV2 Close Pipe Client Video{_videoPipeClient.PipeName}"); _videoPipeClient.Dispose(); _videoPipeClient.LogMsgThrow -= OnLogMsgThrow; _videoPipeClient = null; } } private void OnVideoDataReceived(object sender, byte[] e) { if (e == null) { CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"RtcMultiPusherV2 TRTCClient {_roomInfo.RoomId} {_roomInfo.UserId} received video image is empty"); return; } try { _handlingImageEvent.Reset(); if (_imageFrameData.Size != e.Length) { return; } if (_isPreviewing) { Marshal.Copy(e, 0, _imageFrameData.Data, e.Length); ImageFrameReceived?.Invoke(_category, _imageFrameData); } } catch (Exception ex) { CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"RtcMultiPusherV2 OnVideoDataReceived error: {ex}"); } finally { _handlingImageEvent.Set(); } } public void StopMessagePipe() { if (_messageClient != null) { _messageClient.Dispose(); _messageClient.LogMsgThrow -= OnLogMsgThrow; _messageClient = null; } if (_messageServer != null) { _messageServer.DataReceived -= OnMessageDataReceived; _messageServer.Dispose(); _messageServer.LogMsgThrow -= OnLogMsgThrow; _messageServer = null; } } public void StartPreview() { lock (_previewLocker) { if (_isPreviewing) return; _isPreviewing = true; if (_isConnectedWithClient) { CrossPlatformHelper.Instance.LogWriter?.WriteLineInfo($"RtcMultiPusherV2 MessageClient Send StartPreview"); var startPreviewMessage = new PipeMessageData(PipeMessage.StartPreview, new byte[0]); _messageClient?.SendBytes(startPreviewMessage.ToBytes()); } } } public void SetMute(bool isMute) { if (_isConnectedWithClient) { CrossPlatformHelper.Instance.LogWriter?.WriteLineInfo($"RtcMultiPusherV2 MessageClient Send SetMute {isMute}"); var isMuteMessage = new PipeBoolMessage(isMute); var muteMessage = new PipeMessageData(PipeMessage.Mute, isMuteMessage.ToBytes()); _messageClient?.SendBytes(muteMessage.ToBytes()); } } public void SwitchMic(string micId) { if (_isConnectedWithClient) { CrossPlatformHelper.Instance.LogWriter?.WriteLineInfo($"RtcMultiPusherV2 MessageClient Send SwitchMic {micId}"); var micIdMessage = new PipeUserIdMessage(micId); var message = new PipeMessageData(PipeMessage.SwitchMic, micIdMessage.ToBytes()); _messageClient?.SendBytes(message.ToBytes()); } } public void StopPreview() { lock (_previewLocker) { if (!_isPreviewing) { return; } _isPreviewing = false; if (_isConnectedWithClient) { CrossPlatformHelper.Instance.LogWriter?.WriteLineInfo($"RtcMultiPusherV2 MessageClient Send StopPreview"); var stopPreviewMessage = new PipeMessageData(PipeMessage.StopPreview, new byte[0]); _messageClient?.SendBytes(stopPreviewMessage.ToBytes()); } } } public void Dispose() { Stop(); _handlingImageEvent.WaitOne(); _imageFrameData?.Dispose(); } } } }