WPF 播放器+快进快退+录制 openCvSharp版
vlc 要安装 的太多了, 再来一个简单的,
第一步:新建WPF项目, 安装 NeGet包:共四个
OpenCvSharp4
OpenCvSharp4.Extensions
OpenCvSharp4.runtime.win
OpenCvSharp4.WpfExtensions
第二步:拷贝代码
前端代码:*.xaml
<Window x:Class="TestOpenCvSharpPlay.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestOpenCvSharpPlay"
mc:Ignorable="d"
Title="OpenCvSharp播放器"
Height="600"
Width="800"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Border Grid.Row="0"
Background="Gray" Margin="10">
<Image x:Name="imgCamera"
Grid.Row="0"
Margin="0,1,0,0"
Stretch="Fill" />
</Border>
<Grid Grid.Row="1"
Margin="10"
Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<TextBlock x:Name="txtCurrentTime"
Grid.Column="0"
VerticalAlignment="Center"
Text="00:00"
Foreground="Black"
FontSize="14"
TextOptions.TextRenderingMode="ClearType" />
<Slider x:Name="seekPlay" Margin="5"
Grid.Column="1"
VerticalAlignment="Center"
Background="White"/>
<!-- 右侧:总时长 -->
<TextBlock x:Name="txtTotalTime"
Grid.Column="2"
VerticalAlignment="Center"
Text="00:00"
Foreground="Black"
FontSize="14"
TextOptions.TextRenderingMode="ClearType" />
</Grid>
<!-- 控制按钮区域(纯原生WPF按钮) -->
<StackPanel Grid.Row="2"
Orientation="Horizontal"
HorizontalAlignment="Center"
Margin="10">
<ComboBox x:Name="cmbSeekRate"
Width="60"
Height="30"
Margin="10"
FontSize="15"
VerticalContentAlignment="Center"
<ComboBoxItem Content="1x" />
<ComboBoxItem Content="2x" />
<ComboBoxItem Content="4x" />
<ComboBoxItem Content="8x" />
</ComboBox>
<!-- 本地视频功能 -->
<Button x:Name="btnOpenVideo"
Content="打开视频"
Width="50"
Height="50"
Margin="5"
Click="BtnOpenVideo_Click" />
<Button x:Name="btnRewind"
Content="快退"
Width="50"
Height="50"
Margin="5"
IsEnabled="False"
Click="BtnRewind_Click" />
<Button x:Name="btnPause"
Content="暂停"
Width="50"
Height="50"
Margin="5"
IsEnabled="False"
Click="BtnPause_Click" />
<Button x:Name="btnStop"
Content="停止"
Width="50"
Height="50"
Margin="5"
IsEnabled="False"
Click="BtnStop_Click" />
<Button x:Name="btnFastForward"
Content="快进"
Width="50"
Height="50"
Margin="5"
IsEnabled="False"
Click="BtnFastForward_Click" />
<!-- 摄像头+录制功能 -->
<Button x:Name="btnOpenCamera"
Content="打开摄像头"
Width="50"
Height="50"
Margin="5"
Click="BtnOpenCamera_Click" />
<Button x:Name="btnStartRecord"
Content="开始录制"
Width="50"
Height="50"
Margin="5"
IsEnabled="True"
Click="BtnStartRecord_Click" />
<Button x:Name="btnStopRecord"
Content="停止录制"
Width="50"
Height="50"
Margin="5"
IsEnabled="True"
Click="BtnStopRecord_Click" />
<Button x:Name="btnCapture"
Content="截图"
Width="50"
Height="50"
Margin="5"
IsEnabled="False"
Click="BtnCapture_Click" />
</StackPanel>
</Grid>
</Window>
下面是后台代码*.xaml.cs
namespace TestOpenCvSharpPlay
{
using OpenCvSharp.WpfExtensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private OpenCvSharp.VideoCapture _capture; // 视频捕获对象
private OpenCvSharp.Mat _frame; // 当前帧
private Thread _captureThread; // 捕获线程
private bool _isCapturing; // 是否正在捕获
private bool _isPaused; // 是否暂停
private double _totalFrames; // 视频总帧数
private double _fps; // 视频帧率
private double _currentFrameIndex; // 当前帧索引
private bool _isVideoMode; // 是否是视频文件模式
private bool _isRecording; // 是否正在录制
private OpenCvSharp.VideoWriter _videoWriter; // 视频写入器
private string _recordFilePath; // 录制文件路径
private int _recordFrameCount; // 录制帧计数
private double _playbackRate = 1.0; // 播放速率
private readonly object _lockObj = new object(); // 线程锁
private readonly object _frameLock = new object(); // 帧锁
private DateTime _lastFrameTime = DateTime.Now; // 上一帧时间
private OpenCvSharp.Mat _latestFrame; // 最新帧
private bool _seekPending; // 是否有待处理的跳转
private double _pendingFramePosition; // 待处理的帧位置
private bool _isOpeningCamera; // 是否正在打开摄像头
public MainWindow()
{
InitializeComponent();
InitOpenCvSharp();
}
//初始化OpenCvSharp
private void InitOpenCvSharp()
{
// 设置播放速率下拉框
cmbSeekRate.SelectionChanged += CmbSeekRate_SelectionChanged;
// 禁用暂停按钮初始状态
btnPause.Content = "暂停";
seekPlay.AddHandler(UIElement.MouseLeftButtonUpEvent,
new MouseButtonEventHandler(seekPlay_MouseLeftButtonUp), true);
}
private void seekPlay_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (_isVideoMode && _capture != null)
{
lock (_lockObj)
{
_pendingFramePosition = seekPlay.Value;
_seekPending = true;
}
}
}
private void CmbSeekRate_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (cmbSeekRate.SelectedItem is ComboBoxItem item)
{
string? rate = item.Content.ToString();
if (!string.IsNullOrEmpty(rate))
{
switch (rate)
{
case "1x":
_playbackRate = 1.0;
break;
case "2x":
_playbackRate = 2.0;
break;
case "4x":
_playbackRate = 4.0;
break;
case "8x":
_playbackRate = 8.0;
break;
}
}
}
}
//打开播放文件
private void BtnOpenVideo_Click(object sender, RoutedEventArgs e)
{
var dialog = new Microsoft.Win32.OpenFileDialog
{
Filter = "视频文件|*.mp4;*.avi;*.mkv;*.mov;*.wmv;*.flv|所有文件|*.*",
Title = "选择视频文件"
};
if (dialog.ShowDialog() == true)
{
StopPlayback();
Task.Run(() => OpenVideoFile(dialog.FileName));
}
}
private void OpenVideoFile(string filePath)
{
try
{
OpenCvSharp.VideoCapture tempCapture = null;
OpenCvSharp.Mat tempFrame = null;
try
{
tempCapture = new OpenCvSharp.VideoCapture(filePath);
if (!tempCapture.IsOpened())
{
Dispatcher.Invoke(() => MessageBox.Show("无法打开视频文件"));
return;
}
// 读取第一帧测试
tempFrame = new OpenCvSharp.Mat();
if (!tempCapture.Read(tempFrame) || tempFrame.Empty())
{
Dispatcher.Invoke(() => MessageBox.Show("无法读取视频帧"));
tempCapture?.Dispose();
tempFrame?.Dispose();
return;
}
lock (_lockObj)
{
_capture?.Dispose();
_capture = tempCapture;
tempCapture = null;
_frame?.Dispose();
_frame = tempFrame.Clone();
_fps = _capture.Fps;
if (_fps <= 0) _fps = 25; // 默认帧率
_totalFrames = _capture.FrameCount;
_isVideoMode = true;
_isCapturing = true;
_isPaused = false;
_currentFrameIndex = 0;
_seekPending = false;
// 重置到开始位置
_capture.Set(OpenCvSharp.VideoCaptureProperties.PosFrames, 0);
}
Dispatcher.Invoke(() =>
{
seekPlay.Maximum = _totalFrames;
seekPlay.Value = 0;
seekPlay.IsEnabled = true;
EnableVideoButtons(true);
btnPause.Content = "暂停";
Title = $"OpenCvSharp播放器 - {System.IO.Path.GetFileName(filePath)}";
// 显示第一帧
var bitmap = _frame.ToBitmapSource();
imgCamera.Source = bitmap;
});
}
finally
{
tempCapture?.Dispose();
tempFrame?.Dispose();
}
StartCaptureThread();
}
catch (Exception ex)
{
Dispatcher.Invoke(() => MessageBox.Show($"打开视频失败: {ex.Message}"));
}
}
//快退
private void BtnRewind_Click(object sender, RoutedEventArgs e)
{
if (_capture != null && _isVideoMode)
{
lock (_lockObj)
{
double targetFrame = _currentFrameIndex - _fps * 3;
_pendingFramePosition = Math.Max(0, targetFrame);
_seekPending = true;
}
}
}
//播放暂停
private void BtnPause_Click(object sender, RoutedEventArgs e)
{
_isPaused = !_isPaused;
btnPause.Content = _isPaused ? "播放" : "暂停";
}
//快进
private void BtnFastForward_Click(object sender, RoutedEventArgs e)
{
if (_capture != null && _isVideoMode)
{
lock (_lockObj)
{
double targetFrame = _currentFrameIndex + _fps * 3;
_pendingFramePosition = Math.Min(_totalFrames - 1, targetFrame);
_seekPending = true;
}
}
}
//打开摄像头
private void BtnOpenCamera_Click(object sender, RoutedEventArgs e)
{
if (_isOpeningCamera)
{
return;
}
_isOpeningCamera = true;
btnOpenCamera.IsEnabled = false;
// 异步打开摄像头,避免UI卡死
Task.Run(() => OpenCamera(0));
}
private void OpenCamera(int cameraId)
{
try
{
Console.WriteLine(DateTime.Now);
Dispatcher.Invoke(() =>
{
Title = "OpenCvSharp播放器 - 正在打开摄像头...";
});
StopPlayback();
OpenCvSharp.VideoCapture tempCapture = null;
OpenCvSharp.Mat tempFrame = new OpenCvSharp.Mat();
try
{
tempCapture = new OpenCvSharp.VideoCapture(cameraId);
if (!tempCapture.IsOpened())
{
Dispatcher.Invoke(() =>
{
MessageBox.Show("无法打开摄像头");
btnOpenCamera.IsEnabled = true;
_isOpeningCamera = false;
Title = "OpenCvSharp播放器";
});
return;
}
lock (_lockObj)
{
_capture?.Dispose();
_capture = tempCapture;
tempCapture = null;
_frame?.Dispose();
_frame = tempFrame?.Clone() ?? new OpenCvSharp.Mat();
_fps = 30; // 摄像头固定30fps
_isVideoMode = false;
_isCapturing = true;
_isPaused = false;
_seekPending = false;
}
Dispatcher.Invoke(() =>
{
seekPlay.IsEnabled = false;
EnableVideoButtons(false);
EnableCameraButtons(true);
btnOpenCamera.IsEnabled = true;
_isOpeningCamera = false;
Title = "OpenCvSharp播放器 - 摄像头模式";
// 显示第一帧(如果有)
if (_frame != null && !_frame.Empty())
{
var bitmap = _frame.ToBitmapSource();
imgCamera.Source = bitmap;
}
});
StartCaptureThread();
}
finally
{
tempCapture?.Dispose();
tempFrame?.Dispose();
}
}
catch (Exception ex)
{
Dispatcher.Invoke(() =>
{
MessageBox.Show($"打开摄像头失败: {ex.Message}");
btnOpenCamera.IsEnabled = true;
_isOpeningCamera = false;
Title = "OpenCvSharp播放器";
});
}
Console.WriteLine(DateTime.Now);
}
private void BtnStartRecord_Click(object sender, RoutedEventArgs e)
{
if (_capture == null || !_isCapturing)
{
MessageBox.Show("请先打开视频或摄像头");
return;
}
string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
string defaultPath = System.IO.Path.Combine(desktopPath, $"录制_{DateTime.Now:yyyyMMdd_HHmmss}.avi");
var dialog = new Microsoft.Win32.SaveFileDialog
{
Filter = "AVI文件|*.avi|MP4文件|*.mp4",
Title = "保存录制视频",
FileName = System.IO.Path.GetFileName(defaultPath),
InitialDirectory = desktopPath // 默认打开桌面
};
if (dialog.ShowDialog() == true)
{
btnStartRecord.IsEnabled = false;
Task.Run(() => StartRecording(dialog.FileName));
}
}
private void StartRecording(string filePath)
{
try
{
//选择的路径
_recordFilePath = filePath;
// 重置录制帧数
_recordFrameCount = 0;
int fps = 0;
OpenCvSharp.Size frameSize = new OpenCvSharp.Size();
// 加锁获取摄像头/视频参数,避免线程竞争
lock (_lockObj)
{
if (_capture == null || !_capture.IsOpened())
{
Dispatcher.Invoke(() =>
{
MessageBox.Show("录制初始化失败:捕获设备未就绪", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
btnStartRecord.IsEnabled = true;
});
return;
}
fps = (int)_capture.Get(OpenCvSharp.VideoCaptureProperties.Fps);
frameSize = new OpenCvSharp.Size((int)_capture.Get(OpenCvSharp.VideoCaptureProperties.FrameWidth),
(int)_capture.Get(OpenCvSharp.VideoCaptureProperties.FrameHeight));
}
// 兼容帧率为0的情况
if (fps <= 0) fps = 30;
// 兼容分辨率为0的情况
if (frameSize.Width <= 0 || frameSize.Height <= 0)
{
frameSize = new OpenCvSharp.Size(640, 480);
}
OpenCvSharp.VideoWriter tempWriter = null;
// 根据文件扩展名选择编码格式
var ext = System.IO.Path.GetExtension(_recordFilePath).ToLower();
OpenCvSharp.FourCC fourCC = ext == ".mp4" ? OpenCvSharp.FourCC.H264 : OpenCvSharp.FourCC.MJPG;
try
{
tempWriter = new OpenCvSharp.VideoWriter(_recordFilePath, fourCC, fps, frameSize, true);
}
catch (Exception ex)
{
Dispatcher.Invoke(() =>
{
MessageBox.Show($"录制初始化失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
btnStartRecord.IsEnabled = true;
});
return;
}
if (!tempWriter.IsOpened())
{
Dispatcher.Invoke(() =>
{
MessageBox.Show("录制初始化失败!无法打开视频写入器", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
btnStartRecord.IsEnabled = true;
});
tempWriter?.Dispose();
return;
}
// 加锁赋值视频写入器
lock (_lockObj)
{
_videoWriter = tempWriter;
_isRecording = true;
}
// 切换到主线程更新UI
Dispatcher.Invoke(() =>
{
btnStartRecord.IsEnabled = false;
btnStopRecord.IsEnabled = true;
MessageBox.Show($"开始录制:{_recordFilePath}", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
});
}
catch (Exception ex)
{
// 切换到主线程显示错误
Dispatcher.Invoke(() =>
{
MessageBox.Show($"录制启动出错:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
btnStartRecord.IsEnabled = true;
});
}
}
//停止录像
private void BtnStopRecord_Click(object sender, RoutedEventArgs e)
{
// 立即禁用停止按钮,避免重复点击
btnStopRecord.IsEnabled = false;
// 异步停止录制
Task.Run(() => StopRecording());
}
private void StopRecording()
{
OpenCvSharp.VideoWriter tempWriter = null;
string recordPath = string.Empty;
int recordCount = 0;
lock (_lockObj)
{
tempWriter = _videoWriter;
_videoWriter = null;
_isRecording = false;
recordPath = _recordFilePath;
recordCount = _recordFrameCount;
}
// 在锁外释放资源
tempWriter?.Dispose();
Dispatcher.Invoke(() =>
{
btnStartRecord.IsEnabled = true;
btnStopRecord.IsEnabled = false;
MessageBox.Show($"录制完成,共录制 {recordCount} 帧,已保存到: {recordPath}");
});
}
//截图
private void BtnCapture_Click(object sender, RoutedEventArgs e)
{
CaptureCurrentFrame();
}
private void CaptureCurrentFrame()
{
OpenCvSharp.Mat frameToSave = null;
lock (_frameLock)
{
if (_latestFrame == null || _latestFrame.Empty())
{
MessageBox.Show("没有可捕获的帧");
return;
}
// 复制当前帧,避免线程竞争
frameToSave = _latestFrame.Clone();
}
if (frameToSave == null || frameToSave.Empty())
{
MessageBox.Show("没有可捕获的帧");
return;
}
var dialog = new Microsoft.Win32.SaveFileDialog
{
Filter = "JPEG文件|*.jpg|PNG文件|*.png|BMP文件|*.bmp",
Title = "保存截图",
FileName = $"截图_{DateTime.Now:yyyyMMdd_HHmmss}.jpg"
};
if (dialog.ShowDialog() == true)
{
try
{
frameToSave.SaveImage(dialog.FileName);
MessageBox.Show($"截图已保存: {dialog.FileName}");
}
catch (Exception ex)
{
MessageBox.Show($"保存截图失败: {ex.Message}");
}
finally
{
frameToSave.Dispose();
}
}
else
{
frameToSave.Dispose();
}
}
//停止
private void BtnStop_Click(object sender, RoutedEventArgs e)
{
StopPlayback();
}
private void StartCaptureThread()
{
_captureThread = new Thread(CaptureLoop)
{
IsBackground = true,
Priority = ThreadPriority.Normal
};
_captureThread.Start();
}
private void CaptureLoop()
{
int frameDelay = (int)(1000.0 / (_fps > 0 ? _fps : 30.0));
_lastFrameTime = DateTime.Now;
OpenCvSharp.Mat currentFrame = new OpenCvSharp.Mat();
OpenCvSharp.Mat displayFrame = new OpenCvSharp.Mat();
int errorCount = 0;
while (_isCapturing)
{
try
{
// 处理待处理的跳转
if (_seekPending)
{
lock (_lockObj)
{
if (_capture != null && _isVideoMode)
{
_capture.Set(OpenCvSharp.VideoCaptureProperties.PosFrames, _pendingFramePosition);
_currentFrameIndex = _pendingFramePosition;
}
_seekPending = false;
}
// 跳转后稍微等待
Thread.Sleep(50);
}
if (_isPaused)
{
Thread.Sleep(50);
continue;
}
// 帧率控制
DateTime now = DateTime.Now;
double elapsedMs = (now - _lastFrameTime).TotalMilliseconds;
double targetDelay = frameDelay / Math.Max(_playbackRate, 0.1);
if (elapsedMs < targetDelay)
{
Thread.Sleep((int)Math.Max(1, targetDelay - elapsedMs));
continue;
}
bool frameRead = false;
lock (_lockObj)
{
if (_capture != null && _capture.IsOpened())
{
frameRead = _capture.Read(currentFrame);
}
}
if (frameRead && !currentFrame.Empty())
{
errorCount = 0;
// 更新当前帧索引
if (_isVideoMode)
{
lock (_lockObj)
{
_currentFrameIndex = _capture?.Get(OpenCvSharp.VideoCaptureProperties.PosFrames) ?? _currentFrameIndex;
}
}
// 保存最新帧用于截图
lock (_frameLock)
{
_latestFrame?.Dispose();
_latestFrame = currentFrame.Clone();
}
// 录制帧(在锁外处理,避免阻塞)
if (_isRecording)
{
bool writeSuccess = false;
lock (_lockObj)
{
if (_videoWriter != null && _videoWriter.IsOpened())
{
_videoWriter.Write(currentFrame);
_recordFrameCount++;
writeSuccess = true;
}
}
// 如果写入失败,可能需要停止录制
if (!writeSuccess && _isRecording)
{
Dispatcher.Invoke(() =>
{
MessageBox.Show("录制失败,可能是磁盘空间不足或权限问题");
BtnStopRecord_Click(null, null);
});
}
}
// 显示帧(使用克隆的帧避免跨线程问题)
currentFrame.CopyTo(displayFrame);
Dispatcher.Invoke(() =>
{
try
{
var bitmap = displayFrame.ToBitmapSource();
imgCamera.Source = bitmap;
if (_isVideoMode)
{
seekPlay.Value = _currentFrameIndex;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"显示错误: {ex.Message}");
}
});
_lastFrameTime = now;
}
else if (_isVideoMode)
{
errorCount++;
// 连续多次读取失败,认为视频结束
if (errorCount > 5)
{
_isPaused = true;
Dispatcher.Invoke(() =>
{
btnPause.Content = "播放";
MessageBox.Show("视频播放完毕");
});
break;
}
// 尝试重新定位到当前位置
lock (_lockObj)
{
if (_capture != null && _isVideoMode)
{
_capture.Set(OpenCvSharp.VideoCaptureProperties.PosFrames, _currentFrameIndex);
}
}
Thread.Sleep(100);
}
else
{
// 摄像头模式下读取失败,短暂等待后重试
Thread.Sleep(50);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"捕获循环错误: {ex.Message}");
Thread.Sleep(100);
}
}
currentFrame?.Dispose();
displayFrame?.Dispose();
}
private void StopPlayback()
{
_isCapturing = false;
_isPaused = false;
// 先停止录制
if (_isRecording)
{
OpenCvSharp.VideoWriter tempWriter = null;
lock (_lockObj)
{
tempWriter = _videoWriter;
_videoWriter = null;
_isRecording = false;
}
tempWriter?.Dispose();
}
if (_captureThread != null && _captureThread.IsAlive)
{
_captureThread.Join(2000);
}
lock (_lockObj)
{
_videoWriter?.Dispose();
_videoWriter = null;
_capture?.Dispose();
_capture = null;
_frame?.Dispose();
_frame = null;
}
lock (_frameLock)
{
_latestFrame?.Dispose();
_latestFrame = null;
}
Dispatcher.Invoke(() =>
{
imgCamera.Source = null;
seekPlay.Value = 0;
seekPlay.IsEnabled = false;
EnableVideoButtons(false);
EnableCameraButtons(false);
btnStartRecord.IsEnabled = true;
btnStopRecord.IsEnabled = false;
btnOpenCamera.IsEnabled = true;
_isOpeningCamera = false;
Title = "OpenCvSharp播放器";
});
}
private void EnableVideoButtons(bool enabled)
{
btnRewind.IsEnabled = enabled;
btnPause.IsEnabled = enabled;
btnStop.IsEnabled = enabled;
btnFastForward.IsEnabled = enabled;
btnCapture.IsEnabled = enabled;
btnStartRecord.IsEnabled = enabled;
btnStopRecord.IsEnabled = false;
}
private void EnableCameraButtons(bool enabled)
{
btnCapture.IsEnabled = enabled;
btnStartRecord.IsEnabled = enabled;
btnStopRecord.IsEnabled = false;
btnStop.IsEnabled = enabled;
}
}
}
更多推荐
所有评论(0)