using Com.Tencent.Trtc; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using Vinno.FIS.TRTCClient.Common; using Vinno.FIS.TRTCClient.Common.Enum; using Vinno.FIS.TRTCClient.Common.FileTransfer; using Vinno.FIS.TRTCClient.Common.Log; using Vinno.FIS.TRTCClient.Common.Models; using Vinno.vCloud.FIS.CrossPlatform.Common; using Vinno.vCloud.FIS.CrossPlatform.Common.Enum; using Vinno.vCloud.FIS.CrossPlatform.Common.LiveVideo; using Vinno.vCloud.FIS.CrossPlatform.Common.LiveVideo.Interface; namespace Vinno.vCloud.FIS.CrossPlatform.Android.LiveVideo.RTC { public class RtcMultiPusherV2 : PusherBase, ILiveVideoPusherV2 { private readonly ConcurrentDictionary _pushers; private readonly ConcurrentDictionary _liveStatus; private TRTCPusher _pusher; private IImageSenderV2 _imageSender; private int _videoResolution; 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; bool hasMain = userInfos.Any(x => x.Category == EnumLiveChannelCategory.Main); for (int i = 0; i < userInfos.Count(); i++) { var deviceInfo = deviceInfos.FirstOrDefault(c => c.Category == userInfos[i].Category); if (deviceInfo != null) { if (deviceInfo.Category == EnumLiveChannelCategory.Main) { _videoResolution = GetVideoResolution(userInfos[i].Width, userInfos[i].Height); _pusher = new TRTCPusher(); _pusher.FirstFrameSend += OnFirstFrameSend; if (CommonParameter.Instance.IsSonopost) { _pusher.EnterRoom(userInfos[i].UserId, rtcParams.RoomId, userInfos[i].UserSign, rtcParams.AppId, _videoResolution, userInfos[i].VideoFps, userInfos[i].VideoBitrate, userInfos[i].MinVideoBitrate, rtcParams.IsMute, rtcParams.MicDeviceId); if (_videoResolution == TRTCCloudDef.TrtcVideoResolution640480) { _imageSender = new Camera640X480SenderV2(deviceInfo.VideoDeviceId, userInfos[i].Width, userInfos[i].Height, deviceInfo.Category, false, false); CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"RTC Multi PusherV2:Camera640X480SenderV2"); } else if (_videoResolution == TRTCCloudDef.TrtcVideoResolution960540) { _imageSender = new Camera960X540SenderV2(deviceInfo.VideoDeviceId, userInfos[i].Width, userInfos[i].Height, deviceInfo.Category, false, false); CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"RTC Multi PusherV2:Camera960X540SenderV2"); } else if (_videoResolution == TRTCCloudDef.TrtcVideoResolution960720) { _imageSender = new Camera960X720SenderV2(deviceInfo.VideoDeviceId, userInfos[i].Width, userInfos[i].Height, deviceInfo.Category, false, false); CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"RTC Multi PusherV2:Camera960X720SenderV2"); } else if (_videoResolution == TRTCCloudDef.TrtcVideoResolution1280720) { _imageSender = new Camera1280X720SenderV2(deviceInfo.VideoDeviceId, userInfos[i].Width, userInfos[i].Height, deviceInfo.Category, false, false); CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"RTC Multi PusherV2:Camera1280X720SenderV2"); } else { _imageSender = new Camera1920X1080SenderV2(deviceInfo.VideoDeviceId, userInfos[i].Width, userInfos[i].Height, deviceInfo.Category, false, false); CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"RTC Multi PusherV2:Camera1920X1080SenderV2"); } } else { _pusher.EnterRoom(userInfos[i].UserId, rtcParams.RoomId, userInfos[i].UserSign, rtcParams.AppId, _videoResolution, userInfos[i].VideoFps, userInfos[i].VideoBitrate, userInfos[i].MinVideoBitrate, pushParams.IsMute, pushParams.MicDeviceId); if (_videoResolution == TRTCCloudDef.TrtcVideoResolution640480) { _imageSender = new Terminal640X480SenderV2(deviceInfo.VideoDeviceId, userInfos[i].Width, userInfos[i].Height); CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"RTC Multi PusherV2:Terminal1280X720SenderV2"); } else if (_videoResolution == TRTCCloudDef.TrtcVideoResolution960540) { _imageSender = new Terminal960X540SenderV2(deviceInfo.VideoDeviceId, userInfos[i].Width, userInfos[i].Height); CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"RTC Multi PusherV2:Terminal1280X720SenderV2"); } else if (_videoResolution == TRTCCloudDef.TrtcVideoResolution960720) { _imageSender = new Terminal960X720SenderV2(deviceInfo.VideoDeviceId, userInfos[i].Width, userInfos[i].Height); CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"RTC Multi PusherV2:Terminal1280X720SenderV2"); } else if (_videoResolution == TRTCCloudDef.TrtcVideoResolution1280720) { _imageSender = new Terminal1280X720SenderV2(deviceInfo.VideoDeviceId, userInfos[i].Width, userInfos[i].Height); CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"RTC Multi PusherV2:Terminal1280X720SenderV2"); } else { _imageSender = new Terminal1920X1080SenderV2(deviceInfo.VideoDeviceId, userInfos[i].Width, userInfos[i].Height); CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"RTC Multi PusherV2:Terminal1920X1080SenderV2"); } } _imageSender.VideoFrameDataReceived += OnVideoFrameDataReceived; _imageSender.PreviewImageFrameDataReceived += OnImageFrameReceived; _imageSender.Start(); } else { var device = (CPVideoDeviceOutputInfo)deviceInfo.Clone(); bool isMute = true; string micId = ""; if (i == 0 && !hasMain)//没有主屏的情况 { 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(); } else { pusher.Dispose(); } } } else { CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"Rtc Multi PusherV2 Can not find {userInfos[i].Category} User Info!"); } } return true; } private void OnVideoFrameDataReceived(object sender, CPVideoFrameData e) { _pusher?.SendData(e.Width, e.Height, e.Data); } private int GetVideoResolution(int width, int height) { if (width <= 640 && height <= 480) { return TRTCCloudDef.TrtcVideoResolution640480; } else if (width <= 960 && height <= 544) { return TRTCCloudDef.TrtcVideoResolution960540; } else if (width <= 960 && height <= 720) { return TRTCCloudDef.TrtcVideoResolution960720; } else if (width <= 1280 && height <= 720) { return TRTCCloudDef.TrtcVideoResolution1280720; } else { return TRTCCloudDef.TrtcVideoResolution19201080; } } public bool StopPusher() { try { if (_pusher != null) { _pusher.FirstFrameSend -= OnFirstFrameSend; _pusher.ExitRoom(); _pusher = null; } if (_imageSender != null) { _imageSender.VideoFrameDataReceived -= OnVideoFrameDataReceived; _imageSender.PreviewImageFrameDataReceived -= OnImageFrameReceived; _imageSender.Stop(); _imageSender = null; } foreach (var pusher in _pushers.Values) { pusher.ImageFrameReceived -= OnImageFrameReceived; pusher.FirstFrameReceived -= OnFirstFrameReceived; pusher.Stop(); } _pushers.Clear(); _liveStatus.Clear(); return true; } catch (Exception e) { CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"Rtc Multi PusherV2 Stop Pusher Error:{e}"); } return false; } public void SetMute(bool isMute) { if (_pusher != null) { _pusher.SetMute(isMute); } else { if (_pushers.Count > 0) { _pushers[0].SetMute(isMute); } } } public void SwitchMic(string micId) { if (_pusher != null) { _pusher?.SwitchMic(micId); } else { 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) { item.StopPreview(); } base.StopPreview(); } private void OnImageFrameReceived(object sender, ImageFrameData e) { var category = (EnumLiveChannelCategory)sender; 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)); } } } private void OnFirstFrameSend(object sender, EventArgs e) { if (_liveStatus.TryAdd(EnumLiveChannelCategory.Main, EnumLiveStates.TerminalIsPushing)) { ChannelStateChanged?.Invoke(this, new ChannelStateEventArgsV2(EnumLiveChannelCategory.Main, EnumDeviceLiveState.Pushing)); } } public bool StartSpeedTest(uint appId, string userId, string userSign) { TRTCPusher pusher = null; try { pusher = new TRTCPusher(); return pusher.StartSpeedTest((int)appId, userId, userSign); } catch (Exception ex) { CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"Rtc Multi PusherV2 StartSpeedTest Error:{ex}"); return false; } finally { if (pusher != null) { pusher.StopSpeedTest(); pusher = null; } } } public override void DoDispose() { StopPusher(); base.DoDispose(); } private class TRTCClientV2 : IDisposable { private readonly object _previewLocker = new object(); private readonly ManualResetEvent _handlingImageEvent = new ManualResetEvent(true); private TRTCRoomInfo _roomInfo; private CPVideoDeviceOutputInfo _deviceInfo; private int _trtcVideoResolution; private IImageSenderV2 _imageSender; private FileTransferWriter _localFileWriter; private bool _isPreviewing; private bool _hasInformImageSize; public EnumLiveChannelCategory Category => _deviceInfo.Category; public event EventHandler ImageFrameReceived; public event EventHandler FirstFrameReceived; 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) { _deviceInfo = deviceInfo; GetResolution(outputWidth, outputHeight); int finalOutputWidth = 640; int finalOutputHeight = 480; switch (_trtcVideoResolution) { case TRTCCloudDef.TrtcVideoResolution640480: finalOutputWidth = 640; finalOutputHeight = 480; break; case TRTCCloudDef.TrtcVideoResolution960540: finalOutputWidth = 960; finalOutputHeight = 544; break; case TRTCCloudDef.TrtcVideoResolution960720: finalOutputWidth = 960; finalOutputHeight = 720; break; case TRTCCloudDef.TrtcVideoResolution1280720: finalOutputWidth = 1280; finalOutputHeight = 720; break; case TRTCCloudDef.TrtcVideoResolution19201080: finalOutputWidth = 1920; finalOutputHeight = 1080; break; } _roomInfo = new TRTCRoomInfo { AppId = (uint)appid, UserSig = userSign, UserId = userId, RoomId = (uint)roomId, OutputWidth = finalOutputWidth, OutputHeight = finalOutputHeight, VideoFps = (uint)fps, VideoBitrate = (uint)bitrate, MinVideoBitrate = (uint)minBitrate, MicDeviceId = micId, IsMute = isMute, IsOldServerMode = false, IsLiveMode = true }; switch (deviceInfo.Category) { case EnumLiveChannelCategory.Main: _roomInfo.Category = EnumLiveChannelCategory.Main; break; case EnumLiveChannelCategory.Auxiliary1: _roomInfo.Category = EnumLiveChannelCategory.Auxiliary1; break; case EnumLiveChannelCategory.Auxiliary2: _roomInfo.Category = EnumLiveChannelCategory.Auxiliary2; break; case EnumLiveChannelCategory.Auxiliary3: _roomInfo.Category = EnumLiveChannelCategory.Auxiliary3; break; } } private void GetResolution(int width, int height) { if (width <= 640 && height <= 480) { _trtcVideoResolution = TRTCCloudDef.TrtcVideoResolution640480; } else if (width <= 960 && height <= 544) { _trtcVideoResolution = TRTCCloudDef.TrtcVideoResolution960540; } else if (width <= 960 && height <= 720) { _trtcVideoResolution = TRTCCloudDef.TrtcVideoResolution960720; } else if (width <= 1280 && height <= 720) { _trtcVideoResolution = TRTCCloudDef.TrtcVideoResolution1280720; } else { _trtcVideoResolution = TRTCCloudDef.TrtcVideoResolution19201080; } } public void Start() { FISAndroid.FileReaderFromTRTCLiveVideoToCommon.DataReceived += OnMessageDataReceived; SendEnterRoomMessage(); StartImageSender(); StartLocalFileWriter(); } private void SendEnterRoomMessage() { CrossPlatformHelper.Instance.LogWriter?.WriteLineInfo($"RtcMultiPusherV2 MessageClient Send EnterRoom,{_roomInfo}"); var trtcMessage = new TRTCMessage(EnumMessageType.EnterRoom, _roomInfo.ToBytes()); FISAndroid.FileWrtierFromCommonToTRTCLiveVideo.WriteMessageToFileContinuous(trtcMessage.ToBytes()); } private void StartImageSender() { _imageSender = GetCameraImageSenderV2(_deviceInfo.VideoDeviceId, _roomInfo.OutputWidth, _roomInfo.OutputHeight, _roomInfo.Category, true); _imageSender.VideoFrameDataReceived += OnVideoFrameDataReceived; _imageSender.PreviewImageFrameDataReceived += OnPreviewImageFrameDataReceived; _imageSender.Start(); } private void StopImageSender() { if (_imageSender != null) { _imageSender.VideoFrameDataReceived -= OnVideoFrameDataReceived; _imageSender.PreviewImageFrameDataReceived -= OnPreviewImageFrameDataReceived; _imageSender.Stop(); } } private void StartLocalFileWriter() { if (_localFileWriter == null) { _localFileWriter = new FileTransferWriter($"{FISTRTCConsts.ImageTransferFolderForTRTCConsultationLocalVideo}_{_roomInfo.UserId}", FISTRTCConsts.DataTransferFolderForTRTCLiveVideo, FISAndroid.FISFolderPath, null); _localFileWriter.LogMsgThrow += OnLogMsgThrow; CrossPlatformHelper.Instance.LogWriter?.WriteLineInfo($"Start Local Video File Writer :{_localFileWriter.Name}"); } } 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; } } public void Stop() { CrossPlatformHelper.Instance.LogWriter?.WriteLineInfo($"RtcMultiPusherV2 TRTCClient Stop"); FISAndroid.FileReaderFromTRTCLiveVideoToCommon.DataReceived -= OnMessageDataReceived; StopImageSender(); SendExitMessage(); StopLocalFileWriter(); } private CameraImageSenderV2 GetCameraImageSenderV2(string id, int width, int height, EnumLiveChannelCategory category, bool isFillMode) { if (width <= 640 && height <= 480) { return new Camera640X480SenderV2(id, width, height, category, isFillMode, true); } else if (width <= 960 && height <= 544) { return new Camera960X540SenderV2(id, width, height, category, isFillMode, true); ; } else if (width <= 960 && height <= 720) { return new Camera960X720SenderV2(id, width, height, category, isFillMode, true); ; } else if (width <= 1280 && height <= 720) { return new Camera1280X720SenderV2(id, width, height, category, isFillMode, true); } else { return new Camera1920X1080SenderV2(id, width, height, category, isFillMode, true); } } private void SendExitMessage() { try { CrossPlatformHelper.Instance.LogWriter?.WriteLineInfo($"RtcMultiPusherV2 MessageClient Send Exit"); var trtcMessage = new TRTCMessage(EnumMessageType.ExitRoom, new byte[0]); FISAndroid.FileWrtierFromCommonToTRTCLiveVideo.WriteMessageToFileContinuous(trtcMessage.ToBytes()); Thread.Sleep(50); } catch (Exception ex) { CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"RtcMultiPusherV2 MessageClient SendExitMessage error: {ex}"); } finally { FISAndroid.FileWrtierFromCommonToTRTCLiveVideo.ClearReserveStorageFolderPath(); } } private void OnMessageDataReceived(object sender, byte[] e) { if (e == null) { return; } var trtcMessage = TRTCMessage.FromBytes(e); CrossPlatformHelper.Instance.LogWriter?.WriteLineInfo($"RtcMultiPusherV2 Message Pipe Server Receive PipeMessage{trtcMessage?.MessageType}"); if (trtcMessage.MessageType == EnumMessageType.FirstFrameReceived) { FirstFrameReceived?.Invoke(this, Category); } } private void StopLocalFileWriter() { if (_localFileWriter != null) { _localFileWriter.Dispose(); _localFileWriter.LogMsgThrow -= OnLogMsgThrow; _localFileWriter = null; } } private void OnVideoFrameDataReceived(object sender, CPVideoFrameData e) { if (!_hasInformImageSize) { var imageSize = new TRTCImageSizeData(e.Width, e.Height); var imageSizeMessage = new TRTCMessage(EnumMessageType.ImageSizeChanged, imageSize.ToBytes()); FISAndroid.FileWrtierFromCommonToTRTCLiveVideo.WriteMessageToFileContinuous(imageSizeMessage.ToBytes()); _hasInformImageSize = true; } _localFileWriter?.WriteToFileContinuous(e.Data); } private void OnPreviewImageFrameDataReceived(object sender, ImageFrameData e) { try { _handlingImageEvent.Reset(); if (_isPreviewing) { ImageFrameReceived?.Invoke(Category, e); } } catch (Exception ex) { CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"RtcMultiPusherV2 OnPreviewImageFrameDataReceived error: {ex}"); } finally { _handlingImageEvent.Set(); } } public void StartPreview() { lock (_previewLocker) { if (_isPreviewing) return; _isPreviewing = true; } } public void SetMute(bool isMute) { CrossPlatformHelper.Instance.LogWriter?.WriteLineInfo($"RtcMultiPusherV2 MessageClient Send SetMute {isMute}"); var isMuteMessage = new TRTCBoolMessage(isMute); var muteMessage = new TRTCMessage(EnumMessageType.Mute, isMuteMessage.ToBytes()); FISAndroid.FileWrtierFromCommonToTRTCLiveVideo.WriteMessageToFileContinuous(muteMessage.ToBytes()); } public void SwitchMic(string micId) { } public void StopPreview() { lock (_previewLocker) { if (!_isPreviewing) { return; } _isPreviewing = false; } } public void Dispose() { Stop(); _handlingImageEvent.WaitOne(); } } } }