1,项目介绍。
实现全屏录屏、选择区域录屏、摄像头录像、麦克风录音、主板音频录音、截屏画板的自由组合。并通过FFmpeg
完成音频与视频的合并。
功能界面
画板画笔
参考的项目
https://github.com/yangjinming1062/RecordWin
本项目是在此项目的基础上修复了部分bug,并增加了屏幕区域录屏,与主板音频录音功能。
2,知识点总结
2.1,热键注册。
在WPF
中进行热键注册需要添加钩子,用以监视热键输入。
public IntPtr winHandle;
private HwndSource hWndSource;
private void Window_Loaded(object sender, RoutedEventArgs e)
{
//获取窗口句柄
winHandle = new WindowInteropHelper(this).Handle;
//在Win32窗口呈现wpf内容
hWndSource = HwndSource.FromHwnd(winHandle);
GoToScreenTopMiddle();
SetHotKey(true);
}
private void SetHotKey(bool Add)
{
if (Add)
{
hWndSource.AddHook(MainWindowProc);
HotKeyBF = HotKey.GlobalAddAtom($"{SettingHelp.Settings.播放暂停.Item1}-{Enum.GetName(typeof(System.Windows.Forms.Keys), SettingHelp.Settings.播放暂停.Item2)}");
HotKeyTZ = HotKey.GlobalAddAtom($"{SettingHelp.Settings.停止关闭.Item1}-{Enum.GetName(typeof(System.Windows.Forms.Keys), SettingHelp.Settings.停止关闭.Item2)}");
HotKeyHB = HotKey.GlobalAddAtom($"{SettingHelp.Settings.开关画笔.Item1}-{Enum.GetName(typeof(System.Windows.Forms.Keys), SettingHelp.Settings.开关画笔.Item2)}");
HotKey.RegisterHotKey(winHandle, HotKeyBF, SettingHelp.Settings.播放暂停.Item1, SettingHelp.Settings.播放暂停.Item2);
HotKey.RegisterHotKey(winHandle, HotKeyTZ, SettingHelp.Settings.停止关闭.Item1, SettingHelp.Settings.停止关闭.Item2);
HotKey.RegisterHotKey(winHandle, HotKeyHB, SettingHelp.Settings.开关画笔.Item1, SettingHelp.Settings.开关画笔.Item2);
}
else//暂时没起作用,todo
{
hWndSource.RemoveHook(MainWindowProc);
HotKey.GlobalDeleteAtom((short)HotKeyBF);
HotKey.GlobalDeleteAtom((short)HotKeyTZ);
HotKey.GlobalDeleteAtom((short)HotKeyHB);
HotKey.UnregisterHotKey(winHandle, HotKeyBF);
HotKey.UnregisterHotKey(winHandle, HotKeyTZ);
HotKey.UnregisterHotKey(winHandle, HotKeyHB);
}
}
private IntPtr MainWindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case HotKey.WM_HOTKEY:
{
int sid = wParam.ToInt32();
if (Visibility == Visibility.Visible)
{
if (!SettingPop.IsOpen)
{
if (sid == HotKeyBF)
{
if (btBegin.Visibility == Visibility.Visible)
btBegin_Click(null, null);
else
btParse_Click(null, null);
}
else if (sid == HotKeyTZ)
{
if (btStop.Visibility == Visibility.Visible)
btStop_Click(null, null);
else
BtClose_Click(null, null);
}
}
if (sid == HotKeyHB)
{
btPen.IsChecked = !btPen.IsChecked;
OpenDraweWin();
}
}
handled = true;
break;
}
}
return IntPtr.Zero;
}
外部函数
public class HotKey
{
/// <summary>
/// 如果函数执行成功,返回值不为0。
/// 如果函数执行失败,返回值为0。要得到扩展错误信息,调用GetLastError。.NET方法:Marshal.GetLastWin32Error()
/// </summary>
/// <param name="hWnd">要定义热键的窗口的句柄</param>
/// <param name="id">定义热键ID(不能与其它ID重复) </param>
/// <param name="fsModifiers">标识热键是否在按Alt、Ctrl、Shift、Windows等键时才会生效</param>
/// <param name="vk">定义热键的内容,WinForm中可以使用Keys枚举转换,
/// WPF中Key枚举是不正确的,应该使用System.Windows.Forms.Keys枚举,或者自定义正确的枚举或int常量</param>
[DllImport("user32.dll", SetLastError = true)]
public static extern bool RegisterHotKey(IntPtr hWnd, int id, KeyModifiers fsModifiers, int vk);
/// <summary>
/// 取消注册热键
/// </summary>
/// <param name="hWnd">要取消热键的窗口的句柄</param>
/// <param name="id">要取消热键的ID</param>
[DllImport("user32.dll", SetLastError = true)]
public static extern bool UnregisterHotKey(IntPtr hWnd, int id);
/// <summary>
/// 向全局原子表添加一个字符串,并返回这个字符串的唯一标识符,成功则返回值为新创建的原子ID,失败返回0
/// </summary>
[DllImport("kernel32", SetLastError = true)]
public static extern short GlobalAddAtom(string lpString);
[DllImport("kernel32", SetLastError = true)]
public static extern short GlobalDeleteAtom(short nAtom);
/// <summary>
/// 定义了辅助键的名称(将数字转变为字符以便于记忆,也可去除此枚举而直接使用数值)
/// </summary>
[Flags()]
public enum KeyModifiers
{
None = 0,
Alt = 1,
Ctrl = 2,
Shift = 4,
WindowsKey = 8
}
/// <summary>
/// 热键的对应的消息ID
/// </summary>
public const int WM_HOTKEY = 0x312;
}
2. 2,两种透明。
根据实际需要可以定义两种透明,一种是真透明Transparent
,一种是假透明#01000000
<Color x:Key="FakeTransparentColor" >#01000000</Color>
<Color x:Key="TrueTransparentColor" >Transparent</Color>
Background = Application.Current.Resources[enable ? "FakeTransparent" : "TrueTransparent"] as Brush;
在窗口定义为接受透明时:AllowsTransparency="True"
,采用真透明背景的窗体A,覆盖在应用B上时,应用B的控件可透过窗体A被操作。采用假透明背景的窗体A,覆盖在应用B上时,应用B的控件不能透过窗体A被操作。
2. 3,音视频合并。
音视频的合并一般以命令的形式调用软件FFmpeg
完成。
常用语法:
直接合并(视频流复制+音频转码)
若需保持视频无损且兼容MP4容器,建议将WAV转码为AAC:
ffmpeg -i video.mp4 -i audio.wav -c:v copy -c:a aac -strict experimental output.mp4
参数说明:
-c:v copy
:复制视频流不重新编码-c:a aac
:将WAV音频转码为MP4支持的AAC格式-strict experimental
:早期版本需此参数支持AAC
- 强制替换原视频音频轨道
若原视频已含音频需替换,可指定映射关系:
ffmpeg -i video.mp4 -i audio.wav -c:v copy -c:a aac -map 0:v:0 -map 1:a:0 output.mp4
参数说明
-map 0:v:0
:选择第一个输入文件(video.mp4)的视频流-map 1:a:0
:选择第二个输入文件(audio.wav)的音频流
- 无损合并(需视频无音频)
若视频本身无音频轨道,可直接复制流:
ffmpeg -i video.mp4 -i audio.wav -c copy output.mkv
注意:需改用MKV容器以支持PCM音频流。
示例:
将视频1.mp4与音频1.wav合并为output.mkv
2. 4,自定义动画。
public class CornerRadiusAnimation : AnimationTimeline
{
static CornerRadiusAnimation()
{
FromProperty = DependencyProperty.Register("From", typeof(CornerRadius), typeof(CornerRadius));
ToProperty = DependencyProperty.Register("To", typeof(CornerRadius), typeof(CornerRadius));
}
private bool _fromSetted;
private bool _toSetted;
public static readonly DependencyProperty FromProperty;
public CornerRadius From
{
get => (CornerRadius)GetValue(FromProperty);
set
{
SetValue(FromProperty, value);
_fromSetted = true;
}
}
public static readonly DependencyProperty ToProperty;
public CornerRadius To
{
get => (CornerRadius)GetValue(ToProperty);
set
{
SetValue(ToProperty, value);
_toSetted = true;
}
}
public override object GetCurrentValue(object defaultOriginValue, object defaultDestinationValue, AnimationClock animationClock)
{
var fromVal = _fromSetted ? (CornerRadius)GetValue(FromProperty) : (CornerRadius)defaultOriginValue;
var toVal = _toSetted ? (CornerRadius)GetValue(ToProperty) : (CornerRadius)defaultDestinationValue;
if (animationClock.CurrentProgress != null)
return new CornerRadius(
animationClock.CurrentProgress.Value * (toVal.TopLeft - fromVal.TopLeft) + fromVal.TopLeft,
animationClock.CurrentProgress.Value * (toVal.TopRight - fromVal.TopRight) + fromVal.TopRight,
animationClock.CurrentProgress.Value * (toVal.BottomRight - fromVal.BottomRight) + fromVal.BottomRight,
animationClock.CurrentProgress.Value * (toVal.BottomLeft - fromVal.BottomLeft) + fromVal.BottomLeft);
return new CornerRadius();
}
protected override Freezable CreateInstanceCore() => new CornerRadiusAnimation();
public override Type TargetPropertyType => typeof(CornerRadius);
}
2.5,注意事项
不同目标框架的.NetFramework
,编译后的.dll不一定相同,例如使用.NetFramerwork4.6.1
将生成大量系统自带的.dll
。
如下:面对同一个解决方案
.NetFramerwork4.7
编译清单。
.NetFramerwork4.6.1
编译清单