针对监控摄像机(海康、大华等)进行手动录像的录像文件播放器功能设计
参考上一篇:
录像后需要自定义一个播放器来播放录像文件。
简单设计界面如下:
打开文件,可暂停,可停止,可以拖动进度条。
static int width = 1920, height = 1080; int videoWidth = 639, videoHeight = 479; int YUVWidth = 640, YUVHeight = 480; bool bPause = false; bool bPlayStop = false; ILog log = LogManager.GetLogger("ErrorLog"); int fps = 15, fpsMain = 15, fpsBase = 15; int msPerFrameCamera = 66, msPerFrameCameraBase = 66, msPerFrameCameraMain = 66; //相机每一帧的时间,25帧即40毫秒 public frmPlay() { InitializeComponent(); #region 解码器相关初始化,一般在窗口load中进行初始化 decAttr = new H264Dec.hiH264_DEC_ATTR_S(); decAttr.uPictureFormat = 0; decAttr.uStreamInType = 0; decAttr.uPicWidthInMB = (uint)width; decAttr.uPicHeightInMB = (uint)height; decAttr.uBufNum = 8; decAttr.uWorkMode = 16; //创建、初始化解码器句柄 _decHandle = H264Dec.Hi264DecCreate(ref decAttr); #endregion InitVars(); frmPlay_Resize(null, null); this.Resize += new System.EventHandler(this.frmPlay_Resize); } long offsetPlay = 0; int iFramePlay = 0; Image<Bgr, Byte> imageYUV = new Image<Bgr, Byte>(640, 480); Image<Bgr, Byte> imgNowShow = new Image<Bgr, Byte>(640, 480); int iFontSize16 = 12, iFontSize20 = 15, iFontSize24 = 18; string fontname = "仿宋_GB2312"; private delegate void SetPicVideo(Image<Bgr, Byte> val);//跨线程修改图片框 private Thread SetPicVideoThread; FileStream fs = null; private delegate void SetProgressLabel(string val); Thread SetLabelThread; string labelValue = string.Empty; string sTotalTime = ""; int iFrameCnt = 0; Thread showThread; void SetPic(Emgu.CV.Image<Bgr, Byte> val) { if (bPause || bPlayStop || bAbortPlayThread) { Graphics g = imgBox.CreateGraphics(); g.Clear(Color.Black); imageVideoOverLay.Visible = true; } else if (imageVideoOverLay.Visible) { imageVideoOverLay.Visible = false; } if (val != null && !bAbortPlayThread) { this.imgBox.Image = val; imgBox.Refresh(); } } private void setPicVideo() { if (bPause || bPlayStop || bAbortPlayThread) return; if (imgBox.InvokeRequired) { SetPicVideo d = new SetPicVideo(SetPic); object[] arg = new object[] { imgNowShow }; this.Invoke(d, arg); } else { SetPic(imgNowShow); } } List<BufferPlay> BufferPlayList = new List<BufferPlay>(); private void DrawnShow() { iFramePlay = 0; while (!bAbortPlayThread) { if (BufferPlayList.Count > 0) { DateTime dtStart = DateTime.Now; realstaticinfo = BufferPlayList[0].info; GCHandle handle = GCHandle.Alloc(BufferPlayList[0].yuvs, GCHandleType.Pinned); using (Image<Bgr, Byte> yuv420p = new Image<Bgr, byte>(YUVWidth, (YUVHeight >> 1) * 3, YUVWidth, handle.AddrOfPinnedObject())) { CvInvoke.CvtColor(yuv420p, imageYUV, Emgu.CV.CvEnum.ColorConversion.Yuv420P2Bgr); } if (handle.IsAllocated) handle.Free(); if (imageYUV == null) continue; Bitmap bmp = imageYUV.Bitmap; if (bmp == null) continue; lock (bmp) { if (bShowLine) { DrawInVideo(bmp); } imageYUV.Bitmap = bmp; imgNowShow = imageYUV; SetPicVideoThread = new Thread(new ThreadStart(setPicVideo)); SetPicVideoThread.IsBackground = true; SetPicVideoThread.Start(); } if (BufferPlayList.Count > 0) BufferPlayList.RemoveAt(0); iFramePlay++; if (iFramePlay % fps == 0) { int seconds = (int)((double)iFramePlay / fps); int h = (int)Math.Floor((double)seconds / 3600); int m = (int)Math.Floor((double)(seconds - h * 3600) / 60); int s = (int)Math.Floor((double)(seconds - h * 3600 - m * 60)); string sNowPlayTime = h.ToString().PadLeft(2, '0') + ":" + m.ToString().PadLeft(2, '0') + ":" + s.ToString().PadLeft(2, '0'); labelValue = sNowPlayTime + "/" + sTotalTime; SetLabelThread = new Thread(new ThreadStart(setLableValue)); SetLabelThread.IsBackground = true; SetLabelThread.Start(); } DateTime timeStop2 = DateTime.Now; TimeSpan ts2 = new TimeSpan(); ts2 = timeStop2 - dtStart; if (ts2.TotalMilliseconds < msPerFrameCamera) { Thread.Sleep((int)(msPerFrameCamera - ts2.TotalMilliseconds)); } } } } private void Play(object ofs) { FileStream fsTmp = ofs as FileStream; try { offsetPlay = 0; DateTime timeStart = DateTime.Now; DateTime timeStop = DateTime.Now; while (!bAbortPlayThread && fsTmp.Length > offsetPlay + 4 + 378 + 174) { try { if (bPause) { Thread.Sleep(msPerFrameCamera); continue; } timeStart = DateTime.Now; byte[] intdata = new byte[4]; fsTmp.Read(intdata, 0, 4); offsetPlay = offsetPlay + 4; fsTmp.Seek(offsetPlay, SeekOrigin.Begin); int frameLen = (int)FVD.Common.Common.bytes2uintNoRe(intdata, 0, 4); byte[] buffer = new byte[frameLen]; fsTmp.Read(buffer, 0, frameLen); offsetPlay = offsetPlay + frameLen; fsTmp.Seek(offsetPlay, SeekOrigin.Begin); GCHandle hObject2 = GCHandle.Alloc(buffer, GCHandleType.Pinned); IntPtr pStreamData = hObject2.AddrOfPinnedObject(); if (fsTmp.Length > offsetPlay + 500) { byte[] datas= new byte[500]; //获取叠加数据 fsTmp.Read(datas, 0, 500); Getdatas(datas); } if (H264Dec.Hi264DecAU(_decHandle, pStreamData, (uint)frameLen, 0, ref _decodeFrame, 0) == 0) { int yLength = (int)(_decodeFrame.uHeight * _decodeFrame.uYStride); int uLength = (int)(_decodeFrame.uHeight * _decodeFrame.uUVStride / 2); IntPtr y = _decodeFrame.pY; IntPtr v = _decodeFrame.pV; IntPtr u = _decodeFrame.pU; byte[] ys = new byte[yLength]; Marshal.Copy(y, ys, 0, yLength); byte[] vs = new byte[uLength]; Marshal.Copy(v, vs, 0, uLength); byte[] us = new byte[uLength]; Marshal.Copy(u, us, 0, uLength); byte[] yuvs = new byte[ys.Length + vs.Length + us.Length]; ys.CopyTo(yuvs, 0); vs.CopyTo(yuvs, ys.Length); us.CopyTo(yuvs, ys.Length + vs.Length); BufferPlay bp; bp.info = realstaticinfoTmp; bp.yuvs = yuvs; BufferPlayList.Add(bp); } if (hObject2.IsAllocated) hObject2.Free(); pStreamData = IntPtr.Zero; if (BufferPlayList.Count > 0) { Thread.Sleep((int)((BufferPlayList.Count - 1) * msPerFrameCamera)); } } catch (System.Exception ex) { log.ErrorFormat("播放出错:" + ex.Message); continue; } } } catch (System.Exception ex) { log.ErrorFormat("播放出错:" + ex.Message); MessageBox.Show("播放出错:" + ex.Message); } while (BufferPlayList.Count > 0) { Thread.Sleep(msPerFrameCamera); } if (bLoopPlay) //循环播放,重新初始化 { if (bAbortPlayThread) return; bPlayStop = true; bAbortPlayThread = true; Thread.Sleep(500); bPlayStop = false; bAbortPlayThread = false; labelValue = "00:00:00/" + sTotalTime; SetLabelThread = new Thread(new ThreadStart(setLableValue)); SetLabelThread.IsBackground = true; SetLabelThread.Start(); fsTmp.Seek(0, SeekOrigin.Begin); playThread = new Thread(new ParameterizedThreadStart(Play)); playThread.IsBackground = true; playThread.Start(fsTmp); showThread = new Thread(new ThreadStart(DrawnShow)); showThread.IsBackground = true; showThread.Start(); } else { fsTmp.Close(); fsTmp.Dispose(); bPlayStop = true; labelValue = "00:00:00/00:00:00"; SetLabelThread = new Thread(new ThreadStart(setLableValue)); SetLabelThread.IsBackground = true; SetLabelThread.Start(); } } private void trackBar1_ValueChanged(object sender, EventArgs e) { if (!string.IsNullOrEmpty(sTotalTime) && fs != null && btnStop.Enabled == true && fs.CanRead && iFrameCnt > 0) { bPause = true; Thread.Sleep(msPerFrameCamera); int iNowFrame = (int)((double)trackBar1.Value / trackBar1.Maximum * (double)iFrameCnt); if (iNowFrame <= 0) { iNowFrame = 1; } BufferPlayList.Clear(); fs.Seek(0, SeekOrigin.Begin); long offsetTmp = 0; int iFrame = 0; byte[] intdata = new byte[4]; while (fs.Length > offsetTmp + 504) { fs.Read(intdata, 0, 4); int frameLen = (int)(intdata[0] | intdata[1] << 8 | intdata[2] << 16 | intdata[3] << 24); offsetTmp = offsetTmp + frameLen + 504; //504 = 4 + 500 fs.Seek(offsetTmp, SeekOrigin.Begin); iFrame++; if (iFrame == iNowFrame) { offsetPlay = offsetTmp; iFramePlay = iNowFrame; break; } } bPause = false; } } void SetStatuslabel(string val) { if (bPlayStop) //自动播放完成,跨线程修改状态 { btnStop.Enabled = false; btnPause.Enabled = false; this.Text = "视频播放"; trackBar1.ValueChanged -= new System.EventHandler(trackBar1_ValueChanged); trackBar1.Value = 0; trackBar1.ValueChanged += new System.EventHandler(trackBar1_ValueChanged); trackBar1.Enabled = false; iFramePlay = 0; imageVideoOverLay.Visible = true; bPlayStop = false; } if (bPause || bPlayStop || bAbortPlayThread) return; this.labelProgress.Text = val; if (iFrameCnt > 0 && iFramePlay > 0 && iFramePlay <= iFrameCnt) { trackBar1.ValueChanged -= new System.EventHandler(trackBar1_ValueChanged); trackBar1.Value = (int)((double)iFramePlay / (double)iFrameCnt * trackBar1.Maximum); trackBar1.ValueChanged += new System.EventHandler(trackBar1_ValueChanged); } } private void setLableValue() { if (bPause || bAbortPlayThread) return; if (labelProgress.InvokeRequired) { SetProgressLabel d = new SetProgressLabel(SetStatuslabel); object[] arg = new object[] { labelValue };//要传入的参数值 this.Invoke(d, arg); } else { SetStatuslabel(labelValue); } } private void btnOpen_Click(object sender, EventArgs e) { if (rbMainVideo.Checked) { YUVWidth = width; YUVHeight = height; msPerFrameCamera = msPerFrameCameraMain; fps = fpsMain; } else { YUVWidth = videoWidth + 1; YUVHeight = videoHeight + 1; msPerFrameCamera = msPerFrameCameraBase; fps = fpsBase; } transX = YUVWidth / (videoWidth + 1); transY = YUVHeight / (videoHeight + 1); frmPlay_Resize(null, null); openFileDialog1.Filter = "Zenith视频文件(*.dat)|*.dat"; openFileDialog1.FileName = ""; openFileDialog1.Multiselect = false; openFileDialog1.InitialDirectory = Application.StartupPath; if (openFileDialog1.ShowDialog() == DialogResult.OK) { this.labelProgress.Text = "00:00:00/00:00:00"; bAbortPlayThread = true; Thread.Sleep(200); if (playThread != null && playThread.IsAlive) { playThread.Abort(); } string filename = openFileDialog1.FileName; fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Read); if (fs.Length <= 512) { MessageBox.Show("请确认文件是否有效!" + filename); return; } if (!fs.CanRead || !fs.CanSeek) { MessageBox.Show("文件不可读,请确认!" + filename); return; } imageYUV = new Image<Bgr, Byte>(YUVWidth, YUVHeight); this.Cursor = Cursors.WaitCursor; try { this.Text = "视频播放 " + filename; log.ErrorFormat("视频播放 " + filename); if (bPause) { bPause = false; btnPause.Text = "暂停"; } bPlayStop = false; bAbortPlayThread = false; btnStop.Enabled = true; btnPause.Enabled = true; trackBar1.ValueChanged -= new System.EventHandler(trackBar1_ValueChanged); trackBar1.Value = 0; trackBar1.ValueChanged += new System.EventHandler(trackBar1_ValueChanged); trackBar1.Enabled = true; fs.Seek(0, SeekOrigin.Begin); //获取文件长度 long offset = 0; iFrameCnt = 0; byte[] intdata = new byte[4]; while (fs.Length > offset + 556) { fs.Read(intdata, 0, 4); int frameLen = (int)(intdata[0] | intdata[1] << 8 | intdata[2] << 16 | intdata[3] << 24); offset = offset + frameLen + 556; //556 = 4 + 378 + 174 fs.Seek(offset, SeekOrigin.Begin); iFrameCnt++; } int seconds = (int)((double)iFrameCnt / fps); int h = (int)Math.Floor((double)seconds / 3600); int m = (int)Math.Floor((double)(seconds - h * 3600) / 60); int s = (int)Math.Floor((double)(seconds - h * 3600 - m * 60)); sTotalTime = h.ToString().PadLeft(2, '0') + ":" + m.ToString().PadLeft(2, '0') + ":" + s.ToString().PadLeft(2, '0'); this.labelProgress.Text = "00:00:00/" + sTotalTime; fs.Seek(0, SeekOrigin.Begin); playThread = new Thread(new ParameterizedThreadStart(Play)); playThread.IsBackground = true; playThread.Start(fs); showThread = new Thread(new ThreadStart(DrawnShow)); showThread.IsBackground = true; showThread.Start(); } catch (System.Exception ex) { log.ErrorFormat("视频初始化错误!" + ex.Message); MessageBox.Show("视频初始化错误!"); } finally { this.Cursor = Cursors.Default; } } } private void btnStop_Click(object sender, EventArgs e) { labelValue = "00:00:00/00:00:00"; SetLabelThread = new Thread(new ThreadStart(setLableValue)); SetLabelThread.IsBackground = true; SetLabelThread.Start(); this.Text = "视频播放"; trackBar1.ValueChanged -= new System.EventHandler(trackBar1_ValueChanged); trackBar1.Value = 0; trackBar1.ValueChanged += new System.EventHandler(trackBar1_ValueChanged); trackBar1.Enabled = false; if (fs != null ) { fs.Close(); fs.Dispose(); } bAbortPlayThread = true; btnStop.Enabled = false; btnPause.Enabled = false; if (bPause) { bPause = false; btnPause.Text = "暂停"; } }