本文底层逻辑是通过调用cmd命令去操作ffmpeg.exe文件,以及相应命令去控制摄像头
首先需要去官网下载ffmpeg文件
网址:https://www.ffmpeg.org/download.html
下载好以后解压,找到里面bin文件夹下的ffmpeg.exe,将文件保存到项目中,这块文件夹要记住
首先先做准备工作,先在VS中打开项目csproj文件进行编辑,在文件中加入代码:
<Target Name="CopyFfmpegDlls" AfterTargets="Build">
<Copy SourceFiles="你存放ffmpeg.exe的文件的路径\ffmpeg.exe" DestinationFolder="$(OutputPath)" />
</Target>
这段代码是将ffmpeg.exe在生成解决方案时一并弄到生成项目后的bin目录下,后期代码调用全是基于这个实现的!!!
也可以通过设置VS中文件属性去生成到bin中:
两种随便一种都可以,我两种方法一并使用了,必须保证ffmpeg.exe要在项目生成后的bin目录下!
至此准备工作完成,ffmpeg实体类代码为以下:
这块我怕文件冲突直接将名称都设为当前时间了,部分变量名可以酌情自己去自定义
namespace Test.Utils
{
public class ffmpegManager
{
Process p;
public ffmpegManager()
{
Init();
}
/// <summary>
/// 初始化操作
/// </summary>
private void Init()
{
p = new Process();
//设置要启动的应用程序
p.StartInfo.FileName = "cmd.exe";
//是否使用操作系统shell启动
p.StartInfo.UseShellExecute = false;
// 接受来自调用程序的输入信息
p.StartInfo.RedirectStandardInput = true;
//输出信息
p.StartInfo.RedirectStandardOutput = true;
// 输出错误
p.StartInfo.RedirectStandardError = true;
//不显示程序窗口
p.StartInfo.CreateNoWindow = true;
p.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
p.OutputDataReceived += new DataReceivedEventHandler(p_OutputDataReceived);
p.ErrorDataReceived += new DataReceivedEventHandler(p_ErrorDataReceived);
//启动程序
p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();
}
/// <summary>
/// 录制视频
/// </summary>
/// <param name="videoDeviceName">电脑摄像头的名称</param>
/// <param name="fileName">保存视频的名称</param>
public void RecordVideo(string videoDeviceName, string fileName)
{
//可以根据需要修改命令中的参数
//完整命令:ffmpeg -f dshow -s 320*180 -i video="HP HD Camera" -f dshow -i audio="麦克风 (USB Microphone)" -vcodec mpeg4 -acodec aac -strict -2 myVideo.mp4
string currentDirectory = Directory.GetCurrentDirectory();
string relativePath = "视频文件夹";
string path = Path.Combine(currentDirectory, relativePath);
string command = "ffmpeg -f dshow -s 1920x1080 -framerate 30 -i video=\"" + videoDeviceName + "\" -vcodec mpeg4 -acodec aac -strict -2 \"" + path + "\\"+ fileName + ".mp4\"";
//向cmd窗口发送输入信息
p.StandardInput.WriteLine(command);
p.StandardInput.AutoFlush = true;
}
//停止录制
public void StopRecord()
{
//输入q命令停止录制
p.StandardInput.WriteLine("q");
}
private void p_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
Console.WriteLine(e.Data);
}
}
private void p_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
Console.WriteLine(e.Data);
}
}
//拍照
public void takePhoto(String imageName) {
string currentDirectory = Directory.GetCurrentDirectory();
string relativePath = "截图文件夹";
string path = Path.Combine(currentDirectory, relativePath);
// 生成保存的文件路径(假设文件名为 photo.jpg)
//string currentTime = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string filePath = Path.Combine(path, imageName + ".jpg");
// 构造 ffmpeg 命令
string ffmpegCommand = $"ffmpeg -f dshow -i video=\"USB Camera\" -vframes 1 \"{filePath}\"";
//向cmd窗口发送输入信息
p.StandardInput.WriteLine(ffmpegCommand);
p.StandardInput.AutoFlush = true;
// 确保不会过早调用 Close,允许系统有时间完成资源处理
Thread.Sleep(2000);
}
}
}
我在基础上包装了一层工具类方便直接调用:
namespace Test.Utils
{
public static class VideoUtils
{
/// <summary>
/// 开始录制
/// </summary>
public static bool startRecord = false;
/// <summary>
/// 暂停录制
/// </summary>
private static bool pauseRecord = false;
private static ffmpegManager manager = new ffmpegManager();
public static void Open()
{
//创建监控保存路径
//录制后的文件存到了项目的bin路径下,也可以自己自定义
string currentDirectory = Directory.GetCurrentDirectory();
string relativePath = "视频文件夹";
string path = Path.Combine(currentDirectory, relativePath);
string currentDirectory2 = Directory.GetCurrentDirectory();
string relativePath2 = "截图文件夹";
string path2 = Path.Combine(currentDirectory2, relativePath2);
string folderPath = @path;
string folderPath2 = @path2;
// 调用方法检测和创建文件夹
EnsureFolderExists(folderPath);
EnsureFolderExists(folderPath2);
}
/// <summary>
/// 开始录制
/// </summary>
public static void Start()
{
if (!startRecord)
{
if (pauseRecord)
{
startRecord = true;
pauseRecord = false;
return;
}
string currentTime = DateTime.Now.ToString("yyyyMMdd_HHmmss");
startRecord = true;
pauseRecord = false;
manager = new ffmpegManager();
manager.RecordVideo("USB Camera", currentTime);
}
}
/// <summary>
/// 停止录制
/// </summary>
public static void Stop()
{
startRecord = false;
pauseRecord = false;
// 确保不会过早调用 Close,允许系统有时间完成资源处理
Thread.Sleep(20);
manager.StopRecord();
}
/// <summary>
/// 截图拍照
/// </summary>
public static void TakePhoto(String imageName)
{
manager.takePhoto(imageName);
}
public static void EnsureFolderExists(string folderPath)
{
// 检测路径是否存在
if (!Directory.Exists(folderPath))
{
try
{
// 创建路径
Directory.CreateDirectory(folderPath);
Console.WriteLine("文件夹创建成功!");
}
catch (Exception ex)
{
Console.WriteLine($"创建文件夹失败:{ex.Message}");
}
}
else
{
}
}
}
}
接下来是具体的调用,首先是调用其拍照,这里直接写点击录像的方法:
首先是前端Xaml:
<Button x:Name="last" Content="录制" Style="{StaticResource ButtonStyleEllipse}" Width="170" FontSize="22" Height="70" Click="Start" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Grid.RowSpan="2" Background="#FFE3EFFA" FontFamily="SourceHanSansCN-Bold" Foreground="#FF0065CD" FontWeight="Bold" Margin="60,50,450,61" />
<Button x:Name="next" Content="停止" Style="{StaticResource ButtonStyleEllipse}" Width="170" FontSize="20" Height="70" Click="Stop" Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" Background="#FF0065CD" FontFamily="SourceHanSansCN-Bold" Foreground="White" FontWeight="Bold" Margin="115,50,275,61" />
接下来是录制视频后端cs实现:
namespace DrugControlApp
{
public partial class MainWindow : BaseWindow
{
public MainWindow(bool is_verify = true)
{
InitializeComponent();
//项目启动检测是否有对应文件夹
VideoUtils.Open();
}
private void Start(object sender, RoutedEventArgs e)
{
try
{
VideoUtils.Start();
}
catch (Exception exception)
{
MessageBox.Show(exception.Message);
}
}
private void Stop(object sender, RoutedEventArgs e)
{
try
{
VideoUtils.Stop();
}
catch (Exception exception)
{
MessageBox.Show(exception.Message);
}
}
}
}
其次是拍照截图功能,同样首先放前端xaml代码:
<Label Name="prescribePic" Visibility="Visible" Content="照片" Grid.Row="5" Grid.Column="5" Grid.ColumnSpan="2" HorizontalAlignment="Left" VerticalAlignment="Top" RenderTransformOrigin="1.9,1.88" FontFamily="SourceHanSansCN" FontSize="20" FontWeight="Bold" Foreground="#0E63CD"/>
<Button x:Name="loadPicBtn" Content="上传图片" Visibility="Visible" HorizontalAlignment="Left" VerticalAlignment="Center" Grid.Row="5" Grid.Column="5" Grid.ColumnSpan="2" FontSize="22" Width="180" Height="60" Background="#2BB669" Foreground="#FFFFFF" Style="{DynamicResource ButtonStyleEllipse}" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontFamily="Source Han Sans CN" FontWeight="Bold" Click="uploadImage" />
<Canvas Grid.Row="5" Grid.Column="5" Grid.ColumnSpan="3" Grid.RowSpan="2" Width="400" Height="200" Margin="0,37,80,33" >
<Label Name="prescribePicText" Visibility="Collapsed" Content="请对准摄像头并点击拍照按钮" Grid.Row="5" Grid.Column="5" Grid.ColumnSpan="3" HorizontalAlignment="Left" VerticalAlignment="Top" RenderTransformOrigin="1.9,1.88" FontFamily="SourceHanSansCN" FontSize="20" FontWeight="Bold" Foreground="#0E63CD" Grid.RowSpan="2" Canvas.Left="33" Canvas.Top="87"/>
<Image x:Name="prescribeCamera" Stretch="Fill" Canvas.Left="0" Canvas.Top="0" Width="400" Height="200" />
<Rectangle x:Name="picBorder" Visibility="Collapsed" Width="400" Height="200" Fill="Transparent" Stroke="Gray" StrokeThickness="2" StrokeDashArray="2,2"/>
<Image x:Name="loadPic" Visibility="Collapsed" Stretch="Fill" Canvas.Left="0" Canvas.Top="0" Width="400" Height="200"/>
</Canvas>
<Button x:Name="snapshot" Content="拍照" Visibility="Collapsed" Grid.Row="6" Grid.Column="7" Grid.RowSpan="1" Style="{StaticResource ButtonStyleEllipse}" FontFamily="SourceHanSansCN" FontSize="12" FontWeight="Bold" Background="#FF0E63CD" Foreground="White" Width="50" Height="25" Click="screenshot" Margin="80,77,30,33"/>
<Button x:Name="delSnapshot" Content="删除" Visibility="Collapsed" Grid.Row="6" Grid.Column="7" Grid.RowSpan="1" Style="{StaticResource ButtonStyleEllipse}" FontFamily="SourceHanSansCN" FontSize="12" FontWeight="Bold" Background="Red" Foreground="White" Width="50" Height="25" Click="deleteScreenshot" Margin="80,77,30,33"/>
后端cs实现:
//点击 上传照片 按钮
private void uploadImage(object sender, RoutedEventArgs e) {
loadPicBtn.Visibility = Visibility.Collapsed;
picBorder.Visibility = Visibility.Visible;
snapshot.Visibility = Visibility.Visible;
prescribeCamera.Visibility = Visibility.Visible;
prescribePicText.Visibility = Visibility.Visible;
}
//拍照
private void screenshot(object sender, RoutedEventArgs e) {
string prescriptionName = PrescriptionName.Text;
try
{
string currentTime = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string photoName = "名称_" + prescriptionName + "创建日期_" + currentTime;
VideoUtils.TakePhoto(photoName);
string currentDirectory = Directory.GetCurrentDirectory();
string relativePath = "截图";
string path = System.IO.Path.Combine(currentDirectory, relativePath);
string filePath = System.IO.Path.Combine(path, photoName + ".jpg");
loadPic.Source = getImage(filePath);
LoggerFactory.GetLog().Info(loadPic.Source);
loadPic.UpdateLayout(); // 强制刷新UI
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
prescribeCamera.Visibility = Visibility.Collapsed;
snapshot.Visibility = Visibility.Collapsed;
delSnapshot.Visibility = Visibility.Visible;
loadPic.Visibility = Visibility.Visible;
}
//删除已上传的照片
private void deleteScreenshot(object sender, RoutedEventArgs e) {
delSnapshot.Visibility = Visibility.Collapsed;
snapshot.Visibility = Visibility.Collapsed;
loadPic.Visibility = Visibility.Collapsed;
loadPicBtn.Visibility = Visibility.Visible;
picBorder.Visibility = Visibility.Collapsed;
prescribePicText.Visibility = Visibility.Collapsed;
// 获取BitmapImage对象
BitmapImage bitmap = loadPic.Source as BitmapImage;
// 从UriSource获取本地路径
string imagePath = bitmap.UriSource.LocalPath;
try
{
// 删除图片文件
File.Delete(imagePath);
Console.WriteLine("文件已成功删除");
loadPic.Source = null;
}
catch (Exception ex)
{
CustomDialogTipModel tipModel = new CustomDialogTipModel();
tipModel.InputParameter = "删除文件时发生错误,请联系管理员!";
CustomDialogPrompt dialogPrompt = new CustomDialogPrompt();
dialogPrompt.DataContext = tipModel;
bool? result = dialogPrompt.ShowDialog();
if (result == true)
{
}
LoggerFactory.GetLog().Error(string.Format("删除文件时发生错误:{0}", ex.Message));
}
}
public ImageSource getImage(string file)
{
var bmp = new BitmapImage();
try
{
bmp.BeginInit();
bmp.CacheOption = BitmapCacheOption.OnLoad;
LoggerFactory.GetLog().Info(file);
bmp.UriSource = new Uri(@file);
bmp.EndInit();
}
catch (Exception ex)
{
LoggerFactory.GetLog().Error("Error during image loading", ex);
}
return bmp;
}
部分借鉴到的文章地址:使用ffmpeg调用摄像头录制视频(C#)_c# ffmpeng 打开网络摄像机-优快云博客
能站在巨人肩膀上开发真的很荣幸,非常 感谢上面这位大佬给出的解决。
这个难题困扰了我多个星期,从项目引用Aforge,到accord的dll文件,不断的踩坑不断的填坑,后期询问多个同事以及领导能得出的最优解决方案。