此次項目需求是接入海康威視的網(wǎng)絡(luò)智能攝像頭實現(xiàn)實時監(jiān)控。網(wǎng)上搜羅一番,也有挺多例子的,但是大多數(shù)都是通過官方提供的rstp協(xié)議地址實現(xiàn),為了自己記憶,在下打算在這里記錄一下,不需要的可以跳過這里哈(這里采用一個叫UMP的插件,當(dāng)然還有其他Vlc for unity,openCV等都可以實現(xiàn)的) 首先貼一下??档膔tsp協(xié)議地址: rtsp://[username]:[password]@[ip]:[port]/[codec]/[channel]/[subtype]/av_stream 說明: username: 用戶名。例如admin。 password: 密碼。例如12345。 ip: 為設(shè)備IP。例如 192.0.0.64。 port: 端口號默認(rèn)為554,若為默認(rèn)可不填寫。 codec:有h264、MPEG-4、mpeg4這幾種。 channel: 通道號,起始為1。例如通道1,則為ch1。 subtype: 碼流類型,主碼流為main,輔碼流為sub。
例如,請求??禂z像機通道1的主碼流,Url如下 主碼流: rtsp://admin:12345@192.0.0.64:554/h264/ch1/main/av_stream rtsp://admin:12345@192.0.0.64:554/MPEG-4/ch1/main/av_stream
子碼流: rtsp://admin:12345@192.0.0.64/mpeg4/ch1/sub/av_stream
rtsp://admin:12345@192.0.0.64/h264/ch1/sub/av_stream 然后插件的名字叫UMP 地址我就不貼了,搜一下會有的哈。導(dǎo)入后是這樣的 
然后隨便選一個場景填入你的rtsp地址 
然后這是運行的效果 
但是本人通過測試后發(fā)現(xiàn)延遲~額。。。有一丟丟小高,于是繼續(xù)搜羅,發(fā)現(xiàn)海康的SDK里的函數(shù)是可以回調(diào)獲取視頻流的數(shù)據(jù)的,還提供了一個播放庫的SDK,同過此SDK的方法可以將標(biāo)準(zhǔn)的視頻碼流轉(zhuǎn)換為YV12的格式,相信研究過視頻流的對這個格式都不陌生吧,沒錯,小白我準(zhǔn)備在unity中將這個格式的數(shù)據(jù)實時生成一幀一幀的textur2d,然后,上步驟 首先下載海康最新的SDK包 地址:http://www.hikvision.com/cn/download_more_570.html 記住播放庫的也一起下來哦、 前一部分的實現(xiàn)代碼其實官方已經(jīng)提供了各種各樣的案例 C#和C++的都有,但是看過代碼的同學(xué)應(yīng)該都會知道,在這寫Demo中想要播放視頻都有一個必不可少的參數(shù)就是窗口的句柄Handle,就是一個IntPtr類型的參數(shù),但是untiy中。As you konw ,哪里來的句柄啊,UI都是畫出來的嘛,整個unity才是一個窗口,但是SDK中也說了可以給這個參數(shù)傳空然后給一個回調(diào)函數(shù)來獲取這些視頻數(shù)據(jù), 
記住這個回調(diào)函數(shù)里是用來啟用Play_M4播放庫解碼的(有點長,還是不貼圖片貼代碼號了,不要噴我亂(* ̄︶ ̄))
/// <summary> /// 獲取數(shù)據(jù)流回調(diào)函數(shù) /// </summary> /// <param name="lRealHandle">L real handle.</param> /// <param name="dwDataType">Dw data type.</param> /// <param name="pBuffer">P buffer.</param> /// <param name="dwBufSize">Dw buffer size.</param> /// <param name="pUser">P user.</param> public void RealDataCallBack(Int32 lRealHandle, UInt32 dwDataType, IntPtr pBuffer, UInt32 dwBufSize, IntPtr pUser) { //下面數(shù)據(jù)處理建議使用委托的方式 //MyDebugInfo AlarmInfo = new MyDebugInfo(DebugInfo); switch (dwDataType) { case CHCNetSDK.NET_DVR_SYSHEAD: // sys head if (dwBufSize > 0) { if (m_lPort >= 0) { return; //同一路碼流不需要多次調(diào)用開流接口 } //debugInfo += ("系統(tǒng)頭數(shù)據(jù):" + pBuffer + "數(shù)據(jù)長度:" + dwBufSize); //獲取播放句柄 Get the port to play if (!PlayCtrl.PlayM4_GetPort(ref m_lPort)) { error_num = PlayCtrl.PlayM4_GetLastError(m_lPort); debugInfo += ("PlayM4_GetPort failed, error code= " + error_num); break; }
//設(shè)置流播放模式 Set the stream mode: real-time stream mode if (!PlayCtrl.PlayM4_SetStreamOpenMode(m_lPort, PlayCtrl.STREAME_REALTIME)) { error_num = PlayCtrl.PlayM4_GetLastError(m_lPort); debugInfo += ("Set STREAME_REALTIME mode failed, error code= " + error_num); }
//打開碼流,送入頭數(shù)據(jù) Open stream if (!PlayCtrl.PlayM4_OpenStream(m_lPort, pBuffer, dwBufSize, 1024*1024)) { error_num = PlayCtrl.PlayM4_GetLastError(m_lPort); debugInfo += ("PlayM4_OpenStream failed, error code= " + error_num); break; }
//設(shè)置顯示緩沖區(qū)個數(shù) Set the display buffer number if (!PlayCtrl.PlayM4_SetDisplayBuf(m_lPort, 15)) { error_num = PlayCtrl.PlayM4_GetLastError(m_lPort); debugInfo += ("PlayM4_SetDisplayBuf failed, error code= " + error_num); }
//設(shè)置顯示模式 Set the display mode if (!PlayCtrl.PlayM4_SetOverlayMode(m_lPort, 0, 0)) //play off screen { error_num = PlayCtrl.PlayM4_GetLastError(m_lPort); debugInfo += ("PlayM4_SetOverlayMode failed, error code= " + error_num); }
//設(shè)置解碼回調(diào)函數(shù),獲取解碼后音視頻原始數(shù)據(jù) Set callback function of decoded data m_fDisplayFun = new PlayCtrl.DECCBFUN(DecCallbackFUN); if (!PlayCtrl.PlayM4_SetDecCallBack(m_lPort, m_fDisplayFun)) { debugInfo += ("PlayM4_SetDisplayCallBack fail"); }
//開始解碼 Start to play if (!PlayCtrl.PlayM4_Play(m_lPort, IntPtr.Zero)) { error_num = PlayCtrl.PlayM4_GetLastError(m_lPort); debugInfo += ("PlayM4_Play failed, error code= " + error_num); break; }
} break; case CHCNetSDK.NET_DVR_STREAMDATA: // video stream data if (dwBufSize > 0 && m_lPort != -1) { for (int i = 0; i < 999; i++) { //送入碼流數(shù)據(jù)進(jìn)行解碼 Input the stream data to decode if (!PlayCtrl.PlayM4_InputData(m_lPort, pBuffer, dwBufSize)) { error_num = PlayCtrl.PlayM4_GetLastError(m_lPort); debugInfo += ("PlayM4_InputData failed, error code= " + error_num); Thread.Sleep(10); } else { break; } } } break; default: if (dwBufSize > 0 && m_lPort != -1) { //送入其他數(shù)據(jù) Input the other data for (int i = 0; i < 999; i++) { if (!PlayCtrl.PlayM4_InputData(m_lPort, pBuffer, dwBufSize)) { error_num = PlayCtrl.PlayM4_GetLastError(m_lPort); debugInfo += ("PlayM4_InputData failed, error code= " + error_num); Thread.Sleep(10); } else { break; } } } break; } }
然后在播放庫代碼中在加一個回調(diào)來自己處理數(shù)據(jù) 看圖

這里的數(shù)據(jù)都是別人官方的播放庫函數(shù)處理好的每一幀的數(shù)據(jù),連幀號都有,可以說已經(jīng)非常方便了,我們只需要將每一幀的數(shù)據(jù)拿來然后轉(zhuǎn)換為我們需要的Texutrue2D數(shù)據(jù),然后一幀一幀的顯示到我們的UI上就可以了,那么重點來了,之前說過播放庫拿出來的數(shù)據(jù)是YV12的格式,而我們需要的Texture2D是rgb(YUV)格式的,這些都是圖片數(shù)據(jù)的存儲格式,我實在太懶了,給你們貼個地址,不懂得有興趣的可以看看O(∩_∩)O https://www.cnblogs.com/samaritan/p/YUV.html 這里轉(zhuǎn)換的話,由于數(shù)據(jù)量有點大 一幀1280*720的YV12數(shù)據(jù)大概有2764800,如果自己寫硬生生的在數(shù)據(jù)里復(fù)制替換啥的,額,本人試了一下,不到10幀,感覺成GIF圖了都,所以網(wǎng)上又搜羅一下(有事沒事就搜羅搜羅),看到其他博友測試的五六種方法,有直接硬轉(zhuǎn)的(通過查表法優(yōu)化過的,其實也沒優(yōu)化多少),然后看到效率比較高的兩種,用opencv和ffmpeg實現(xiàn)的,opencv和ffmpeg都試了下,最后選擇了ffmpeg,因為ffmpeg的算法效率最好(別人說的,別人說測試了的) 這里如果要用ffmpeg的格式轉(zhuǎn)換算法需要兩個庫avutil-55.dll和swscale-4.dll 然后呢當(dāng)然就是調(diào)用這兩個庫,寫一個轉(zhuǎn)換方法導(dǎo)出可以供untiy調(diào)用的Dll了 下面貼出Dll里的轉(zhuǎn)換函數(shù)代碼,比較粗糙,大家不要介意.
AVPixelFormat SRC_pixfmt; AVPixelFormat DST_pixfmt;
AVPicture SRC_frameinfo; AVPicture DST_frameinfo; struct SwsContext *img_convert_ctx; TransformResolution *TP;
FFMPEG_FOR_UNITY_API bool StartConvert_Updated(bool start_or_end,int src_width,int src_height,int dst_width,int dst_height,int src_type,int dst_type) { if(start_or_end) { SRC_pixfmt = (AVPixelFormat)src_type;//0 DST_pixfmt = (AVPixelFormat)dst_type;//2
int ret=0; TP = new TransformResolution(); TP->SRC_WIDTH = src_width; TP->SRC_HEIGHT = src_height; TP->DST_WIDTH = dst_width; TP->DST_HEIGHT = dst_height; ret= av_image_alloc(SRC_frameinfo.data, SRC_frameinfo.linesize,src_width, src_height, SRC_pixfmt, 1); if (ret< 0) { //printf( "Could not allocate source image\n"); //strcpy(rst,"Could not allocate source image\n"); return false; } ret = av_image_alloc(DST_frameinfo.data, DST_frameinfo.linesize,dst_width, dst_height, DST_pixfmt, 1); if (ret< 0) { //printf( "Could not allocate destination image\n"); //strcpy(rst,"Could not allocate destination image\n"); return false; }
//Init Method 1 img_convert_ctx =sws_alloc_context(); //Show AVOption //av_opt_show2(img_convert_ctx,stdout,AV_OPT_FLAG_VIDEO_PARAM,NULL); //Set Value av_opt_set_int(img_convert_ctx,"sws_flags",SWS_BICUBIC|SWS_PRINT_INFO,NULL); av_opt_set_int(img_convert_ctx,"srcw",src_width,NULL); av_opt_set_int(img_convert_ctx,"srch",src_height,NULL); av_opt_set_int(img_convert_ctx,"src_format",SRC_pixfmt,NULL); //'0' for MPEG (Y:0-235);'1' for JPEG (Y:0-255) av_opt_set_int(img_convert_ctx,"src_range",1,NULL); av_opt_set_int(img_convert_ctx,"dstw",dst_width,NULL); av_opt_set_int(img_convert_ctx,"dsth",dst_height,NULL); av_opt_set_int(img_convert_ctx,"dst_format",DST_pixfmt,NULL); av_opt_set_int(img_convert_ctx,"dst_range",1,NULL); sws_init_context(img_convert_ctx,NULL,NULL); return true; } else { sws_freeContext(img_convert_ctx); av_freep(&SRC_frameinfo); av_freep(&DST_frameinfo); delete TP; return true; }
} StartConvert_Update函數(shù)主要做一些初始化,c++里面大家知道的申請內(nèi)存啊什么的
然后是轉(zhuǎn)化函數(shù) ///update FFMPEG_FOR_UNITY_API bool YV12toRgb_Updated(uint8_t* pDst, uint8_t* pSrc) { if(!pDst) { //strcpy(rst,"pDst is null"); return false; } if(!TP) { return false; }
switch(SRC_pixfmt){ case AV_PIX_FMT_GRAY8:{ memcpy(SRC_frameinfo.data[0],pSrc,TP->SRC_WIDTH*TP->SRC_HEIGHT); break; } case AV_PIX_FMT_YUV420P:{ memcpy(SRC_frameinfo.data[0],pSrc,TP->SRC_WIDTH*TP->SRC_HEIGHT); //Y memcpy(SRC_frameinfo.data[1],pSrc+TP->SRC_WIDTH*TP->SRC_HEIGHT*5/4,TP->SRC_WIDTH*TP->SRC_HEIGHT/4); //V memcpy(SRC_frameinfo.data[2],pSrc+TP->SRC_WIDTH*TP->SRC_HEIGHT,TP->SRC_WIDTH*TP->SRC_HEIGHT/4); //U break; } case AV_PIX_FMT_YUV422P:{ memcpy(SRC_frameinfo.data[0],pSrc,TP->SRC_WIDTH*TP->SRC_HEIGHT); //Y memcpy(SRC_frameinfo.data[1],pSrc+TP->SRC_WIDTH*TP->SRC_HEIGHT,TP->SRC_WIDTH*TP->SRC_HEIGHT/2); //U memcpy(SRC_frameinfo.data[2],pSrc+TP->SRC_WIDTH*TP->SRC_HEIGHT*3/2,TP->SRC_WIDTH*TP->SRC_HEIGHT/2); //V break; } case AV_PIX_FMT_YUV444P:{ memcpy(SRC_frameinfo.data[0],pSrc,TP->SRC_WIDTH*TP->SRC_HEIGHT); //Y memcpy(SRC_frameinfo.data[1],pSrc+TP->SRC_WIDTH*TP->SRC_HEIGHT,TP->SRC_WIDTH*TP->SRC_HEIGHT); //U memcpy(SRC_frameinfo.data[2],pSrc+TP->SRC_WIDTH*TP->SRC_HEIGHT*2,TP->SRC_WIDTH*TP->SRC_HEIGHT); //V break; } case AV_PIX_FMT_YUYV422:{ memcpy(SRC_frameinfo.data[0],pSrc,TP->SRC_WIDTH*TP->SRC_HEIGHT*2); //Packed break; } case AV_PIX_FMT_RGB24:{ memcpy(SRC_frameinfo.data[0],pSrc,TP->SRC_WIDTH*TP->SRC_HEIGHT*3); //Packed break; } default:{ //printf("Not Support Input Pixel Format.\n"); break; } }
sws_scale(img_convert_ctx, SRC_frameinfo.data, SRC_frameinfo.linesize, 0, TP->SRC_HEIGHT, DST_frameinfo.data, DST_frameinfo.linesize);
switch(DST_pixfmt){ case AV_PIX_FMT_GRAY8:{ memcpy(pDst,DST_frameinfo.data[0],TP->DST_WIDTH*TP->DST_HEIGHT); break; } case AV_PIX_FMT_YUV420P:{ memcpy(pDst,DST_frameinfo.data[0],TP->DST_WIDTH*TP->DST_HEIGHT); //Y memcpy(pDst,DST_frameinfo.data[1],TP->DST_WIDTH*TP->DST_HEIGHT/4); //U memcpy(pDst,DST_frameinfo.data[2],TP->DST_WIDTH*TP->DST_HEIGHT/4); //V break; } case AV_PIX_FMT_YUV422P:{ memcpy(pDst,DST_frameinfo.data[0],TP->DST_WIDTH*TP->DST_HEIGHT); //Y memcpy(pDst,DST_frameinfo.data[1],TP->DST_WIDTH*TP->DST_HEIGHT/2); //U memcpy(pDst,DST_frameinfo.data[2],TP->DST_WIDTH*TP->DST_HEIGHT/2); //V break; } case AV_PIX_FMT_YUV444P:{ memcpy(pDst,DST_frameinfo.data[0],TP->DST_WIDTH*TP->DST_HEIGHT); //Y memcpy(pDst,DST_frameinfo.data[1],TP->DST_WIDTH*TP->DST_HEIGHT); //U memcpy(pDst,DST_frameinfo.data[2],TP->DST_WIDTH*TP->DST_HEIGHT); //V break; } case AV_PIX_FMT_YUYV422:{ memcpy(pDst,DST_frameinfo.data[0],TP->DST_WIDTH*TP->DST_HEIGHT*2);//Packed break; } case AV_PIX_FMT_RGB24:{ //fwrite(dstFrameInfo.data[0],1,dst_w*dst_h*3,dst_file); memcpy(pDst,DST_frameinfo.data[0],TP->DST_WIDTH*TP->DST_HEIGHT*3);//Packed // strcpy(rst,"轉(zhuǎn)換成功!"); return true; break; } default:{ //printf("Not Support Output Pixel Format.\n"); break; } } //strcpy(rst,"轉(zhuǎn)換失??!"); return false; }
然后unity里的調(diào)用 
然后調(diào)用此函數(shù)將之前回調(diào)函數(shù)里存起來的YV12數(shù)據(jù) 轉(zhuǎn)換為rgb數(shù)據(jù) 
然后,額,創(chuàng)建Texture2D?,還是貼一下吧,雖然代碼很丑。。 
最后貼一下運行效果吧(里面其他的功能都很簡單,就不多做介紹) 試一下GIF圖o(* ̄︶ ̄*)o 

共同學(xué)習(xí),歡迎留言評論以及給我更好的建議或者實現(xiàn)方案,謝謝大家!
|