Opencvsharp对RTSP视频流的显示、抓拍、分段录像

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
    }
}

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值