1. 程式人生 > >【GStreamer學習】基於NVIDIA TX系列板卡的硬體解碼及視訊推流

【GStreamer學習】基於NVIDIA TX系列板卡的硬體解碼及視訊推流

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