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 { /// /// 直播调用对象全放这里 /// public static class LiveClassroom { const uint SDKAppID = 1400463444; const string SDKAppKEY = "6ee2586282eb8ab5bff3f917b44500c4ffd9bbd3d820258b1fa8cdd470cfd1ee"; /// /// TRTC实例 /// public static ITRTCCloud lTRTCCloud; /// /// 设备管理器 /// public static ITXDeviceManager lTXDeviceManager; /// /// live窗口的调用窗口 /// public static Window CallerWindow { get; private set; } /// /// 直播窗口 /// public static Window CurrentLiveWindow; private static LiveWindowViewModel liveWinMode; public static ClassroomEntity CurrentClassroomEntity { get; private set; } public static TRTCCloudCallback TRTCCloudCallback = new TRTCCloudCallback(); /// /// 摄像头帧 /// public static Mat MainFrame = new Mat(); /// /// 屏幕帧 /// public static Mat SubFrame = new Mat(); /// /// 背景帧 /// public static Mat BackgroundFrame = null; //public static int UserCount = 999; /// /// 进入直播教室 /// /// 调用窗口 /// 教室数据 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; /// /// 录制内容的具体路径 /// 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(); /// /// 视频画面 /// public static Dictionary VideoViews = new Dictionary(); /// /// 启动摄像头 /// /// 视频容器 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; } /// /// 停止摄像头 /// /// public static void StopVideoMain(Panel parent) { if (liveWinMode.CameraRunning) { liveWinMode.CameraRunning = false; lTRTCCloud.stopLocalPreview(); RemoveCustomVideoView(parent, CurrentClassroomEntity.TeacherId, TRTCVideoStreamType.TRTCVideoStreamTypeBig, true); } } /// /// 启动屏幕分享 /// /// 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; } /// /// 停止屏幕分享 /// /// public static void StopVideoSub(Panel parent) { if (liveWinMode.ScreenRunning) { liveWinMode.ScreenRunning = false; lTRTCCloud.stopScreenCapture(); RemoveCustomVideoView(parent, CurrentClassroomEntity.TeacherId, TRTCVideoStreamType.TRTCVideoStreamTypeSub, true); } } /// /// 选择要分享的屏幕 /// public static void SelectVieoSub() { var current = liveWinMode.CurrentShareScreen; var rect = new RECT(); var property = new TRTCScreenCaptureProperty(); lTRTCCloud.selectScreenCaptureTarget(ref current, ref rect, ref property); } /// /// 启动麦克风 /// 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); } } } /// /// 关闭麦克风 /// public static void StopMic() { if (liveWinMode.MicRunning) { liveWinMode.MicRunning = false; if (liveWinMode.AudioRecordRunning) { liveWinMode.AudioRecordRunning = false; lTRTCCloud.stopAudioRecording(); } lTRTCCloud.stopLocalAudio(); } } /// /// 设置麦克风静音 /// 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 bitmaps = new ConcurrentQueue(); //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); } /// /// 添加自定义渲染 View 并绑定渲染回调 /// 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; } /// /// 获取屏幕列表需要暂停屏幕分享渲染 /// /// 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(); } } /// /// 移除自定义渲染 View 并解绑渲染回调 /// 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); } } /// /// 重启音频录制 /// public static void RestartAudio() { } /// /// 重设主视频(摄像头) /// public static void ResetVideoMain() { } /// /// 重设副视频(录屏) /// 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; } /// /// 计算 UserSig 签名 /// /// 函数内部使用 HMAC-SHA256 非对称加密算法,对 SDKAPPID、userId 和 EXPIRETIME 进行加密 /// /// 该方案仅适合本地跑通demo和功能调试,产品真正上线发布,要使用服务器获取方案避免私钥被破解。 /// /// /// 请不要将如下代码发布到您的线上正式版本的 App 中,原因如下: /// /// 本文件中的代码虽然能够正确计算出 UserSig,但仅适合快速调通 SDK 的基本功能,不适合线上产品, /// 这是因为客户端代码中的 SECRETKEY 很容易被反编译逆向破解,尤其是 Web 端的代码被破解的难度几乎为零。 /// 一旦您的密钥泄露,攻击者就可以计算出正确的 UserSig 来盗用您的腾讯云流量。 /// /// 正确的做法是将 UserSig 的计算代码和加密密钥放在您的业务服务器上,然后由 App 按需向您的服务器获取实时算出的 UserSig。 /// 由于破解服务器的成本要高于破解客户端 App,所以服务器计算的方案能够更好地保护您的加密密钥。 /// /// 文档:https://cloud.tencent.com/document/product/647/17275#GetFromServer /// 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)); } } }