最近由于工程需要开始研发基于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-难点:视频旋转核心代码
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-难点:录屏核心代码(这部分代码视频格式可以自行调整,颜色代码原理已经理解。)
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 }
以上是这几天研究的成果,目前还在进一步研究中,效果图如下:
时间: 2024-10-23 01:24:27