以NVIDIA TX1爲例硬解碼就是利用硬件芯片來解碼的,TX1有單獨的解碼模塊,NVDEC.
軟解碼是用軟件程序來解碼,比較佔用CPU資源
查看cpu gpu 以及編解碼模塊的使用:
sudo ./tegrastats
1 Gstreamer概述
Gstreamer是一個libraries和plugins的集合,用於幫助實現各種類型的多媒體應用程序,比如播放器,轉碼工具,多媒體服務器等。
利用Gstreamer編寫多媒體應用程序,就是利用elements構建一個pipeline。element是一個對多媒體流進行處理的object,比如如下的處理:
*讀取文件。
*不同格式的編解碼。
*從硬件採集設備上採集數據。
*在硬件設備上播放多媒體。
*多個流的複用。
elements的輸入叫做sink pads,輸出叫做source pads。應用程序通過pad把element連接起來構成pipeline,如下圖所示,其中順着流的方向爲downstream,相反方向是upstream。
應用程序會收到來自pipeline的消息和通知,比如EOS等。
總體設計
Gstreamer的設計目標如下:
快速處理大規模數據。
對多線程處理的完全支持。
能處理各種格式的流媒體。
不同數據流的同步。
處理多種設備的能力。
基於Gstreamer的應用程序能夠具備的處理能力依賴於系統中安裝的不同種類功能的elements的數量。
Gstreamer核心不具備處理具體的media的功能,但是element處理media時需要具備的特性很多是由Gstreamer的核心提供的。
elements
element是pipeline的最小組成部分。element提供了多個pads,或者爲sink,或者爲source。一個element有四種可能的狀態,分別是NULL,READY,PAUSED,PLAYING。NULL和READY狀態下,element不對數據做任何處理,PLAYING狀態對數據進行處理,PAUSE狀態介於兩者之間,對數據進行preroll。應用程序通過函數調用控制pipeline在不同狀態之間進行轉換。
element的狀態變換不能跳過中間狀態,比如不能從READY狀態直接變換到PLAYING狀態,必須經過中間的PAUSE狀態。
element的狀態轉換成PAUSE會激活element的pad。首先是source pad被激活,然後是sink pad。pad被激活後會調用activate函數,有一些pad會啓動一個Task。
PAUSE狀態下,pipeline會進行數據的preroll,目的是爲後續的PLAYING狀態準備好數據,使得PLAYING啓動的速度更快。一些element需接收到足夠的數據才能完成向PAUSE狀態的轉變,sink pad只有在接收到第一個數據才能實現向PAUSE的狀態轉變。
通常情況下,element的狀態轉變需要協調一致。
可對element進行如下分類:
source,只提供數據源。
sink,比如播放設備。
transform
demuxer
muxer
Bin
bin是由多個element構成的特殊的element,用圖來說明:
Pipeline
pipeline是具備如下特性的特殊的bin:
選擇並管理一個全局的時鐘。
基於選定的時鐘管理running_time。running_time用於同步,指的是pipeline在 PLAYING狀態下花費的時間。
管理pipeline的延遲。
通過GstBus提供element與應用程序間的通訊方式。
管理elements的全局狀態,比如EOS,Error等。
Dataflow and buffers
Gstreamer支持兩種類型的數據流,分別是push模式和pull模式。在push模式下,upstream的element通過調用downstream的sink pads的函數實現數據的傳送。在pull模式下,downstream的element通過調用upstream的source pads的函數實現對數據的請求。
push模式是常用的模式,pull模式一般用於demuxer或者低延遲的音頻應用等。
在pads之間傳送的數據封裝在Buffer裏,Buffer中有一個指向實際數據的指針以及一些metadata。metadata的內容包括:
Timestamp
Offset
Duration
media type
其它
在push模式下,element通過調用gst_pad_push()函數把buffer傳送給對應的pad。在pull模式下,element通過調用gst_pad_pull_range()函數把pull過來。
element在push buffer之前需要確認對應的element具備處理buffer中的數據類型的能力。在傳說紅之前首先查詢對應的element能夠處理的格式的種類,並從中選擇合適的格式,通過gst_buffer_set_caps()函數對buffer進行設置,然後才傳送數據。
收到一個buffer後,element要首先對buffer進行檢查以確認是否能夠處理。
可以調用gst_buffer_new()函數創建一個新的buffer,也可以調用gst_pad_alloc_buffer()函數申請一個可用的buffer。採用第二種方法接收數據的buffer可以設定接收其它類型的數據,這是通過對buffer的caps進行設定來實現的。
選擇媒體類型並對buffer進行設定的處理過程叫做caps negotianation。
Caps
Caps,也就是媒體類型,採用key/value對的列表來描述。key是一個字符串類型,value的類型可能是int/float/string類型的single/list/range。
Data flow and events
除了數據流,還有events流。與數據流不同,events的傳送方向既有downstream的,也有upstream的。
events用於傳遞EOS,flushing,seeking等消息。
有的events必須和data flow一起進行serialized。serialized的events比如TAG,非serialized的events比如FLUSH。
Pipeline construction
gst_pipeline_create()函數用於創建一個pipeline,gst_bin_add()函數用於向pipeline中添加element,gst_bin_remove()函數用於從pipeline中移除element。gst_element_get_pad()函數用於檢索pipeline中的element。gst_pad_link()函數用於把pads連接在一起。
有的element會在數據流開始傳送的時候創建新的pads,通過調用函數g_signal_connect()函數,能在新的pads被創建的時候接收到消息。
由於處理的數據互相不兼容,有的elements是不能被連接到一起的。gst_pad_get_caps()函數查詢element能夠處理的數據類型。
Pipeline clock
Pipeline的一個重要功能是爲pipeline中的所有elements選擇一個全局時鐘。
時鐘的作用是提供一個每秒爲GST_SECOND的單調遞增的時鐘,單位是納秒。element利用這個時鐘時間來播放數據。
在pipeline被設爲PLAYING之前,pipeline查詢每一個element是否能提供clock,並按照如下次序來選擇clock:
應用程序選擇了一個clock。
如果source element提供了clock。
其它任何提供了clock的element。
選擇一個默認的系統clock。
也有特殊的情況,比如存在音頻sink提供了clock,那麼就選擇其提供的clock。
Pipeline states
完成了pads的鏈接和signals的鏈接,就可以設定pipeline爲PAUSED狀態啓動數據流的處理。當bin(這裏指的是pipeline)進行狀態轉換的時候要轉換所有的children的狀態,轉換的次序是從sink element開始到source element結束,這樣做的目的是爲了確保upstream element提供數據的時候,downstream element已經準備好。
Pipeline status
Pipeline會通過bus嚮應用程序通報發生的events。bus是由pipeline提供的一個object,可以通過gst_pipeline_get_bus()函數取得。
bus分佈到加入pipeline的每一個element。element利用bus來發布messages。有各種不同類型的messages,比如ERRORS,WARNINGS,EOS,STATE_CHANGED等。
pipeline以特殊的方式處理接收到的EOS message,只有當所有的sink element發送了EOS message的時候,pipeline纔會把EOS發送給應用程序。
也可以通過gst_element_query()函數獲取pipeline status,比如獲取當前的位置或者播放的時間。
Pipeline EOS
當source filter遇上了流結束,會沿着downstream的方向向下一個element發送一個EOS的event,這個event依次傳送給每一個element,接收到EOS event的element不再接收數據。
啓動了線程的element發送了EOS event後就不再發送數據。
EOS event最終會到達sink element。sink element會發送一個EOS消息,通告流結束。pipeline在接收到EOS消息以後,把消息發送給應用程序。只有在PLAYING狀態下會把EOS的消息傳送給應用程序。
發送了EOS以後,pipeline保持PLAYING狀態,等待應用程序把pipeline的狀態置爲PAUSE或者READY。應用程序也可以進行seek操作。
2 Gstreamer解碼
2.1 調用Gstreamer解碼多路rtsp(部分代碼)
/*
*Author:mxj
*/
#ifndef __GSTREAMER_CAMERA_H__
#define __GSTREAMER_CAMERA_H__
#include <gst/gst.h>
#include <string>
struct _GstAppSink;//聲明結構體和類
class QWaitCondition;
class QMutex;
/*** gstreamer CSI camera using nvcamerasrc (or optionally v4l2src)
* @ingroup util
*/
class gstCamera
{
public:
// 創建camera類
static gstCamera* Create( int v4l2_device=-1 ); // use onboard camera by default (>=0 for V4L2)
static gstCamera* Create( uint32_t width, uint32_t height, int v4l2_device=-1 );
// 析構函數
~gstCamera();
// 開始和停止流
bool Open();
void Close();
// 採集YUV(NV12格式)
bool Capture( void** cpu, void** cuda, unsigned long timeout=ULONG_MAX );
// 抓取YUV-NV12 CUDA image, 轉換成 float4 RGBA (像素範圍在 0-255)
// 轉換如果在CPU上進行,設置zeroCopy=true,默認只在CUDA上.
bool ConvertRGBA( void* input, void** output, bool zeroCopy=false );
// 圖像大小信息 inline(內聯函數,適合簡單的函數)
inline uint32_t GetWidth() const { return mWidth; }
inline uint32_t GetHeight() const { return mHeight; }
inline uint32_t GetPixelDepth() const { return mDepth; }
inline uint32_t GetSize() const { return mSize; }
// 默認圖像大小,可以在create時改變
static const uint32_t DefaultWidth = 1280;
static const uint32_t DefaultHeight = 720;
private:
static void onEOS(_GstAppSink* sink, void* user_data);
static GstFlowReturn onPreroll(_GstAppSink* sink, void* user_data);//GstFlowReturn 傳遞流
static GstFlowReturn onBuffer(_GstAppSink* sink, void* user_data);
gstCamera();
bool init();
bool buildLaunchStr();
void checkMsgBus();
void checkBuffer();
//GstBus
_GstBus* mBus;//GstBus 異步同步消息
_GstAppSink* mAppSink;
_GstElement* mPipeline;
std::string mLaunchStr="rtspsrc location=rtsp://admin:[email protected]:554/h264/ch1/main/av_stream latency=0 ! queue ! rtph264depay ! h264parse ! queue ! omxh264dec ! appsink name=mysink";
uint32_t mWidth;
uint32_t mHeight;
uint32_t mDepth;
uint32_t mSize;
static const uint32_t NUM_RINGBUFFERS = 16;//環形隊列來解決數據阻塞問題
void* mRingbufferCPU[NUM_RINGBUFFERS];
void* mRingbufferGPU[NUM_RINGBUFFERS];
QWaitCondition* mWaitEvent;
//mutex.lock() //鎖住互斥量(mutex)。如果互斥量是解鎖的,那麼當前線程就立即佔用並鎖定它。否則,當前線程就會被阻塞,知道掌握這個互斥量的線程對它解鎖爲止。
//mutex.unlock()//解鎖
//mutex.tryLock()//嘗試解鎖,如果該互斥量已經鎖住,它就會立即返回
QMutex* mWaitMutex;
QMutex* mRingMutex;
uint32_t mLatestRGBA;
uint32_t mLatestRingbuffer;
bool mLatestRetrieved;
void* mRGBA[NUM_RINGBUFFERS];
int mV4L2Device; // -1 for onboard, >=0 for V4L2 device
inline bool onboardCamera() const { return (mV4L2Device < 0); }
};
#endif
bool gstCamera::Capture( void** cpu, void** cuda, unsigned long timeout )
{
/*wait() 函數必須傳入一個已上鎖的 mutex 對象,在 wait() 執行過程中,
mutex一直保持上鎖狀態,直到調用操作系統的wait_block 在阻塞的一瞬間把 mutex 解鎖
(嚴格說來應該是原子操作,即系統能保證在真正執行阻塞等待指令時才解鎖)。
另一線程喚醒後,wait() 函數將在第一時間重新給 mutex 上鎖(這種操作也是原子的)
,直到顯示調用 mutex.unlock() 解鎖。*/
mWaitMutex->lock();
const bool wait_result = mWaitEvent->wait(mWaitMutex, timeout);
mWaitMutex->unlock();
if( !wait_result )
return false;
mRingMutex->lock();
const uint32_t latest = mLatestRingbuffer;
const bool retrieved = mLatestRetrieved;
mLatestRetrieved = true;
mRingMutex->unlock();
// skip if it was already retrieved
if( retrieved )
return false;
if( cpu != NULL )
*cpu = mRingbufferCPU[latest];
if( cuda != NULL )
*cuda = mRingbufferGPU[latest];
return true;
}
#define release_return { gst_sample_unref(gstSample); return; }
// checkBuffer
void gstCamera::checkBuffer()
{
bool write_flags=true;//默認寫數據
if( !mAppSink )
return;
// block waiting for the buffer 函數被喚醒until A sample or EOS 可用 或者appsink 被設置成 ready/null state
GstSample* gstSample = gst_app_sink_pull_sample(mAppSink);
if( !gstSample )
{
printf(LOG_GSTREAMER "gstreamer camera -- gst_app_sink_pull_sample() returned NULL...\n");
return;
}
//get buffer from gstSample
GstBuffer* gstBuffer = gst_sample_get_buffer(gstSample);
if( !gstBuffer )
{
printf(LOG_GSTREAMER "gstreamer camera -- gst_sample_get_buffer() returned NULL...\n");
return;
}
// retrieve
GstMapInfo map;
if( !gst_buffer_map(gstBuffer, &map, GST_MAP_READ) )
{
printf(LOG_GSTREAMER "gstreamer camera -- gst_buffer_map() failed...\n");
return;
}
//gst_util_dump_mem(map.data, map.size);
void* gstData = map.data; //GST_BUFFER_DATA(gstBuffer);
const uint32_t gstSize = map.size; //GST_BUFFER_SIZE(gstBuffer);
if( !gstData )
{
printf(LOG_GSTREAMER "gstreamer camera -- gst_buffer had NULL data pointer...\n");
release_return;
}
// 取出caps
GstCaps* gstCaps = gst_sample_get_caps(gstSample);
if( !gstCaps )
{
printf(LOG_GSTREAMER "gstreamer camera -- gst_buffer had NULL caps...\n");
release_return;
}
GstStructure* gstCapsStruct = gst_caps_get_structure(gstCaps, 0);
if( !gstCapsStruct )
{
printf(LOG_GSTREAMER "gstreamer camera -- gst_caps had NULL structure...\n");
release_return;
}
// get width & height of the buffer
int width = 0;
int height = 0;
if( !gst_structure_get_int(gstCapsStruct, "width", &width) ||
!gst_structure_get_int(gstCapsStruct, "height", &height) )
{
printf(LOG_GSTREAMER "gstreamer camera -- gst_caps missing width/height...\n");
release_return;
}
if( width < 1 || height < 1 )
release_return;
mWidth = width;
mHeight = height;
mDepth = (gstSize * 8) / (width * height);
mSize = gstSize;
//printf(LOG_GSTREAMER "gstreamer camera recieved %ix%i frame (%u bytes, %u bpp)\n", width, height, gstSize, mDepth);
// make sure ringbuffer is allocated
if( !mRingbufferCPU[0] )
{
for( uint32_t n=0; n < NUM_RINGBUFFERS; n++ )
{
if( !cudaAllocMapped(&mRingbufferCPU[n], &mRingbufferGPU[n], gstSize) )
printf(LOG_CUDA "gstreamer camera -- failed to allocate ringbuffer %u (size=%u)\n", n, gstSize);
}
printf(LOG_CUDA "gstreamer camera -- allocated %u ringbuffers, %u bytes each\n", NUM_RINGBUFFERS, gstSize);
}
// copy to next ringbuffer
const uint32_t nextRingbuffer = (mLatestRingbuffer + 1) % NUM_RINGBUFFERS;
//printf(LOG_GSTREAMER "gstreamer camera -- using ringbuffer #%u for next frame\n", nextRingbuffer);
memcpy(mRingbufferCPU[nextRingbuffer], gstData, gstSize);
// FILE *fp=fopen("out.yuv","w+");
// fwrite(gstData,gstSize,1,fp);
// fclose(fp);
//test h264 write
//void *writedata=map.data;
// while(write_flags==true)
// {
// FILE *fp=fopen("out.264","a+");
// fwrite(writedata,gstSize,1,fp);
// write_flags=false;
// fclose(fp);
// }
gst_buffer_unmap(gstBuffer, &map);
//gst_buffer_unref(gstBuffer);
gst_sample_unref(gstSample);
// update and signal sleeping threads
mRingMutex->lock();
mLatestRingbuffer = nextRingbuffer;
mLatestRetrieved = false;
mRingMutex->unlock();
mWaitEvent->wakeAll();
}
可以在帶顯示的時候解碼4路1080rtsp流.
不加顯示可以做到6路1080p解碼
2.2 opencv中使用Gstreamer解碼海康rtsp攝像頭
A.安裝gstreamer依賴
B.重新編譯安裝opencv
cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -D CUDA_GENERATION=Kepler …
C.確認opencv cmake後的提示中Gstreamer的五個選項都爲on
D.編譯opencv之後的使用方法:
VideoCapture(“rtspsrc location=\”rtsp://admin:[email protected]/h264/h264/main/av_stream\” latency=10 ! rtph264depay ! h264parse ! omxh264dec ! videoconvert ! appsink sync=false”)
2.3 調用tegra_multimedia解碼
這是nvidia自帶的編解碼框架 目前用官方的解碼1080p的h264可以做到解碼速度達到150fps
接入rtsp的相機流
解決方法:ffmpeg/live555解析之後做個數據拷貝
我在看nvidia官方的demo,瞭解清楚數據結構之後就可以對接解析好的h264視頻流(後續更新)
2.4 視頻推流
需求:將處理後的opencv數據進行推流到網頁
解決方案:
A> 用tegra_multimedia編碼數據爲h264, live555推流爲rtsp
B> 直接用gstreamer推流(gstreamer自帶rtsp的部分)
性能:兩路2448*2048 basyer相機編碼,編碼後25fps,延遲200ms左右
編碼:調用nvidia底層的api, 參考tegra_multimedia_api下的cuda_encode例子。只需要修改read_from_file()函數的數據爲相機輸入數據即可
目前只支持輸入I420格式,但是basyer相機沒有I420的格式,用cuda做轉換,代碼如下:
// Grab.cpp /*
Note: Before getting started, Basler recommends reading the Programmer's Guide topic
in the pylon C++ API documentation that gets installed with pylon.
If you are upgrading to a higher major version of pylon, Basler also
strongly recommends reading the Migration topic in the pylon C++ API documentation.
This sample illustrates how to grab and process images using the CInstantCamera class.
The images are grabbed and processed asynchronously, i.e.,
while the application is processing a buffer, the acquisition of the next buffer is done
in parallel.
The CInstantCamera class uses a pool of buffers to retrieve image data
from the camera device. Once a buffer is filled and ready,
the buffer can be retrieved from the camera object for processing. The buffer
and additional image data are collected in a grab result. The grab result is
held by a smart pointer after retrieval. The buffer is automatically reused
when explicitly released or when the smart pointer object is destroyed.
*/
// Include files to use the PYLON API.
#include <pylon/PylonIncludes.h>
#ifdef PYLON_WIN_BUILD
# include <pylon/PylonGUI.h>
#endif
#include"opencv2/opencv.hpp"
#include"opencv2/highgui.hpp"
#include<sys/time.h>
#include"opencv2/core/core.hpp"
#include <pylon/gige/BaslerGigECamera.h>
#include <cuda_runtime.h>
#include "cudaYUV.h"
#include "cudaRGB.h"
//定義是否保存圖片 0-否 1-是
#define saveImages 0
//定義是否記錄視頻 0-否 1-是
#define recordVideo 0
// Namespace for using pylon objects.
using namespace Pylon;
// Namespace for using cout.
using namespace std;
#include "opencv2/gpu/gpu.hpp"
using namespace cv;
// Number of images to be grabbed.
static const uint32_t c_countOfImagesToGrab = 1000000;
unsigned char *I420Buffer=NULL;
unsigned char *BGRABuffer=NULL;
unsigned char *RGBBuffer=NULL;
/*void BGR_YUV420(unsigned char*bsrc,unsigned char *bdst,int width,int height)
{
}*/
int main(int argc, char* argv[])
{
// The exit code of the sample application.
int exitCode = 0;
// Before using any pylon methods, the pylon runtime must be initialized.
Pylon::PylonAutoInitTerm autoInitTerm;
try {
//創建相機對象(以最先識別的相機)
CInstantCamera camera(CTlFactory::GetInstance().CreateFirstDevice());
// 打印相機的名稱
std::cout << "Using device " << camera.GetDeviceInfo().GetModelName() << endl;
//獲取相機節點映射以獲得相機參數
GenApi::INodeMap& nodemap = camera.GetNodeMap();
//打開相機
camera.Open();
//獲取相機成像寬度和高度
GenApi::CIntegerPtr width = nodemap.GetNode("Width");
GenApi::CIntegerPtr height = nodemap.GetNode("Height");
//設置相機最大緩衝區,默認爲10
camera.MaxNumBuffer = 5;
// 新建pylon ImageFormatConverter對象.
CImageFormatConverter formatConverter;
//確定輸出像素格式
formatConverter.OutputPixelFormat = PixelType_RGB8packed;
// 創建一個Pylonlmage後續將用來創建OpenCV images
CPylonImage pylonImage;
//聲明一個整形變量用來計數抓取的圖像,以及創建文件名索引
int grabbedlmages = 0;
// 新建一個OpenCV video creator對象.
VideoWriter cvVideoCreator;
//新建一個OpenCV image對象.
Mat openCvImage;
Mat dst_image;
// 視頻文件名
std::string videoFileName = "openCvVideo.avi";
// 定義視頻幀大小
cv::Size frameSize = Size((int)width->GetValue(), (int)height->GetValue());
//設置視頻編碼類型和幀率,有三種選擇 // 幀率必須小於等於相機成像幀率
cvVideoCreator.open(videoFileName, CV_FOURCC('D', 'I', 'V','X'), 10, frameSize, true);
//cvVideoCreator.open(videoFileName, CV_F0URCC('M','P',,4','2’), 20, frameSize, true); //cvVideoCreator.open(videoFileName, CV_FOURCC('M', '3', 'P', 'G'), 20, frameSize, true);
// 開始抓取
c_countOfImagesToGrab images.
//相機默認設置連續抓取模式
camera.StartGrabbing(c_countOfImagesToGrab, GrabStrategy_LatestImageOnly);
//抓取結果數據指針
CGrabResultPtr ptrGrabResult;
// 當c_countOfImagesToGrab images獲取恢復成功時,
Camera.StopGrabbing()
//被RetrieveResult()方法自動調用停止抓取
FILE *fp=fopen("1.yuv","w+");
struct timeval start,end;
while (camera.IsGrabbing())
{
// 等待接收和恢復圖像,超時時間設置爲5000 ms.
camera.RetrieveResult(5000, ptrGrabResult, TimeoutHandling_ThrowException);
//如果圖像抓取成功
if (ptrGrabResult->GrabSucceeded())
{
// 獲取圖像數據
int m_width=ptrGrabResult->GetWidth();
int m_height=ptrGrabResult->GetHeight();
cout <<"SizeX: "<<m_width<<endl; cout <<"SizeY: "<<m_height<<endl;
//將抓取的緩衝數據轉化成pylon image.
formatConverter.Convert(pylonImage, ptrGrabResult);
// 將 pylon image轉成OpenCV image.
//openCvImage = cv::Mat(ptrGrabResult->GetHeight(), ptrGrabResult->GetWidth(), CV_8UC3, (uint8_t *) pylonImage.GetBuffer());
//cvtColor(openCvImage,dst_image,CV_RGB2RGBA);
#if 0
unsigned long long timeusd=0;
gettimeofday(&start,NULL);
cvtColor(openCvImage,dst_image,CV_BGR2YUV_I420);
gettimeofday(&end,NULL);
//timeusd=end.tv_sec-start.tv_sec+(end.tv_usec-start.tv_usec)/1000000;
cout<<"convert cost "<<end.tv_sec-start.tv_sec+(end.tv_usec-start.tv_usec)/1000000<< " sec"<<endl<<endl;
#endif #if 1 if(!I420Buffer)
{
if(CUDA_FAILED(cudaMalloc((void**)&I420Buffer,m_height*m_width*3/2)))
{
cout<<"cudaMalloc For I420Buffer Error"<<endl;
}
}
#endif #if 1 if(!RGBBuffer)
{
if(CUDA_FAILED(cudaMalloc((void**)&RGBBuffer,m_height*m_width*3)))
{
cout<<"cudaMalloc For RGBBuffer Error"<<endl;
}
}
#endif
#if 1
if(!BGRABuffer)
{
if(CUDA_FAILED(cudaMalloc((void**)&BGRABuffer,m_height*m_width*4)))
{
cout<<"cudaMalloc For BGRABuffer Error"<<endl;
}
}
#endif
cudaMemcpy(RGBBuffer,pylonImage.GetBuffer(),m_height*m_width*3,cudaMemcpyHostToDevice);
#if 1
if(CUDA_FAILED(cudaRGBToRGBAf((uchar3 *)RGBBuffer,(float4 *)BGRABuffer, m_width,m_height)))
{
cout<<"Failed to Convert RGB2RGBA"<<endl;
}
#endif
#if 1
//cudaMemcpy(BGRABuffer,dst_image.data,m_height*m_width*4,cudaMemcpyHostToDevice); if(CUDA_FAILED(cudaRGBAToI420((uchar4 *)BGRABuffer,I420Buffer,m_width,m_height)))
{ cout<<"Failed to Convert RGBAToI420"<<endl; }
#endif
unsigned char g_buffer[m_height*m_width*3/2];
#if 1
cudaMemcpy(g_buffer,I420Buffer,m_width*m_height*3/2,cudaMemcpyDeviceToHost); fwrite(g_buffer,2448*2048*3/2,1,fp);
#endif
//如果需要保存圖片
if (saveImages)
{ std::ostringstream s;
// 按索引定義文件名存儲圖片
s << "image_" << grabbedlmages << ".jpg";
std::string imageName(s.str());
//保存OpenCV image. cv::imwrite(imageName, openCvImage);
grabbedlmages++;
}
//如果需要記錄視頻
if (recordVideo)
{ cvVideoCreator.write(openCvImage); }
//新建OpenCV display window.
//cv::namedWindow("OpenCV Display Window", CV_WINDOW_NORMAL);
// other options: CV_AUTOSIZE, CV_FREERATIO
//顯示及時影像.
//cv::imshow("OpenCV Display Window", dst_image);
// Define a timeout for customer's input in
// '0' means indefinite, i.e. the next image will be displayed after closing the window.
// '1' means live stream
waitKey(1);
}
}
fclose(fp);
}
catch (GenICam::GenericException &e)
{
// Error handling. cerr << "An exception occurred." << endl << e.GetDescription() << endl;
}
return exitCode;
}