이전 강좌에서는 POLL을 이용하여 TCP/IP 통신을 하면서 파일을 주고 받을 수 있었습니다. 이번 시간에는 카메라 영상을 구해서 클라이언트로 전송하는 것을 처리해 보도록 하겠습니다.

그러나 이번 강좌에서는 카메라 영상을 클라이언트로 전송하는 것 뿐만 아니라 카메라 영상을 구하는 방법에도 POLL을 적용했다는 점을 주목해 주셨으면 합니다.

ETIO 보드를 이용하여 카메라 영상을 이미지로 구하기 위해서는 시간이 걸립니다. 즉, ETIO보드로 캡쳐 명령을 전송하고 ETIO가 카멜라로부터 영상을 촬영한 후에 버퍼에 이미지를 담아서 반환하기 까지 소요되는 시간은 인간 시계로는 매우 짧은 시간이지만 컴퓨터 시계로는 매우 긴 시간이죠.

이를 함수에서 명령을 내리고 이미지가 완성이 될 때 까지 기다린다면 그 시간 만큼 다른 요구 사항을 처리할 수 없는 Block되어 버릴 것입니다.

이렇게 block되는 것을 막기 위해 POLL을 구성하고 아래의 코드에서 처럼 시간이 날 때마다 카메라 영상을 구하는 함수, capture_process()를 호출하게 합니다.

capture_process()는 switch 구문에서 capture_mode 값에 따라 필요한 작업을 처리하고 기다림 없이 바로 복귀합니다.

이번 강좌에 말씀드리고 싶은 것이 바로 이 "기다림 없이 바로 복귀"라는 것입니다.

  1. capture_mode 에 따라 ETIO로 캡쳐 명령을 전송하고,
  2. POLL 이벤트에서 POLLIN이 발생하면 읽어 들인 영상 데이터를 JPEG로 변환합니다.
  3. JPEG를 이미지 파일을 구해 놓았다면 다음 capture_process()가 호출될 때, JPEG 파일을 전송합니다.
  4. 파일을 전송 후에는, 다음 capture_process()가 호출될 때, 위의 1번) ETIO로 캡쳐 명령이 전송이 되어 계속 작업이 순환이 되도록 capture_mode 값을 0으로 초기화 합니다.
void capture_process( void)
{
   unsigned short *endmark;

   endmark = ( unsigned short * ) g_buff;

   switch( capture_mode )
   {
   case 0 :                                                          // 320 X 240 캡쳐를 시작한다.
            ecs_set_mode( ecs_handle, ECSM_320X240_YUV );
            ecs_capture( ecs_handle );
            capture_mode = 1;
            break;
   case 1 :                                                          // JPEG 로 변환 한다.
            ecs_convert_jpeg( ecs_handle, 0 );
            capture_mode = 2;
            break;
   case 2 :                                                          // 데이터를 전송한 후 다시 캡쳐 한다.
            image_size = ecs_get_jpeg_image_size( ecs_handle );
            if( 0 < image_size)
            {
               ecs_get_jpeg_image( ecs_handle, g_buff);
               if( ( endmark[image_size/2 - 1] == 0xFFD9 ) ¦¦ ( endmark[image_size/2 - 1] == 0xD9FF ) )
               {
                  if ( 0 < client_socket)
                  {
                     write( client_socket, (char *)&image_size, sizeof( image_size)); // 이미지의 크기
                     write( client_socket, (char *)g_buff     , image_size         );  // 이미지 전송
                  }
               }
               else
               {
                  printf( "jpeg image error\n" );
                  printf( "start mark %04X\n", endmark[0] );
                  printf( "end   mark %04X\n", endmark[image_size/2 - 1] );
               }
               capture_mode = 0;
            }
            break;
   }
}

capture_process() 함수는 아래와 같은 경우일 때 호출됩니다.

  1. 클라이언트와 연결되었을 때
  2. POLL() 체크가 타임아웃으로 복귀되었을 때
  3. 클라이언트로 버퍼가 모두 비어 발생하는 POLLOUT 이벤트가 발생했을 때
  4. 또한 카메라로부터 입력된 자료가 있는 POLLIN 이벤트가 발생했을 때

즉, main()함수 내에서 while() 이나 for() 루프 안에서 계속 호출되는 것이 아니라 어떤 이벤트가 발생할 때만 호출되기 때문에 시스템에 주는 부담이 적습니다. 또한 프로세스 내에서도 다른 작업을 할 수 있는 시간을 더 많이 가질 수 있으므로 프로그램 실행이 더 원할합니다.

전체 소스를 보시겠습니다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include "ecs_api.h"

#define  BUFF_SIZE         1024
#define  POLL_MAX_COUNT    3
#define  POLL_SERVER       0
#define  POLL_CLIENT       1
#define  POLL_CAMERA       2

int      server_socket;
int      client_socket;

struct   pollfd  poll_array[POLL_MAX_COUNT];             // poll 배열

unsigned char  g_buff[320*240*2];         // 카메라 이미비를 받을 버퍼
int              image_size;                // 최종 이미지의 크기
ecs_t            *ecs_handle     = NULL;     // 카메라 디바이스 핸들
int              capture_mode   = 0;        //

void capture_process( void)
{
   unsigned short *endmark;

   endmark = ( unsigned short * ) g_buff;

   switch( capture_mode )
   {
   case 0 :                                                          // 320 X 240 캡쳐를 시작한다.
            ecs_set_mode( ecs_handle, ECSM_320X240_YUV );
            ecs_capture( ecs_handle );
            capture_mode = 1;
            break;
   case 1 :                                                          // JPEG 로 변환 한다.
            ecs_convert_jpeg( ecs_handle, 0 );
            capture_mode = 2;
            break;
   case 2 :                                                          // 데이터를 전송한 후 다시 캡쳐 한다.
            image_size = ecs_get_jpeg_image_size( ecs_handle );
            if( 0 < image_size)
            {
               ecs_get_jpeg_image( ecs_handle, g_buff);
               if( ( endmark[image_size/2 - 1] == 0xFFD9 ) ¦¦ ( endmark[image_size/2 - 1] == 0xD9FF ) )
               {
                  if ( 0 < client_socket)
                  {
                     write( client_socket, (char *)&image_size, sizeof( image_size)); // 이미지의 크기
                     write( client_socket, (char *)g_buff     , image_size         ); // 이미지 전송
                  }
               }
               else
               {
                  printf( "jpeg image error\n" );
                  printf( "start mark %04X\n", endmark[0] );
                  printf( "end   mark %04X\n", endmark[image_size/2 - 1] );
               }
               capture_mode = 0;
            }
            break;
   }
}

void  connect_client( void)
{
   struct   sockaddr_in   client_addr;
   int      client_addr_size;

   client_addr_size  = sizeof( client_addr);
   client_socket     = accept( server_socket, (struct sockaddr*)&client_addr, &client_addr_size);

   if ( -1 == client_socket)
   {
      printf( "클라이언트 연결 수락 실패\n");
      return;
   }
   printf( "클라이언트 연결 성공\n");
   poll_array[POLL_CLIENT].fd       = client_socket;     // 클라이언트 소켓은 아직 없음
   if( !capture_mode)                                   // 다음 화면 캡쳐
      capture_process();
}

void  disconnect_client( void)
{
   if ( 0 < client_socket)
   {
      printf( "클라이언트 연결 종료\n");

      close( client_socket);
   }
   poll_array[POLL_CLIENT].fd    = -1;
}

void  init_camera( void)
{
   int      ndx;

   ecs_handle = ecs_open( 0, 1 );                                       //  CMOS 센서 디바이스 드라이버를 연다.
   if( ecs_handle == NULL )
   {
      printf( "카메라 장치 열기 실패\n");
      exit( 1);
   }

   ecs_set_mode( ecs_handle, ECSM_320X240_YUV );
   for( ndx = 0; ndx < 10; ndx++ )
   {
      if( ecs_capture( ecs_handle           ) )   continue;
      if( ecs_capture_wait( ecs_handle, 100 ) )   continue;
   }

   printf( "카메라 초기화 완료\n");
}

int   main( void)
{
   struct   sockaddr_in   server_addr;
   int      option;
   int      poll_state;

   server_socket  = socket( PF_INET, SOCK_STREAM, 0);
   option         = 1;                                      // SO_REUSEADDR 의 옵션 값을 TRUE 로
   setsockopt( server_socket, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));

   if( -1 == server_socket)
   {
      printf( "server socket 생성 실패\n");
      exit( 1);
   }

   memset( &server_addr, 0, sizeof( server_addr));
   server_addr.sin_family     = AF_INET;
   server_addr.sin_port       = htons( 4000);
   server_addr.sin_addr.s_addr= htonl( INADDR_ANY);

   if( -1 == bind( server_socket, (struct sockaddr*)&server_addr, sizeof( server_addr) ) )
   {
      printf( "bind() 실행 에러\n");
      exit( 1);
   }

   if( -1 == listen(server_socket, 5))
   {
      printf( "대기상태 모드 설정 실패\n");
      exit( 1);
   }

   init_camera();

   poll_array[POLL_SERVER].fd       = server_socket;        // poll 에 디스크립터를 등록한다.
   poll_array[POLL_SERVER].events   = POLLIN;

   poll_array[POLL_CLIENT].fd       = -1;                   // 클라이언트 소켓은 아직 없음
   poll_array[POLL_CLIENT].events   = POLLIN ¦ POLLOUT ¦ POLLERR ¦ POLLHUP;

   poll_array[POLL_CAMERA].fd       = ecs_handle->fd;       // 카메라에 대한 폴
   poll_array[POLL_CAMERA].events   = POLLIN;

   while( 1)
   {
      poll_state = poll( (struct pollfd *)&poll_array, POLL_MAX_COUNT, 1000);
      if ( 0 > poll_state)
      {
         printf( "치명적인 에러 발생\n");
         return -1;
      }
      if ( 0 == poll_state)
      {
         if( !capture_mode)                                 // 계속 이미지를 전송할 수 있도록
            capture_process();                              // 다음 화면 캡쳐
      }
      else
      {
         if( poll_array[POLL_SERVER].revents & POLLIN)      // 클라이언트로 연결 요청을 받음
         {
            connect_client();
         }
         if( poll_array[POLL_CLIENT].revents & ( POLLHUP ¦ POLLERR )) // 라인이 끝어 졌다면
         {
            disconnect_client();
         }
         if( poll_array[POLL_CLIENT].revents & ( POLLOUT))  // 계속 카메라로부터 이미지를 캡쳐하도록 호출
         {
            if ( !capture_mode)
               capture_process();
         }
         if( poll_array[POLL_CAMERA].revents & ( POLLIN))   // 카메라로부터 데이터가 수신되면
         {
            capture_process();
         }
      }


     // 다른 작업을 호출하여 실행할 수 있습니다.
     // 물론 그 작업은 많은 시간을 소모해서는 안 되고
     // 가급적 빨리 일 처리를 끝내고 바로 복귀해야 합니다.

   }
   return 0;
}

이전 강좌 내용과 차이나는 부분을 다른 색상으로 출력해 보았습니다. 이상으로 3회에 걸쳐서 EZ-ETIO 보드를 이용해서 카메라 영상을 TCP/IP를 이용하여 가져 오는 것을 알아 보았습니다.

당연히 카메라 영상을 가져오는 부분을 말씀드리고 싶었습니다만 그에 못지 않게 POLL을 이용하여 어느 한 루틴에서 block 되는 것을 피하고 이벤트에 따라서 일을 처리할 시기에 처리되도록 함으로써 시스템에 부하를 적게 하도록 하는 방법에 대해서도 말씀 드리고 싶었습니다.

지금까지의 프로그램 소스를 보시면 아시겠습니다만, 저 같은 경우 어떤 일을 한 번에 처리하기에는 대기 시간이 있거나 시간이 오래 걸리는 작업이라면 작업 흐름 변수를 두고 switch() 구문으로 작업을 부분부분으로 나누어 처리하는 것을 좋아합니다.

void  process( void)
{
   swtch( [작업흐름변수])
   {
   case 작업1  :  작업 1 처리
                  처리가 완료되면 [작업흐름변수]를 작업 2로 변경
                  break;
   case 작업2  :  작업 2 처리
                  처리가 완료되면 [작업흐름변수]를 작업 3로 변경
                  break;
   case 작업3  :  작업 3 처리
                  처리가 완료되면 [작업흐름변수]를 작업 4로 변경
                  break;
   case 작업4  :  작업 4 처리
                  처리가 완료되면 [작업흐름변수]를 작업 1로 변경
                  break;
   }
}

main()
{
   [작업흐름변수]를 작업 1로 설정

   while( 1 )
   {
      process()를 계속 호출
   }
}

이렇게 함으로써 다른 작업에 CPU 타임을 할애하기가 편하기 때문입니다.

태그:*영상, *캡쳐, *EZ-ETIO, *카메라 *EZ-X5