2017년 8월 24일 목요일

C# 쓰레드 올바른 사용법

C# Thread 관련 API 중 왠만하면 절대 사용하면 안되는 함수

Thread.Abort()
Thread.Interrupt()

위 두 함수는 극단적인 상황이 아니면 절대 사용해서는 안된다. 대신 모든 쓰레드의 종료는
Thread.Join() 함수로 정상종료를 확인해야한다.

쓰레드는 자체 메모리 공간을 가지고있고 프로세스의 전역변수를 사용할 수 있다.
Abort/Interrupt 함수는 해당 쓰레드가 어떤 동작을 하는중인지 상관없이 CPU 사용권을 빼앗아 강제종료 시키기때문에 함수 호출시 어떤일이 발생할지 알 수가 없다.
예를 들어 쓰레드가 전역변수 뮤텍스를 사용중인데 강제종료 되었다면 해당 뮤텍스를 사용하는 다른 쓰레드는 데드락에 빠져버린다.
굳이 이런 상황이 아니더라도 쓰레드는 반드시 정상종료 시켜야 프로그램의 완성도를 높이고 오동작을 방지할 수 있다.

쓰레드 시작과 종료는 보통 아래코드와 같이하면 문제가 없다.

     private Thread threadDoWork;  
     private bool bThreadDoWorkRun = false;     
   
     private void StartDoWork()  
     {  
       if (!bThreadDoWorkRun)  
       {  
         bThreadDoWorkRun= true;  
         threadDoWork = new Thread(new ThreadStart(ThreadDoWork));  
         threadDoWork.Start();  
       }  
     }  
   
     private void StopDoWork()  
     {  
       if (bThreadDoWorkRun)  
       {  
         bThreadDoWorkRun = false;  
         threadDoWork.Join();  
       }  
     }  
   
     private void ThreadDoWork()  
     {  
       while (bThreadDoWorkRun)  
       {          
         ...  
       }  
     }  
   

추가적으로 Suspend/Resume 같은 함수도 사용을 권장하지않는다.
두 함수 역시 해당 쓰레드의 동작여부에 상관없이 중지/재시작을 하기때문에 어떤 상황이 발생할지 알 수가 없다.

2017년 1월 23일 월요일

C# 유용한 소켓 Select 함수 핸들러 소스코드 - useful socket select method handler source code

소켓 수신 이벤트 처리에 유용한 코드이다.
소켓 이벤트 핸들러를 등록하면 수신 이벤트 발생시 콜백으로 호출해준다.
TCP/UDP-서버/클라이언트 구분없이 모두 사용할 수 있다.

 using System;  
 using System.Collections.Generic;  
 using System.Text;  
 using System.Diagnostics;  
 using System.Net;  
 using System.Net.Sockets;  
 using System.Collections;  
 using System.Threading;  
   
 namespace MySocketLib  
 {  
   public delegate void SocketReadHandlerCallback(Object data);  
   
   public class SocketHandler  
   {  
     public Socket sock;  
     public SocketReadHandlerCallback handler;  
     public object data;  
   }  
   
   public class SocketTaskScheduler  
   {  
     protected Dictionary<Socket, SocketHandler> sockHandlerTable = new Dictionary<Socket, SocketHandler>();  
     protected bool isRunning = false;  
     protected Thread thread;  
     protected int TIMEOUT = 1000000;  
   
     public SocketTaskScheduler()  
     {  
     }  
   
     public void RegisterSocketHandler(Socket sock, SocketReadHandlerCallback handler, object data)  
     {  
       lock (sockHandlerTable)  
       {  
         if (sock != null)  
         {  
           SocketHandler sockHandler = new SocketHandler();  
           sockHandler.sock = sock;  
           sockHandler.handler = handler;  
           sockHandler.data = data;  
           sockHandlerTable.Add(sockHandler.sock, sockHandler);  
         }  
       }  
     }  
   
     public void UnregisterSocketHandler(Socket sock)  
     {  
       lock (sockHandlerTable)  
       {  
         if (sock != null && sockHandlerTable.ContainsKey(sock))  
         {  
           sockHandlerTable.Remove(sock);  
         }  
       }  
     }  
   
     protected void SingleStep()  
     {  
       lock (sockHandlerTable)  
       {  
         try  
         {  
           ArrayList selectList = new ArrayList();  
           foreach (var handler in sockHandlerTable)  
           {  
             selectList.Add(handler.Key);  
           }  
   
           if (selectList.Count == 0)  
           {  
             Thread.Sleep(10);  
             return;  
           }  
   
           Socket.Select(selectList, null, null, TIMEOUT);  
   
           foreach (Socket sock in selectList)  
           {  
             var handler = sockHandlerTable[sock];  
             if (handler != null && handler.handler != null) handler.handler(handler.data);  
           }            
         }  
         catch (Exception ex)  
         {  
           Trace.WriteLine(ex.ToString());  
         }  
       }  
     }  
   
     protected void DoEventLoop()  
     {  
       while (isRunning == true)  
       {  
         SingleStep();  
       }  
     }  
   
     public void StartEventLoop()  
     {  
       if (isRunning == true) return;  
   
       thread = new Thread(new ThreadStart(DoEventLoop));  
       thread.IsBackground = true;  
       isRunning = true;  
       thread.Start();  
     }  
   
     public void StopEventLoop()  
     {  
       isRunning = false;  
       if (thread != null)  
       {  
         if (Thread.CurrentThread != thread)  
         {  
           thread.Join();  
           thread = null;  
         }  
       }  
       sockHandlerTable.Clear();  
     }  
   }  
 }  
   


< 사용법 >

Socket clientSock;
SocketTaskScheduler task = new SocketTaskScheduler();
...
clientSock.Blocking = false;
clientSock.Connect(serverIP, serverPort);

task.RegisterSocketHandler(clientSock, IncomingPacketHandler, this);
task.StartEventLoop();
...

private void IncomingPacketHandler(object data)
{
            int ret = clientSock.Receive(recvBuffer, 0, recvBuffer.Length, SocketFlags.None);
            if (ret <= 0)
            {
                task.UnregisterSocketHandler(clientSock);
                CloseSocket();

                task.StopEventLoop();
            }
            else
            {
                 // process recvBuffer
            }
}




2017년 1월 18일 수요일

CUDA 코덱 비디오 인코더 사용 소스코드 - CUDA Codec Video Encoder source code

NvEncoder 샘플소스를 수정한 소스코드, QT 기반으로 작성되었으며 yuv420 포멧으로 입력받은 영상 데이터를 h264로 인코딩한다.
인코딩된 프레임은 m_pEncodeBuffer 버퍼에 저장

< encodethread.h >
 #ifndef ENCODETHREAD_H  
 #define ENCODETHREAD_H  
   
 #include <QThread>  
 #include <opencv2/imgproc/imgproc_c.h>  
 #include "imagequeue.h"  
 #include "./common/inc/NvHWEncoder.h"  
   
   
 #define MAX_ENCODE_QUEUE 32  
 #define FRAME_QUEUE 240  
   
 #define SET_VER(configStruct, type) {configStruct.version = type##_VER;}  
   
 template<class T>  
 class CNvQueue {  
   T** m_pBuffer;  
   unsigned int m_uSize;  
   unsigned int m_uPendingCount;  
   unsigned int m_uAvailableIdx;  
   unsigned int m_uPendingndex;  
 public:  
   CNvQueue(): m_pBuffer(NULL), m_uSize(0), m_uPendingCount(0), m_uAvailableIdx(0),  
         m_uPendingndex(0)  
   {  
   }  
   
   ~CNvQueue()  
   {  
     delete[] m_pBuffer;  
   }  
   
   bool Initialize(T *pItems, unsigned int uSize)  
   {  
     m_uSize = uSize;  
     m_uPendingCount = 0;  
     m_uAvailableIdx = 0;  
     m_uPendingndex = 0;  
     m_pBuffer = new T *[m_uSize];  
     for (unsigned int i = 0; i < m_uSize; i++)  
     {  
       m_pBuffer[i] = &pItems[i];  
     }  
     return true;  
   }  
   
   
   T * GetAvailable()  
   {  
     T *pItem = NULL;  
     if (m_uPendingCount == m_uSize)  
     {  
       return NULL;  
     }  
     pItem = m_pBuffer[m_uAvailableIdx];  
     m_uAvailableIdx = (m_uAvailableIdx+1)%m_uSize;  
     m_uPendingCount += 1;  
     return pItem;  
   }  
   
   T* GetPending()  
   {  
     if (m_uPendingCount == 0)  
     {  
       return NULL;  
     }  
   
     T *pItem = m_pBuffer[m_uPendingndex];  
     m_uPendingndex = (m_uPendingndex+1)%m_uSize;  
     m_uPendingCount -= 1;  
     return pItem;  
   }  
 };  
   
 typedef struct _EncodeFrameConfig  
 {  
   uint8_t *yuv[3];  
   uint32_t stride[3];  
   uint32_t width;  
   uint32_t height;  
 }EncodeFrameConfig;  
   
 typedef enum  
 {  
   NV_ENC_DX9 = 0,  
   NV_ENC_DX11 = 1,  
   NV_ENC_CUDA = 2,  
   NV_ENC_DX10 = 3,  
 } NvEncodeDeviceType;  
   
 class EncodeThread : public QThread  
 {  
   Q_OBJECT  
 public:  
   explicit EncodeThread(QThread *parent = 0, ImageQueue<EncodeFrame> *queue = NULL);  
   virtual ~EncodeThread();  
   
   void start();  
   void stop();  
   
 protected:  
   virtual void run();  
   
 signals:  
   void started();  
   void finished();  
   
 private:  
   bool openEncoder(NV_ENC_BUFFER_FORMAT format, int width, int height);  
   void closeEncoder();  
   
   NVENCSTATUS deinitialize(uint32_t devicetype);  
   NVENCSTATUS encodeFrame(EncodeFrameConfig *pEncodeFrame, bool bFlush, uint32_t width, uint32_t height);  
   NVENCSTATUS initCuda(uint32_t deviceID = 0);  
   NVENCSTATUS allocateIOBuffers(uint32_t uInputWidth, uint32_t uInputHeight, NV_ENC_BUFFER_FORMAT inputFormat);  
   NVENCSTATUS releaseIOBuffers();  
   NVENCSTATUS flushEncoder();  
   
 private:  
   bool  m_bRun;  
   
 private:  
   ImageQueue<EncodeFrame>* m_pQueue;  
   
   EncodeConfig    m_stEncodeConfig;  
   
   CNvHWEncoder*    m_pNvHWEncoder;  
   uint32_t      m_uEncodeBufferCount;  
   uint32_t      m_uPicStruct;  
   void*        m_pDevice;  
   
   CUcontext              m_cuContext;  
   EncodeConfig            m_stEncoderInput;  
   EncodeBuffer            m_stEncodeBuffer[MAX_ENCODE_QUEUE];  
   CNvQueue<EncodeBuffer>       m_EncodeBufferQueue;  
   EncodeOutputBuffer         m_stEOSOutputBfr;  
   
   uint8_t*  m_pEncodeBuffer;  
   int     m_nEncodeBufferSize;  
   
   FILE*    m_pFile;  
 };  
   
 #endif // ENCODETHREAD_H  
   


< encodethread.cpp >
 #include "encodethread.h"  
 #include <QDebug>  
   
 #include "DXMediaPlayerCtrl.h"  
 #include "DXUtil.h"  
 #include "MediaBuffer.h"  
 #include "GlobalTimer.h"  
   
 #define BITSTREAM_BUFFER_SIZE 2 * 1024 * 1024  
   
 EncodeThread::EncodeThread(QThread *parent, ImageQueue<EncodeFrame> *queue) : QThread(parent)  
 {  
   m_bRun = false;  
   m_pQueue = queue;  
   
   m_pNvHWEncoder = new CNvHWEncoder;  
   m_cuContext = NULL;  
   
   m_uEncodeBufferCount = 0;  
   memset(&m_stEncoderInput, 0, sizeof(m_stEncoderInput));  
   memset(&m_stEOSOutputBfr, 0, sizeof(m_stEOSOutputBfr));  
   memset(&m_stEncodeBuffer, 0, sizeof(m_stEncodeBuffer));  
   
   m_pEncodeBuffer = new uint8_t[BITSTREAM_BUFFER_SIZE];  
   m_nEncodeBufferSize = 0;  
   
   m_pFile = NULL;  
 }  
   
 EncodeThread::~EncodeThread()  
 {  
   stop();  
   if (m_pNvHWEncoder) {  
     delete m_pNvHWEncoder;  
     m_pNvHWEncoder = NULL;  
   }  
   
   if (m_pEncodeBuffer) {  
     delete[] m_pEncodeBuffer;  
     m_pEncodeBuffer = NULL;  
   }  
   
   RTSPServer::destroy();  
   GlobalTimer::destroy();  
 }  
   
 void EncodeThread::start()  
 {  
   m_bRun = true;  
   QThread::start();  
 }  
   
 void EncodeThread::stop()  
 {  
   m_bRun = false;  
   wait();  
 }  
   
 void EncodeThread::run()  
 {  
   emit started();  
   
   bool bInit = false;  
   int count = 0;  
   
   EncodeFrameConfig stEncodeFrame;  
   
   CDXMediaPlayerCtrl *player = new CDXMediaPlayerCtrl(NULL, NULL);  
   player->openCaptureServerSession("stream1", AV_CODEC_ID_H264, AV_CODEC_ID_NONE);  
   player->playCaptureServerSession();  
   
   uint16_t port = 8554;  
   if (player->startServer(port) < 0)  
     qDebug() << "failed to start server, port : " << port;  
   
   uint64_t timestamp = 0;  
   MediaBuffer *pBuffer = NULL;  
   
   while (m_bRun) {  
     EncodeFrame *frame = m_pQueue->pop();  
     if (frame == NULL) {  
       QThread::usleep(1);  
       continue;  
     }  
   
     count = m_pQueue->count();  
     if (count > 0) qDebug() << "decode queue : " << count;  
   
     if (!bInit) {  
       NV_ENC_BUFFER_FORMAT format = NV_ENC_BUFFER_FORMAT_UNDEFINED;  
       if (frame->format == 0) format = NV_ENC_BUFFER_FORMAT_NV12;  
       else format = NV_ENC_BUFFER_FORMAT_YUV444;  
   
       if (openEncoder(format, frame->width, frame->height)) {  
         //m_pFile = fopen("output.264", "wb");  
         bInit = true;  
       } else {  
         closeEncoder();  
       }  
     }  
   
     if (bInit) {  
       memset(&stEncodeFrame, 0, sizeof(stEncodeFrame));  
   
       stEncodeFrame.yuv[0] = frame->yuv[0];  
       stEncodeFrame.yuv[1] = frame->yuv[1];  
       stEncodeFrame.yuv[2] = frame->yuv[2];  
   
       stEncodeFrame.stride[0] = frame->stride[0];  
       stEncodeFrame.stride[1] = frame->stride[1];  
       stEncodeFrame.stride[2] = frame->stride[2];  
   
       stEncodeFrame.width = frame->width;  
       stEncodeFrame.height = frame->height;  
   
       if (encodeFrame(&stEncodeFrame, false, frame->width, frame->height) == NV_ENC_SUCCESS) {  
         if (m_pFile) fwrite(m_pEncodeBuffer, 1, m_nEncodeBufferSize, m_pFile);  
   
         timestamp = GetTimeOfDay();  
         pBuffer = MediaBuffer::createBuffer(VideoMedia, m_pEncodeBuffer, m_nEncodeBufferSize, timestamp, timestamp);  
         if (player->pushCaptureInput(pBuffer) < 0) {  
           qDebug() << "cannot push capture input";  
           delete pBuffer;  
         }  
       }  
     }  
   
     delete frame;  
   }  
   
   if (bInit) encodeFrame(NULL, true, m_stEncodeConfig.width, m_stEncodeConfig.height);  
   
   player->close();  
   player->stopServer();  
   delete player;  
   
   closeEncoder();  
   
   if (m_pFile) {  
     fclose(m_pFile);  
     m_pFile = NULL;  
   }  
   
   emit finished();  
 }  
   
 bool EncodeThread::openEncoder(NV_ENC_BUFFER_FORMAT format, int width, int height)  
 {  
   memset(&m_stEncodeConfig, 0, sizeof(EncodeConfig));  
   
   m_stEncodeConfig.endFrameIdx = INT_MAX;  
   m_stEncodeConfig.bitrate = 5000000;  
   m_stEncodeConfig.rcMode = NV_ENC_PARAMS_RC_CONSTQP;  
   //m_stEncodeConfig.gopLength = NVENC_INFINITE_GOPLENGTH;  
   m_stEncodeConfig.deviceType = NV_ENC_CUDA;  
   m_stEncodeConfig.codec = NV_ENC_H264;  
   //m_stEncodeConfig.fps = 30;  
   m_stEncodeConfig.qp = 28;  
   m_stEncodeConfig.i_quant_factor = DEFAULT_I_QFACTOR;  
   m_stEncodeConfig.b_quant_factor = DEFAULT_B_QFACTOR;  
   m_stEncodeConfig.i_quant_offset = DEFAULT_I_QOFFSET;  
   m_stEncodeConfig.b_quant_offset = DEFAULT_B_QOFFSET;  
   m_stEncodeConfig.presetGUID = NV_ENC_PRESET_DEFAULT_GUID;  
   m_stEncodeConfig.pictureStruct = NV_ENC_PIC_STRUCT_FRAME;  
   m_stEncodeConfig.inputFormat = format;  
   
   m_stEncodeConfig.repeatSPSPPS = 1;  
   m_stEncodeConfig.width = width;  
   m_stEncodeConfig.height = height;  
   m_stEncodeConfig.gopLength = 15;  
   m_stEncodeConfig.fps = 15;  
   //m_stEncodeConfig.encoderPreset = "hq";    
   
   switch (m_stEncodeConfig.deviceType)  
   {  
 #if defined(NV_WINDOWS)  
   case NV_ENC_DX9:  
     InitD3D9(m_stEncodeConfig.deviceID);  
     break;  
   
   case NV_ENC_DX10:  
     InitD3D10(m_stEncodeConfig.deviceID);  
     break;  
   
   case NV_ENC_DX11:  
     InitD3D11(m_stEncodeConfig.deviceID);  
     break;  
 #endif  
   case NV_ENC_CUDA:  
     initCuda(m_stEncodeConfig.deviceID);  
     break;  
   }  
   
   NVENCSTATUS nvStatus = NV_ENC_SUCCESS;  
   
   if (m_stEncodeConfig.deviceType != NV_ENC_CUDA)  
     nvStatus = m_pNvHWEncoder->Initialize(m_pDevice, NV_ENC_DEVICE_TYPE_DIRECTX);  
   else  
     nvStatus = m_pNvHWEncoder->Initialize(m_pDevice, NV_ENC_DEVICE_TYPE_CUDA);  
   
   if (nvStatus != NV_ENC_SUCCESS)  
     return false;  
   
   m_stEncodeConfig.presetGUID = m_pNvHWEncoder->GetPresetGUID(m_stEncodeConfig.encoderPreset, m_stEncodeConfig.codec);  
 #if 0  
   m_stEncodeConfig.fOutput = fopen("output.264", "wb");  
   if (!m_stEncodeConfig.fOutput)  
     qDebug() << "failed to open output file";    
 #endif  
   nvStatus = m_pNvHWEncoder->CreateEncoder(&m_stEncodeConfig);  
   if (nvStatus != NV_ENC_SUCCESS)  
     return false;  
   
   m_stEncodeConfig.maxWidth = m_stEncodeConfig.maxWidth ? m_stEncodeConfig.maxWidth : m_stEncodeConfig.width;  
   m_stEncodeConfig.maxHeight = m_stEncodeConfig.maxHeight ? m_stEncodeConfig.maxHeight : m_stEncodeConfig.height;  
   
   m_stEncoderInput.enableAsyncMode = m_stEncodeConfig.enableAsyncMode;  
   
   if (m_stEncodeConfig.numB > 0)  
   {  
     m_uEncodeBufferCount = m_stEncodeConfig.numB + 4; // min buffers is numb + 1 + 3 pipelining  
   }  
   else  
   {  
     int numMBs = ((m_stEncodeConfig.maxHeight + 15) >> 4) * ((m_stEncodeConfig.maxWidth + 15) >> 4);  
     int NumIOBuffers;  
     if (numMBs >= 32768) //4kx2k  
       NumIOBuffers = MAX_ENCODE_QUEUE / 8;  
     else if (numMBs >= 16384) // 2kx2k  
       NumIOBuffers = MAX_ENCODE_QUEUE / 4;  
     else if (numMBs >= 8160) // 1920x1080  
       NumIOBuffers = MAX_ENCODE_QUEUE / 2;  
     else  
       NumIOBuffers = MAX_ENCODE_QUEUE;  
     m_uEncodeBufferCount = NumIOBuffers;  
   }  
   m_uPicStruct = m_stEncodeConfig.pictureStruct;  
   
   nvStatus = allocateIOBuffers(m_stEncodeConfig.width, m_stEncodeConfig.height, m_stEncodeConfig.inputFormat);  
   if (nvStatus != NV_ENC_SUCCESS)  
     return 1;  
   
   unsigned int preloadedFrameCount = FRAME_QUEUE;  
   if (m_stEncodeConfig.preloadedFrameCount >= 2)  
   {  
     preloadedFrameCount = m_stEncodeConfig.preloadedFrameCount;  
   }  
   
   qDebug() << "encoder " << width << "x" << height << " opened";  
   
 #if 0  
   GUID guids[10];  
   uint32_t count;  
   m_pNvHWEncoder->NvEncGetEncodeGUIDs(guids, 10, &count);  
   
   NV_ENC_BUFFER_FORMAT fmt[20];  
   memset(fmt, 0, sizeof(fmt));  
   m_pNvHWEncoder->NvEncGetInputFormats(guids[0], fmt, 10, &count);  
 #endif  
   
   return true;  
 }  
   
 void EncodeThread::closeEncoder()  
 {  
   if (m_stEncodeConfig.fOutput) {  
     fclose(m_stEncodeConfig.fOutput);  
     m_stEncodeConfig.fOutput = NULL;  
   }  
   deinitialize(m_stEncodeConfig.deviceType);  
 }  
   
 void convertYUVpitchtoNV12( unsigned char *yuv_luma, unsigned char *yuv_cb, unsigned char *yuv_cr,  
               unsigned char *nv12_luma, unsigned char *nv12_chroma,  
               int width, int height , int srcStride, int dstStride)  
 {  
   int y;  
   int x;  
   if (srcStride == 0)  
     srcStride = width;  
   if (dstStride == 0)  
     dstStride = width;  
   
   for ( y = 0 ; y < height ; y++)  
   {  
     memcpy( nv12_luma + (dstStride*y), yuv_luma + (srcStride*y) , width );  
   }  
   
   for ( y = 0 ; y < height/2 ; y++)  
   {  
     for ( x= 0 ; x < width; x=x+2)  
     {  
       nv12_chroma[(y*dstStride) + x] =  yuv_cb[((srcStride/2)*y) + (x >>1)];  
       nv12_chroma[(y*dstStride) +(x+1)] = yuv_cr[((srcStride/2)*y) + (x >>1)];  
     }  
   }  
 }  
   
 void convertYUV10pitchtoP010PL(unsigned short *yuv_luma, unsigned short *yuv_cb, unsigned short *yuv_cr,  
   unsigned short *nv12_luma, unsigned short *nv12_chroma, int width, int height, int srcStride, int dstStride)  
 {  
   int x, y;  
   
   for (y = 0; y < height; y++)  
   {  
     for (x = 0; x < width; x++)  
     {  
       nv12_luma[(y*dstStride / 2) + x] = yuv_luma[(srcStride*y) + x] << 6;  
     }  
   }  
   
   for (y = 0; y < height / 2; y++)  
   {  
     for (x = 0; x < width; x = x + 2)  
     {  
       nv12_chroma[(y*dstStride / 2) + x] = yuv_cb[((srcStride / 2)*y) + (x >> 1)] << 6;  
       nv12_chroma[(y*dstStride / 2) + (x + 1)] = yuv_cr[((srcStride / 2)*y) + (x >> 1)] << 6;  
     }  
   }  
 }  
   
 void convertYUVpitchtoYUV444(unsigned char *yuv_luma, unsigned char *yuv_cb, unsigned char *yuv_cr,  
   unsigned char *surf_luma, unsigned char *surf_cb, unsigned char *surf_cr, int width, int height, int srcStride, int dstStride)  
 {  
   int h;  
   
   for (h = 0; h < height; h++)  
   {  
     memcpy(surf_luma + dstStride * h, yuv_luma + srcStride * h, width);  
     memcpy(surf_cb + dstStride * h, yuv_cb + srcStride * h, width);  
     memcpy(surf_cr + dstStride * h, yuv_cr + srcStride * h, width);  
   }  
 }  
   
 void convertYUV10pitchtoYUV444(unsigned short *yuv_luma, unsigned short *yuv_cb, unsigned short *yuv_cr,  
   unsigned short *surf_luma, unsigned short *surf_cb, unsigned short *surf_cr,  
   int width, int height, int srcStride, int dstStride)  
 {  
   int x, y;  
   
   for (y = 0; y < height; y++)  
   {  
     for (x = 0; x < width; x++)  
     {  
       surf_luma[(y*dstStride / 2) + x] = yuv_luma[(srcStride*y) + x] << 6;  
       surf_cb[(y*dstStride / 2) + x] = yuv_cb[(srcStride*y) + x] << 6;  
       surf_cr[(y*dstStride / 2) + x] = yuv_cr[(srcStride*y) + x] << 6;  
     }  
   }  
 }  
   
 NVENCSTATUS EncodeThread::encodeFrame(EncodeFrameConfig *pEncodeFrame, bool bFlush, uint32_t width, uint32_t height)  
 {  
   NVENCSTATUS nvStatus = NV_ENC_SUCCESS;  
   uint32_t lockedPitch = 0;  
   EncodeBuffer *pEncodeBuffer = NULL;  
   
   if (bFlush)  
   {  
     flushEncoder();  
     return NV_ENC_SUCCESS;  
   }  
   
   if (!pEncodeFrame)  
   {  
     return NV_ENC_ERR_INVALID_PARAM;  
   }  
   
   pEncodeBuffer = m_EncodeBufferQueue.GetAvailable();  
   if(!pEncodeBuffer)  
   {  
     m_pNvHWEncoder->ProcessOutput(m_EncodeBufferQueue.GetPending());  
     pEncodeBuffer = m_EncodeBufferQueue.GetAvailable();  
   }  
   
   unsigned char *pInputSurface;  
   
   nvStatus = m_pNvHWEncoder->NvEncLockInputBuffer(pEncodeBuffer->stInputBfr.hInputSurface, (void**)&pInputSurface, &lockedPitch);  
   if (nvStatus != NV_ENC_SUCCESS)  
     return nvStatus;  
   
   if (pEncodeBuffer->stInputBfr.bufferFmt == NV_ENC_BUFFER_FORMAT_NV12_PL)  
   {  
     unsigned char *pInputSurfaceCh = pInputSurface + (pEncodeBuffer->stInputBfr.dwHeight*lockedPitch);  
     convertYUVpitchtoNV12(pEncodeFrame->yuv[0], pEncodeFrame->yuv[1], pEncodeFrame->yuv[2], pInputSurface, pInputSurfaceCh, width, height, width, lockedPitch);  
   }  
   else if (pEncodeBuffer->stInputBfr.bufferFmt == NV_ENC_BUFFER_FORMAT_YUV444)  
   {  
     unsigned char *pInputSurfaceCb = pInputSurface + (pEncodeBuffer->stInputBfr.dwHeight * lockedPitch);  
     unsigned char *pInputSurfaceCr = pInputSurfaceCb + (pEncodeBuffer->stInputBfr.dwHeight * lockedPitch);  
     convertYUVpitchtoYUV444(pEncodeFrame->yuv[0], pEncodeFrame->yuv[1], pEncodeFrame->yuv[2], pInputSurface, pInputSurfaceCb, pInputSurfaceCr, width, height, width, lockedPitch);  
   }  
   else if (pEncodeBuffer->stInputBfr.bufferFmt == NV_ENC_BUFFER_FORMAT_YUV420_10BIT)  
   {  
     unsigned char *pInputSurfaceCh = pInputSurface + (pEncodeBuffer->stInputBfr.dwHeight*lockedPitch);  
     convertYUV10pitchtoP010PL((uint16_t *)pEncodeFrame->yuv[0], (uint16_t *)pEncodeFrame->yuv[1], (uint16_t *)pEncodeFrame->yuv[2], (uint16_t *)pInputSurface, (uint16_t *)pInputSurfaceCh, width, height, width, lockedPitch);  
   }  
   else //if (pEncodeBuffer->stInputBfr.bufferFmt == NV_ENC_BUFFER_FORMAT_YUV444_10BIT)  
   {  
     unsigned char *pInputSurfaceCb = pInputSurface + (pEncodeBuffer->stInputBfr.dwHeight * lockedPitch);  
     unsigned char *pInputSurfaceCr = pInputSurfaceCb + (pEncodeBuffer->stInputBfr.dwHeight * lockedPitch);  
     convertYUV10pitchtoYUV444((uint16_t *)pEncodeFrame->yuv[0], (uint16_t *)pEncodeFrame->yuv[1], (uint16_t *)pEncodeFrame->yuv[2], (uint16_t *)pInputSurface, (uint16_t *)pInputSurfaceCb, (uint16_t *)pInputSurfaceCr, width, height, width, lockedPitch);  
   }  
   nvStatus = m_pNvHWEncoder->NvEncUnlockInputBuffer(pEncodeBuffer->stInputBfr.hInputSurface);  
   if (nvStatus != NV_ENC_SUCCESS)  
     return nvStatus;  
   
   nvStatus = m_pNvHWEncoder->NvEncEncodeFrame(pEncodeBuffer, NULL, width, height, (NV_ENC_PIC_STRUCT)m_uPicStruct);  
   if (nvStatus == NV_ENC_SUCCESS) {  
     NV_ENC_LOCK_BITSTREAM lockBitstreamData;  
   
     memset(&lockBitstreamData, 0, sizeof(lockBitstreamData));  
     SET_VER(lockBitstreamData, NV_ENC_LOCK_BITSTREAM);  
     lockBitstreamData.outputBitstream = pEncodeBuffer->stOutputBfr.hBitstreamBuffer;  
     lockBitstreamData.doNotWait = false;  
   
     if (m_pNvHWEncoder->NvEncLockBitstream(&lockBitstreamData) == NV_ENC_SUCCESS) {        
       memcpy(m_pEncodeBuffer, lockBitstreamData.bitstreamBufferPtr, lockBitstreamData.bitstreamSizeInBytes);  
       m_nEncodeBufferSize = lockBitstreamData.bitstreamSizeInBytes;  
       m_pNvHWEncoder->NvEncUnlockBitstream(pEncodeBuffer->stOutputBfr.hBitstreamBuffer);  
     }  
   }  
   
   return nvStatus;  
 }  
   
 NVENCSTATUS EncodeThread::initCuda(uint32_t deviceID)  
 {  
   CUresult cuResult;  
   CUdevice device;  
   CUcontext cuContextCurr;  
   int deviceCount = 0;  
   int SMminor = 0, SMmajor = 0;  
   
 #if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)  
   typedef HMODULE CUDADRIVER;  
 #else  
   typedef void *CUDADRIVER;  
 #endif  
   CUDADRIVER hHandleDriver = 0;  
   cuResult = cuInit(0, __CUDA_API_VERSION, hHandleDriver);  
   if (cuResult != CUDA_SUCCESS)  
   {  
     PRINTERR("cuInit error:0x%x\n", cuResult);  
     assert(0);  
     return NV_ENC_ERR_NO_ENCODE_DEVICE;  
   }  
   
   cuResult = cuDeviceGetCount(&deviceCount);  
   if (cuResult != CUDA_SUCCESS)  
   {  
     PRINTERR("cuDeviceGetCount error:0x%x\n", cuResult);  
     assert(0);  
     return NV_ENC_ERR_NO_ENCODE_DEVICE;  
   }  
   
   // If dev is negative value, we clamp to 0  
   if ((int)deviceID < 0)  
     deviceID = 0;  
   
   if (deviceID >(unsigned int)deviceCount - 1)  
   {  
     PRINTERR("Invalid Device Id = %d\n", deviceID);  
     return NV_ENC_ERR_INVALID_ENCODERDEVICE;  
   }  
   
   cuResult = cuDeviceGet(&device, deviceID);  
   if (cuResult != CUDA_SUCCESS)  
   {  
     PRINTERR("cuDeviceGet error:0x%x\n", cuResult);  
     return NV_ENC_ERR_NO_ENCODE_DEVICE;  
   }  
   
   cuResult = cuDeviceComputeCapability(&SMmajor, &SMminor, deviceID);  
   if (cuResult != CUDA_SUCCESS)  
   {  
     PRINTERR("cuDeviceComputeCapability error:0x%x\n", cuResult);  
     return NV_ENC_ERR_NO_ENCODE_DEVICE;  
   }  
   
   if (((SMmajor << 4) + SMminor) < 0x30)  
   {  
     PRINTERR("GPU %d does not have NVENC capabilities exiting\n", deviceID);  
     return NV_ENC_ERR_NO_ENCODE_DEVICE;  
   }  
   
   cuResult = cuCtxCreate((CUcontext*)(&m_pDevice), 0, device);  
   if (cuResult != CUDA_SUCCESS)  
   {  
     PRINTERR("cuCtxCreate error:0x%x\n", cuResult);  
     assert(0);  
     return NV_ENC_ERR_NO_ENCODE_DEVICE;  
   }  
   
   cuResult = cuCtxPopCurrent(&cuContextCurr);  
   if (cuResult != CUDA_SUCCESS)  
   {  
     PRINTERR("cuCtxPopCurrent error:0x%x\n", cuResult);  
     assert(0);  
     return NV_ENC_ERR_NO_ENCODE_DEVICE;  
   }  
   return NV_ENC_SUCCESS;  
 }  
   
 NVENCSTATUS EncodeThread::allocateIOBuffers(uint32_t uInputWidth, uint32_t uInputHeight, NV_ENC_BUFFER_FORMAT inputFormat)  
 {  
   NVENCSTATUS nvStatus = NV_ENC_SUCCESS;  
   
   m_EncodeBufferQueue.Initialize(m_stEncodeBuffer, m_uEncodeBufferCount);  
   for (uint32_t i = 0; i < m_uEncodeBufferCount; i++)  
   {  
     nvStatus = m_pNvHWEncoder->NvEncCreateInputBuffer(uInputWidth, uInputHeight, &m_stEncodeBuffer[i].stInputBfr.hInputSurface, inputFormat);  
     if (nvStatus != NV_ENC_SUCCESS)  
       return nvStatus;  
   
     m_stEncodeBuffer[i].stInputBfr.bufferFmt = inputFormat;  
     m_stEncodeBuffer[i].stInputBfr.dwWidth = uInputWidth;  
     m_stEncodeBuffer[i].stInputBfr.dwHeight = uInputHeight;  
     nvStatus = m_pNvHWEncoder->NvEncCreateBitstreamBuffer(BITSTREAM_BUFFER_SIZE, &m_stEncodeBuffer[i].stOutputBfr.hBitstreamBuffer);  
     if (nvStatus != NV_ENC_SUCCESS)  
       return nvStatus;  
      m_stEncodeBuffer[i].stOutputBfr.dwBitstreamBufferSize = BITSTREAM_BUFFER_SIZE;  
     if (m_stEncoderInput.enableAsyncMode)  
     {  
       nvStatus = m_pNvHWEncoder->NvEncRegisterAsyncEvent(&m_stEncodeBuffer[i].stOutputBfr.hOutputEvent);  
       if (nvStatus != NV_ENC_SUCCESS)  
         return nvStatus;  
       m_stEncodeBuffer[i].stOutputBfr.bWaitOnEvent = true;  
     }  
     else  
       m_stEncodeBuffer[i].stOutputBfr.hOutputEvent = NULL;  
   }  
   
   m_stEOSOutputBfr.bEOSFlag = TRUE;  
   
   if (m_stEncoderInput.enableAsyncMode)  
   {  
     nvStatus = m_pNvHWEncoder->NvEncRegisterAsyncEvent(&m_stEOSOutputBfr.hOutputEvent);  
     if (nvStatus != NV_ENC_SUCCESS)  
       return nvStatus;  
   }  
   else  
     m_stEOSOutputBfr.hOutputEvent = NULL;  
   
   return NV_ENC_SUCCESS;  
 }  
   
 NVENCSTATUS EncodeThread::releaseIOBuffers()  
 {  
   for (uint32_t i = 0; i < m_uEncodeBufferCount; i++)  
   {  
     m_pNvHWEncoder->NvEncDestroyInputBuffer(m_stEncodeBuffer[i].stInputBfr.hInputSurface);  
     m_stEncodeBuffer[i].stInputBfr.hInputSurface = NULL;  
     m_pNvHWEncoder->NvEncDestroyBitstreamBuffer(m_stEncodeBuffer[i].stOutputBfr.hBitstreamBuffer);  
     m_stEncodeBuffer[i].stOutputBfr.hBitstreamBuffer = NULL;  
     if (m_stEncoderInput.enableAsyncMode)  
     {  
       m_pNvHWEncoder->NvEncUnregisterAsyncEvent(m_stEncodeBuffer[i].stOutputBfr.hOutputEvent);  
       nvCloseFile(m_stEncodeBuffer[i].stOutputBfr.hOutputEvent);  
       m_stEncodeBuffer[i].stOutputBfr.hOutputEvent = NULL;  
     }  
   }  
   
   if (m_stEOSOutputBfr.hOutputEvent)  
   {  
     if (m_stEncoderInput.enableAsyncMode)  
     {  
       m_pNvHWEncoder->NvEncUnregisterAsyncEvent(m_stEOSOutputBfr.hOutputEvent);  
       nvCloseFile(m_stEOSOutputBfr.hOutputEvent);  
       m_stEOSOutputBfr.hOutputEvent = NULL;  
     }  
   }  
   
   return NV_ENC_SUCCESS;  
 }  
   
 NVENCSTATUS EncodeThread::flushEncoder()  
 {  
   NVENCSTATUS nvStatus = m_pNvHWEncoder->NvEncFlushEncoderQueue(m_stEOSOutputBfr.hOutputEvent);  
   if (nvStatus != NV_ENC_SUCCESS)  
   {  
     assert(0);  
     return nvStatus;  
   }  
   
   EncodeBuffer *pEncodeBufer = m_EncodeBufferQueue.GetPending();  
   while (pEncodeBufer)  
   {  
     m_pNvHWEncoder->ProcessOutput(pEncodeBufer);  
     pEncodeBufer = m_EncodeBufferQueue.GetPending();  
   }  
   
 #if defined(NV_WINDOWS)  
   if (m_stEncoderInput.enableAsyncMode)  
   {  
   
     if (WaitForSingleObject(m_stEOSOutputBfr.hOutputEvent, 500) != WAIT_OBJECT_0)  
     {  
       assert(0);  
       nvStatus = NV_ENC_ERR_GENERIC;  
     }  
   }  
 #endif  
   
   return nvStatus;  
 }  
   
 NVENCSTATUS EncodeThread::deinitialize(uint32_t devicetype)  
 {  
   NVENCSTATUS nvStatus = NV_ENC_SUCCESS;  
   
   releaseIOBuffers();  
   
   nvStatus = m_pNvHWEncoder->NvEncDestroyEncoder();  
   
   if (m_pDevice)  
   {  
     switch (devicetype)  
     {  
 #if defined(NV_WINDOWS)  
     case NV_ENC_DX9:  
       ((IDirect3DDevice9*)(m_pDevice))->Release();  
       break;  
   
     case NV_ENC_DX10:  
       ((ID3D10Device*)(m_pDevice))->Release();  
       break;  
   
     case NV_ENC_DX11:  
       ((ID3D11Device*)(m_pDevice))->Release();  
       break;  
 #endif  
   
     case NV_ENC_CUDA:  
       CUresult cuResult = CUDA_SUCCESS;  
       cuResult = cuCtxDestroy((CUcontext)m_pDevice);  
       if (cuResult != CUDA_SUCCESS)  
         PRINTERR("cuCtxDestroy error:0x%x\n", cuResult);  
     }  
   
     m_pDevice = NULL;  
   }  
   
 #if defined (NV_WINDOWS)  
   if (m_pD3D)  
   {  
     m_pD3D->Release();  
     m_pD3D = NULL;  
   }  
 #endif  
   
   return nvStatus;  
 }  
   

2016년 12월 29일 목요일

C# BeginInvoke 올바른 사용법

BeginInvoke 호출할때 보통 람다식을 사용해서 아래와 같이 쓴다

        private void SetPictureImage(PictureBox picture, Bitmap bitmap)
        {
            Action action = () =>
            {
                if (picture.Image != null) picture.Image.Dispose();
                picture.Image = bitmap;              
            };
            if (this.InvokeRequired) BeginInvoke(action);
            else action();
        }

위 코드의 문제점은 action 이 수행되는 동안 SetPictureImage 함수가 외부 쓰레드에서 호출될때 사용중인 picture, bitmap 변수가 변경될 수 있다는 점이다.

이게 무슨 말이냐면 action 함수 내의 코드와 바깥의 SetPictureImage 함수가 서로 다른 쓰레드에서 호출될 수 있다는 말이다. (InvokeRequired = true 인 경우)
이럴경우 action 함수에서 picture,bitmap 변수를 사용하는 도중 SetPictureImage 함수가 호출되면 picture,bitmap 변수값이 사용도중 바뀌어 에러를 발생시킨다.

이것을 방지하기위해 아래와 같이 action 호출시 변수들을 매개변수로 넘겨주면 버그를 방지할 수 있다.

        private void SetPictureImage(PictureBox picture, Bitmap bitmap)
        {
            Action<PictureBox, Bitmap> action = (picture1, bitmap1) =>
                {
                    if (picture1.Image != null) picture1.Image.Dispose();
                    picture1.Image = bitmap1;
                };
            if (this.InvokeRequired) BeginInvoke(action, picture, bitmap);
            else action(picture, bitmap);
        }

2016년 10월 12일 수요일

리눅스 ffmpeg CUDA 비디오 코덱 연동 빌드 - build ffmpeg with cuda video codec on linux

1. nvidia 드라이버, cuda 툴킷 설치 - /usr/local/cuda
2. cuda video codec 설치 - /home/ubuntu/work/Video_Codec_SDK_7.0.1
3. x264 설치(비교 테스트용) - /home/ubuntu/work/x264/build
4. ffmpeg 빌드(64비트)

./configure --prefix=./build/ --enable-shared --disable-static --enable-cuda --enable-nvenc --enable-nonfree --extra-cflags=-I/usr/local/cuda-8.0/include \
--extra-cflags=-I/home/ubuntu/work/Video_Codec_SDK_7.0.1/Samples/common/inc --arch=x86_6
4 --enable-libx264 --enable-gpl --extra-cflags=-I/home/ubuntu/work/x264/build/include --extra-ldflags=-L/home/ubuntu/work/x264/build/lib

2016년 7월 13일 수요일

소켓 프로그래밍에 대한 조언

언어와 플랫폼을 불문하고 소켓 프로그래밍은 형태가 조금씩 다를뿐이지 내부구현은 완전히 동일하다.
기본적으로 POSIX 소켓 API 로 제공되는 low level API 들이 있고 각 플랫폼마다 이것들을 래핑한 상위 레이어들이 존재하는 형태이다.

응용 프로그램에서는 이러한 low level API를 가지고 개발할 것인지 좀더 편리한 상위 라이브러리를
이용할 것인지 결정해서 개발하면된다.

소켓 프로그래밍을 처음 입문하는 개발자들은 개발에 용이한 상위 라이브러리가 아무래도 많이 끌릴것이다. 심지어 경력 개발자들도 이러한 라이브러리를 이용해서 개발하는것을 선호하기도한다.

개인적으로 소켓 프로그래밍은 반드시 low level API로 개발해야한다고 생각한다. 사실 말이 low level이지 그냥 소켓 API 들이다. C#/Java 등의 언어에서 제공되는 상위 클래스들은 이러한 소켓 API 들을 래핑해서 사용하기 편리하게 만들어주는 역할밖에 하지않는다.

문제는 이러한 라이브러리를 사용해서 개발하다보면 내부구현을 볼 수가 없어서 다양한 상황들이 발생하는 네트워크 프로그래밍에서 올바른 대처를 하기가 어렵다는 점이다. 물론 잘 사용하면 대부분 문제는 없지만 이러한 방법에 길들여지면 소켓 프로그래밍이나 TCP/IP의 기본원리를 이해하기 힘들다는 점도있다.

사실 소켓 프로그래밍을 시작하기 전에 기본적인 TCP/IP 에 대한 이해가 필요하다. 개발을 해가면서 이해하든 미리 공부를 하든 기본적인 TCP/UDP의 동작정도는 알고 시작하는것이 좋다.

여기서 중요한 점은 응용 프로그램과 TCP/IP간의 관계에 대해서 잘 알아야한다는것이다. 
사실 실제적인 데이터 전송과 수신은 TCP/IP가 하는것이지 응용 프로그램이 하는것이 아니다.
응용 프로그램과 TCP/IP는 서로 다른 계층에 존재하고 소켓 API를 통해 서로 데이터를 주고받는데 응용 프로그램의 역할과 TCP/IP의 역할을 잘 알아야 특정한 상황이 발생했을때 이것을 분석해서 원인을 유추하는것이 가능하다.
또한 응용 프로그램간의 프로토콜을 정의하고 구현하는데도 이러한 지식이 필요하다.

마지막으로 한가지 더하자면 통신 프로그램은 아주 정교하게 작성해야한다. TCP 서버/클라이언트 개발자는 잘 알겠지만 통신 상황에서는 너무나도 다양한 케이스들이 발생한다. 대상이 일반 유저라면 생각할 수 있는 거의 모든 경우의 수가 다 발생한다고 보면된다. 
적당히 데이터 주고받는 테스트 정도만 수행하고 릴리즈를 했다간 큰 낭패를 볼 수 있다. 네트워크 상황은 시시각각 변하기때문에 특정 상황에 대한 스냅샷을 잡기가 굉장히 힘들고 이것을 사후에 분석하는것도 어렵기때문에 애초에 robust한 프로그래밍과 테스트가 필요하다.

2016년 7월 4일 월요일

C# 소켓 Select 함수 사용방법 - C# Socket Select

C# 소켓 프로그래밍에서도 기존 POSIX select 함수를 그대로 사용할 수 있다. 방식은 제공되는 Socket 클래스의 Select 정적 멤버함수를 사용하면된다.

< 일반적인 select 사용 >
         try  
         {  
           ArrayList selectList = new ArrayList();  
             
           selectList.Add(mySock);             
   
           if (selectList.Count == 0)  
           {  
             Thread.Sleep(10);  
             return;  
           }  
   
           Socket.Select(selectList, null, null, 1000000);  
   
           foreach (Socket sock in selectList)  
           {                    
             if (sock == mySock)
             {
                 // do something with mySock...
             }
           }            
         }  
         catch (Exception ex)  
         {  
           Trace.WriteLine(ex.ToString());  
         }  

< TCP Connection 타임아웃 >
      
Socket clientSock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
clientSock.Blocking = false;
...
public bool Connect(IPAddress serverIP, int serverPort, int timeout) 
{
       try  
       {  
         clientSock.Connect(serverIP, serverPort);        
         return true;  
       }  
       catch (SocketException ex)  
       {          
         if (ex.SocketErrorCode == SocketError.WouldBlock)  
         {  
           ArrayList selectArray = new ArrayList();  
           selectArray.Add(clientSock);  
   
           Socket.Select(null, selectArray, null, timeout * 1000000);  
   
           if (selectArray.Count == 0)  
           {
             Trace.WriteLine(ex.ToString());  
             return false;  
           }
   
           return true;  
         }    
       }  
       catch (Exception ex)  
       {          
         Trace.WriteLine(ex.ToString());  
         return false;  
       }  
}