簡述
使用海康攝像頭採集圖像時,需要在圖像上添加圖標、文字等額外數據。可選擇使用海康SDK繪圖回調函數疊加字符、圖像等(請參考上一篇文章);也可使用海康SDK的解碼回調函數,對視頻流數據進行解碼後處理。該方法流程爲:調用視頻預覽函數NET_DVR_RealPlay_V40()時將第三個參數設置爲實時數據回調函數RealDataCallBack()的函數指針,然後在RealDataCallBack()回調函數中註冊視頻流數據解碼函數DecCallbackFUN(),最後在DecCallbackFUN()函數中對數據解析解碼、疊加字符等處理。
本文只調用解碼回調函數將YV12格式視頻流數據轉換爲RGB32格式數據,然後將RGB32格式數據轉換爲Image,並使用PictrueBox顯示該Image數據,暫不做疊加字符、圖像等處理。
代碼
1、攝像頭操作代碼
struct CameraInfo
{
public string strIP;
public short nPort;
public string strUserName;
public string strPassword;
}
class IDeviceCamera
{
public Image m_img = null;
public virtual bool InitCamera( CameraInfo stInfo )
{
return true;
}
}
class DeviceCamera : IDeviceCamera
{
private CameraInfo m_stCameraInfo;
private bool m_bInitSDK = false;
private Int32 m_lUserID = -1;
private Int32 m_lRealHandle = -1;
private Int32 m_lPort = -1;
CHCNetSDK.REALDATACALLBACK RealData = null; //必須得定義爲成員變量
public override bool InitCamera( CameraInfo stInfo )
{
m_stCameraInfo = stInfo;
m_bInitSDK = CHCNetSDK.NET_DVR_Init();
if ( !m_bInitSDK )
{
uint nError = CHCNetSDK.NET_DVR_GetLastError();
}
CHCNetSDK.NET_DVR_SetConnectTime( 5000, 1 );
CHCNetSDK.NET_DVR_SetReconnect( 10000, 1 );
if ( m_bInitSDK == false )
{
MessageBox.Show( "NET_DVR_Init error!" );
return false;
}
else
{
//保存SDK日誌 To save the SDK log
CHCNetSDK.NET_DVR_SetLogToFile( 3, "C:\\SdkLog\\", true );
}
string DVRIPAddress = stInfo.strIP; //設備IP地址或者域名 Device IP
Int16 DVRPortNumber = stInfo.nPort; //設備服務端口號 Device Port
string DVRUserName = stInfo.strUserName;//設備登錄用戶名 User name to login
string DVRPassword = stInfo.strPassword;//設備登錄密碼 Password to login
CHCNetSDK.NET_DVR_DEVICEINFO_V30 DeviceInfo = new CHCNetSDK.NET_DVR_DEVICEINFO_V30();
m_lUserID = CHCNetSDK.NET_DVR_Login_V30( DVRIPAddress, DVRPortNumber, DVRUserName, DVRPassword, ref DeviceInfo );
if ( m_lUserID < 0 )
{
MessageBox.Show( "登錄失敗!" );
CHCNetSDK.NET_DVR_Cleanup();
return false;
}
//
CHCNetSDK.NET_DVR_PREVIEWINFO lpPreviewInfo = new CHCNetSDK.NET_DVR_PREVIEWINFO();
lpPreviewInfo.hPlayWnd = (IntPtr)null;
lpPreviewInfo.lChannel = 1;
lpPreviewInfo.dwStreamType = 0; //碼流類型:0-主碼流,1-子碼流,2-碼流3,3-碼流4,以此類推
lpPreviewInfo.dwLinkMode = 0; //連接方式:0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4-RTP/RTSP,5-RSTP/HTTP
lpPreviewInfo.bBlocked = true; //0- 非阻塞取流,1- 阻塞取流
lpPreviewInfo.dwDisplayBufNum = 15; //播放庫播放緩衝區最大緩衝幀數
//使用回調函數取攝像頭數據
RealData = new CHCNetSDK.REALDATACALLBACK( RealDataCallBack );//預覽實時流回調函數
IntPtr pUser = new IntPtr();//用戶數據
m_lRealHandle = CHCNetSDK.NET_DVR_RealPlay_V40( m_lUserID, ref lpPreviewInfo, RealData, pUser );
CHCNetSDK.NET_DVR_RigisterDrawFun( m_lRealHandle, new CHCNetSDK.DRAWFUN( cbDrawFun ), 0 );//回調函數:繪製圖標
return true;
}
private uint nLastErr = 0;
private static PlayCtrl.DECCBFUN m_fDisplayFun = null;
private IntPtr m_ptrRealHandle;
public void RealDataCallBack( Int32 lRealHandle, UInt32 dwDataType, IntPtr pBuffer, UInt32 dwBufSize, IntPtr pUser )
{
//下面數據處理建議使用委託的方式
switch ( dwDataType )
{
case CHCNetSDK.NET_DVR_SYSHEAD: // sys head
if ( dwBufSize > 0 )
{
if ( m_lPort >= 0 )
return; //同一路碼流不需要多次調用開流接口
//獲取播放句柄 Get the port to play
if ( !PlayCtrl.PlayM4_GetPort( ref m_lPort ) )
{
nLastErr = PlayCtrl.PlayM4_GetLastError( m_lPort );
break;
}
//設置流播放模式 Set the stream mode: real-time stream mode
if ( !PlayCtrl.PlayM4_SetStreamOpenMode( m_lPort, PlayCtrl.STREAME_REALTIME ) )
{
nLastErr = PlayCtrl.PlayM4_GetLastError( m_lPort );
//str = "Set STREAME_REALTIME mode failed, error code= " + nLastErr;
//this.BeginInvoke( AlarmInfo, str );
}
//打開碼流,送入頭數據 Open stream
if ( !PlayCtrl.PlayM4_OpenStream( m_lPort, pBuffer, dwBufSize, 2 * 1024 * 1024 ) )
{
nLastErr = PlayCtrl.PlayM4_GetLastError( m_lPort );
//str = "PlayM4_OpenStream failed, error code= " + nLastErr;
//this.BeginInvoke( AlarmInfo, str );
break;
}
//設置顯示緩衝區個數 Set the display buffer number
if ( !PlayCtrl.PlayM4_SetDisplayBuf( m_lPort, 15 ) )
{
nLastErr = PlayCtrl.PlayM4_GetLastError( m_lPort );
//str = "PlayM4_SetDisplayBuf failed, error code= " + nLastErr;
//this.BeginInvoke( AlarmInfo, str );
}
//設置顯示模式 Set the display mode
//if ( !PlayCtrl.PlayM4_SetOverlayMode( m_lPort, 0, 0) ) //play off screen
//{
// nLastErr = PlayCtrl.PlayM4_GetLastError( m_lPort );
// //str = "PlayM4_SetOverlayMode failed, error code= " + nLastErr;
// //this.BeginInvoke( AlarmInfo, str );
//}
//設置解碼回調函數,獲取解碼後音視頻原始數據 Set callback function of decoded data
m_fDisplayFun = new PlayCtrl.DECCBFUN( DecCallbackFUN );
if ( !PlayCtrl.PlayM4_SetDecCallBackEx( m_lPort, m_fDisplayFun, IntPtr.Zero, 0 ) )
{
//this.BeginInvoke( AlarmInfo, "PlayM4_SetDisplayCallBack fail" );
}
//開始解碼 Start to play
if ( !PlayCtrl.PlayM4_Play( m_lPort, m_ptrRealHandle ) )
{
nLastErr = PlayCtrl.PlayM4_GetLastError( m_lPort );
//str = "PlayM4_Play failed, error code= " + nLastErr;
//this.BeginInvoke( AlarmInfo, str );
break;
}
}
break;
case CHCNetSDK.NET_DVR_STREAMDATA: // video stream data
default: //the other data
if ( dwBufSize > 0 && m_lPort != -1 )
{
for ( int i = 0; i < 999; i++ )
{
//送入碼流數據進行解碼 Input the stream data to decode
if ( !PlayCtrl.PlayM4_InputData( m_lPort, pBuffer, dwBufSize ) )
{
nLastErr = PlayCtrl.PlayM4_GetLastError( m_lPort );
//str = "PlayM4_InputData failed, error code= " + nLastErr;
Thread.Sleep( 2 );
}
else
break;
}
}
break;
}
}
//解碼回調函數
private void DecCallbackFUN( int nPort, IntPtr pBuf, int nSize, ref PlayCtrl.FRAME_INFO pFrameInfo, int nReserved1, int nReserved2 )
{
if ( pFrameInfo.nType == 3 ) //#define T_YV12 3
{
byte[] byteBuffYV12 = new byte[ nSize ];
Marshal.Copy( pBuf, byteBuffYV12, 0, nSize );
long lRGBSize = (long)pFrameInfo.nWidth * pFrameInfo.nHeight * 4;
byte[] bufferRGB32 = new byte[ lRGBSize ];
CommonFun.YV12_to_RGB32( byteBuffYV12, bufferRGB32, pFrameInfo.nWidth, pFrameInfo.nHeight );
byteBuffYV12 = null;
Bitmap bmpFromGRB32 = CommonFun.RGB32_to_Image( bufferRGB32, pFrameInfo.nWidth, pFrameInfo.nHeight );
bufferRGB32 = null;
if ( null == bmpFromGRB32 )
return;
m_img = bmpFromGRB32;
}
}
}
注:得到的m_img用於PictureBox進行顯示。
2、YV12轉換爲RGB32、RGB32轉換爲Image代碼
class CommonFun
{
public static bool YV12_to_RGB32( byte[] buffYV12, byte[] bufferRGB32, int nWidth, int nHeight )
{
if( buffYV12.Length < 0 || bufferRGB32.Length < 0 )
return false;
long nYLen = (long)nWidth * nHeight;
int nHfWidth = ( nWidth >> 1 );
if ( nYLen < 1 || nHfWidth < 1 )
return false;
byte[] byteYData = buffYV12.Skip( 0 ).Take( nWidth*nHeight ).ToArray();
byte[] byteUData = buffYV12.Skip( nWidth*nHeight ).Take( (nHeight/2)*(nWidth/2) ).ToArray();
byte[] byteVData = buffYV12.Skip( nWidth*nHeight + (nHeight/2)*(nWidth/2) ).Take( (nHeight/2)*(nWidth/2) ).ToArray();
if ( byteYData.Length < 0 || byteVData.Length < 0 || byteUData.Length < 0 )
return false;
int[] nRgb = new int[4];
for( int nRow = 0; nRow < nHeight; nRow++ )
{
for( int nCol = 0; nCol < nWidth; nCol++ )
{
int Y = byteYData[nRow*nWidth + nCol];
int U = byteUData[(nRow / 2)*(nWidth / 2) + (nCol / 2)];
int V = byteVData[(nRow / 2)*(nWidth / 2) + (nCol / 2)];
int R = Y + (U - 128) + (((U - 128) * 103) >> 8);
int G = Y - (((V - 128) * 88) >> 8) - (((U - 128) * 183) >> 8);
int B = Y + (V - 128) + (((V - 128) * 198) >> 8);
// r分量值
R = R<0 ? 0 : R;
nRgb[2] = R > 255 ? 255 : R;
// g分量值
G = G<0 ? 0 : G;
nRgb[1] = G > 255 ? 255 : G;
// b分量值
B = B<0 ? 0 : B;
nRgb[0] = B > 255 ? 255 : B;
//A分量值
nRgb[ 3 ] = 255;
//Out RGB Buffer
bufferRGB32[4 * (nRow*nWidth + nCol) + 0] = (byte)nRgb[0];
bufferRGB32[4 * (nRow*nWidth + nCol) + 1] = (byte)nRgb[1];
bufferRGB32[4 * (nRow*nWidth + nCol) + 2] = (byte)nRgb[2];
bufferRGB32[4 * (nRow*nWidth + nCol) + 3] = (byte)nRgb[3];
}
}
return true;
}
public static Bitmap RGB32_to_Image( byte[] byteBuff, int nWidth, int nHeight )
{
if ( byteBuff.Length <= 0 || byteBuff.Length < nWidth*nHeight )
return null;
Bitmap bmp = new Bitmap( nWidth, nHeight, PixelFormat.Format32bppArgb );
//鎖定內存數據
BitmapData bmpData = bmp.LockBits( new Rectangle( 0, 0, nWidth, nHeight ), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb );
//輸入顏色數據
System.Runtime.InteropServices.Marshal.Copy( byteBuff, 0, bmpData.Scan0, byteBuff.Length );
//解鎖
bmp.UnlockBits( bmpData );
return bmp;
}
}