using FFmpeg.AutoGen; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Vinno.FIS.TRTCClient.Common.Enum; 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; using Vinno.vCloud.FIS.CrossPlatform.Windows.LiveVideo.RTC; namespace Vinno.vCloud.FIS.CrossPlatform.Windows.LiveVideo { public abstract unsafe class MergePusherBaseV2 : PusherBase, ILiveVideoPusherV2 { private readonly ManualResetEvent _handlingImageEvent = new ManualResetEvent(true); private readonly Dictionary _captures; private readonly IMergeUtil _mergeUtil; private readonly FormatConvertUtil _convertUtil; private AVFrame* _srcFrame; private AVFrame* _destFrame; private CancellationTokenSource _tokenSource; protected bool IsTRTCMode; protected const int MergeWidth = 1920; protected const int MergeHeight = 1080; protected const int MergeFrameRate = 20; public event EventHandler ChannelStateChanged; public MergePusherBaseV2() { _captures = new Dictionary(); _mergeUtil = new RGBMergeUtilV2(MergeWidth, MergeHeight); _convertUtil = new FormatConvertUtil(); } public virtual bool StartPusher(IExtendedData pushParams, IEnumerable deviceInfos) { InitPushersAndMergeOffsets(pushParams, deviceInfos); StartCaptures(); StartPostMergeImageDataThread(); return true; } public virtual bool StopPusher() { StopPostMergeImageDataThread(); StopCaptures(); return true; } public abstract void SetMute(bool isMute); public abstract void SwitchMic(string micId); protected void InitCacheImage(AVPixelFormat destFormat) { _srcFrame = AvFrameOperateUtil.Create(MergeWidth, MergeHeight, AVPixelFormat.AV_PIX_FMT_BGRA); _destFrame = AvFrameOperateUtil.Create(MergeWidth, MergeHeight, destFormat); } protected virtual void DealWithFullBuffer(AVFrame* frame) { } protected void PushLiveStateChanged() { ChannelStateChanged?.Invoke(this, new ChannelStateEventArgsV2(EnumLiveChannelCategory.Main, EnumDeviceLiveState.Pushing)); } private void InitPushersAndMergeOffsets(IExtendedData pushParams, IEnumerable deviceInfos) { var areaImageInfos = new List(); if (pushParams is RtcExtendedData rtcExtendedData) { foreach (var user in rtcExtendedData.UserInfos) { var deviceInfo = deviceInfos.FirstOrDefault(c => c.Category == user.Category); if (deviceInfo != null) { ICapturer capturer; if (!CommonParameter.Instance.IsSonopost && user.Category == EnumLiveChannelCategory.Main) { capturer = new TerminalImageCapturerV2(deviceInfo.VideoDeviceId, user.Width, user.Height, EnumImageType.ImageFrameData); } else if (!CommonParameter.Instance.IsSonopost && user.Category != EnumLiveChannelCategory.Main) { capturer = new Capturer(deviceInfo.VideoDeviceId, user.Width, user.Height, 15, user.Category, deviceInfo.OutputWidth, deviceInfo.OutputHeight, true); } else { capturer = new Capturer(deviceInfo.VideoDeviceId, user.Width, user.Height, 20, user.Category, deviceInfo.OutputWidth, deviceInfo.OutputHeight, false); } areaImageInfos.Add(new AreaImageInfo { Width = user.Width, Height = user.Height, Category = user.Category }); _captures.Add(user.Category, capturer); } else { CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"MergePusherBaseV2 Can not find {user.Category} Device Info!"); } } } else if (pushParams is RtmpExtendedData rtmpExtendedData) { foreach (var user in rtmpExtendedData.UserInfos) { var deviceInfo = deviceInfos.FirstOrDefault(c => c.Category == user.Category); if (deviceInfo != null) { ICapturer capturer; if (!CommonParameter.Instance.IsSonopost && user.Category == EnumLiveChannelCategory.Main) { capturer = new TerminalImageCapturerV2(deviceInfo.VideoDeviceId, user.Width, user.Height, EnumImageType.ImageFrameData); } else if (!CommonParameter.Instance.IsSonopost && user.Category != EnumLiveChannelCategory.Main) { capturer = new Capturer(deviceInfo.VideoDeviceId, user.Width, user.Height, 15, user.Category, deviceInfo.OutputWidth, deviceInfo.OutputHeight, true); } else { capturer = new Capturer(deviceInfo.VideoDeviceId, user.Width, user.Height, 20, user.Category, deviceInfo.OutputWidth, deviceInfo.OutputHeight, false); } areaImageInfos.Add(new AreaImageInfo { Width = user.Width, Height = user.Height, Category = user.Category }); _captures.Add(user.Category, capturer); } else { CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"MergePusherBaseV2 Can not find {user.Category} Device Info!"); } } } _mergeUtil.InitAreaOffset(areaImageInfos); } private void StartCaptures() { foreach (var capturer in _captures.Values) { capturer.ImageFrameReceived += OnImageReceived; capturer.StartCapture(); } } private void StopCaptures() { foreach (var capture in _captures.Values) { capture.ImageFrameReceived -= OnImageReceived; capture.StopCapture(); capture.Dispose(); } _captures.Clear(); } private unsafe void StartPostMergeImageDataThread() { if (_tokenSource != null && !_tokenSource.IsCancellationRequested) { _tokenSource.Cancel(); } _tokenSource = new CancellationTokenSource(); Task.Run(() => { while (!_tokenSource.IsCancellationRequested) { try { _handlingImageEvent.Reset(); _mergeUtil.GetFullBuffer(new IntPtr(_srcFrame->data[0])); _convertUtil.Convert(_srcFrame, _destFrame); DealWithFullBuffer(_destFrame); } catch (Exception e) { CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"Get Full Buffer Error:{e}"); } finally { _handlingImageEvent.Set(); Thread.Sleep(35); } } _convertUtil.Dispose(); }, _tokenSource.Token); } private void StopPostMergeImageDataThread() { _tokenSource?.Cancel(); } private void OnImageReceived(object sender, ImageFrameData e) { try { if (sender is ICapturer capture) { var area = GetAreaAndCategory(capture.Id, out var category); _mergeUtil.CopyToArea(area, e); CopyPreviewData(category, e); } } catch (Exception ex) { CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"OnImageReceived Error:{ex}"); } } private EnumArea GetAreaAndCategory(string id, out EnumLiveChannelCategory category) { foreach (var item in _captures) { if (item.Value.Id == id) { category = item.Key; return GetArea(category); } } throw new Exception("Can not get correct area"); } protected abstract EnumArea GetArea(EnumLiveChannelCategory category); public bool StartSpeedTest(uint appId, string userId, string userSign) { if (!IsTRTCMode) { CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"StartSpeedTest Fail,it only support TRTC Modes"); return false; } 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() { _handlingImageEvent.WaitOne(); AvFrameOperateUtil.Destory(_srcFrame); AvFrameOperateUtil.Destory(_destFrame); _mergeUtil?.Dispose(); base.DoDispose(); } } }