using ManageLiteAV; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Imaging; namespace JianGongYun.TRTC.Components { public class TXLiteAVVideoView : Canvas, IDisposable { private bool mOccupy = false; // view 是否已被占用 private bool mLocalView = false; // 是否为本地画面 private bool mPause = false; private bool mFirstFrame = false; private string mUserId; private TRTCVideoStreamType mStreamType; private TRTCVideoFillMode mRenderMode = TRTCVideoFillMode.TRTCVideoFillMode_Fit; // 0:填充,1:适应 private volatile FrameBufferInfo mArgbFrame = new FrameBufferInfo(); // 帧缓存 // 位图缓存,防止GC频繁 private WriteableBitmap mWriteableBitmap; private Int32Rect mInt32Rect; private Pen mPen; public TXLiteAVVideoView() { mPen = new Pen { Brush = Brushes.DarkGray, Thickness = 1 }; } /// /// 设置 View 绑定参数 /// /// 需要渲染画面的 userId,如果是本地画面,则传空字符串。 /// 渲染类型 /// TRTCCloud 实例,用户注册视频数据回调。 /// 渲染本地画面,SDK 返回的 userId 为"" /// true:绑定成功,false:绑定失败 public bool RegEngine(string userId, TRTCVideoStreamType type, ITRTCCloud engine, bool local = false) { if (mOccupy) return false; mLocalView = local; mUserId = userId; mStreamType = type; int count = TXLiteAVVideoViewManager.GetInstance().Count; if (engine != null) { if (count == 0) { engine.setLocalVideoRenderCallback(TRTCVideoPixelFormat.TRTCVideoPixelFormat_BGRA32, TRTCVideoBufferType.TRTCVideoBufferType_Buffer, TXLiteAVVideoViewManager.GetInstance()); } if (!mLocalView) { engine.setRemoteVideoRenderCallback(userId, TRTCVideoPixelFormat.TRTCVideoPixelFormat_BGRA32, TRTCVideoBufferType.TRTCVideoBufferType_Buffer, TXLiteAVVideoViewManager.GetInstance()); } } if (mLocalView) TXLiteAVVideoViewManager.GetInstance().AddView("", type, this); else TXLiteAVVideoViewManager.GetInstance().AddView(userId, type, this); lock (mArgbFrame) ReleaseBuffer(mArgbFrame); mOccupy = true; this.InvalidateVisual(); return true; } /// /// 移除 View 绑定参数 /// /// TRTCCloud 实例,用户注册视频数据回调。 public void RemoveEngine(ITRTCCloud engine) { if (mLocalView) TXLiteAVVideoViewManager.GetInstance().RemoveView("", mStreamType, this); else TXLiteAVVideoViewManager.GetInstance().RemoveView(mUserId, mStreamType, this); int count = TXLiteAVVideoViewManager.GetInstance().Count; if (engine != null) { if (count == 0) { engine.setLocalVideoRenderCallback(TRTCVideoPixelFormat.TRTCVideoPixelFormat_Unknown, TRTCVideoBufferType.TRTCVideoBufferType_Unknown, null); } if (!mLocalView && !TXLiteAVVideoViewManager.GetInstance().HasUserId(mUserId)) { engine.setRemoteVideoRenderCallback(mUserId, TRTCVideoPixelFormat.TRTCVideoPixelFormat_Unknown, TRTCVideoBufferType.TRTCVideoBufferType_Unknown, null); } } lock (mArgbFrame) ReleaseBuffer(mArgbFrame); mUserId = ""; mOccupy = false; mLocalView = false; mFirstFrame = false; mRenderMode = TRTCVideoFillMode.TRTCVideoFillMode_Fit; //this.OnViewRemove?.Invoke(); this.InvalidateVisual(); } /// /// 设置 View 的渲染模式 /// /// 渲染模式 public void SetRenderMode(TRTCVideoFillMode mode) { mRenderMode = mode; } /// /// 判断 View 是否被占用 /// /// true:当前 View 已被占用,false:当前 View 未被占用 public bool IsViewOccupy() { return mOccupy; } /// /// 暂停渲染,显示默认图片 /// /// 是否暂停 public void SetPause(bool pause) { if (mPause != pause) { mPause = pause; if (mPause) { this.Background = new SolidColorBrush(Color.FromArgb(0xFF, 0x20, 0x20, 0x20)); } else { this.Background = new SolidColorBrush(Color.FromArgb(0xFF, 0x00, 0x00, 0x00)); // 避免刷新最后一帧数据 lock (mArgbFrame) ReleaseBuffer(mArgbFrame); } this.InvalidateVisual(); } } /// /// 清除所有映射信息 /// public static void RemoveAllRegEngine() { TXLiteAVVideoViewManager.GetInstance().RemoveAllView(); } public bool AppendVideoFrame(byte[] data, int width, int height, TRTCVideoPixelFormat videoFormat, TRTCVideoRotation rotation) { if (!mFirstFrame) mFirstFrame = true; OnRenderVideoFrameHandler?.Invoke(data, width, height); if (mPause) return false; if (data == null || data.Length <= 0) return false; // data 数据有误 if (videoFormat == TRTCVideoPixelFormat.TRTCVideoPixelFormat_BGRA32 && width * height * 4 != data.Length) return false; // 暂时不支持其他 YUV 类型 if (videoFormat == TRTCVideoPixelFormat.TRTCVideoPixelFormat_I420 && width * height * 3 / 2 != data.Length) return false; // 目前只实现了 BGRA32 的数据类型,如需其他类型请重写,并设置回调的数据类型 if (videoFormat == TRTCVideoPixelFormat.TRTCVideoPixelFormat_BGRA32) { lock (mArgbFrame) { if (mArgbFrame.data == null || mArgbFrame.width != width || mArgbFrame.height != height) { ReleaseBuffer(mArgbFrame); mArgbFrame.width = width; mArgbFrame.height = height; mArgbFrame.data = new byte[data.Length]; } Buffer.BlockCopy(data, 0, mArgbFrame.data, 0, (int)data.Length); mArgbFrame.newFrame = true; mArgbFrame.rotation = rotation; } } // 回到主线程刷新当前画面 this.Dispatcher.Invoke(new Action(() => { this.InvalidateVisual(); })); return true; } protected override void OnRender(DrawingContext dc) { bool needDrawFrame = true; if (mPause) needDrawFrame = false; if (mArgbFrame.data == null) needDrawFrame = false; if (!needDrawFrame) { return; } lock (mArgbFrame) { if (mArgbFrame.data == null) return; if (mRenderMode == TRTCVideoFillMode.TRTCVideoFillMode_Fill) { RenderFillMode(dc, mArgbFrame.data, mArgbFrame.width, mArgbFrame.height, (int)mArgbFrame.rotation * 90); } else if (mRenderMode == TRTCVideoFillMode.TRTCVideoFillMode_Fit) { RenderFitMode(dc, mArgbFrame.data, mArgbFrame.width, mArgbFrame.height, (int)mArgbFrame.rotation * 90); } } } //public event Action OnRenderVideoFrameHandler; /// /// 渲染回调byte[] data,int width, int height /// public event Action OnRenderVideoFrameHandler; //public event Action OnViewRemove; private void RenderFillMode(DrawingContext dc, byte[] data, int width, int height, int rotation) { int viewWidth = (int)this.ActualWidth, viewHeight = (int)this.ActualHeight; PixelFormat pixelFormat = PixelFormats.Pbgra32; int bytesPerPixel = (pixelFormat.BitsPerPixel + 7) / 8; int stride = bytesPerPixel * width; if (mWriteableBitmap == null || mWriteableBitmap.PixelWidth != width || mWriteableBitmap.PixelHeight != height) { mWriteableBitmap = new WriteableBitmap(width, height, 96, 96, pixelFormat, null); mInt32Rect = new Int32Rect(0, 0, width, height); } mWriteableBitmap.Lock(); Marshal.Copy(data, 0, mWriteableBitmap.BackBuffer, data.Length); mWriteableBitmap.AddDirtyRect(mInt32Rect); mWriteableBitmap.Unlock(); ImageBrush brush = new ImageBrush(mWriteableBitmap); if (rotation > 0) { Matrix transform = Matrix.Identity; double scale = (double)viewWidth / (double)viewHeight; if (rotation == 90 || rotation == 270) transform.ScaleAt(scale, scale, 0.5, 0.5); transform.RotateAt(rotation, 0.5, 0.5); brush.RelativeTransform = new MatrixTransform(transform); } brush.Stretch = Stretch.UniformToFill; Rect rect = new Rect(0, 0, viewWidth, viewHeight); dc.DrawRectangle(brush, mPen, rect); } private void RenderFitMode(DrawingContext dc, byte[] data, int width, int height, int rotation) { int viewWidth = (int)this.ActualWidth, viewHeight = (int)this.ActualHeight; PixelFormat pixelFormat = PixelFormats.Pbgra32; int bytesPerPixel = (pixelFormat.BitsPerPixel + 7) / 8; int stride = bytesPerPixel * width; if (mWriteableBitmap == null || mWriteableBitmap.PixelWidth != width || mWriteableBitmap.PixelHeight != height) { mWriteableBitmap = new WriteableBitmap(width, height, 96, 96, pixelFormat, null); mInt32Rect = new Int32Rect(0, 0, width, height); } mWriteableBitmap.Lock(); Marshal.Copy(data, 0, mWriteableBitmap.BackBuffer, data.Length); mWriteableBitmap.AddDirtyRect(mInt32Rect); mWriteableBitmap.Unlock(); //OnRenderVideoFrameHandler?.Invoke(mWriteableBitmap); ImageBrush brush = new ImageBrush(mWriteableBitmap); if (rotation > 0) { Matrix transform = Matrix.Identity; double scale = (double)viewHeight / (double)viewWidth; if (rotation == 90 || rotation == 270) transform.ScaleAt(1, scale, 0.5, 0.5); transform.RotateAt(rotation, 0.5, 0.5); brush.RelativeTransform = new MatrixTransform(transform); } brush.Stretch = Stretch.Uniform; Rect rect = new Rect(0, 0, viewWidth, viewHeight); dc.DrawRectangle(brush, mPen, rect); } private void ReleaseBuffer(FrameBufferInfo info) { if (info.data != null) info.data = null; info.width = 0; info.height = 0; info.newFrame = false; info.rotation = TRTCVideoRotation.TRTCVideoRotation0; } #region Dispose private bool disposed = false; public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposed) return; if (disposing) { ReleaseBuffer(mArgbFrame); mWriteableBitmap = null; } disposed = true; } ~TXLiteAVVideoView() { this.Dispose(false); } #endregion } public class TXLiteAVVideoViewManager : ITRTCVideoRenderCallback { private volatile Dictionary mMapViews; public static TXLiteAVVideoViewManager sInstance; private static Object mLocker = new Object(); public static TXLiteAVVideoViewManager GetInstance() { if (sInstance == null) { lock (mLocker) { if (sInstance == null) sInstance = new TXLiteAVVideoViewManager(); } } return sInstance; } private TXLiteAVVideoViewManager() { mMapViews = new Dictionary(); } private string GetKey(string userId, TRTCVideoStreamType type) { return String.Format("{0}_{1}", userId, type); } // 主要用于判断当前 user 是否还有存在流画面,存在则不移除监听。 public bool HasUserId(string userId) { bool exit = false; lock (mMapViews) { exit = mMapViews.ContainsKey(GetKey(userId, TRTCVideoStreamType.TRTCVideoStreamTypeBig)) || mMapViews.ContainsKey(GetKey(userId, TRTCVideoStreamType.TRTCVideoStreamTypeSub)); } return exit; } public void AddView(string userId, TRTCVideoStreamType type, TXLiteAVVideoView view) { lock (mMapViews) { bool find = false; foreach (var item in mMapViews) { if (item.Key.Equals(GetKey(userId, type))) { find = true; break; } } if (!find) { mMapViews.Add(GetKey(userId, type), view); } } } public void RemoveView(string userId, TRTCVideoStreamType type, TXLiteAVVideoView view) { lock (mMapViews) { foreach (var item in mMapViews.ToList()) { if (item.Key.Equals(GetKey(userId, type))) { if (item.Value != null) { item.Value.Dispose(); } mMapViews.Remove(item.Key); break; } } } } public void RemoveAllView() { lock (mMapViews) mMapViews.Clear(); } public int Count { get { lock (mMapViews) return mMapViews.Count; } } //public event Action OnRenderVideoFrameHandler; /// /// 自定义渲染回调,只能存在一个回调 /// /// /// /// public void onRenderVideoFrame(string userId, TRTCVideoStreamType streamType, TRTCVideoFrame frame) { //OnRenderVideoFrameHandler?.Invoke(userId, streamType, frame); // 大小视频是占一个视频位,底层支持动态切换。 if (streamType == TRTCVideoStreamType.TRTCVideoStreamTypeSmall) streamType = TRTCVideoStreamType.TRTCVideoStreamTypeBig; TXLiteAVVideoView view = null; lock (mMapViews) { foreach (var item in mMapViews) { if (item.Key.Equals(GetKey(userId, streamType)) && item.Value != null) { view = item.Value; break; } } } if (view != null) view.AppendVideoFrame(frame.data, (int)frame.width, (int)frame.height, frame.videoFormat, frame.rotation); } } class FrameBufferInfo { public byte[] data { get; set; } public int width { get; set; } public int height { get; set; } public bool newFrame { get; set; } public TRTCVideoRotation rotation { get; set; } public FrameBufferInfo() { rotation = TRTCVideoRotation.TRTCVideoRotation0; newFrame = false; width = 0; height = 0; data = null; } } }