493 lines
18 KiB
C#
493 lines
18 KiB
C#
|
|
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;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|