514 lines
21 KiB
C#
514 lines
21 KiB
C#
using JianGongYun.TRTC.Components;
|
||
using JianGongYun.TRTC.Models;
|
||
using JianGongYun.TRTC.Utils;
|
||
using JianGongYun.TRTC.ViewModels;
|
||
using JianGongYun.TRTC.Windows;
|
||
using ManageLiteAV;
|
||
using OpenCvSharp;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Text;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using System.Windows;
|
||
using System.Windows.Controls;
|
||
using Window = System.Windows.Window;
|
||
using System.Collections.Concurrent;
|
||
using System.Windows.Data;
|
||
using System.Drawing;
|
||
using System.Diagnostics;
|
||
using System.Runtime.InteropServices;
|
||
|
||
namespace JianGongYun.TRTC
|
||
{
|
||
/// <summary>
|
||
/// 直播调用对象全放这里
|
||
/// </summary>
|
||
public static class LiveClassroom
|
||
{
|
||
const uint SDKAppID = 1400463444;
|
||
const string SDKAppKEY = "6ee2586282eb8ab5bff3f917b44500c4ffd9bbd3d820258b1fa8cdd470cfd1ee";
|
||
/// <summary>
|
||
/// TRTC实例
|
||
/// </summary>
|
||
public static ITRTCCloud lTRTCCloud;
|
||
/// <summary>
|
||
/// 设备管理器
|
||
/// </summary>
|
||
public static ITXDeviceManager lTXDeviceManager;
|
||
/// <summary>
|
||
/// live窗口的调用窗口
|
||
/// </summary>
|
||
public static Window CallerWindow { get; private set; }
|
||
/// <summary>
|
||
/// 直播窗口
|
||
/// </summary>
|
||
public static Window CurrentLiveWindow;
|
||
private static LiveWindowViewModel liveWinMode;
|
||
public static ClassroomEntity CurrentClassroomEntity { get; private set; }
|
||
public static TRTCCloudCallback TRTCCloudCallback = new TRTCCloudCallback();
|
||
/// <summary>
|
||
/// 摄像头帧
|
||
/// </summary>
|
||
public static Mat MainFrame = new Mat();
|
||
/// <summary>
|
||
/// 屏幕帧
|
||
/// </summary>
|
||
public static Mat SubFrame = new Mat();
|
||
/// <summary>
|
||
/// 背景帧
|
||
/// </summary>
|
||
public static Mat BackgroundFrame = null;
|
||
//public static int UserCount = 999;
|
||
/// <summary>
|
||
/// 进入直播教室
|
||
/// </summary>
|
||
/// <param name="callerWindow">调用窗口</param>
|
||
/// <param name="classroomEntity">教室数据</param>
|
||
public static void EnterTheClassroom(Window callerWindow, ClassroomEntity classroomEntity)
|
||
{
|
||
if (CurrentLiveWindow != null)
|
||
{
|
||
_ = AduSkin.Controls.Metro.AduMessageBox.Show("已经进入教室", "提醒");
|
||
CurrentLiveWindow.Topmost = true;
|
||
return;
|
||
}
|
||
CallerWindow = callerWindow;
|
||
CurrentClassroomEntity = classroomEntity;
|
||
CurrentLiveWindow = new LiveWindow();
|
||
liveWinMode = CurrentLiveWindow.DataContext as LiveWindowViewModel;
|
||
CurrentLiveWindow.Closed += CurrentLiveWindow_Closed;
|
||
|
||
lTRTCCloud = ITRTCCloud.getTRTCShareInstance();//创建TRTC实例
|
||
var roomPars = new TRTCParams
|
||
{
|
||
sdkAppId = SDKAppID,
|
||
userId = CurrentClassroomEntity.TeacherId,
|
||
userSig = GenTestUserSig(CurrentClassroomEntity.TeacherId),
|
||
roomId = 0,//使用strRoomId
|
||
strRoomId = CurrentClassroomEntity.TeacherId,
|
||
role = TRTCRoleType.TRTCRoleAnchor
|
||
};
|
||
lTRTCCloud.enterRoom(ref roomPars, TRTCAppScene.TRTCAppSceneLIVE);//创建房间
|
||
|
||
//设备
|
||
//var settingViewMode = ViewModels.SettingWindowViewModel.GetInstance();
|
||
lTXDeviceManager = lTRTCCloud.getDeviceManager();
|
||
var currentMic = settingWindowViewModel.CurrentMic;//麦克风
|
||
if (!string.IsNullOrEmpty(currentMic))
|
||
{
|
||
var res = lTXDeviceManager.setCurrentDevice(TRTCDeviceType.TXMediaDeviceTypeMic, currentMic);
|
||
Console.WriteLine($"设置麦克风:{res}");
|
||
}
|
||
lTXDeviceManager.setCurrentDeviceVolume(TRTCDeviceType.TXMediaDeviceTypeMic, settingWindowViewModel.MicVolume);//麦克风采集音量
|
||
lTRTCCloud.setSystemAudioLoopbackVolume(settingWindowViewModel.SytemGatherVolume);//系统声音采集音量
|
||
if (settingWindowViewModel.AudioSource == "2")
|
||
{
|
||
lTRTCCloud.startSystemAudioLoopback(null);
|
||
}
|
||
else
|
||
{
|
||
lTRTCCloud.stopSystemAudioLoopback();
|
||
}
|
||
//设备完结
|
||
|
||
//liveWinMode.LoadAllScreen();
|
||
_RecoderDir = $"{classroomEntity.ClassHead}_{classroomEntity.ClassSubHead}";
|
||
|
||
lTRTCCloud.addCallback(TRTCCloudCallback);//注册回调
|
||
|
||
callerWindow.Hide();//隐藏调用窗口
|
||
CurrentLiveWindow.Show();
|
||
}
|
||
|
||
private static string _RecoderDir;
|
||
/// <summary>
|
||
/// 录制内容的具体路径
|
||
/// </summary>
|
||
public static string RecoderDir
|
||
{
|
||
get
|
||
{
|
||
var temp = Path.Combine(settingWindowViewModel.ScreenRecordingDir, _RecoderDir);
|
||
if (!Directory.Exists(temp))
|
||
{
|
||
Directory.CreateDirectory(temp);
|
||
}
|
||
return temp;
|
||
}
|
||
}
|
||
|
||
private static SettingWindowViewModel settingWindowViewModel = SettingWindowViewModel.GetInstance();
|
||
|
||
|
||
/// <summary>
|
||
/// 视频画面
|
||
/// </summary>
|
||
public static Dictionary<string, TXLiteAVVideoView> VideoViews = new Dictionary<string, TXLiteAVVideoView>();
|
||
/// <summary>
|
||
/// 启动摄像头
|
||
/// </summary>
|
||
/// <param name="parent">视频容器</param>
|
||
public static TXLiteAVVideoView StartVideoMain(Panel parent)
|
||
{
|
||
if (!liveWinMode.CameraRunning)
|
||
{
|
||
var encParams = settingWindowViewModel.MainEncParams;
|
||
var qosParams = settingWindowViewModel.MainQosParams;
|
||
var renderParams = settingWindowViewModel.MainRenderParams;
|
||
lTRTCCloud.setVideoEncoderParam(ref encParams);
|
||
lTRTCCloud.setNetworkQosParam(ref qosParams);
|
||
lTRTCCloud.setLocalRenderParams(ref renderParams);
|
||
lTRTCCloud.startLocalPreview(IntPtr.Zero);
|
||
liveWinMode.CameraRunning = true;
|
||
var view = AddCustomVideoView(parent, CurrentClassroomEntity.TeacherId, TRTCVideoStreamType.TRTCVideoStreamTypeBig, true);
|
||
if (liveWinMode.IsLive)
|
||
{
|
||
//Stopwatch sw = new Stopwatch();
|
||
//VideoRecordTask(view, TRTCVideoStreamType.TRTCVideoStreamTypeBig);
|
||
view.OnRenderVideoFrameHandler += (data, w, h) =>
|
||
{
|
||
//sw.Restart();
|
||
lock (MainFrame)
|
||
{
|
||
MainFrame.Create(h, w, MatType.CV_8UC4);
|
||
Marshal.Copy(data, 0, MainFrame.Data, data.Length);
|
||
}
|
||
//sw.Stop();
|
||
//Debug.Print("main" + sw.ElapsedMilliseconds.ToString());
|
||
};
|
||
}
|
||
return view;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 停止摄像头
|
||
/// </summary>
|
||
/// <param name="parent"></param>
|
||
public static void StopVideoMain(Panel parent)
|
||
{
|
||
if (liveWinMode.CameraRunning)
|
||
{
|
||
liveWinMode.CameraRunning = false;
|
||
lTRTCCloud.stopLocalPreview();
|
||
RemoveCustomVideoView(parent, CurrentClassroomEntity.TeacherId, TRTCVideoStreamType.TRTCVideoStreamTypeBig, true);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 启动屏幕分享
|
||
/// </summary>
|
||
/// <param name="parent"></param>
|
||
public static TXLiteAVVideoView StartVideoSub(Panel parent)
|
||
{
|
||
if (!liveWinMode.ScreenRunning)
|
||
{
|
||
liveWinMode.ScreenRunning = true;
|
||
SelectVieoSub();
|
||
lTRTCCloud.startScreenCapture(IntPtr.Zero, TRTCVideoStreamType.TRTCVideoStreamTypeSub, settingWindowViewModel.SubEncParams);
|
||
var view = AddCustomVideoView(parent, CurrentClassroomEntity.TeacherId, TRTCVideoStreamType.TRTCVideoStreamTypeSub, true);
|
||
if (liveWinMode.IsLive)
|
||
{
|
||
Stopwatch sw = new Stopwatch();
|
||
//VideoRecordTask(view, TRTCVideoStreamType.TRTCVideoStreamTypeSub);
|
||
view.OnRenderVideoFrameHandler += (data, w, h) =>
|
||
{
|
||
sw.Restart();
|
||
lock (SubFrame)
|
||
{
|
||
SubFrame.Create(h, w, MatType.CV_8UC4);
|
||
Marshal.Copy(data, 0, SubFrame.Data, data.Length);
|
||
}
|
||
sw.Stop();
|
||
Debug.Print("sub" + sw.ElapsedMilliseconds.ToString());
|
||
};
|
||
}
|
||
return view;
|
||
}
|
||
return null;
|
||
}
|
||
/// <summary>
|
||
/// 停止屏幕分享
|
||
/// </summary>
|
||
/// <param name="parent"></param>
|
||
public static void StopVideoSub(Panel parent)
|
||
{
|
||
if (liveWinMode.ScreenRunning)
|
||
{
|
||
liveWinMode.ScreenRunning = false;
|
||
lTRTCCloud.stopScreenCapture();
|
||
RemoveCustomVideoView(parent, CurrentClassroomEntity.TeacherId, TRTCVideoStreamType.TRTCVideoStreamTypeSub, true);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 选择要分享的屏幕
|
||
/// </summary>
|
||
public static void SelectVieoSub()
|
||
{
|
||
var current = liveWinMode.CurrentShareScreen;
|
||
var rect = new RECT();
|
||
var property = new TRTCScreenCaptureProperty();
|
||
lTRTCCloud.selectScreenCaptureTarget(ref current, ref rect, ref property);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 启动麦克风
|
||
/// </summary>
|
||
public static void StartMic()
|
||
{
|
||
if (!liveWinMode.MicRunning)
|
||
{
|
||
liveWinMode.MicRunning = true;
|
||
lTRTCCloud.startLocalAudio(settingWindowViewModel.LiveAudioLevel);
|
||
if (liveWinMode.IsLive && !liveWinMode.AudioRecordRunning)
|
||
{
|
||
liveWinMode.AudioRecordRunning = true;
|
||
var time = Util.TimeStr();
|
||
var pars = new TRTCAudioRecordingParams { filePath = Path.Combine(RecoderDir, $"{time}audio.wav") };
|
||
var res = lTRTCCloud.startAudioRecording(ref pars);
|
||
Console.WriteLine(res);
|
||
}
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 关闭麦克风
|
||
/// </summary>
|
||
public static void StopMic()
|
||
{
|
||
if (liveWinMode.MicRunning)
|
||
{
|
||
liveWinMode.MicRunning = false;
|
||
if (liveWinMode.AudioRecordRunning)
|
||
{
|
||
liveWinMode.AudioRecordRunning = false;
|
||
lTRTCCloud.stopAudioRecording();
|
||
}
|
||
lTRTCCloud.stopLocalAudio();
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 设置麦克风静音
|
||
/// </summary>
|
||
public static void SetMicMute(bool? mute = true)
|
||
{
|
||
if (liveWinMode.MicRunning)
|
||
{
|
||
liveWinMode.MicMute = mute.HasValue ? mute.Value : !liveWinMode.MicMute;
|
||
lTRTCCloud.muteLocalAudio(liveWinMode.MicMute);
|
||
}
|
||
}
|
||
|
||
|
||
public static void VideoRecordTask(ref Action onEnd)
|
||
{
|
||
var end = false;
|
||
onEnd = () =>
|
||
{
|
||
end = true;
|
||
BackgroundFrame?.Dispose();
|
||
BackgroundFrame = null;
|
||
};
|
||
var resolution = settingWindowViewModel.MainEncParams.videoResolution.ToString().Split('_');//屏幕分辨率
|
||
var fps = settingWindowViewModel.LiveFps;
|
||
BackgroundFrame = new Mat(int.Parse(resolution[1]), int.Parse(resolution[2]), MatType.CV_8UC3, Scalar.FromRgb(0x20, 0x20, 0x20));
|
||
|
||
//Stopwatch sw1 = new Stopwatch();
|
||
//Stopwatch sw2 = new Stopwatch();
|
||
//ConcurrentQueue<Mat> bitmaps = new ConcurrentQueue<Mat>();
|
||
//view.OnRenderVideoFrameHandler += (b) =>
|
||
//{
|
||
// sw1.Restart();
|
||
// bitmaps.Enqueue(b.ToMat());
|
||
// sw1.Stop();
|
||
// //Debug.Print("a" + sw1.Elapsed.TotalMilliseconds.ToString());
|
||
//};
|
||
////view.OnRenderVideoFrameHandler1 += (a, b, c) =>
|
||
////{
|
||
//// var bb = System.Runtime.InteropServices.Marshal.AllocHGlobal(System.Runtime.InteropServices.Marshal.SizeOf(a[0]) * a.Length);
|
||
//// System.Runtime.InteropServices.Marshal.Copy(a, 0, bb, a.Length);
|
||
//// var mat = new Mat(c, b, MatType.CV_32SC4, bb);
|
||
//// mat.SaveImage("1.bmp");
|
||
////};
|
||
////view.OnViewRemove += () =>
|
||
////{
|
||
//// end = true;
|
||
////};
|
||
//var task = Task.Factory.StartNew(() =>
|
||
//{
|
||
// Console.WriteLine($"VideoRecordTask Start {streamType}");
|
||
// var _recoderDir = RecoderDir;
|
||
// var imgTemp = Path.Combine(_recoderDir, $"{streamType}.png");
|
||
// var videoFile = Path.Combine(_recoderDir, $"{Util.TimeStr()}_{streamType}.avi");
|
||
// VideoWriter vw = new VideoWriter();
|
||
// bool videoWriterInit = false;
|
||
// var frameCount = 0;
|
||
// Timer timer = new Timer((a) =>
|
||
// {
|
||
// //Console.WriteLine($"{streamType} fps {frameCount}");
|
||
// Debug.Print($"{streamType} fps {frameCount}");
|
||
// Interlocked.Exchange(ref frameCount, 0);
|
||
// }, null, 0, 1000);
|
||
// while (!end || bitmaps.Count > 0)
|
||
// {
|
||
// if (bitmaps.Count == 0)
|
||
// {
|
||
// Thread.Sleep(100);
|
||
// continue;
|
||
// }
|
||
// if (bitmaps.TryDequeue(out var mat))
|
||
// {
|
||
// sw2.Restart();
|
||
// //mat.SaveImage(imgTemp);
|
||
// //mat.Dispose();
|
||
// //mat = Cv2.ImRead(imgTemp, ImreadModes.AnyColor);
|
||
// //Cv2.ImShow(streamType.ToString(), mat);
|
||
// //Cv2.WaitKey(1);
|
||
// if (!videoWriterInit)
|
||
// {
|
||
// vw.Open(videoFile, FourCC.H264, settingWindowViewModel.LiveFps, mat.Size());
|
||
// videoWriterInit = true;
|
||
// }
|
||
// vw.Write(mat);
|
||
// mat.Dispose();
|
||
// Interlocked.Increment(ref frameCount);
|
||
// //Debug.Print("b"+sw2.Elapsed.TotalMilliseconds.ToString());
|
||
// }
|
||
// else
|
||
// {
|
||
// Thread.Sleep(100);
|
||
// }
|
||
// }
|
||
// vw?.Release();
|
||
// vw?.Dispose();
|
||
// if (File.Exists(imgTemp))
|
||
// {
|
||
// File.Delete(imgTemp);
|
||
// }
|
||
// timer.Dispose();
|
||
// Console.WriteLine($"VideoRecordTask End {streamType}");
|
||
//}, TaskCreationOptions.LongRunning);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加自定义渲染 View 并绑定渲染回调
|
||
/// </summary>
|
||
private static TXLiteAVVideoView AddCustomVideoView(Panel parent, string userId, TRTCVideoStreamType streamType, bool local = false)
|
||
{
|
||
TXLiteAVVideoView videoView = new TXLiteAVVideoView();
|
||
videoView.RegEngine(userId, streamType, lTRTCCloud, local);
|
||
videoView.SetRenderMode(streamType == TRTCVideoStreamType.TRTCVideoStreamTypeBig ? settingWindowViewModel.MainRenderParams.fillMode : settingWindowViewModel.SubRenderParams.fillMode);
|
||
//videoView.Width = parent.ActualWidth;
|
||
videoView.SetBinding(TXLiteAVVideoView.WidthProperty, new Binding("ActualWidth") { Source = parent });
|
||
//videoView.Height = parent.ActualHeight;
|
||
videoView.SetBinding(TXLiteAVVideoView.HeightProperty, new Binding("ActualHeight") { Source = parent });
|
||
parent.Dispatcher.Invoke(new Action(() =>
|
||
{
|
||
parent.Children.Add(videoView);
|
||
}));
|
||
string key = string.Format("{0}_{1}", userId, streamType);
|
||
VideoViews.Add(key, videoView);
|
||
return videoView;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取屏幕列表需要暂停屏幕分享渲染
|
||
/// </summary>
|
||
/// <param name="action"></param>
|
||
public static void WillGetScreens(Action action)
|
||
{
|
||
if (liveWinMode.ScreenRunning && VideoViews.TryGetValue($"{CurrentClassroomEntity.TeacherId}_{TRTCVideoStreamType.TRTCVideoStreamTypeSub}", out var view))
|
||
{
|
||
view.SetPause(true);
|
||
action.Invoke();
|
||
view.SetPause(false);
|
||
}
|
||
else
|
||
{
|
||
action.Invoke();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 移除自定义渲染 View 并解绑渲染回调
|
||
/// </summary>
|
||
private static void RemoveCustomVideoView(Panel parent, string userId, TRTCVideoStreamType streamType, bool local = false)
|
||
{
|
||
TXLiteAVVideoView videoView = null;
|
||
string key = string.Format("{0}_{1}", userId, streamType);
|
||
if (VideoViews.TryGetValue(key, out videoView))
|
||
{
|
||
videoView.RemoveEngine(lTRTCCloud);
|
||
parent.Dispatcher.Invoke(new Action(() =>
|
||
{
|
||
parent.Children.Remove(videoView);
|
||
}));
|
||
VideoViews.Remove(key);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重启音频录制
|
||
/// </summary>
|
||
public static void RestartAudio() { }
|
||
/// <summary>
|
||
/// 重设主视频(摄像头)
|
||
/// </summary>
|
||
public static void ResetVideoMain() { }
|
||
/// <summary>
|
||
/// 重设副视频(录屏)
|
||
/// </summary>
|
||
public static void ResetVideoSub() { }
|
||
|
||
|
||
private static void CurrentLiveWindow_Closed(object sender, EventArgs e)
|
||
{
|
||
CurrentLiveWindow = null;
|
||
CurrentClassroomEntity = null;
|
||
CallerWindow.Show();//还原调用者窗口
|
||
|
||
lTXDeviceManager.Dispose();
|
||
lTXDeviceManager = null;
|
||
lTRTCCloud.exitRoom();
|
||
lTRTCCloud.removeCallback(TRTCCloudCallback);//注册回调
|
||
ITRTCCloud.destroyTRTCShareInstance();//销毁TRTC实例
|
||
lTRTCCloud.Dispose();
|
||
lTRTCCloud = null;
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 计算 UserSig 签名
|
||
///
|
||
/// 函数内部使用 HMAC-SHA256 非对称加密算法,对 SDKAPPID、userId 和 EXPIRETIME 进行加密
|
||
///
|
||
/// 该方案仅适合本地跑通demo和功能调试,产品真正上线发布,要使用服务器获取方案避免私钥被破解。
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 请不要将如下代码发布到您的线上正式版本的 App 中,原因如下:
|
||
///
|
||
/// 本文件中的代码虽然能够正确计算出 UserSig,但仅适合快速调通 SDK 的基本功能,不适合线上产品,
|
||
/// 这是因为客户端代码中的 SECRETKEY 很容易被反编译逆向破解,尤其是 Web 端的代码被破解的难度几乎为零。
|
||
/// 一旦您的密钥泄露,攻击者就可以计算出正确的 UserSig 来盗用您的腾讯云流量。
|
||
///
|
||
/// 正确的做法是将 UserSig 的计算代码和加密密钥放在您的业务服务器上,然后由 App 按需向您的服务器获取实时算出的 UserSig。
|
||
/// 由于破解服务器的成本要高于破解客户端 App,所以服务器计算的方案能够更好地保护您的加密密钥。
|
||
///
|
||
/// 文档:https://cloud.tencent.com/document/product/647/17275#GetFromServer
|
||
/// </remarks>
|
||
public static string GenTestUserSig(string userId)
|
||
{
|
||
if (SDKAppID == 0 || string.IsNullOrEmpty(SDKAppKEY)) return null;
|
||
TLSSigAPIv2 api = new TLSSigAPIv2((int)SDKAppID, SDKAppKEY);
|
||
// 统一转换为UTF8,SDK内部是用UTF8编码。
|
||
return api.GenSig(Util.UTF16To8(userId));
|
||
}
|
||
}
|
||
}
|