C# 视频监控系列(8):服务器端——预览和可被客户端连接

简介:

前言

     在客户端相关的文章还没有写出来的时候,服务器端已经差不多了,没有很及时的把文章一篇接一篇的写是有理由的——有些功能我项目中暂时没有加入,只是对照API知道有这个功能,边写文章边做例子,这样一来发现有些API封装的不对,所以把这系列的文章写的速度都放慢了,以求尽量每一篇文章都正确。当然还是免不了找借口说太忙,现在在写播放器部分的代码,进展目前看来还顺利: )

 

注意

     本系列文章限于学习交流,注重过程,由于涉及公司,所以不提供源代码下载,非常抱歉!!但是请大家放心,核心、实现以及其他能够贴出来的代码我都会贴出来,并且争取尽所能的回答留言里的每一个问题,感谢大家关注,欢迎交流 :)

 

系列

 

系列

     1.     C# 视频监控系列(1):准备

     2.     C# 视频监控系列(2):客户端——封装API

     3.     C# 视频监控系列(3):客户端——连接服务器

     4.     C# 视频监控系列(4):客户端——音频接收和抓图

     5.     C# 视频监控系列(5):客户端——给服务器端发送字符串和录像(数据捕获)

     6.     C# 视频监控系列(6):服务器端——封装API(上) [HikServer.dll]

     7.     C# 视频监控系列(7):服务器端——封装API(下) [DS40xxSDK.dll]

     8.     C# 视频监控系列(8):服务器端——预览和可被客户端连接

 

推荐文章

     1.     如果非托管代码需要多次调用托管代码中的委托,请将委托保存为成员变量。  极其重要的一篇文章,非常建议你把每一个委托都实例成成员变量,在正文中代码可以看到,很多执行委托的地方报错绝大部分都是这个原因!!

 

正文

     一、VC++ Demo里关于这两个功能的实现和分析

          基本上每段代码都可以从OnInitDialog这个方法开始分析

          1.1.     VC++ Code:

               HikVisionDlg.cpp 的OnInitDialog方法中的关键代码

复制代码
     for (i  =   0 ; i  <  GetTotalDSPs(); i ++ )
    {
        ChannelHandle[i] 
=  ChannelOpen(i);
        
if  (ChannelHandle[i] < 0
        {
            AfxMessageBox(
" channel open error > 0 " );

        }
        
else   if  (ChannelHandle[i]  == (HANDLE)  0xffff )
        {
            AfxMessageBox(
" channel open error 0xffff " );

        }

        gChannelTotalLength[i] 
=   0 ;
        nowstate[i]
= 0 ;

        
if  (servertype  ==  DIALTYPE)
        {
            SetIBPMode(ChannelHandle[i],
211 , 2 , 1 , 8 );
            SetDefaultQuant(ChannelHandle[i],
18 , 18 , 23 );
            SetStreamType(ChannelHandle[i],STREAM_TYPE_VIDEO);
        }
        
else
        {
            SetIBPMode(ChannelHandle[i],
100 , 2 , 1 , 25 );
            SetDefaultQuant(ChannelHandle[i],
15 , 15 , 20 );
        }
    }

    
if  (servertype  ==  DIALTYPE)
    {
        
for (i  =   0 ; i  <  GetTotalDSPs(); i ++ )
            SetEncoderPictureFormat(ChannelHandle[i], ENC_QCIF_FORMAT);

    }
    
else
    {

        
for (i  =   0 ; i  <  GetTotalDSPs(); i ++ )
        {
            
if  ( i == 0  )
            {
                
// when initiated,set the first channel as 4CIF encode,others as CIF
                SetEncoderPictureFormat(ChannelHandle[ 0 ], ENC_4CIF_FORMAT);
                bEncodeCifAndQcif[
0 =  FALSE;
            }
            
else
            {
                SetEncoderPictureFormat(ChannelHandle[i], ENC_CIF_FORMAT);
            }

        }

    }

//     int id = IDC_CHECK2;
//     for(i = 0; i < MAX_CHANNELS; i++){
//         GetDlgItem(id + i)->EnableWindow(FALSE);
//     }
    RegisterStreamDirectReadCallback(::StreamDirectReadCallback, this );
    RegisterMessageNotifyHandle(m_hWnd, MsgDataReady);


    MP4_ServerSetMessage(WM_MYCOMMAND,
this -> m_hWnd);

    gCapImages 
=   0 ;

    SetOverlayColorKey(gBackgroundColor);

    gTimer 
=  SetTimer( 1 1000 0 );
    SetTimer(
2 , 2000 , 0 );
    SetTimer(
5 , 5000 , 0 );

    
for  (i = 0 ;i < MAX_CHANNELS;i ++ )
        gCurrentFileLen[i] 
=   0 ;

    SERVER_VIDEOINFO videoinfo;
    g_nChannelTotal 
=  GetTotalDSPs();

    
for ( i = 0  ; i  <  g_nChannelTotal; i ++  )
    {
        
if (i  ==   0 )
        {
            MP4_ServerSetBufNum(i,
90 );
        }
        
else
        {
            MP4_ServerSetBufNum(i,
80 );
        }
        
        
if  (servertype  ==  DIALTYPE)
            videoinfo.m_datatype[i] 
=  DIALING;
        
else
            videoinfo.m_datatype[i] 
=  NORMAL;
    }

    videoinfo.m_datatype[
0 =  SMALLPIC;

    videoinfo.m_channum 
=  g_nChannelTotal;
    videoinfo.m_waittime 
=   2 ;

    MP4_ServerSetStart(StartCap);
    MP4_ServerSetStop(StopCap);

    MP4_ServerSetIBPMode(SetIBP);
    MP4_ServerSetCapIFrame(MakeIFrame);

    MP4_ServerSetTTL(
64 );
    MP4_ServerSetNetPort(
5050 , 6050 );

    MP4_ServerCheckIP(CheckIP);
    MP4_ServerCheckPassword(checkpassword);

    
// set the max connector of  channel 0
    MP4_ServerMaxUser( 0 , 24 );

    
// 如果想不使用缺省方式进行多播,
    
// 可以调用下面的函数设置自己的多播信息
    
// 详细信息请参考SDK文档
//     MP4_ServerCastGroup(TRUE,0,"228.0.0.132",9988);

    
if  ( ! MP4_ServerStart( & videoinfo))
    {
        MessageBox(
" error " , " error " ,MB_OK);
    }
复制代码

               HikVisionDlg.cpp 的StreamDirectReadCallback方法

复制代码
int  __cdecl StreamDirectReadCallback(ULONG channelNum, void   * DataBuf,DWORD Length, int  frameType, void   * context)
{


    
int  i,status = 0 ;
    CString ctip;
    
int  nframetype  = 0 ;

    
//  if cap images we need clean the queue here
//     if (!bCapture)
//         return 0;

     
//  no errors
      if (frameType  >   0 ) {
         
if (frameType  ==  PktSysHeader){     
             
//  store the file header             
             memcpy(FileHeader[channelNum], DataBuf, Length);
             FileHeaderLen 
=  Length;
             TRACE(
" channel %d get the file header !\n " ,channelNum);
            
         }

         
if (frameType  ==  PktIFrames  ||  frameType  == PktSubIFrames){
             status 
=   1 ;
         }
         
else {
             status 
=   0 ;
         }
            
         
if (frameType  ==  PktMotionDetection){
//              m_VideoWin.DrawVect(channelNum, (char *)DataBuf, Length);
              return   0 ;
         }
         
if (frameType  ==  PktOrigImage){
             
return   0 ;
         }


     }

     
if (Length  ==   0 ){
         TRACE(
" no data ?\n " );
         
return   0 ;
     }

//      if(frameType == PktIFrames){
//          int iii=1;
//      }

    ULONG currentTime 
=  timeGetTime();

    gChannelTotalLength[channelNum] 
+=  Length;
    gCurrentFileLen[channelNum] 
+=  Length;

    
if (currentTime  >  StartTime + 1000 ){

        CString str,str2;
        str.Format(
" %d " , (gChannelTotalLength[dcurrentwin]  * 8 / (currentTime  -  StartTime)));
        
for (i = 0 ;i < g_nChannelTotal;i ++ )
            gChannelTotalLength[i] 
=   0 ;
         StartTime
=  currentTime; 
        CHKVisionDlg 
* pMain  =  (CHKVisionDlg  * )AfxGetMainWnd();
         pMain
-> GetDlgItem(IDC_BPS) -> SetWindowText((LPCTSTR)str);
    }

//     if (m_sframe && channelNum ==0)
//     {
 
//           if((frameType == PktSFrames && nframetype ==4 )||(frameType == PktSysHeader))
//          {
//             MP4_ServerWriteData(channelNum,(unsigned char *)DataBuf, Length,frameType,status);    
//          }
//     }
    
//     MP4_ServerWriteData(channelNum,(unsigned char *)DataBuf, Length,frameType,status);    
    
    
if (frameType  == PktAudioFrames)
    {
        _write(gFileHandleQcif[channelNum],DataBuf,Length);
        MP4_ServerWriteDataEx(channelNum,(unsigned 
char   * )DataBuf, Length,frameType,status, 1 );
        _write(gFileHandle[channelNum], DataBuf, Length);
        MP4_ServerWriteDataEx(channelNum,(unsigned 
char   * )DataBuf, Length,frameType,status, 0 );
        
    }
else   if  (frameType  == PktSubIFrames  ||  frameType  == PktSubPFrames  ||  frameType  ==  PktSubBBPFrames  ||  frameType  ==  PktSubSysHeader)
    {
        
        _write(gFileHandleQcif[channelNum],DataBuf,Length);
        MP4_ServerWriteDataEx(channelNum,(unsigned 
char   * )DataBuf, Length,frameType,status, 1 );    
    }
else  
    {
        _write(gFileHandle[channelNum], DataBuf, Length);
        MP4_ServerWriteDataEx(channelNum,(unsigned 
char   * )DataBuf, Length,frameType,status, 0 );
    }

    
return   0 ;
}
复制代码

               VideoWin.cpp的OnPaint方法               

StartVideoPreview( & dc);

               VideoWin.cpp的StartVideoPreview方法

复制代码
     for ( int  i  =   0 ; i  <  GetTotalDSPs(); i ++ ){
        StopVideoPreview(ChannelHandle[i]);
    }

    RECT previewWnd;
    GetClientRect(
& previewWnd);

    
// CDC *pDC = GetDlgItem(IDC_VIDEOWIN)->GetDC();
    CBrush tempBrush(RGB( 10 10 10 ));
    CBrush 
* oldBrush  =  dc -> SelectObject( & tempBrush);
    dc
-> Rectangle( & previewWnd);
    dc
-> SelectObject(oldBrush);

    
int  rectWidth  =  previewWnd.right  -  previewWnd.left;
    
int  rectHeight  =  previewWnd.bottom  -  previewWnd.top;

    
int  numRects  =  GetTotalDSPs();


    ZeroMemory(rectList, 
sizeof (rectList));

    numRects 
=  CacRects(GetTotalDSPs());
    
    
for (i  =   0 ; i  <  GetTotalDSPs(); i ++ ){
        
if (bDdrawMode)
            ::StartVideoPreview(ChannelHandle[i], m_hWnd, 
& rectList[i], FALSE, vdfRGB16,  25 );
        
else  
            ::StartVideoPreview(ChannelHandle[i], m_hWnd, 
& rectList[i], FALSE, vdfYUV422Planar,  25 );
    }
复制代码

          1.2.     代码分析

               1.     从OnInitDialog中并参照《DS-4000HC、HCS、HC+、HF、HS、MD卡的Windows编程指南V4.3》的[API调用顺序](pdf 21页)以及对应的注释能看得出基本上是做板卡的初始化,服务器的初始化等。

               2.     StreamDirectReadCallback回调函数主要是通过MP4_ServerWriteDataEx将数据写入内存(文档注释:往发送缓存写数据。)和用_write写文件做存储视频录像。

               3.     预览的代码是在OnPaint事件调用的。

     二、服务器端预览

          C# Code:

复制代码
         #region  变量

        IntPtr ChannelHandle;

        
#endregion

        
#region  窗体事件

        
private   void  Form2_Load( object  sender, EventArgs e)
        {
            
// 设置系统默认的视频制式
            HikVisionSDK.SetDefaultVideoStandard(VideoStandard_t.StandardNTSC);

            
// 初始化板卡
             if  (HikVisionSDK.InitDSPs()  <   0 )
            {
                MessageBox.Show(
" 初始化DSPs失败!! " );
                
return ;
            }

            
if  (HikVisionSDK.GetTotalDSPs()  ==   0 )
            {
                MessageBox.Show(
" 没有可用的通道!!您是否已经启动服务器端? " );
                
return ;
            }

            
// 打开通道
            ChannelHandle  =  HikVisionSDK.ChannelOpen( 0 );
            
// 设置编码帧结构、帧率
            HikVisionSDK.SetIBPMode(ChannelHandle,  100 2 1 25 );
            
// 设置编码图像质量
            HikVisionSDK.SetDefaultQuant(ChannelHandle,  15 15 20 );

            
// 视频预览
            StartVideoPreview();
        }

        
///   <summary>
        
///  视频预览
        
///   </summary>
         private   void  StartVideoPreview()
        {
            Rectangle rect 
=  panel1.ClientRectangle;
            HikVisionSDK.StartVideoPreview(ChannelHandle, panel1.Handle, 
ref  rect,  false , ( int )TypeVideoFormat.vdfRGB16,  25 );
        }

        
///   <summary>
        
///  窗体移动
        
///   </summary>
        
///   <param name="sender"></param>
        
///   <param name="e"></param>
         private   void  Form2_Move( object  sender, EventArgs e)
        {
            HikVisionSDK.StopVideoPreview(ChannelHandle);
            StartVideoPreview();
        }

        
#endregion
复制代码

          代码说明:

               1.     仅仅实现服务器端的预览代码并不多,这也是在VC++ Demo中不断注释代码、在已经成功完成大部分功能的基础上才试出来的,可见预览和服务器启动是相对独立的。

               2.     Form2_Move是窗体移动时执行的,在VC++的也是在窗体移动中进行了同样处理,否则你一移动窗体会出现难看的一幕呢 : )

               3.     StartVideoPreview的参数RECT *rect 直接使用Rectangle结构体即可。

               4.     panel1是窗体是的一个面板Panel。

     三、让客户端连接并预览

          C# Code:

复制代码
         // 将委托声明为成员变量!!
        STREAM_DIRECT_READ_CALLBACK sdrc;

        
///   <summary>
        
///  预览并客户端连接
        
///   </summary>
         private   void  PreviewAndClientConnect()
        {

            sdrc 
=   new  STREAM_DIRECT_READ_CALLBACK(STREAM_DIRECT_READ_CALLBACK1);

            
// [必须]注册编码图像数据流直接读取回调函数
            HikVisionSDK.RegisterStreamDirectReadCallback(sdrc,  this .Handle);

            
// [必须]启动服务端
            HikServer.MP4_ServerSetStart( new  StartCap(StartCap));
            
// HikServer.MP4_ServerSetStop(sc);

            
// HikServer.MP4_ServerSetIBPMode(new SetIBP(SetIBP));
            
// [必须]设置回调,重新生成一个I帧
            HikServer.MP4_ServerSetCapIFrame( new  MakeIFrame(MakeIFrame));

            
// HikServer.MP4_ServerSetTTL(64);
            
// HikServer.MP4_ServerSetNetPort(5050, 6050);

            PSERVER_VIDEOINFO videoInfo 
=   new  PSERVER_VIDEOINFO();
            
// 初始化
            videoInfo.m_datatype  =   new   byte [ 64 ];
            
// 设置发送缓冲区大小
            HikServer.MP4_ServerSetBufNum(( ushort ) 0 , ( ushort ) 90 );
            videoInfo.m_datatype[
0 =  ( byte )ChannelDataType.SMALLPIC;
            videoInfo.m_channum 
=  ( byte ) 1 ;
            videoInfo.m_waittime 
=   5 ;

            
// 设置每个通道的最大用户数量
            
// HikServer.MP4_ServerMaxUser(0, 24);

            
if  (HikServer.MP4_ServerStart( ref  videoInfo)  ==   0 )
            {
                MessageBox.Show(
" 服务端启动错误!! " );
            }

            
// 开启视频预览
            StartVideoPreview();
        }



        
#region  回调函数

        
public   void  StartCap( int  port)
        {
            HikVisionSDK.StartVideoCapture(ChannelHandle);
        }

        
public   void  MakeIFrame( ulong  port)
        {
            HikVisionSDK.CaptureIFrame(ChannelHandle);
        }

        
public   int  STREAM_DIRECT_READ_CALLBACK1( int  channelNum, IntPtr DataBuf,  int  Length, FrameType_t frameType, IntPtr context)
        {
            
int  status  =   0 ;
            HikServer.MP4_ServerWriteDataEx(channelNum, DataBuf, Length, (
int )frameType, status,  0 );
            
return   0 ;
        }

        
#endregion
复制代码

          代码说明:

               1.     将Form2_Load中最后一行代码StartVideoPreview替换成PreviewAndClientConnect调用即可。

               2.     调用注释前面带了"[必须]"的方法是必须调用的,而被我的注释掉的方法参照源代码可以加也可以不加,因为他是有默认设置的。

               3.     MakeIFrame这个回调函数是客户端连接服务器的关键,如果没有执行这个回调客户端将不能够连接并显示画面!

               4.     STREAM_DIRECT_READ_CALLBACK1回调函数在VC++代码说明里面已经说明了,因为本章不写视频存储,所以把其他代码都注释掉了,只管往内存写数据就行了。

 

注意

     1.     StartVideoPreview的参数用结构体RECT会报错,直接使用Rectangle结构体即可。

     2.     使用GetDspCount总是只返回可用的Dsp数量,而用GetTotalDSPs可以获取所有的Dsp数量。

     3.     再强调一遍,虽然我这里没有把委托实例化成 成员变量,也能调试通过,但是强烈建议您把这些都写成 成员变量然后在窗体初始化时初始化!

     4.     本文是后续服务器端文章的基础,务必细心调试,我敢说如果本文的功能你达到了——你的服务器端可以说完成了60%!!

 

修改记录

     1.     2009-3-30

          将STREAM_DIRECT_READ_CALLBACK声明为成员变量,发现不声明成成员变量在VS里面调试可以运行通过(有时候),但是直接运行exe文件会报内存出错!!

 

结束

     这篇文章在我研究的时候花了将近1个多星期,主要症状就是能预览,客户端死活都看不到画面,能连接!!甚至找了VC++牛人(不会C#)帮忙分析了都没能出来,不过倒是帮我弄得能调试源代码了,也是在无意中从头到尾整理代码的时候出来的(得到上司提醒整理代码),极度兴奋!!


转载:http://www.cnblogs.com/over140/archive/2009/03/11/1397378.html

目录
相关文章
|
9天前
|
JavaScript 前端开发
vue配合axios连接express搭建的node服务器接口_简单案例
文章介绍了如何使用Express框架搭建一个简单的Node服务器,并使用Vue结合Axios进行前端开发和接口调用,同时讨论了开发过程中遇到的跨域问题及其解决方案。
12 0
vue配合axios连接express搭建的node服务器接口_简单案例
文件上传 图片上传 客户端图片上传到服务器
文件上传 图片上传 客户端图片上传到服务器
|
2月前
|
数据可视化 Ubuntu Linux
PyCharm连接远程服务器配置的全过程
相信很多人都遇见过这种情况:实验室成员使用同一台服务器,每个人拥有自己的独立账号,我们可以使用服务器更好的配置完成实验,毕竟自己哪有money拥有自己的3090呢。 通常服务器系统采用Linux,而我们平常使用频繁的是Windows系统,二者在操作方面存在很大的区别,比如我们实验室的服务器采用Ubuntu系统,创建远程交互任务时可以使用Terminal终端或者VNC桌面化操作,我觉得VNC很麻烦,所以采用Terminal进行实验,但是Terminal操作给我最不好的体验就是无法可视化中间实验结果,而且实验前后的数据上传和下载工作也让我头疼不已。
|
2月前
|
网络协议 C# 开发者
WPF与Socket编程的完美邂逅:打造流畅网络通信体验——从客户端到服务器端,手把手教你实现基于Socket的实时数据交换
【8月更文挑战第31天】网络通信在现代应用中至关重要,Socket编程作为其实现基础,即便在主要用于桌面应用的Windows Presentation Foundation(WPF)中也发挥着重要作用。本文通过最佳实践,详细介绍如何在WPF应用中利用Socket实现网络通信,包括创建WPF项目、设计用户界面、实现Socket通信逻辑及搭建简单服务器端的全过程。具体步骤涵盖从UI设计到前后端交互的各个环节,并附有详尽示例代码,助力WPF开发者掌握这一关键技术,拓展应用程序的功能与实用性。
69 0
|
2月前
|
API C# 开发框架
WPF与Web服务集成大揭秘:手把手教你调用RESTful API,客户端与服务器端优劣对比全解析!
【8月更文挑战第31天】在现代软件开发中,WPF 和 Web 服务各具特色。WPF 以其出色的界面展示能力受到欢迎,而 Web 服务则凭借跨平台和易维护性在互联网应用中占有一席之地。本文探讨了 WPF 如何通过 HttpClient 类调用 RESTful API,并展示了基于 ASP.NET Core 的 Web 服务如何实现同样的功能。通过对比分析,揭示了两者各自的优缺点:WPF 客户端直接处理数据,减轻服务器负担,但需处理网络异常;Web 服务则能利用服务器端功能如缓存和权限验证,但可能增加服务器负载。希望本文能帮助开发者根据具体需求选择合适的技术方案。
67 0
|
2月前
|
网络安全 数据安全/隐私保护
VSC通过 SSH 连接到远程服务器时,每次都需要输入密码
VSC通过 SSH 连接到远程服务器时,每次都需要输入密码
196 0
|
2月前
|
Linux 网络安全 网络架构
如何处理在学校Linux连接不上服务器
如何处理在学校Linux连接不上服务器
39 0
|
12月前
|
数据采集 监控 网络协议
linux系统中利用QT实现视频监控的基本方法
linux系统中利用QT实现视频监控的基本方法
344 0
|
监控 定位技术 安全
Qt编写安防视频监控系统10-视频轮询
一、前言 视频轮询在视频监控系统中是一个基础的核心功能,尤其是上了大屏以后,这个功能是必须的,根据预先设定的轮询间隔逐个加载视频到预先设定的通道画面数中,轮询间隔、轮询画面数、轮询采用的码流类型(主码流、子码流)都可以在系统设置中进行统一设置,轮询的视频源采用摄像机表中的所有摄像机,当画面数不够的时候,其余留空显示即可,轮询到最后一个视频,重新从第一个开始轮询。
1615 0
|
5月前
|
存储 监控 安全
【亮剑】指导初学者如何搭建和使用网络视频监控系统。
【4月更文挑战第30天】本文指导初学者如何搭建和使用网络视频监控系统。核心设备包括摄像头(如固定、PTZ、多目、夜视)、存储选项(NVR、DVR、云存储)及网络交换机等。安装配置步骤涉及规划布局、安装摄像头、设置存储设备和软件配置。实时监控包括实时查看、接收警报和录像回放。理解设备功能、合理布局并细心操作,就能建立稳定监控体系。随着技术进步,未来监控系统将更智能、高效,保障安全。
492 0

热门文章

最新文章

下一篇
无影云桌面