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; namespace Vinno.vCloud.FIS.CrossPlatform.Windows.LiveVideo { public abstract unsafe class MergePusherBase : PusherBase, ILiveVideoPusherForSonopost { private readonly Dictionary _captures; private readonly IMergeUtil _mergeUtil; private readonly FormatConvertUtil _convertUtil; private readonly ManualResetEvent _handlingImageEvent = new ManualResetEvent(true); private CancellationTokenSource _tokenSource; private AVFrame* _srcFrame; private AVFrame* _destFrame; protected const int MergeWidth = 1920; protected const int MergeHeight = 1080; protected const int MergeFrameRate = 20; public event EventHandler ChannelStateChanged; public MergePusherBase() { _captures = new Dictionary(); _mergeUtil = new RGBMergeUtil(MergeWidth, MergeHeight); _convertUtil = new FormatConvertUtil(); } public virtual bool StartPusher(IExtendedData pushParams, IEnumerable deviceInfos) { InitPushersAndMergeOffsets(pushParams, deviceInfos); StartCaptures(); StartPostMergeImageDataThread(); return true; } public abstract void SetMute(bool isMute); public virtual bool StopPusher() { StopPostMergeImageDataThread(); StopCaptures(); return true; } 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 ChannelStateEventArgs(EnumLiveChannelCategory.Main, EnumLiveStates.TerminalIsPushing)); } 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) { var capturer = new Capturer(deviceInfo.Id, user.Width, user.Height, user.Category == EnumLiveChannelCategory.Main ? 20 : 15, user.Category, deviceInfo.Width, deviceInfo.Height, false); areaImageInfos.Add(new AreaImageInfo { Width = user.Width, Height = user.Height, Category = user.Category }); _captures.Add(user.Category, capturer); } else { CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"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) { var capturer = new Capturer(deviceInfo.Id, user.Width, user.Height, user.Category == EnumLiveChannelCategory.Main ? 20 : 15, user.Category, deviceInfo.Width, deviceInfo.Height, false); areaImageInfos.Add(new AreaImageInfo { Width = user.Width, Height = user.Height, Category = user.Category }); _captures.Add(user.Category, capturer); } else { CrossPlatformHelper.Instance.LogWriter?.WriteLineError($"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 override void DoDispose() { _handlingImageEvent.WaitOne(); AvFrameOperateUtil.Destory(_srcFrame); AvFrameOperateUtil.Destory(_destFrame); _mergeUtil?.Dispose(); base.DoDispose(); } } }