c#中有许多方法可以对RTSP流进行显示,抓拍或录像,经过不懈努力,觉得Opencvsharp不错,然后动手集成了一个可以预览、停止、抓拍、分段录像的控件dll,方便大家使用,

解决视频流的稳定性问题,还有吃内存问题。经过24小时的录像测试,完美运行。

控件界面如下:

下面的图片是显示两个流,大家可以根据您的需要加载不同数量的视频。

点击预览按钮后,显示效果如下:

控件按钮:预览、停止、抓拍、录像、停止录像

控件特点:设置视频帐号密码IP后可以快速进行连接,抓拍图片可以自定义一些字符串,如项目名称。录像可以设置段大小缺省120s两分钟一个文件,根据场景自由的设置。另外系统中还演示如何压缩指定目录下的所有录像文件。

录像文件一分钟mp4大概有300-700MB,可以压缩到20倍左右。

一、创建控件,名为HKRtsp

        NuGet引用:OpenCvSharp3-AnyCPU v4.0.0.20181129

        目标框架:Net Framework 4.7.2

        这是读视频,抓拍,录像控件

using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;

using System.Drawing;
using OpenCvSharp.Extensions;
using OpenCvSharp;

namespace CaseRecords.Control
{
    public partial class HKRtsp : UserControl
    {
        public HKRtsp()
        {
            InitializeComponent();
            CheckForIllegalCrossThreadCalls = false;
            syncPostUpdate = SynchronizationContext.Current;
        }

        #region 变量

        CancellationTokenSource cts;
        //OpenCvSharp 使用
        VideoCapture capture;
        private VideoWriter videoWriter;

        private bool recordingFlag = false;        
        private SynchronizationContext syncPostUpdate = null;


        #endregion

        #region 自定义事件

        //包含异常或者正常的消息, type = 0正常,type = 1异常消息
        public delegate void VideoHasMessage(long videoId, int type, long caseId, string msg, string memo);
        //type = 0 抓拍,type = 1 录像,memo为当前监控的说明
        public delegate void UpdateDBAction(long videoId, int type, long caseId, string fileName, string memo);
        public VideoHasMessage videoHasMessage;
        public UpdateDBAction updateDBAction;

        #endregion

        #region 海康HKRtsp取流属性

        private string _rtspUrl = "";
        private string _rtspLoginUserName = "";
        private string _rtspLoginPassword = "";
        private int _rtspPort = 554;
        private string _rtspIp = "";
        private long _caseId = 0;  //CaseId
        private string _rtspMemo = "";
        private string _rtspRecordingFileName = "";
        private string _rtspRecordingFileNamePattern = "";
        // segmentDuration 录像分段时长(秒) 5分钟 5 * 60 = 300
        private int segmentDuration = 60;  
        private int segmentCounter = 0;    //当前的分段数量
        private string _projectName = "";  //项目名称
        private string _videoDescription = ""; //视频位置描述
        private long lastSaveTime = 0;     //当前开始录像的点
        private bool stopFlag = false;     //停止
        private bool manualControl = false; //手动控制
        private int _fps = 25;   //缺省的流帧数量
        private long _videoId;       //当前视频配置Id
        private string _sourceDir = "";  //文件存储目录
        private bool _useVideo = true;   //缺省有效

        [Description("监控手动控制"), Category("项目配置")]
        public bool ManualControl
        {
            set
            {
                manualControl = value;
            }
            get
            {
                return manualControl;
            }
        }

        [Description("摄像头是否有效"), Category("项目配置")]
        public bool UseVideo
        {
            set
            {
                _useVideo = value;
            }
            get
            {
                return _useVideo;
            }
        }

        [Description("项目抓拍录像存储存目录"), Category("项目配置")]
        public string SourceDir
        {
            set
            {
                _sourceDir = value;
            }
            get
            {
                return _sourceDir;
            }
        }

        [Description("视频配置ID"), Category("项目配置")]
        public long VideoId
        {
            set
            {
                _videoId = value;
            }
            get
            {
                return _videoId;
            }
        }

        [Description("项目名称"), Category("项目配置")]
        public string ProjectName
        {
            set
            {
                _projectName = value;
            }
            get
            {
                return _projectName;
            }
        }

        [Description("视频位置说明"), Category("项目配置")]
        public string VideoDescription
        {
            set
            {
                _videoDescription = value;
            }
            get
            {
                return _videoDescription;
            }
        }

        [Description("Rtsp流备注说明"), Category("流配置")]
        public string RtspMemo
        {
            set
            {
                _rtspMemo = value;
            }
            get
            {
                return _rtspMemo;
            }
        }

        [Description("Rtsp流地址"), Category("流配置")]
        public string RtspUrl
        {
            set
            {
                _rtspUrl = value;
            }
            get
            {
                return _rtspUrl;
            }
        }

        [Description("Rtsp流IP地址"), Category("流配置")]
        public string RtspIp
        {
            set
            {
                _rtspIp = value;
            }
            get
            {
                return _rtspIp;
            }
        }

        [Description("Rtsp流登录用户名"), Category("流配置")]
        public string RtspLoginUserName
        {
            set
            {
                _rtspLoginUserName = value;
            }
            get
            {
                return _rtspLoginUserName;
            }
        }

        [Description("Rtsp流登录密码"), Category("流配置")]
        public string RtspLoginPassword
        {
            set
            {
                _rtspLoginPassword = value;
            }
            get
            {
                return _rtspLoginPassword;
            }
        }

        [Description("Rtsp流端口"), Category("流配置")]
        public int RtspPort
        {
            set
            {
                _rtspPort = value;
            }
            get
            {
                return _rtspPort;
            }
        }

        [Description("唯一案件Id"), Category("流配置")]
        public long CaseId
        {
            set
            {
                _caseId = value;
            }
            get
            {
                return _caseId;
            }
        }

        [Description("Rtsp录像时间S"), Category("流配置")]
        public int SegmentDuration
        {
            set
            {
                segmentDuration = value;
            }
            get
            {
                return segmentDuration;
            }
        }

        [Description("RTSP帧数量"), Category("流配置")]
        public int FPS
        {
            get
            {
                return _fps;
            }
            set
            {
                _fps = value;
            }
        }

        #endregion

        #region 海康HKRtsp取流操作函数

        //开始取流
        public void StartReadStream()
        {
            try
            {
                if (String.IsNullOrEmpty(_rtspUrl) && (String.IsNullOrEmpty(_rtspIp) || String.IsNullOrEmpty(_rtspLoginUserName) ||
                    String.IsNullOrEmpty(_rtspLoginPassword) || _rtspPort == 0))
                {
                    syncPostUpdate.Post(UpdateDeviceStatus, "视频参数设置不全,无法播放视频!");
                    return;
                }
                if (String.IsNullOrEmpty(_rtspUrl))
                {
                    _rtspUrl = String.Format("rtsp://{0}:{1}@{2}:{3}/mpeg4/Channels/1", _rtspLoginUserName, _rtspLoginPassword, _rtspIp, _rtspPort);
                }

                syncPostUpdate.Post(UpdateDeviceStatus, "开始播放视频:" + _rtspUrl);
                if (cts != null)
                {
                    cts.Dispose();
                    cts = null;
                }
                cts = new CancellationTokenSource();
                Task task = new Task(() =>
                {

                    //打开视频流
                    if (capture == null)
                    {
                        capture = new VideoCapture();
                    }
                    toolOpen.Enabled = false;
                    capture.Open(_rtspUrl);
                    int time = 10;
                    if (capture.IsOpened())
                    {
                        syncPostUpdate.Post(UpdateButtonStatus, "");
                        syncPostUpdate.Post(UpdateVideoStatus, 1);
                        // 获取视频的帧率和分辨率
                        double fps = capture.Fps;
                        time = Convert.ToInt32(Math.Round(1000 / capture.Fps));

                        Debug.Print(String.Format("开始预览视频分辩率:{0},宽:{1},高:{2},时间:{3}", capture.Fps, capture.FrameWidth, capture.FrameHeight, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")));
                        while (!stopFlag)
                        {
                            if (!capture.IsOpened())
                            {
                                Debug.Print("断开连接时,断线重连 ...." + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
                                capture.Release();
                                capture.Open(_rtspUrl);
                                continue;
                            }
                            Mat frame = new Mat();
                            //判断是否被取消
                            if (cts.Token.IsCancellationRequested)
                            {
                                if (picShowImage != null)
                                {
                                    Image image = picShowImage.Image;
                                    picShowImage.Image = null;
                                    if (image != null)
                                    {
                                        image.Dispose();
                                    }
                                    
                                }
                                if (frame != null)
                                {
                                    frame.Release();
                                    frame.Dispose();
                                    frame = null;
                                }
                                break;
                            }
                            try
                            {
                                bool get = capture.Read(frame);
                                if (get)
                                {
                                    if (frame.Empty())
                                    {
                                        syncPostUpdate.Post(UpdateDeviceStatus, String.Format("帧内容为空! VideCapture状态:{0},时间:{1},重连中  ..." + capture.IsOpened(), DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")));
                                        syncPostUpdate.Post(UpdateVideoStatus, 2);
                                        capture.Release();
                                        capture.Open(_rtspUrl);
                                        continue;
                                    }
                                    //录像状态时
                                    if (recordingFlag & videoWriter != null && !videoWriter.IsDisposed && videoWriter.IsOpened())
                                    {
                                        // 将当前帧写入VideoWriter对象中
                                        videoWriter.Write(frame);
                                        long currentTime = DateTime.Now.Ticks / TimeSpan.TicksPerSecond;
                                        // 判断是否需要开始保存下一个分段视频
                                        //Debug.Print(String.Format("当前时间:{0},最后保存时间:{1},片段长度:{2}", currentTime, lastSaveTime, segmentDuration));
                                        if (currentTime >= lastSaveTime + segmentDuration)
                                        {
                                            SaveSegment("正常分段");
                                        }
                                    }
                                    //监控预览状态                   
                                    try
                                    {
                                        if (picShowImage != null && frame != null && !frame.Empty())
                                        {
                                            Image image = picShowImage.Image;
                                            picShowImage.Image = BitmapConverter.ToBitmap(frame);
                                            if (image != null)
                                            {
                                                image.Dispose();
                                            }
                                        }
                                    }
                                    catch (Exception picEx)
                                    {
                                        Debug.Print("显示预览失败:" + picEx.Message);                                        
                                    }
                                }
                                else
                                {
                                    //异常时自动保存录像文件
                                    if (recordingFlag & videoWriter != null && videoWriter.IsOpened())
                                    {
                                        SaveSegment("无帧异常分段");
                                    }
                                    Debug.Print("Frame为空,断线重连 ...." + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
                                    capture.Release();
                                    capture.Open(_rtspUrl);
                                    continue;
                                }
                            }
                            catch (Exception takeError)
                            {
                                Debug.Print("捕获RTSP帧错误:" + takeError.Message);
                            }
                            finally
                            {
                                frame.Dispose();
                            }                            
                            Thread.Sleep(10);
                        }
                    }
                    else
                    {
                        syncPostUpdate.Post(UpdateDeviceStatus, "预览视频失败!");
                    }
                    //用户退出时自动保存录像文件
                    if (recordingFlag & videoWriter != null && videoWriter.IsOpened())
                    {
                        saveRecordingFile("用户关闭视频");
                    }
                    capture.Release();
                    capture.Dispose();
                    capture = null;
                    syncPostUpdate.Post(UpdateButtonStatus, "");
                    syncPostUpdate.Post(UpdateVideoStatus, 0);

                }, cts.Token);
                task.Start();

            }
            catch (Exception ex)
            {
                syncPostUpdate.Post(UpdateDeviceStatus, String.Format("打开视频失败:{0}", ex.Message));
            }
        }
     

        //保存分段录像
        private void SaveSegment(string type)
        {
            try
            {
                if(videoWriter != null)
                {
                    saveRecordingFile(type);
                    segmentCounter++;
                    // 重新开始计时
                    lastSaveTime = DateTime.Now.Ticks / TimeSpan.TicksPerSecond;

                    _rtspRecordingFileName = String.Format(_rtspRecordingFileNamePattern, segmentCounter.ToString().PadLeft(3, '0'));
                    // 创建新的VideoWriter对象,设置输出视频的参数
                    videoWriter.Open(_rtspRecordingFileName, FourCC.MPG4, _fps, new OpenCvSharp.Size(capture.FrameWidth, capture.FrameHeight));
                }
            }
            catch (Exception ex)
            {
                syncPostUpdate.Post(UpdateDeviceStatus, "保存录像分段失败:" + ex.Message);
            }
        }

        private void saveRecordingFile(String type)
        {
            try
            {
                // 释放之前的VideoWriter对象
                videoWriter.Release();
                updateDBAction(_videoId, 1, _caseId, _rtspRecordingFileName, _rtspMemo);
                syncPostUpdate.Post(UpdateDeviceStatus,String.Format("{0},{1},保存录像文件: {2} 成功!", type, 
                                    DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), _rtspRecordingFileName));
            }
            catch (Exception ex)
            {
                syncPostUpdate.Post(UpdateDeviceStatus, String.Format("{0},保存录像失败:{1}", type, ex.Message));
            }
        }

        //停止取流
        public void StopReadStream()
        {
            try
            {
                if (recordingFlag)
                {
                    StopRecording();
                }
                cts.Cancel();
                syncPostUpdate.Post(UpdateDeviceStatus, "关闭视频");
            }
            catch (Exception ex)
            {
                syncPostUpdate.Post(UpdateDeviceStatus, String.Format("停止视频:{0},失败:{0}", _rtspRecordingFileName, ex.Message));
            }            

        }

        //抓拍,保存按项目路径,编号,备注来保存
        public bool SaveImage(string projectPath, string projectName, out string filePath)
        {
            filePath = "";
            try
            {
                if (capture == null || capture.IsDisposed)
                {
                    syncPostUpdate.Post(UpdateDeviceStatus, "没有创建OpenCv对象,无法保存抓拍!");
                    return false;
                }
                if (!capture.IsOpened())
                {
                    syncPostUpdate.Post(UpdateDeviceStatus, "没有Rtsp流,无法保存抓拍!");
                    return false;
                }
                //如果不指定存储目录时,缺省保存到当前应用程序目录
                if (String.IsNullOrEmpty(projectPath))
                {
                    projectPath = Application.StartupPath;
                }
                //如果最后一个字符中 \时,移走
                if(projectPath.Substring(projectPath.Length - 1).Equals(@"\")) projectPath = Path.GetDirectoryName(projectPath);
                //抓拍保存为当前目录下,原来是按年月日分类保存
                //projectPath = projectPath + @"\Images\" + DateTime.Now.ToString("yyyy") + @"\" + DateTime.Now.ToString("MM") + @"\"
                //               + DateTime.Now.ToString("dd") + @"\"  + projectName + @"\Capture\No_" + _caseId.ToString();
                //更新存储路径
                projectPath = projectPath + @"\Images\" + projectName + @"\" + _videoId + @"\";                
                if (!Directory.Exists(projectPath))
                {
                    Directory.CreateDirectory(projectPath);
                }
                projectPath = projectPath + DateTime.Now.ToString("yyyyMMddHHmmss_ff") + ".jpg";                
                
                //此方法是通过pictureBox进行抓拍
                if (picShowImage.Image != null)
                {
                    filePath = projectPath;
                    // 将PictureBox中的图像保存为JPG文件
                    picShowImage.Image.Save(projectPath, System.Drawing.Imaging.ImageFormat.Jpeg);
                    syncPostUpdate.Post(UpdateDeviceStatus, "抓拍保存:" + projectPath);
                    updateDBAction(_videoId, 0, _caseId, projectPath, _rtspMemo);
                    return true;
                }
                else
                {
                    filePath = "";
                    syncPostUpdate.Post(UpdateDeviceStatus, "抓拍失败,当前图片为空!");
                    return false;
                }
            }
            catch (Exception ex)
            {
                filePath = "";
                syncPostUpdate.Post(UpdateDeviceStatus, String.Format("抓拍失败:{0}", ex.Message));
                return false;
            }           
        }

        //开始录像
        public bool StartRecording(string projectPath, string projectName, out string filePath)
        {
            filePath = "";
            try
            {
                if (capture == null || capture.IsDisposed)
                {
                    syncPostUpdate.Post(UpdateDeviceStatus, "没有创建OpenCv对象,无法保存录像!");
                    return false;
                }
                if (!capture.IsOpened())
                {
                    syncPostUpdate.Post(UpdateDeviceStatus, "没有Rtsp流,无法保存录像!");
                    return false;
                }
                if (String.IsNullOrEmpty(projectPath))
                {
                    projectPath = Application.StartupPath;
                }
                //如果最后一个字符中 \时,移走
                if (projectPath.Substring(projectPath.Length - 1).Equals(@"\")) projectPath = Path.GetDirectoryName(projectPath);
                //录像保存为当前目录下,Video\2024\04\01\汨罗\
                //projectPath = projectPath + @"\Video\" + DateTime.Now.ToString("yyyy") + @"\" + DateTime.Now.ToString("MM") + @"\"
                //               + DateTime.Now.ToString("dd") + @"\" + projectName + @"\VideoRecord\No_" + _caseId.ToString();\
                //更新存储路径
                projectPath = projectPath + @"\Video\" + projectName + @"\" + _videoId + @"\";
                if (!Directory.Exists(projectPath))
                {
                    Directory.CreateDirectory(projectPath);
                }
                projectPath = projectPath + @"{0}_" + DateTime.Now.ToString("yyyyMMddHHmmss_ff") + ".mp4";
                _rtspRecordingFileNamePattern = projectPath;
                //初始化第一个分段
                segmentCounter = 1;
                filePath = String.Format(projectPath, segmentCounter.ToString().PadLeft(3, '0'));
                _rtspRecordingFileName = filePath;

                syncPostUpdate.Post(UpdateDeviceStatus, "开始录像:" + _rtspRecordingFileName);

                if (videoWriter != null)
                {
                    Debug.Print("释放原来的录像对象 VideoWrite");
                    videoWriter.Release();
                    videoWriter.Dispose();
                    videoWriter = null;
                }
                videoWriter = new VideoWriter(_rtspRecordingFileName, FourCC.MPG4, _fps, new OpenCvSharp.Size(capture.FrameWidth, capture.FrameHeight));
                lastSaveTime = DateTime.Now.Ticks / TimeSpan.TicksPerSecond;
                recordingFlag = true;

                return true;
            }
            catch (Exception ex)
            {
                filePath = "";                
                syncPostUpdate.Post(UpdateDeviceStatus, String.Format("录像操作失败:{0}",  ex.Message));
                return false;
            }
        }
               

        //停止录像
        public void StopRecording()
        {
            try
            {                
                recordingFlag = false;
                if (videoWriter != null && !videoWriter.IsDisposed)
                {
                    videoWriter.Release();
                    videoWriter.Dispose();
                    videoWriter = null;
                    syncPostUpdate.Post(UpdateDeviceStatus, String.Format("保存录像成功:{0}", _rtspRecordingFileName));
                    updateDBAction(_videoId, 1, _caseId, _rtspRecordingFileName, _rtspMemo);
                }
                else
                {
                    //界面显示当前的操作状态
                    syncPostUpdate.Post(UpdateDeviceStatus, "录像已经停止");
                }
            }
            catch (Exception ex)
            {                
                syncPostUpdate.Post(UpdateDeviceStatus, String.Format("停止录像:{0},失败:{1}", _rtspRecordingFileName, ex.Message));
            }
           
        }

        //释放
        public void Release()
        {
            try
            {
                if (recordingFlag)
                {
                    StopRecording();
                }
                stopFlag = true;

                if (videoWriter != null && !videoWriter.IsDisposed)
                {
                    if(recordingFlag) saveRecordingFile("系统关闭");
                    videoWriter.Release();
                    videoWriter.Dispose();
                    videoWriter = null;
                }
                if (capture != null && !capture.IsDisposed)
                {
                    capture.Release();
                    capture.Dispose();
                    capture = null;
                }
                               
            }
            catch (Exception ex)
            {
                LogHelper.WriteError(String.Format("释放OpenCv错误:{0}", ex.Message));
            }
        }

        public void UpdateVideoStatus(object status)
        {
            try
            {
                switch (status)
                {
                    case 0:
                        if(lblVideoStatus.Image != Properties.Resources.gray16) lblVideoStatus.Image = Properties.Resources.gray16;
                        break;
                    case 1:
                        if (lblVideoStatus.Image != Properties.Resources.green) lblVideoStatus.Image = Properties.Resources.green;
                        break;
                    case 2:
                        if (lblVideoStatus.Image != Properties.Resources.red) lblVideoStatus.Image = Properties.Resources.red;
                        break;
                }
            }
            catch (Exception ex)
            {
                LogHelper.WriteError("更新视频状态灯错误:" + ex.Message);
            }
        }

        public void UpdateButtonStatus(object status)
        {
            try
            {
                if (!manualControl)
                {
                    toolOpen.Enabled = false;
                    toolStop.Enabled = false;
                    toolCapture.Enabled = false;
                    toolStartRecording.Enabled = false;
                    toolStopRecording.Enabled = false;
                    return;
                }
                // 已经打开流时,不可以再打开,停止按钮有效
                if (capture != null)
                {
                    toolOpen.Enabled = false;
                    toolStop.Enabled = true;
                    toolCapture.Enabled = true;
                    if (recordingFlag)
                    {
                        toolStartRecording.Enabled = false;
                        toolStopRecording.Enabled = true;
                    }
                    else
                    {
                        toolStartRecording.Enabled = true;
                        toolStopRecording.Enabled = false;
                    }                    
                }
                else
                {
                    toolOpen.Enabled = true;
                    toolStop.Enabled = false;
                    toolCapture.Enabled = false;
                    toolStartRecording.Enabled = false;
                    toolStopRecording.Enabled = false;
                }



            }
            catch (Exception ex)
            {
                syncPostUpdate.Post(UpdateDeviceStatus, String.Format("更新控制按钮状态错误:{0}", ex.Message));
            }
        }

        #endregion

        private void HKRtsp_SizeChanged(object sender, EventArgs e)
        {
            try
            {
                lyMain.Left = 0;
                lyMain.Top = 0;
                lyMain.Width = this.Width;
                lyMain.Height = this.Height - ssStatus.Height - 10;
                picShowImage.Width = this.Width;
                picShowImage.Height = this.Height - ssStatus.Height - 20;
            }
            catch (Exception ex)
            {
                UpdateDeviceStatus("监控预览改变窗口错误:" + ex.Message);
            }
        }

        private void UpdateDeviceStatus(object status)
        {
            try
            {
                string txt = (string)status;
                ssStatus.Text = txt;
                videoHasMessage?.Invoke(_videoId, 0, _caseId, txt, _rtspMemo);
                LogHelper.WriteInfo(txt);
                Debug.Print("UpdateDeviceStatus:" + txt);
            }
            catch (Exception ex)
            {
                LogHelper.WriteException("UpdateDeviceStatus错误", ex);
            }
        }
        private void toolStop_Click(object sender, EventArgs e)
        {            
            try
            {
                toolStop.Enabled = false;
                StopReadStream();
                syncPostUpdate.Post(UpdateButtonStatus, "");
            }
            catch (Exception ex)
            {
                syncPostUpdate.Post(UpdateDeviceStatus, "停止视频错误:" + ex.Message);
            }
            finally
            {
                syncPostUpdate.Post(UpdateButtonStatus, "");
            }
        }

        private void toolOpen_Click(object sender, EventArgs e)
        {
            try
            {
                toolOpen.Enabled = false;
                StartReadStream();
            }
            catch (Exception ex)
            {
                syncPostUpdate.Post(UpdateDeviceStatus, "打开视频错误:" + ex.Message);
            }
            finally
            {
                syncPostUpdate.Post(UpdateButtonStatus, "");
            }
        }

        private void toolCapture_Click(object sender, EventArgs e)
        {
            
            try
            {
                toolCapture.Enabled = false;
                string fileName = "";
                SaveImage(_sourceDir, _projectName, out fileName);
                Debug.Print("抓拍图片:" + fileName);
                syncPostUpdate.Post(UpdateButtonStatus, "");
            }
            catch (Exception ex)
            {
                syncPostUpdate.Post(UpdateDeviceStatus, "抓拍错误:" + ex.Message);
            }
            finally
            {
                syncPostUpdate.Post(UpdateButtonStatus, "");
            }
        }

        private void toolStartRecording_Click(object sender, EventArgs e)
        {
            try
            {
                toolStartRecording.Enabled = false;
                string fileName = "";
                StartRecording(_sourceDir, _projectName, out fileName);
                Debug.Print("录像文件:" + fileName);
                syncPostUpdate.Post(UpdateButtonStatus, "");
            }
            catch (Exception ex)
            {
                syncPostUpdate.Post(UpdateDeviceStatus, "开始录像错误:" + ex.Message);
            }
            finally
            {
                syncPostUpdate.Post(UpdateButtonStatus, "");
            }
        }

        private void toolStopRecording_Click(object sender, EventArgs e)
        {
            try
            {
                toolStartRecording.Enabled = false;
                StopRecording();
                syncPostUpdate.Post(UpdateButtonStatus, "");
            }
            catch (Exception ex)
            {
                syncPostUpdate.Post(UpdateDeviceStatus, "停止录像错误:" + ex.Message);
            }
            finally
            {
                syncPostUpdate.Post(UpdateButtonStatus, "");
            }
        }

        private void HKRtsp_Load(object sender, EventArgs e)
        {

        }
    }
}

二、创建控件,名为HKPlayer

        引用系统COM中的Windows Media Player

        这是播放视频控件

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using System.Diagnostics;
using WMPLib;

/// <summary>
///
/*      1、首先要在项目中导入 COM中选择 WindowsMediaPlayer
 *      2、下面是使用实例
 *      //播放单个文件
 *      frmPlayer player = new frmPlayer(
                                true,  //播放单个文件
                                @"G:\CSharp\VideoRecorder\CaseRecords\bin\x64\Debug\Video\proj1\%e7%a3%85%e4%b8%8a%e6%91%84%e5%83%8f%e5%a4%b4\001_20240508162416_91.mp4",  //mp4文件路径
                                null,  //多个文件列表
                                "");   //当前mp4位置,以后打包使用
        player.ShowDialog();

        //播放多个文件时
        frmPlayer player = new frmPlayer(
                            false, //单个文件播放试为假
                            "",    //单个文件为空
                            list,  //多个播放文件集合
                            "");   //Mp4文件当前目录,暂时保留为空
        player.ShowDialog();

 *      //创建播放实例 frmPlayer窗体 ============================================================================================
 *      private HKPlayer player = new HKPlayer();

        private void frmPlayer_Load(object sender, EventArgs e)
        {
            //播放单个文件时,SinglePlay=True,并且 Mp4FileUrl 必须赋值Mp4文件路径
            player.Parent = this;
            player.SinglePlay = true;
            player.Mp4FileFolder = "";
            player.Mp4FileList = null;
            player.Mp4FileUrl = @"G:\CSharp\VideoRecorder\CaseRecords\bin\x64\Debug\Video\2024\05\03\miluo\VideoRecord\No_0\1.mp4";
            player.Play();

            //播放多个文件,SinglePlay=false,并且 Mp4FileList 必须赋值 List<string> 播放列表。
            player.Parent = this;
            player.SinglePlay = false;
            player.Mp4FileFolder = "";
            List<string> list = new List<string> {
                        @"G:\CSharp\VideoRecorder\CaseRecords\bin\x64\Debug\Video\2024\05\03\miluo\VideoRecord\No_0\1.mp4",
                        @"G:\CSharp\VideoRecorder\CaseRecords\bin\x64\Debug\Video\2024\05\03\miluo\VideoRecord\No_0\2.mp4",
                        @"G:\CSharp\VideoRecorder\CaseRecords\bin\x64\Debug\Video\2024\05\03\miluo\VideoRecord\No_0\3.mp4"
                    };
            player.Mp4FileList = list;
            player.Play();

            //播放窗体自适应
            frmPlayer_Resize(null, null);
        }

        private void frmPlayer_FormClosed(object sender, FormClosedEventArgs e)
        {
            player.Close();
        }

        private void frmPlayer_Resize(object sender, EventArgs e)
        {
            player.Left = 0;
            player.Top = 0;
            player.Width = Width - 20;
            player.Height = Height - 50;
        }
        */

/// </summary>
namespace CaseRecords.Control
{
    public partial class HKPlayer : UserControl
    {
        public HKPlayer()
        {
            InitializeComponent();
            player.Parent = this;
            player.Dock = DockStyle.Fill;
            player.MediaError += Player_MediaError;
            player.StatusChange += Player_StatusChange;
            // 监听播放结束事件来播放下一个文件
            player.PlayStateChange += Player_PlayStateChange;
        }

        private void Player_PlayStateChange(object sender, AxWMPLib._WMPOCXEvents_PlayStateChangeEvent e)
        {
            try
            {
                PlayNextMediaFile();
            }
            catch (Exception ex)
            {
                playstatus?.Invoke(_projectName, _videoDescription,
                       string.Format("播放Mp4 Player_PlayStateChange发生错误因为:{0}。", ex.Message));
            }
        }

        private void PlayNextMediaFile()
        {
            // 假设你有一个文件路径的字符串数组
            if (!_singlePlay && player.playState == WMPPlayState.wmppsMediaEnded)
            {
                
            }
        }

        private void Player_StatusChange(object sender, EventArgs e)
        {
            try
            {
                /*  
                 * 0 Undefined Windows Media Player is in an undefined state.(未定义) 
                   1 Stopped Playback of the current media item is stopped.(停止) 
                   2 Paused Playback of the current media item is paused. When a media item is paused, resuming playback begins from the same location.(停留) 
                   3 Playing The current media item is playing.(播放) 
                   4 ScanForward The current media item is fast forwarding. 
                   5 ScanReverse The current media item is fast rewinding. 
                   6 Buffering The current media item is getting additional data from the server.(转换) 
                   7 Waiting Connection is established, but the server is not sending data. Waiting for session to begin.(暂停) 
                   8 MediaEnded Media item has completed playback. (播放结束) 
                   9 Transitioning Preparing new media item. 
                   10 Ready Ready to begin playing.(准备就绪) 
                   11 Reconnecting Reconnecting to stream.(重新连接) 
                   wmppsUndefined = 0,
                    wmppsStopped = 1,
                    wmppsPaused = 2,
                    wmppsPlaying = 3,
                    wmppsScanForward = 4,
                    wmppsScanReverse = 5,
                    wmppsBuffering = 6,
                    wmppsWaiting = 7,
                    wmppsMediaEnded = 8,
                    wmppsTransitioning = 9,
                    wmppsReady = 10,
                    wmppsReconnecting = 11,
                    wmppsLast = 12
               */
                switch ((int)player.playState)
                {
                    case 1:
                        Debug.Print("播放状态:停止");
                        break;
                    case 2:
                        Debug.Print("播放状态:暂停");
                        break;
                    case 3:
                        Debug.Print("播放状态:播放");
                        break;
                    case 9:
                        Debug.Print("播放状态:传输");
                        break;
                }

            }
            catch (Exception ex)
            {
                playstatus?.Invoke(_projectName, _videoDescription,
                       string.Format("播放Mp4 Player_StatusChange发生错误因为:{0}。", ex.Message));
            }
        }

        private void Player_MediaError(object sender, AxWMPLib._WMPOCXEvents_MediaErrorEvent e)
        {
            try
            {
                IWMPMedia2 errSource = e.pMediaObject as IWMPMedia2;
                IWMPErrorItem errorItem = errSource.Error;
                playstatus?.Invoke(_projectName, _videoDescription, 
                        string.Format("播放Mp4发生错误代码:{0}在{1}。", errorItem.errorCode.ToString("X") , errSource.sourceURL));                
            }
            catch (Exception ex)
            {
                playstatus?.Invoke(_projectName, _videoDescription,
                        string.Format("播放Mp4发生错误因为:{0}。", ex.Message));
            }
        }

       

        #region 自定义返回事件

        public delegate void PlayerStatus(string projectName, string videoDescription, string msg);
        public PlayerStatus playstatus;



#endregion

        #region Mp4播放器属性

        private string _mp4FileUrl = "";
        private bool _singlePlay = false;
        private string _mp4FileFolder = "";
        private List<string> _mp4FileList;
        private string _projectName = "";  //项目名称
        private string _videoDescription = ""; //视频位置描述
        private AxWMPLib.AxWindowsMediaPlayer player = new AxWMPLib.AxWindowsMediaPlayer();
        private string currentPlayingFile = ""; //当前播放的文件
        
        [Description("Mp4文件地址"), Category("播放配置")]
        public string Mp4FileUrl
        {
            set
            {
                _mp4FileUrl = value;
            }
            get
            {
                return _mp4FileUrl;
            }
        }

        /// <summary>
        /// SinglePlay=true为播放单一文件,=false时,播所有目录下文件
        /// </summary>
        [Description("Mp4文件播放类型"), Category("播放配置")]
        public bool SinglePlay
        {
            set
            {
                _singlePlay = value;
            }
            get
            {
                return _singlePlay;
            }
        }

        [Description("Mp4播放目录"), Category("播放配置")]
        public string Mp4FileFolder
        {
            set
            {
                _mp4FileFolder = value;
            }
            get
            {
                return _mp4FileFolder;
            }
        }

        [Description("Mp4播放文件列表"), Category("播放配置")]
        public List<string> Mp4FileList
        {
            set
            {
                _mp4FileList = value;
            }
            get
            {
                return _mp4FileList;
            }
        }

        [Description("项目名称"), Category("项目配置")]
        public string ProjectName
        {
            set
            {
                _projectName = value;
            }
            get
            {
                return _projectName;
            }
        }

        [Description("视频位置说明"), Category("项目配置")]
        public string VideoDescription
        {
            set
            {
                _videoDescription = value;
            }
            get
            {
                return _videoDescription;
            }
        }

        #endregion

        #region 播放操作

       

        public void Play()
        {
            try
            {
                //单个文件播放,并且地址为空时
                if ((_singlePlay && string.IsNullOrEmpty(_mp4FileUrl)) || (!_singlePlay && Mp4FileList.Count == 0))
                {
                    Debug.Print("播放文件Url不能为空!");
                    playstatus?.Invoke(_projectName, _videoDescription, "播放文件Url不能为空!");
                    return;
                }

                //开始播放视频
                if (_singlePlay)
                {                    
                    currentPlayingFile = Mp4FileUrl;
                    player.URL = currentPlayingFile;
                }
                else
                {
                    //多文件循环播放,一个接一个,除非关闭或者停止
                    currentPlayingFile = Mp4FileList[0];
                    player.currentPlaylist = player.newPlaylist("DragonPlayList", "");
                    foreach (string item in Mp4FileList)
                    {
                        player.currentPlaylist.appendItem(player.newMedia(item));
                    }                    
                }
                playstatus?.Invoke(_projectName, _videoDescription, "开始播放文件:" + currentPlayingFile);                
                player.Ctlcontrols.play();
            }
            catch (Exception ex)
            {
                playstatus?.Invoke(_projectName, _videoDescription, "播放失败:" + ex.Message);
            }
        }

        public void Stop()
        {
            try
            {
                //停止播放
                if (player.playState == WMPPlayState.wmppsPlaying)
                {
                    player.Ctlcontrols.stop();
                    playstatus?.Invoke(_projectName, _videoDescription, "停止播放:" + currentPlayingFile);
                }
            }
            catch (Exception ex)
            {
                playstatus?.Invoke(_projectName, _videoDescription, "停止播放失败:" + ex.Message);
            }
        }

        public void Close()
        {
            try
            {
                player.MediaError -= Player_MediaError;
                player.StatusChange -= Player_StatusChange;
                player.PlayStateChange -= Player_PlayStateChange;
                if (player != null)
                {
                    if(player.playState == WMPPlayState.wmppsPlaying)
                    {
                        player.Ctlcontrols.stop();
                    }
                    player.Dispose();
                    player = null;
                }                
            }
            catch (Exception ex)
            {
                playstatus?.Invoke(_projectName, _videoDescription, "释放播放资源失败:" + ex.Message);
            }
        }

        #endregion
    }
}

GitHub 加速计划 / opencv31 / opencv
142
15
下载
OpenCV: 开源计算机视觉库
最近提交(Master分支:3 个月前 )
d9a139f9 Animated WebP Support #25608 related issues #24855 #22569 ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake 2 天前
09030615 V4l default image size #25500 Added ability to set default image width and height for V4L capture. This is required for cameras that does not support 640x480 resolution because otherwise V4L capture cannot be opened and failed with "Pixel format of incoming image is unsupported by OpenCV" and then with "can't open camera by index" message. Because of the videoio architecture it is not possible to insert actions between CvCaptureCAM_V4L::CvCaptureCAM_V4L and CvCaptureCAM_V4L::open so the only way I found is to use environment variables to preselect the resolution. Related bug report is [#25499](https://github.com/opencv/opencv/issues/25499) Maybe (but not confirmed) this is also related to [#24551](https://github.com/opencv/opencv/issues/24551) This fix was made and verified in my local environment: capture board AVMATRIX VC42, Ubuntu 20, NVidia Jetson Orin. ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [X] I agree to contribute to the project under Apache 2 License. - [X] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [X] The PR is proposed to the proper branch - [X] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake 2 天前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐