VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > temp > C#教程 >
  • c#视频监控系列(7)服务器端—预览和可被客户端连接

制作者:剑锋冷月 单位:无忧统计网,www.51stat.net
 

  前言

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

  正文

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

 

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



相关教程