• 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(110000);
        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(101010));
        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, 1002125);
                
    //设置编码图像质量
                HikVisionSDK.SetDefaultQuant(ChannelHandle, 151520);

                
    //视频预览
                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#)帮忙分析了都没能出来,不过倒是帮我弄得能调试源代码了,也是在无意中从头到尾整理代码的时候出来的(得到上司提醒整理代码),极度兴奋!!

  • 相关阅读:
    继承和多态
    访问限制
    返回函数
    类和实例
    requests
    函数的参数
    代码块的快速放置
    19进阶、基于TSP的直流电机控制设计
    18进阶、TLC语言
    17高级、Simulink代码生成技术详解
  • 原文地址:https://www.cnblogs.com/over140/p/1397378.html
Copyright © 2020-2023  润新知