博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C# 视频监控系列(8):服务器端——预览和可被客户端连接
阅读量:6340 次
发布时间:2019-06-22

本文共 9542 字,大约阅读时间需要 31 分钟。

一、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%!!

本文转自博客园农民伯伯的博客,原文链接:,如需转载请自行联系原博主。

你可能感兴趣的文章
Android 禁止转屏的方法
查看>>
38属性的种种,只读只写属性、自动属性、静态属性、抽象属性、接口属性
查看>>
动态加载脚本和样式(转)
查看>>
ASP.NET Web API实现缓存的2种方式
查看>>
arcgis desktop按ctrl键后地图乱移的解决办法
查看>>
OutputStream write文件比原来的大
查看>>
Thread Runnable建立新线程 Handler AsycTask执行异步任务(不一定建立新的线程)
查看>>
学完了在线课程?如何开启深度学习论文的阅读模式
查看>>
RabbitMQ Config
查看>>
topcoder srm 605 div1
查看>>
CSS Text-Shadow in Safari, Opera, Firefox and more
查看>>
sql:查询创建表的结构
查看>>
MySql: Year, Quarter, Month, Day, Hour statistics
查看>>
如何读书? 我一年读500本书,你呢?
查看>>
一个人住七年
查看>>
数据分析常见数学公式(更新中...)
查看>>
Android开发环境搭建全程演示(jdk+eclipse+android sdk)
查看>>
CookieThemeResolver
查看>>
MonoDevelop 3.0——更好的代码完成、性能与快速修复建议
查看>>
C/C++怎么产生随机数【转】
查看>>