V4L2とYUV422とGTK+2

Linux

YUV422対応のUSBカメラをVMware Player上のLinuxでV4L2を使って動作させてみることにしました。

環境

  • ELECOM UCAM-DLE300TBK
  • ELECOM UCAM-DLM130HSV
  • luvcview-20070512.tar.gz

USBカメラの動作テスト

luvcviewを使って動作テストを行った。
素でインストールしたDebian wheezy 32bit/64bitで動作した。
VMware player上では真っ黒で画像が表示されなかった。
luvcviewを実行した後は、自作プログラムが動作しない場合があるので、一度カメラを抜き差しする。

luvcview -f YUV -s 640x480
luvcview -f YUV -s 320x240
luvcview -f YUV -s 240x120

ELCOM UCAM-DLE300TBK

Full HD対応300万画素Webカメラ - UCAM-DLE300TBK
ワイドスクリーンでFull HDの鮮明な動画を届けよう!1920×1080ピクセルのワイドサイズでで動画を楽しめるFull HD対応の一発接続300万画素Webカメラ。

対応フォーマット

実際にUSBカメラ「ELCOM UCAM-DLE300TBK」を接続してみると、V4L2では以下のフォーマットしか対応していなかった。

pixelformat = 'YUYV', description = 'YUV 4:2:2 (YUYV)'
pixelformat = 'MJPG', description = 'MJPEG'

ヘッダファイル

「/usr/include/linux/videodev2.h」の317、370行目に記述されている。

#define V4L2_PIX_FMT_YUYV  v4l2_fourcc('Y', 'U', 'Y', 'V') /* 16  YUV 4:2:2 */

#define V4L2_PIX_FMT_MJPEG v4l2_fourcc('M', 'J', 'P', 'G') /* Motion-JPEG */

YUYV→RGB変換式

実質、YUYVにしか対応していないのでRGBへの変換式を作る。

U = U - 128
V = V - 128

R = Y          + 1.402V
G = Y - 0.344U - 0.714V
B = Y + 1.772U

係数に256(8bit)を掛ける(四捨五入)と、

U = U - 128
V = V - 128

R = Y       + 359V
G = Y - 88U - 183V
B = Y + 454U

YUV422のポンチ絵

必要なライブラリ

sudo aptitude install libjpeg62-dev
sudo aptitude install libgtk2.0-dev
sudo aptitude install libgtk-3-dev

コンパイル・実行

make -f Makefile-yuv-gtk2

./v4l2-yuv -d /dev/video0
又は、
./v4l2-yuv

参考

「8bitフルスケールYUVと8bitフルスケールRGBの相互変換」項目の式「YUV to RGB 」。
http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html

参考

fourcc.orgにあるYUYVの説明。
http://www.fourcc.org/yuv.php#YUYV

参考

V4L2を使ったYUYVのサンプル。
http://ivis-mynikki.blogspot.jp/2011/01/v4l2_31.html

参考

10進数を2進数に変換にする計算方法。
ググってください。

参考

EIZOのYUVの資料。
http://www.eizo.co.jp/products/tech/files/2010/WP10-009.pdf

参考

YUYV→RGB変換の方法。
http://marsee101.blog19.fc2.com/blog-entry-1300.html

参考

YUV420→RGB変換の方法。
http://code.google.com/p/android/issues/detail?id=823

参考

C++で作ったRGB⇔YUV変換アプリケーションとソース。
http://www.codeproject.com/Articles/402391/RGB-to-YUV-conversion-with-different-chroma-sampli


Makefile

#	Makefile for v4ls-yuv.c

CC = gcc
#CFLAGS = -Wall -g
CFLAGS =
GTK_INCLUDE = `/usr/bin/pkg-config --cflags gtk+-2.0`
GTK_LIB = `/usr/bin/pkg-config --libs gtk+-2.0`
JPEG_LIB = -l jpeg
TARGET = v4l2-yuv
OBJECTS = color.o utils.o $(TARGET).o

$(TARGET): $(OBJECTS)
	$(CC) -o $@ $(JPEG_LIB) $(GTK_LIB) $^
	rm -f $(OBJECTS)

.c.o:
	$(CC) -c $(CFLAGS) $(GTK_INCLUDE) $<

clean:
	rm -f $(OBJECTS) $(TARGET)

その他のソース

YUV422からRGB24の変換にluvcviewのソースの一部を使っている。
http://sourceforge.jp/projects/sfnet_v4l-lib/releases/
・utils.h
・utils.c
・color.h
・color.c

v4l2-yuv.c

//
// V4L2 capture sample program for YUV422
//
// 2013.12.10 modified
// 2013.12.08 release
//
// ■環境
// gcc ver.4.7.2
// Gtk+2.0
// Jpeg Library ver.6
// Linux kernel ver.3.2
//
// ■コンパイル・実行
// make -f makefile
//
// ./v4l2-yuv
// ./v4l2-yuv -d /dev/video0
//
// ■luvcview(http://sourceforge.jp/projects/sfnet_v4l-lib/releases/)
// utils.c
// color.c
// color.h
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <getopt.h> // getopt_long()
#include <fcntl.h>  // low-level I/O
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <asm/types.h> // videodev2.h
#include <linux/videodev2.h>
#include <gtk/gtk.h>
#include <jpeglib.h>  // JPEG Library
#include "utils.h"    // luvcview
#include "color.h"    // luvcview

// V4L2 format for YUYV (YUV422)
#define PIXELFORMAT V4L2_PIX_FMT_YUYV

// 設定項目 ここから
static int save_jpeg = 0;  // JPEGで保存
static int save_ppm  = 0;  // PPMで保存

#define CAPTURE_WIDTH    320 // キャプチャの横サイズ
#define CAPTURE_HEIGHT   240 // キャプチャの縦サイズ
#define CAPTURE_CHANNEL  3   // キャプチャのchannel

//#define USE_GTK_TIMEOUT_ADD 0 // gtk_timeout_add()を使用する。無効の場合はgtk_idle_add()を使用する。
//#define GTK_INTERVAL 33       // キャプチャ間隔[ms] gtk_timeout_add()を使用した場合に使用する。
// ここまで

#define PKGNAME "V4L2 Capture"
#define CLEAR( x ) memset( &(x), 0, sizeof (x) )
#define BTN_MAX 3 // GTK のボタン数

static void CheckArg( int, char ** );
static void Usage( FILE *, int, char ** );
static void Open_Device( void );
static void Close_Device( void );
static void Init_Device( void );
static void Init_userp( unsigned int );
static void Init_mmap( void );
static void Init_read( unsigned int );
static void UnInit_Device( void );
static void Start_Capture( void );
static void Stop_Capture( void );
static void MainLoop( void );
static int  Read_Frame( void );
static void Process_Image( const void * );
static int  Xioctl( int, int, void * );
static void Errno_Exit( const char * );
static void Start_Proc( GtkWidget *, gpointer );
static void Stop_Proc( GtkWidget *, gpointer );
static void Quit_Proc( GtkWidget *, gpointer );
static void CreateCaptureWindow( void );
static void GetImageMemory( void );

typedef enum {
    IO_METHOD_READ,
    IO_METHOD_MMAP,
    IO_METHOD_USERPTR,
} io_method;

struct buffer {
    void   *start;
    size_t length;
};

static char          *dev_name = NULL;
static io_method     io        = IO_METHOD_MMAP;
static int           fd        = -1;
struct buffer        *buffers  = NULL;
static unsigned int  n_buffers = 0;
static unsigned int  ImgNum    = 0; // 画像番号
static guint         gtk_id;
static unsigned char *rgb;

// GTK
GtkWidget *gtk_top;
GtkWidget *gtk_second;
GtkWidget *gtk_darea;
GtkWidget *gtk_table;
GtkWidget *gtk_label;
GtkWidget *gtk_btn;

//
// エラー処理
//
static void Errno_Exit( const char *s )
{
    fprintf(stderr, "%s error %d, %s\n", s, errno, strerror (errno));
    exit( EXIT_FAILURE );
}

//
// デバイス操作
//
static int Xioctl( int fd, int request, void *arg )
{
    int r;

    do
    {
        r = ioctl( fd, request, arg );
    }
    while ( r == -1 && errno == EINTR );

    return r;
}

/*
//
// translate YUV422Packed to rgb24
// luvcview(http://sourceforge.jp/projects/sfnet_v4l-lib/releases/)
// utils.c
// color.c
// color.h
//
// ここから luvcview
#define CLIP(color) (unsigned char)(((color)>0xFF)?0xff:(((color)<0)?0:(color)))

static int *LutRv = NULL;
static int *LutGu = NULL;
static int *LutGv = NULL;
static int *LutBu = NULL;

void initLut(void)
{
    int i;

    #define CoefRv 1402
    #define CoefGu 714 // 344
    #define CoefGv 344 // 714
    #define CoefBu 1772
    
    LutRv = malloc(256*sizeof(int));
    LutGu = malloc(256*sizeof(int));
    LutGv = malloc(256*sizeof(int));
    LutBu = malloc(256*sizeof(int));

    for (i= 0;i < 256;i++){
        LutRv[i] = (i-128)*CoefRv/1000;
        LutBu[i] = (i-128)*CoefBu/1000;
        LutGu[i] = (128-i)*CoefGu/1000;
        LutGv[i] = (128-i)*CoefGv/1000;
    }    
}

void freeLut(void)
{
    free(LutRv);
    free(LutGu);
    free(LutGv);
    free(LutBu);
}

unsigned char
R_FROMYV(unsigned char y, unsigned char v)
{
    return CLIP((y) + LutRv[(v)]);
}

unsigned char
G_FROMYUV(unsigned char y, unsigned char u, unsigned char v)
{
    return CLIP((y) + LutGu[(u)] + LutGv[(v)]);
}

unsigned char
B_FROMYU(unsigned char y, unsigned char u)
{
    return CLIP((y) + LutBu[(u)]);
}

unsigned int
Pyuv422torgb24(unsigned char * input_ptr, unsigned char * output_ptr, unsigned int image_width, unsigned int image_height)
{
    unsigned int i, size;
    unsigned char Y, Y1, U, V;
    unsigned char *buff = input_ptr;
    unsigned char *output_pt = output_ptr;
    size = image_width * image_height /2;
//    printf("size; %d \n", size);
    for (i = size; i > 0; i--) {
        Y = buff[0] ;
        U = buff[1] ;
        Y1 = buff[2];
        V = buff[3];
        buff += 4;
        *output_pt++ = R_FROMYV(Y,V);
        *output_pt++ = G_FROMYUV(Y,U,V); //b
        *output_pt++ = B_FROMYU(Y,U); //v

        *output_pt++ = R_FROMYV(Y1,V);
        *output_pt++ = G_FROMYUV(Y1,U,V); //b
        *output_pt++ = B_FROMYU(Y1,U); //v
    }
    
    return 0;
} 
// ここまで luvcview
*/

//
// キャプチャした画像の操作
//
static void Process_Image( const void *p )
{
    int           i, j, ct, size, tmp;
    int           r, g, b;
    int           y, u, y1, v;
    unsigned char *reader, *writer;
    unsigned char fname_jpeg[FILENAME_MAX];
    unsigned char fname_ppm[FILENAME_MAX];
    unsigned char ext[4+1]; // 拡張子4文字 + 終端文字
    FILE          *fp;

    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr       jerr;
    unsigned char               *line;


    fputc ('.', stdout);
    fflush (stdout);

    writer = (unsigned char *)rgb;
    reader = (unsigned char *)p;

    // initLutは起動直後、freeLutはプログラム終了時に実行した方が良い。
///*
    initLut();
    Pyuv422torgb24(p, rgb, CAPTURE_WIDTH, CAPTURE_HEIGHT);
    freeLut();
//*/

/*
    ct = 0;
    for (i= 0; i < CAPTURE_HEIGHT; i++)
    {
        for (j= 0; j < CAPTURE_WIDTH; j++)
        {
            if ( ct == 0 ) y = reader[0] - 16;
            else           y = reader[2] - 16;
//          if ( ct == 0 ) y = reader[0] << 8;
//          else           y = reader[2] << 8;

            u = reader[1] - 128;
            v = reader[3] - 128;

            // 8bitに丸める
            r = (y + 359 * v)          >> 8;
            g = (y - 88 * u - 183 * v) >> 8;
            b = (y + 454 * u)          >> 8;

            *writer++ = (r > 255) ? 255 : ((r < 0) ? 0 : r);
            *writer++ = (g > 255) ? 255 : ((g < 0) ? 0 : g);
            *writer++ = (b > 255) ? 255 : ((b < 0) ? 0 : b);

            if ( ct > 0 )
            {
                ct = 0;
                reader += 4;
            }

            ct++;
        }
    }
*/

    // DrawAreaに描画
    gdk_draw_rgb_image( gtk_darea->window, gtk_darea->style->fg_gc[GTK_STATE_NORMAL], 0, 0, CAPTURE_WIDTH, CAPTURE_HEIGHT, GDK_RGB_DITHER_MAX, writer, CAPTURE_CHANNEL * CAPTURE_WIDTH );

    // JPEG
    if ( save_jpeg == 1 )
    {
        strcpy( ext, ".jpg" );
        // 終端文字の付加
        ext[4+1] = '\0';
        sprintf( fname_jpeg, "out%05d%s", ImgNum, ext );
        fp = fopen(fname_jpeg, "w");
        if ( fp == NULL )
        {
            Errno_Exit("Error:    Save image file");
        }

        cinfo.err = jpeg_std_error( &jerr );
        jpeg_create_compress( &cinfo );
        jpeg_stdio_dest(&cinfo, fp);
        cinfo.image_width      = CAPTURE_WIDTH;
        cinfo.image_height     = CAPTURE_HEIGHT;
        cinfo.input_components = CAPTURE_CHANNEL; // color component per pixel
        cinfo.in_color_space   = JCS_RGB;
        jpeg_set_defaults( &cinfo );
        jpeg_set_quality(&cinfo, 100, TRUE); // 品質 [0-100]
        jpeg_start_compress(&cinfo, TRUE);
        for (i=0, line = writer; i < CAPTURE_HEIGHT; i++, line += CAPTURE_CHANNEL * CAPTURE_WIDTH)
        {
            jpeg_write_scanlines(&cinfo, &line, 1);
        }
        jpeg_finish_compress( &cinfo );
        jpeg_destroy_compress( &cinfo );

        fclose( fp );
    }

    // PPM
    if ( save_ppm == 1 )
    {
        strcpy( ext, ".ppm" );
        ext[4+1] = '\0'; // 終端文字の付加
        sprintf( fname_ppm, "out%05d%s", ImgNum, ext );
        fp = fopen(fname_ppm, "w");
        if ( fp == NULL )
        {
            Errno_Exit("Error:    Save image file");
        }
        fprintf(fp, "P6 %d %d 255\n", CAPTURE_WIDTH, CAPTURE_HEIGHT);
        fwrite(writer, 1, CAPTURE_WIDTH * CAPTURE_HEIGHT * CAPTURE_CHANNEL, fp);
        fclose( fp );
    }

    ImgNum++;
}

//
// フレームを読み込む
//
static int Read_Frame( void )
{
    unsigned int       i;
    struct v4l2_buffer buf;

    switch ( io )
    {
        case IO_METHOD_READ:
                if ( read(fd, buffers[0].start, buffers[0].length) == -1 )
                {
                    switch ( errno )
                    {
                        case EAGAIN:
                                return 0;
                        case EIO:
                                // Could ignore EIO, see spec.
                                // fall through
                        default:
                                Errno_Exit("read");
                    }
                }
                Process_Image( buffers[0].start );
                break;
        case IO_METHOD_MMAP:
                CLEAR( buf );
                buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                buf.memory = V4L2_MEMORY_MMAP;
                if ( Xioctl(fd, VIDIOC_DQBUF, &buf) == -1 )
                {
                    switch ( errno )
                    {
                        case EAGAIN:
                                return 0;
                        case EIO:
                                // Could ignore EIO, see spec.
                                // fall through
                        default:
                                Errno_Exit("VIDIOC_DQBUF");
                    }
                }
                assert( buf.index < n_buffers );
                Process_Image( buffers[buf.index].start );
                if ( Xioctl(fd, VIDIOC_QBUF, &buf) == -1 )
                {
                    Errno_Exit("VIDIOC_QBUF");
                }
                break;
        case IO_METHOD_USERPTR:
                CLEAR( buf );
                buf.type    = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                buf.memory    = V4L2_MEMORY_USERPTR;
                if ( Xioctl(fd, VIDIOC_DQBUF, &buf) == -1 )
                {
                    switch ( errno )
                    {
                        case EAGAIN:
                                return 0;
                        case EIO:
                                // Could ignore EIO, see spec.
                                // fall through
                        default:
                                Errno_Exit("VIDIOC_DQBUF");
                    }
                }
                for (i = 0; i < n_buffers; ++i)
                {
                    if ( buf.m.userptr == (unsigned long) buffers[i].start && buf.length == buffers[i].length )
                    {
                        break;
                    }
                }
                assert( i < n_buffers );
                Process_Image( (void *) buf.m.userptr );
                if ( -1 == Xioctl(fd, VIDIOC_QBUF, &buf) )
                {
                    Errno_Exit("VIDIOC_QBUF");
                }
                break;
    }

    return 1;
}

//
// ループ
//
static void MainLoop( void )
{
    for (;;)
    {
        fd_set         fds;
        struct timeval tv;
        int            r;

        FD_ZERO( &fds );
        FD_SET( fd, &fds );
        // Timeout
        tv.tv_sec  = 2;
        tv.tv_usec = 0;

        r = select( fd + 1, &fds, NULL, NULL, &tv );
        if ( r == -1 )
        {
            if ( EINTR == errno )
            {
                continue;
            }
            Errno_Exit("select");
        }

        if ( r == 0 )
        {
            fprintf(stderr, "select timeout\n");
            exit( EXIT_FAILURE );
        }

        if ( Read_Frame() )
        {
            break;
        }
        // EAGAIN - continue select loop
    }
}

static void GetImageMemory( void )
{
    rgb = ( unsigned char *)malloc( CAPTURE_CHANNEL * CAPTURE_WIDTH * CAPTURE_HEIGHT );
    if ( ! rgb )
    {
        fprintf(stderr, "Out of memory rgb \n");
        exit( EXIT_FAILURE );
    }
}

//
// キャプチャ停止
//
static void Stop_Capture( void )
{
    enum v4l2_buf_type    type;

    switch ( io )
    {
        case IO_METHOD_READ:
                // Nothing to do
                break;
        case IO_METHOD_MMAP:
        case IO_METHOD_USERPTR:
                type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                if ( -1 == Xioctl(fd, VIDIOC_STREAMOFF, &type) )
                {
                    Errno_Exit("VIDIOC_STREAMOFF");
                }
                break;
    }
}

//
// キャプチャ開始
//
static void Start_Capture( void )
{
    unsigned int       i;
    enum v4l2_buf_type type;

    switch ( io )
    {
        case IO_METHOD_READ:
                break;
        case IO_METHOD_MMAP:
                for (i = 0; i < n_buffers; ++i)
                {
                    struct v4l2_buffer    buf;

                    CLEAR( buf );
                    buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                    buf.memory = V4L2_MEMORY_MMAP;
                    buf.index  = i;

                    if ( Xioctl(fd, VIDIOC_QBUF, &buf) == -1 )
                    {
                        Errno_Exit("VIDIOC_QBUF");
                    }
                }
                type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                if ( Xioctl(fd, VIDIOC_STREAMON, &type)  == -1 )
                {
                    Errno_Exit("VIDIOC_STREAMON");
                }
                break;
        case IO_METHOD_USERPTR:
                for (i = 0; i < n_buffers; ++i)
                {
                    struct v4l2_buffer    buf;

                    CLEAR( buf );
                    buf.type      = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                    buf.memory    = V4L2_MEMORY_USERPTR;
                    buf.m.userptr = (unsigned long) buffers[i].start;
                    buf.length    = buffers[i].length;

                    if ( -1 == Xioctl(fd, VIDIOC_QBUF, &buf) )
                    {
                        Errno_Exit("VIDIOC_QBUF");
                    }
                }
                type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                if ( Xioctl(fd, VIDIOC_STREAMON, &type) == -1 )
                {
                    Errno_Exit("VIDIOC_STREAMON");
                }
                break;
        }
}

//
// デバイスの終了処理
//
static void UnInit_Device( void )
{
    unsigned int i;

    switch ( io )
    {
        case IO_METHOD_READ:
                free ( buffers[0].start );
                break;
        case IO_METHOD_MMAP:
                for ( i = 0; i < n_buffers; ++i )
                {
                    if ( munmap (buffers[i].start, buffers[i].length) == -1 )
                    {
                        Errno_Exit("munmap");
                    }
                }
                break;
        case IO_METHOD_USERPTR:
                for ( i = 0; i < n_buffers; ++i )
                {
                    free( buffers[i].start );
                }
                break;
    }

    free( buffers );
    free( rgb );
}

//
// メモリ割り当て malloc calloc
//
static void Init_read( unsigned int buffer_size )
{
    // ヒープメモリからsize[byte]のブロックをn個割り当てる
    // void *calloc(size_t n, size_t size);
    buffers = calloc(1, sizeof (*buffers));

    if ( ! buffers )
    {
        fprintf(stderr, "Out of memory\n");
        exit( EXIT_FAILURE );
    }

    buffers[0].length = buffer_size;
    buffers[0].start  = malloc( buffer_size );

    if ( ! buffers[0].start )
    {
        fprintf(stderr, "Out of memory\n");
        exit( EXIT_FAILURE );
    }
}

//
// メモリ割り当て V4L2_MEMORY_MMAP
//
static void Init_mmap( void )
{
    struct v4l2_requestbuffers req;

    CLEAR( req );
    req.count  = 4;
    req.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;

    if ( Xioctl(fd, VIDIOC_REQBUFS, &req) == -1 )
    {
        if ( EINVAL == errno )
        {
            fprintf(stderr, "%s does not support " "memory mapping\n", dev_name);
            exit( EXIT_FAILURE );
        }
        else
        {
            Errno_Exit("VIDIOC_REQBUFS");
        }
    }

    if ( req.count < 2 )
    {
        fprintf(stderr, "Insufficient buffer memory on %s\n", dev_name);
        exit( EXIT_FAILURE );
    }

    buffers = calloc(req.count, sizeof (*buffers));
    if ( ! buffers )
    {
        fprintf(stderr, "Out of memory mmap \n");
        exit( EXIT_FAILURE );
    }

    for ( n_buffers = 0; n_buffers < req.count; ++n_buffers )
    {
        struct v4l2_buffer buf;

        CLEAR( buf );
        buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index  = n_buffers;

        if ( Xioctl(fd, VIDIOC_QUERYBUF, &buf) == -1 )
        {
            Errno_Exit("VIDIOC_QUERYBUF mmap");
        }
        buffers[n_buffers].length = buf.length;
        buffers[n_buffers].start =
        mmap ( NULL,                   // Start Anywhere
               buf.length,
               PROT_READ | PROT_WRITE, // required
               MAP_SHARED,             // recommended
               fd, buf.m.offset );
        if ( MAP_FAILED == buffers[n_buffers].start )
        {
            Errno_Exit("mmap");
        }
    }
}

//
// メモリ割り当て V4L2_MEMORY_USERPTR
//
static void Init_userp( unsigned int buffer_size )
{
    struct v4l2_requestbuffers req;

    CLEAR( req );
    req.count  = 4;
    req.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_USERPTR;

    if ( Xioctl(fd, VIDIOC_REQBUFS, &req) == -1 )
    {
        if ( EINVAL == errno )
        {
            fprintf(stderr, "%s does not support " "user pointer i/o\n", dev_name);
            exit( EXIT_FAILURE );
        }
        else
        {
            Errno_Exit("VIDIOC_REQBUFS");
        }
    }

    buffers = calloc(4, sizeof (*buffers));
    if ( ! buffers )
    {
        fprintf(stderr, "Out of memory\n");
        exit( EXIT_FAILURE );
    }
    for (n_buffers = 0; n_buffers < 4; ++n_buffers)
    {
        buffers[n_buffers].length = buffer_size;
        buffers[n_buffers].start  = malloc( buffer_size );

        if ( ! buffers[n_buffers].start )
        {
            fprintf(stderr, "Out of memory\n");
            exit( EXIT_FAILURE );
        }
    }
}

//
// デバイスの初期化
//
static void Init_Device( void )
{
    struct v4l2_capability cap;
    struct v4l2_cropcap    cropcap;
    struct v4l2_crop       crop;
    struct v4l2_format     fmt;
    unsigned int           min;

    if ( Xioctl(fd, VIDIOC_QUERYCAP, &cap) == -1 )
    {
        if ( EINVAL == errno )
        {
            fprintf(stderr, "%s is no V4L2 device\n", dev_name);
            exit( EXIT_FAILURE );
        }
        else
        {
            Errno_Exit("VIDIOC_QUERYCAP");
        }
    }

    if ( ! ( cap.capabilities & V4L2_CAP_VIDEO_CAPTURE ) )
    {
        fprintf(stderr, "%s is no video capture device\n", dev_name);
        exit( EXIT_FAILURE );
    }

    switch ( io )
    {
        case IO_METHOD_READ:
                if ( ! ( cap.capabilities & V4L2_CAP_READWRITE ) )
                {
                    fprintf(stderr, "%s does not support read i/o\n", dev_name);
                    exit( EXIT_FAILURE );
                }
                break;
        case IO_METHOD_MMAP:
        case IO_METHOD_USERPTR:
                if ( ! ( cap.capabilities & V4L2_CAP_STREAMING ) )
                {
                    fprintf(stderr, "%s does not support streaming i/o\n", dev_name);
                    exit( EXIT_FAILURE );
                }
                break;
    }

    // Select video input, video standard and tune here.
    cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if ( Xioctl(fd, VIDIOC_CROPCAP, &cropcap) == -1 )
    {
        // Errors ignored.
        fprintf(stderr, "VIDIOC_CROPCAP error. Ignored ! \n");
    }
    crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    crop.c    = cropcap.defrect; // reset to default

    if ( Xioctl(fd, VIDIOC_S_CROP, &crop) == -1 )
    {
        switch ( errno )
        {
            case EINVAL:
                    break; // Cropping not supported.
            default:
                    break; // Errors ignored.
        }
    }

    CLEAR( fmt );
    fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width       = CAPTURE_WIDTH; 
    fmt.fmt.pix.height      = CAPTURE_HEIGHT;
    fmt.fmt.pix.pixelformat = PIXELFORMAT;
    fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;

    if ( -1 == Xioctl(fd, VIDIOC_S_FMT, &fmt) )
    {
        Errno_Exit("VIDIOC_S_FMT");
    }

    // Note VIDIOC_S_FMT may change width and height.
    // Buggy driver paranoia.
    min = 2 * fmt.fmt.pix.width;
    if ( fmt.fmt.pix.bytesperline < min )
    {
        fmt.fmt.pix.bytesperline = min;
    }
    min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
    if ( fmt.fmt.pix.sizeimage < min )
    {
        fmt.fmt.pix.sizeimage = min;
    }

    switch ( io )
    {
        case IO_METHOD_READ:
                Init_read( fmt.fmt.pix.sizeimage );
                break;
        case IO_METHOD_MMAP:
                Init_mmap();
                break;
        case IO_METHOD_USERPTR:
                Init_userp( fmt.fmt.pix.sizeimage );
                break;
    }
}

//
// デバイスを閉じる
//
static void Close_Device( void )
{
    if ( close (fd) == -1 )
    {
        Errno_Exit("close");
    }
    fd = -1;
}

//
// デバイスを開く
//
static void Open_Device( void )
{
    struct stat        st; 

    // stat構造体を得る
    if ( stat(dev_name, &st) == -1 )
    {
        fprintf(stderr, "Cannot identify '%s': %d, %s\n", dev_name, errno, strerror (errno));
        exit( EXIT_FAILURE );
    }

    // キャラクタデバイスかどうかの検査
    if ( ! S_ISCHR( st.st_mode ) )
    {
        fprintf(stderr, "%s is no device\n", dev_name);
        exit( EXIT_FAILURE );
    }

    fd = open(  dev_name, O_RDWR | O_NONBLOCK, 0 );
    if ( fd == -1 )
    {
        fprintf(stderr, "Cannot open '%s': %d, %s\n", dev_name, errno, strerror (errno));
        exit( EXIT_FAILURE );
    }
}


//
// 使用方法
//
static void Usage( FILE *fp, int argc, char **argv )
{
    fprintf(fp,
        "Usage: %s [options]\n\n"
        "Options:\n"
        "-d | --device name   Video device name [default: /dev/video0]\n"
        "-h | --help          Print this message\n"
        "-m | --mmap          Use memory mapped buffers\n"
        "-r | --read          Use read() calls\n"
        "-u | --userp         Use application allocated buffers\n"
        "",
        argv[0]);
}

//
// 描画用のWindow作成
//
static void CreateCaptureWindow( void )
{
    gtk_second = gtk_window_new( GTK_WINDOW_TOPLEVEL );
    gtk_widget_set_usize( GTK_WIDGET( gtk_second ), CAPTURE_WIDTH, CAPTURE_HEIGHT );
    gtk_window_set_title( GTK_WINDOW( gtk_second ), PKGNAME );

    // Drawエリア作成
    gtk_darea = gtk_drawing_area_new();
    gtk_container_add( GTK_CONTAINER( gtk_second ), gtk_darea );
    gtk_widget_realize( gtk_second );
    gtk_widget_realize( gtk_darea );
    gtk_widget_show_all( gtk_second );
}

//
// 引数の設定
//
static const char short_options [] = "d:hmru";
static const struct option
long_options [] = {
    { "device",        required_argument,    NULL,    'd' },
    { "help",        no_argument,        NULL,    'h' },
    { "mmap",        no_argument,        NULL,    'm' },
    { "read",        no_argument,        NULL,    'r' },
    { "userp",        no_argument,        NULL,    'u' },
    { 0, 0, 0, 0 }
};

//
// 引数の検査
//
static void CheckArg( int argc, char **argv )
{
    dev_name = "/dev/video0";

    for (;;)
    {
        int index;
        int c;

        c = getopt_long( argc, argv, short_options, long_options, &index );

        if ( c == -1 )
        {
            break;
        }

        switch ( c )
        {
            case 0: // getopt_long() flag
                    break;
            case 'd':
                    dev_name = optarg;
                    break;
            case 'h':
                    Usage( stdout, argc, argv );
                    exit(EXIT_SUCCESS);
            case 'm':
                    io = IO_METHOD_MMAP;
                    break;
            case 'r':
                    io = IO_METHOD_READ;
                    break;
            case 'u':
                    io = IO_METHOD_USERPTR;
                    break;
            default:
                    Usage( stderr, argc, argv );
                    exit( EXIT_FAILURE );
        }
    }
}

//
// キャプチャ開始
//
static void Start_Proc( GtkWidget *w, gpointer p )
{
    ImgNum = 0;
    Start_Capture();

#ifdef USE_GTK_TIMEOUT_ADD
    gtk_id = gtk_timeout_add( GTK_INTERVAL, (GtkFunction) MainLoop, p );
    printf("add \n");
#else
    gtk_id = gtk_idle_add( (GtkFunction) MainLoop, p );
    printf("idle \n");
#endif
}

//
// キャプチャ停止
//
static void Stop_Proc( GtkWidget *w, gpointer p )
{
#ifdef USE_GTK_TIMEOUT_ADD
    gtk_timeout_remove( gtk_id );
#else
    gtk_idle_remove( gtk_id );
#endif

    Stop_Capture();
}

//
// プログラム終了
//
static void Quit_Proc( GtkWidget *w, gpointer p )
{
    UnInit_Device();
    Close_Device();
    gtk_main_quit();
}

//
// メイン
//
int main( int argc, char **argv )
{
    int   i;
    gchar *btn_label[] = { "Capture", "Stop", "Exit" };
    void  *btn_func[]  = { Start_Proc, Stop_Proc, Quit_Proc };

    CheckArg( argc, argv );

    // 初期設定
    gtk_set_locale();    // 日本語可
    gtk_init( &argc, &argv );
    gdk_rgb_init();
    gtk_top = gtk_window_new( GTK_WINDOW_TOPLEVEL );
    gtk_window_set_title( GTK_WINDOW( gtk_top ), PKGNAME );
    gtk_widget_show( gtk_top );

    gtk_container_set_border_width( GTK_CONTAINER( gtk_top ), 20 );

    // テーブルパッキング
    gtk_table = gtk_table_new( 5, 2, FALSE );
    gtk_container_add( GTK_CONTAINER( gtk_top ), gtk_table );
    gtk_table_set_col_spacings( GTK_TABLE( gtk_table ), 10 );
    gtk_table_set_row_spacings( GTK_TABLE( gtk_table ), 5 );
    gtk_widget_show( gtk_table );

    // ボタン
    for ( i=0; i < BTN_MAX; i++ )
    {
        gtk_btn = gtk_button_new_with_label( btn_label[i] );
        gtk_table_attach_defaults( GTK_TABLE( gtk_table ), gtk_btn, 0, 2, i, i + 1 );
        gtk_signal_connect( GTK_OBJECT( gtk_btn ), "clicked", GTK_SIGNAL_FUNC( btn_func[i] ), NULL );
        gtk_widget_show( gtk_btn );
    }

    Open_Device();
    Init_Device();
    GetImageMemory();
    CreateCaptureWindow();
//  Start_Capture();
//  MainLoop();
//  Stop_Capture();
//  UnInit_Device();
//  Close_Device();
    gtk_main();
//  exit( EXIT_SUCCESS );

    return 0;
}

Comments