WPF 录屏软件研发心得及思路分享(已结束开发)
最近由于工程需要开始研发基于Windows的自动录屏软件,很多细节很多功能需要处理,毕竟一个完美的录屏软件不是你随随便便就可以写出来的。首先参考了大部分的录屏软件,在研发的过程中遇到了很多的问题;比如-视频加载、麦克风加载、麦克风音量调节、视频播放进度控、视频音量控制、等等很多细节部分都需要好好规划才能开始做。录屏采用的是视频帧的思维逻辑进行编写的。
目前已经基本上成型,基于WPF采用了Model - View框架进行动态加载,每个线程与线程之间采用Async异步执行,并使用线程等待;录屏基本功能包含了(展示历史录屏记录、删除、录屏、视频编码、视频播放及删除、麦克风调用(音量调节-跟随系统)、加载视频(拖拉-旋转)、系统遮罩 等);编码的核心是采用FFMPEG(这个工具真的非常强大);
这边提供几个核心代码仅供参考:
1-难点:系统遮罩核心方法(使用Windows API):
1 /// <summary> 2 /// 视图模型属性改变 3 /// </summary> 4 /// <param name="sender"> 5 /// The sender. 6 /// </param> 7 /// <param name="propertyChangedEventArgs"> 8 /// 属性改变事件参数 9 /// </param> 10 private void ViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs) 11 { 12 if (propertyChangedEventArgs.PropertyName == "IsRecording") 13 { 14 this.Locked = this.ViewModel.IsRecording; 15 if (this.ViewModel.IsRecording) 16 { 17 var hwnd = new WindowInteropHelper(this).Handle; 18 NativeWindowHelper.SetWindowExTransparent(hwnd); 19 } 20 } 21 22 if (propertyChangedEventArgs.PropertyName == "IsFullScreen") 23 { 24 this.IsFullScreen = this.ViewModel.IsFullScreen; 25 } 26 }
改变属性的时候触发
1 #region Constants 2 3 /// <summary> 4 /// The gw l_ exstyle. 5 /// </summary> 6 [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:FieldNamesMustNotContainUnderscore", 7 Justification = "Reviewed. Suppression is OK here.")] 8 private const int GWL_EXSTYLE = -20; 9 10 /// <summary> 11 /// The w s_ e x_ transparent. 12 /// </summary> 13 [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:FieldNamesMustNotContainUnderscore", 14 Justification = "Reviewed. Suppression is OK here.")] 15 private const int WS_EX_TRANSPARENT = 0x00000020; 16 17 18 19 20 #endregion 21 22 #region Public Methods and Operators 23 24 /// <summary> 25 /// 窗口前置透明设置命令 26 /// </summary> 27 /// <param name="hwnd"> 28 /// The hwnd. 29 /// </param> 30 public static void SetWindowExTransparent(IntPtr hwnd) 31 { 32 var extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE); 33 SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_TRANSPARENT); 34 } 35 36 #endregion 37 38 #region Methods 39 40 /// <summary> 41 /// The get window long. 42 /// </summary> 43 /// <param name="hwnd"> 44 /// The hwnd. 45 /// </param> 46 /// <param name="index"> 47 /// The index. 48 /// </param> 49 /// <returns> 50 /// The <see cref="int"/>. 51 /// </returns> 52 [DllImport("user32.dll")] 53 private static extern int GetWindowLong(IntPtr hwnd, int index); 54 55 /// <summary> 56 /// The set window long. 57 /// </summary> 58 /// <param name="hwnd"> 59 /// The hwnd. 60 /// </param> 61 /// <param name="index"> 62 /// The index. 63 /// </param> 64 /// <param name="newStyle"> 65 /// The new style. 66 /// </param> 67 /// <returns> 68 /// The <see cref="int"/>. 69 /// </returns> 70 [DllImport("user32.dll")] 71 private static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle); 72 73 #endregion
API方法
2-难点:麦克风获取及控制
<Slider x:Name="volumeSlider" Grid.Column="7" Grid.ColumnSpan="3" Grid.Row="1" Width="100" Height="20" Minimum="0" Maximum="100" Value="100" VerticalAlignment="Center" />
1 //定义一个获取之前拉动时候的value值,然后跟当前的value对比,选择触发 2 private bool isUserChangeVolume = true; 3 private VolumeControl volumeControl; 4 //private DispatcherTimer volumeControlTimer; 5 6 /// <summary> 7 /// 加载拖动条的事件 8 /// </summary> 9 /// <param name="sender"></param> 10 /// <param name="e"></param> 11 private void volumeSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) 12 { 13 if (isUserChangeVolume) 14 { 15 volumeControl.MasterVolume = volumeSlider.Value; 16 } 17 } 18 19 private void InitializeAudioControl() 20 { 21 volumeControl = VolumeControl.Instance; 22 volumeControl.OnAudioNotification += volumeControl_OnAudioNotification; 23 volumeControl_OnAudioNotification(null, new AudioNotificationEventArgs() { MasterVolume = volumeControl.MasterVolume }); 24 25 //volumeControlTimer = new DispatcherTimer(); 26 //volumeControlTimer.Interval = TimeSpan.FromTicks(150); 27 //volumeControlTimer.Tick += volumeControlTimer_Tick; 28 } 29 30 void volumeControl_OnAudioNotification(object sender, AudioNotificationEventArgs e) 31 { 32 this.isUserChangeVolume = false; 33 try 34 { 35 this.Dispatcher.Invoke(new Action(() => { volumeSlider.Value = e.MasterVolume; })); 36 } 37 catch { } 38 this.isUserChangeVolume = true; 39 } 40 41 void volumeControlTimer_Tick(object sender, EventArgs e) 42 { 43 //获取系统主声道、左声道、右声道音量值 44 //double[] information = volumeControl.AudioMeterInformation; 45 //mMasterPBar.Value = information[0]; 46 //mLeftPBar.Value = information[1]; 47 //mRightPBar.Value = information[2]; 48 }
3-难点:系统遮罩(其实也不能算难点,这个是API调用的时候颜色控制);
4-难点:视频旋转核心代码(已更新为方法8)
1 /// <summary> 2 /// 旋转视频 3 /// </summary> 4 /// <param name="sender"></param> 5 /// <param name="e"></param> 6 private void RotateCamera_bt(object sender, RoutedEventArgs e) 7 { 8 if (AnAngle > 360 || AnAngle == 0) 9 { 10 AnAngle = 90; 11 } 12 TransformGroup transformGroup = new TransformGroup(); 13 14 ScaleTransform scaleTransform = new ScaleTransform(); 15 scaleTransform.ScaleX = -1; 16 transformGroup.Children.Add(scaleTransform); 17 18 RotateTransform rotateTransform = new RotateTransform(AnAngle); 19 transformGroup.Children.Add(rotateTransform); 20 videoPlayer.RenderTransform = transformGroup; 21 AnAngle += 90; 22 }
旋转视频代码
5-难点:录屏核心代码(这部分代码视频格式可以自行调整,颜色代码原理已经理解。)--已更新为方法10
1 /// <summary> 2 /// Starts the recording. 3 /// </summary> 4 public void StartRecording() 5 { 6 this.notifyIcon.HideBalloonTip(); 7 this.IsRecording = true; 8 9 10 var fileName = string.Format("Recording {0}.mp4", DateTime.Now.ToString("yy-MM-dd HH-mm-ss")); 11 var outputFilePath = Path.Combine(this.settings.StoragePath, fileName); 12 this.fileViewModel = new ScreenGunFileViewModel(outputFilePath, RecordingStage.DoingNothing); 13 14 var opts = new ScreenRecorderOptions(this.RecordingRegion) 15 { 16 DeleteMaterialWhenDone = true, 17 OutputFilePath = outputFilePath, 18 RecordMicrophone = this.UseMicrophone, 19 AudioRecordingDeviceNumber = this.settings.RecordingDeviceNumber 20 }; 21 22 var progress = new Progress<RecorderState>(state => this.fileViewModel.RecordingStage = state.Stage); 23 this.recorder.Start(opts, progress); 24 }
录屏代码
6-难点:屏幕画框代码(采集X,Y坐标及遮幕的宽,高)
1 /// <summary> 2 /// 设置初始区域 3 /// </summary> 4 private void SetupInitialRegion() 5 { 6 var cursorPos = System.Windows.Forms.Cursor.Position; 7 foreach (var screen in Screen.AllScreens) 8 { 9 if (screen.Bounds.Contains(cursorPos) == false) 10 { 11 continue; 12 } 13 14 var regionWidth = (double)screen.Bounds.Width / 2; 15 var regionHeight = (double)screen.Bounds.Height / 2; 16 double x = ((double)screen.Bounds.Width / 2) - (regionWidth / 2); 17 double y = ((double)screen.Bounds.Height / 2) - (regionHeight / 2); 18 x -= this.virtualScreen.X - screen.Bounds.X; 19 y -= this.virtualScreen.Y - screen.Bounds.Y; 20 21 this.startPosition = new Point(x, y); 22 this.endPosition = new Point(x + regionWidth, y + regionHeight); 23 this.UpdatePosition(); 24 break; 25 } 26 }
7-放大缩小(根据屏幕大小范围随意拉伸缩小) 核心代码如下:
当你有摄像头长跟宽不一样的时候,旋转-缩小-放大然后根据给定的边缘坐标是一个非常头疼的事情,单单这个问题就使我加班到凌晨4点了,不过最终还是解决了;
1 void resizer_Resize(object sender, ControlResizeEventArgs e) 2 { 3 if (!this.RectangleU.IsMouseCaptured) return; 4 if (AnAngle == 180 || AnAngle == 360) 5 { 6 #region --竖直拉伸-- 7 double Image_xx = 0; 8 double Image_yy = 0; 9 double point_xx = 0; 10 double point_yy = 0; 11 double point_center = Math.Abs(this.MainGrid.Width / 2 - this.MainGrid.Height / 2);//当前中心点值 12 double actual_center = Math.Abs(this.videoPlayer.MinWidth / 2 - this.videoPlayer.MinHeight / 2);//实际中心点,用于比较初始值 13 14 if (Math.Abs(Image_PointX) == 0) 15 { 16 point_xx = -25;//初始化原点未动 17 } 18 else 19 { 20 //拖动到其他位置时偏移量(必须是固定值) 21 if (Image_PointX < -25) 22 { 23 point_xx = -25; 24 } 25 else 26 { 27 point_xx = Image_PointX; 28 } 29 } 30 31 if (Math.Abs(Image_PointY) == 0) 32 { 33 point_yy = -25;//初始化原点未动 34 } 35 else 36 { 37 //拖动到其他位置时偏移量(必须是固定值) 38 if (Image_PointY < -25) 39 { 40 point_yy = -25; 41 } 42 else 43 { 44 point_yy = Image_PointY; 45 } 46 47 } 48 if (Math.Abs(point_xx) == 25) 49 { 50 Image_xx = videoPlayer.ActualHeight; 51 } 52 else 53 { 54 Image_xx = videoPlayer.ActualHeight + Math.Abs(Image_PointX) - 25; 55 } 56 if (Math.Abs(point_yy) == 25) 57 { 58 Image_yy = videoPlayer.ActualWidth; 59 } 60 else 61 { 62 Image_yy = Math.Abs(Image_PointY) + videoPlayer.ActualWidth - 25; 63 } 64 65 //左右拉伸(只能往右拉伸) 66 if (e.LeftDirection.HasValue) 67 { 68 var value = videoPlayer.Height + e.HorizontalChange; 69 if (value > videoPlayer.MinHeight) 70 { 71 videoPlayer.Height = value; 72 MainGrid.Height = value; 73 if (videoPlayer.ActualHeight < value) 74 { 75 MainGrid.Height = videoPlayer.ActualHeight; 76 } 77 if (Image_xx >= RecordingArea.Width) 78 { 79 MainGrid.Height = videoPlayer.ActualHeight; 80 videoPlayer.Height = videoPlayer.ActualHeight; 81 } 82 } 83 } 84 //上下拉伸(只能往上拉伸) 85 if (e.TopDirection.HasValue) 86 { 87 var value = videoPlayer.Width + e.VerticalChange; 88 if (value > videoPlayer.MinWidth) 89 { 90 videoPlayer.Width = value; 91 MainGrid.Width = value; 92 if (videoPlayer.ActualWidth < value) 93 { 94 MainGrid.Width = videoPlayer.ActualWidth; 95 } 96 97 if (Image_yy >= RecordingArea.Height) 98 { 99 MainGrid.Width = videoPlayer.ActualWidth; 100 videoPlayer.Width = videoPlayer.ActualWidth; 101 } 102 103 } 104 } 105 106 #region 调整位置 107 108 Matrix m = MainGrid.RenderTransform.Value; 109 110 111 //初始值(-25,-25)-->(x,y) 112 if ((Image_xx >= RecordingArea.Width) || Image_yy >= RecordingArea.Height) 113 { 114 115 } 116 else 117 { 118 if (point_center >= actual_center) 119 { 120 // (point_center - actual_center)为x-y轴偏移量 121 //point_xx--point_yy为当前x,y轴坐标 122 m.OffsetX = point_xx - (point_center - actual_center); 123 m.OffsetY = point_yy - (point_center - actual_center); 124 125 } 126 } 127 MainGrid.RenderTransform = new MatrixTransform(m);//重新定位 128 129 #endregion 130 131 132 133 #endregion 134 } 135 else 136 { 137 #region --正常拉伸-- 138 //左右拉伸(只能往右拉伸) 139 if (e.LeftDirection.HasValue) 140 { 141 var value = videoPlayer.Width + e.HorizontalChange; 142 if (value > videoPlayer.MinWidth) 143 { 144 videoPlayer.Width = value; 145 MainGrid.Width = value; 146 if (videoPlayer.ActualWidth < value) 147 { 148 MainGrid.Width = videoPlayer.ActualWidth; 149 } 150 if (Image_PointX + videoPlayer.ActualWidth >= RecordingArea.Width) 151 { 152 MainGrid.Width = videoPlayer.ActualWidth; 153 videoPlayer.Width = videoPlayer.ActualWidth; 154 } 155 } 156 } 157 //上下拉伸(只能往上拉伸) 158 if (e.TopDirection.HasValue) 159 { 160 var value = videoPlayer.Height + e.VerticalChange; 161 if (value > videoPlayer.MinHeight) 162 { 163 videoPlayer.Height = value; 164 MainGrid.Height = value; 165 if (videoPlayer.ActualHeight < value) 166 { 167 MainGrid.Height = videoPlayer.ActualHeight; 168 } 169 170 if (Math.Abs(Image_PointY) + videoPlayer.ActualHeight >= RecordingArea.Height) 171 { 172 MainGrid.Height = videoPlayer.ActualHeight; 173 videoPlayer.Height = videoPlayer.ActualHeight; 174 } 175 176 } 177 } 178 #endregion 179 } 180 }
放大缩小-分长宽不一致情况
8-旋转,核心代码如下:
1 private void RotateCamera_bt(object sender, RoutedEventArgs e) 2 { 3 if (MainGrid.ActualWidth > SystemParameters.PrimaryScreenHeight) 4 { 5 return; 6 } 7 if (AnAngle > 360 || AnAngle == 0) 8 { 9 AnAngle = 90; 10 } 11 12 TransformGroup transformGroup = new TransformGroup(); 13 RotateTransform rotateTransform = new RotateTransform(AnAngle); 14 transformGroup.Children.Add(rotateTransform); 15 MainGrid.RenderTransform = transformGroup; 16 #region 特殊四个角反转需要变换长跟宽 17 //重新调整坐标坐标位置 18 Matrix m = MainGrid.RenderTransform.Value; 19 //求出中心点坐标 20 double point_xx = (this.MainGrid.ActualWidth) / 2 - (this.MainGrid.ActualHeight) / 2; 21 // Image_PointX,Image_Point为当前坐标 22 if (AnAngle == 90 || AnAngle == 270) 23 { 24 if (AnAngle == 270) 25 { 26 RectangleU.VerticalAlignment = VerticalAlignment.Bottom; 27 RectangleU.HorizontalAlignment = System.Windows.HorizontalAlignment.Right; 28 RectangleU.BorderThickness = new Thickness(0, 0, 8, 8); 29 RectangleU.CornerRadius = new CornerRadius(0, 0, 1, 0); 30 31 } 32 else 33 { 34 RectangleU.VerticalAlignment = VerticalAlignment.Top; 35 RectangleU.HorizontalAlignment = System.Windows.HorizontalAlignment.Left; 36 RectangleU.BorderThickness = new Thickness(8, 8, 0, 0); 37 RectangleU.CornerRadius = new CornerRadius(1, 0, 0, 0); 38 39 } 40 if (!IschangeAngle) 41 { 42 if (Image_PointX <= -point_xx) 43 { 44 m.OffsetX = -point_xx; 45 } 46 else 47 { 48 m.OffsetX = Image_PointX - point_xx; 49 } 50 if (Image_PointY >= -point_xx) 51 { 52 m.OffsetY = -point_xx; 53 } 54 else 55 { 56 m.OffsetY = Image_PointY + point_xx; 57 } 58 59 if (m.OffsetX >= this.RecordingArea.Width - this.videoPlayer.Width - point_xx) 60 { 61 m.OffsetX = m.OffsetX + point_xx * 2; 62 63 } 64 if (m.OffsetY >= -point_xx) 65 { 66 m.OffsetY = -point_xx; 67 } 68 } 69 else 70 { 71 //旋转为竖直拉到某个坐标时触发 72 if (Image_PointX <= -point_xx) 73 { 74 m.OffsetX = -point_xx; 75 } 76 else 77 { 78 m.OffsetX = Image_PointX; 79 } 80 if (Image_PointY >= point_xx) 81 { 82 m.OffsetY = -point_xx; 83 } 84 else 85 { 86 m.OffsetY = Image_PointY; 87 } 88 } 89 90 if (this.MainGrid.Width >= this.RecordingArea.Height) 91 { 92 //触发 93 //相对于屏幕的x,y轴不变 94 m.OffsetX = -point_xx; 95 m.OffsetY = -point_xx; 96 97 98 } 99 100 } 101 else 102 { 103 if (AnAngle == 180) 104 { 105 RectangleU.VerticalAlignment = VerticalAlignment.Bottom; 106 RectangleU.HorizontalAlignment = System.Windows.HorizontalAlignment.Left; 107 RectangleU.BorderThickness = new Thickness(8, 0, 0, 8); 108 RectangleU.CornerRadius = new CornerRadius(0, 0, 0, 1); 109 110 } 111 else 112 { 113 RectangleU.VerticalAlignment = VerticalAlignment.Top; 114 RectangleU.HorizontalAlignment = System.Windows.HorizontalAlignment.Right; 115 RectangleU.BorderThickness = new Thickness(0, 8, 8, 0); 116 RectangleU.CornerRadius = new CornerRadius(0, 1, 0, 0); 117 118 } 119 if (IschangeAngle) 120 { 121 if (this.MainGrid.Width >= this.RecordingArea.Height) 122 { 123 Image_PointX = 0; 124 } 125 else 126 { 127 if (Image_PointX + this.videoPlayer.Width > this.RecordingArea.Width) 128 { 129 m.OffsetX = Image_PointX - point_xx; 130 } 131 else 132 { 133 m.OffsetX = Image_PointX + point_xx; 134 } 135 } 136 //旋转为竖直拉到某个坐标时触发 137 if (Image_PointY >= -point_xx) 138 { 139 m.OffsetY = 0; 140 } 141 else 142 { 143 m.OffsetY = Image_PointY - point_xx; 144 } 145 146 } 147 else 148 { 149 150 if (this.MainGrid.Width >= this.RecordingArea.Width) 151 { 152 m.OffsetX = 0; 153 m.OffsetY = 0; 154 } 155 else 156 { 157 //正常情况 158 if (Image_PointX <= 0) 159 { 160 m.OffsetX = 0; 161 } 162 else 163 { 164 m.OffsetX = Image_PointX; 165 } 166 if (Image_PointY >= 0) 167 { 168 m.OffsetY = 0; 169 } 170 else 171 { 172 m.OffsetY = Image_PointY; 173 } 174 } 175 } 176 } 177 //IschangeAngle = false; 178 //更换坐标位置 179 MainGrid.RenderTransform = new MatrixTransform(m); 180 181 182 var x = Math.Min(this.startPosition.X, this.endPosition.X); 183 var y = Math.Min(this.startPosition.Y, this.endPosition.Y); 184 if (AnAngle == 90 || AnAngle == 270) 185 { 186 if (this.MainGrid.Width >= this.RecordingArea.Height) 187 { 188 this.relativeRecordingArea = new Rect(x, y, this.MainGrid.Height, this.MainGrid.Width); 189 this.UpdateUI(); 190 } 191 } 192 else 193 { 194 if (this.MainGrid.Width >= this.RecordingArea.Width) 195 { 196 this.relativeRecordingArea = new Rect(x, y, this.MainGrid.Width, this.MainGrid.Height); 197 this.UpdateUI(); 198 } 199 } 200 //UpdatePosition(); 201 202 AnAngle += 90; 203 #endregion 204 }
旋转代码-分长宽不一致情况
9-不同屏幕百分比自适应边框-采用DPIX
这个稍微简单点:只要获取出每个屏幕差值即可。
dpiX = graphics.DpiX / 96;//当前屏幕的DPI然后除以正常值96得出的值即为扩展百分比
10-录屏核心代码:(不采用之前的位图编译,直接通过引用第三方插件)
通过AForge对FFMPEG进行录屏封装,我们可以轻松的录制想要录制的内容,关于录屏时间上则采用的是异步执行Timer。
private void video_NewFrame(object sender, NewFrameEventArgs e){//if (this.IScreenRecording)//{this.videoWriter.WriteVideoFrame(e.Frame);//异步执行时间this.stopWatchLabel.Dispatcher.Invoke(new Action(() => this.stopWatchLabel.Text = string.Format(@"{0}", this.stopWatch.Elapsed.ToString("hh\\:mm\\:ss"))));//}//else//{// stopWatch.Reset();// videoStreamer.Stop();// videoWriter.Close();//}}
11-比较重要的一步:任何商用的录屏软件都需要实现播放、暂停、继续功能,这款软件也不例外:
/// <summary>/// 点击之后更换图标并判断是否需要停止or启用/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void PauseOrRecording_Checked(object sender, RoutedEventArgs e){//暂停计时//暂停保存图片//暂停保存麦克风 if (IScreenRecording){this.stopWatch.Stop();//时间表//this.videoStreamer.Stop();this.videoStreamer.SignalToStop();if (PassMediaMessage.IsrecordingSound){//暂停 PassMediaMessage.IsrecordingSound = false;PassMediaMessage.Is_Recording = false;}IScreenRecording = false;}else{if (this.IsMicrophone.IsChecked == true){PassMediaMessage.IsrecordingSound = true;PassMediaMessage.Is_Recording = true;}//启用(只是暂停并没有真正的释放)this.stopWatch.Start();//时间表this.videoStreamer.Start();IScreenRecording = true;}}
12-由于我们软件是面向世界的,所以必须有增加世界12国语言支持,这边就不再详细贴出代码了。
13-整体效果展示:
转载于:https://www.cnblogs.com/BarryJames/p/6679407.html
WPF 录屏软件研发心得及思路分享(已结束开发)相关推荐
- 盘点B站up主常用的免费录屏软件,自用工具分享
盘点B站up主常用的免费录屏软件,自用工具分享! 今天分享 4 款超好用的免费录屏软件,能够满足大部分同学录制教学视频.游戏视频.网络课程等录制场景的需求. 而且,这 4 款录屏软件还是主播.自媒体达 ...
- Win7系统自带的录屏工具怎么打开操作教学分享
Win7系统自带的录屏工具怎么打开操作教学分享.很多的用户不知道Win7系统其实有自带了一个录屏工具.我们通过代码打开它之后,就可以非常方便的进行内容录制了.一起来看看如何开启它的操作教学吧. 操作步 ...
- 录屏工具有哪些?分享:电脑录制微课的工具和方法
在录制微课的时候,人们在所难免都要录制电脑内的操作步骤,此时就不得不用录屏工具去录制这些内容.虽然现目前软件市场上的录屏工具非常之多,但有些软件录制的画面并不是很清晰,操作也不是很方便,那么那些录屏工 ...
- 怎样录屏不带水印?分享一款无水印录制视频软件!
案例:怎样录制无水印的视频? [我平常录制的录屏带有软件自带水印,这样十分影响观感.怎样才能录制无水印的视频?] 一款好的录屏软件,可以更好地帮助我们录制电脑屏幕上的操作或是制作教学视频.然而,很多录 ...
- 录屏软件哪个好?分享:好用的录屏软件,一款就够!
生活中有非常多地方都需要用到录屏软件,如上网课如果我们没有时间听,往往会选择将课程录制下来听回放:如线上会议我们为了更好的做会议纪要,也会选择将会议录制下来:再比如我们在打游戏时,想记录自己的高光时刻 ...
- oppo手机如何录屏?好的方法分享一波
OPPO手机是一款非常流行的手机品牌,它有许多实用的功能,其中一个是录屏功能.通过录屏,你可以轻松地记录你的手机屏幕内容并与别人分享,因此在这篇文章中,我们将重点介绍如何在OPPO手机上录屏,以及其他 ...
- 录录(高清录屏) - Video321 如何快速分享视频
大家好,今天我们录录录屏又和大家见面啦- 录录录屏-Video321是一款通过视频录制.视频分享的方式提高办公效率的SaaS型产品.通过使用"录录录屏 - Video321",你可 ...
- 录屏软件有哪些?分享几种简单实用的录屏工具
有哪些好用的录屏软件呢?如果你需要向他人展示如何操作一个软件或者如何进行某项任务,录制一个演示视频是非常有用的.你可以在视频中演示每个步骤,然后分享给你的观众.这对于教育和培训领域非常有用,因为它可以 ...
- 电脑录像软件哪个好用?录屏大神的技巧分享
身边有一些人做游戏视频解说和直播,看到他们都让我用超级捕快. 然后跟我说还能用来录制微课视频.录制网上的在线电影. 方法确实小白都能学会,然后录制的画质好,体积也小. 我直接教大家怎么快速录制高清无损 ...
最新文章
- JSP标准标签库(JSTL)--简介
- CF思维联系–CodeForces - 222 C Reducing Fractions(数学+有技巧的枚举)
- 使用matlab播放特定频率的声音
- Harvard-X免费生物信息课程 (代码、文档、数据) - 适合系统学习
- SqlBulkCopy类进行大数据(一万条以上)插入测试
- VSCode自定义代码片段13——Vue的状态大管家
- 3分钟了解ServiceStage 应用智能化运维【华为云分享】
- python实现数据恢复_数据恢复/电子取证 非常有用的python库——Construct | 学步园...
- 解决Spring JdbcTemplate调用queryForObject()方法结果集为空时报异常
- 浏览器下载文件时一共发送2次请求,如何把“下载次数”只记录为1次?
- Linux命令(二)
- Pulseaudio之nemo(二十二)
- 前景检测算法(六)--平均背景原理
- 据说,上次获奖的同学拿了奖金泡了班花还get到了2个offer
- 中小企业网络推广如何找到切入点
- Panabit安装snmp插件
- mapxtreme java_用mapXtreme Java开发web gis应用 (下)
- 活动报名 | 保护儿童,保护未来!智源发布《面向儿童的人工智能北京共识》
- 第四批入围企业公示——年度TOP100智能网联供应商评选
- 【洛谷P4826】Superbull S【最大生成树】
热门文章
- 原理c语言for循环延时1s,for循环实现C语言精确延时
- Windows bat批处理 结束进程
- error: cannot lock ref 'refs/remotes/origin/test/pressure-test': 'refs/remotes/origin/test' exists;
- 2019杭电多校第七场 Kejin Player HDU - 6656 (期望)
- ActiveSync与设备连接
- C语言中的连等式解析
- 小学生图片_2020中秋节对家人的祝福语 送手抄报小学生图片大全简单又漂亮
- 百度搜索公正性彻底调查
- Mysql-mmm高可用集群
- 华为鸿蒙os多少钱一部手机,华为的鸿蒙OS,你了解多少?