JianGongYun/ClientDemo/TXLiteAVVideoView.cs

493 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 };
}
/// <summary>
/// 设置 View 绑定参数
/// </summary>
/// <param name="userId">需要渲染画面的 userId如果是本地画面则传空字符串。</param>
/// <param name="type">渲染类型</param>
/// <param name="engine">TRTCCloud 实例,用户注册视频数据回调。</param>
/// <param name="local">渲染本地画面SDK 返回的 userId 为""</param>
/// <returns>true绑定成功false绑定失败</returns>
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;
}
/// <summary>
/// 移除 View 绑定参数
/// </summary>
/// <param name="engine">TRTCCloud 实例,用户注册视频数据回调。</param>
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();
}
/// <summary>
/// 设置 View 的渲染模式
/// </summary>
/// <param name="mode">渲染模式</param>
public void SetRenderMode(TRTCVideoFillMode mode)
{
mRenderMode = mode;
}
/// <summary>
/// 判断 View 是否被占用
/// </summary>
/// <returns>true当前 View 已被占用false当前 View 未被占用</returns>
public bool IsViewOccupy()
{
return mOccupy;
}
/// <summary>
/// 暂停渲染,显示默认图片
/// </summary>
/// <param name="pause">是否暂停</param>
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();
}
}
/// <summary>
/// 清除所有映射信息
/// </summary>
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<WriteableBitmap> OnRenderVideoFrameHandler;
/// <summary>
/// 渲染回调byte[] data,int width, int height
/// </summary>
public event Action<byte[], int, int> 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<string, TXLiteAVVideoView> 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<string, TXLiteAVVideoView>();
}
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<string, TRTCVideoStreamType, TRTCVideoFrame> OnRenderVideoFrameHandler;
/// <summary>
/// 自定义渲染回调,只能存在一个回调
/// </summary>
/// <param name="userId"></param>
/// <param name="streamType"></param>
/// <param name="frame"></param>
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;
}
}
}