← Documents Documentation/userspace-api/media/v4l/v4l2grab.c.rst GitHub 원문 ↗

Linux 6.18.37 · 사용자 공간 API

v4l2grab.c 전체 예제

v4l2_open부터 RGB24 형식 협상, MMAP 버퍼, 스트리밍, 20개 PPM 프레임 저장과 정리까지 전체 C 예제를 해설합니다.

Source pathDocumentation/userspace-api/media/v4l/v4l2grab.c.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약·해설

v4l2grab.c.rst:1-169

v4l2_open부터 RGB24 형식 협상, MMAP 버퍼, 스트리밍, 20개 PPM 프레임 저장과 정리까지 전체 C 예제를 해설합니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 .. SPDX-License-Identifier: GFDL-1.1-no-invariants-or-later
2
3 file: media/v4l/v4l2grab.c
4 ==========================
5
6 .. code-block:: c
7
8 /* V4L2 video picture grabber
9 Copyright (C) 2009 Mauro Carvalho Chehab <[email protected]>
10
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation version 2 of the License.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19 */
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <fcntl.h>
25 #include <errno.h>
26 #include <sys/ioctl.h>
27 #include <sys/types.h>
28 #include <sys/time.h>
29 #include <sys/mman.h>
30 #include <linux/videodev2.h>
31 #include "../libv4l/include/libv4l2.h"
32
33 #define CLEAR(x) memset(&(x), 0, sizeof(x))
34
35 struct buffer {
36 void *start;
37 size_t length;
38 };
39
40 static void xioctl(int fh, int request, void *arg)
41 {
42 int r;
43
44 do {
45 r = v4l2_ioctl(fh, request, arg);
46 } while (r == -1 && ((errno == EINTR) || (errno == EAGAIN)));
47
48 if (r == -1) {
49 fprintf(stderr, "error %d, %s\n", errno, strerror(errno));
50 exit(EXIT_FAILURE);
51 }
52 }
53
54 int main(int argc, char **argv)
55 {
56 struct v4l2_format fmt;
57 struct v4l2_buffer buf;
58 struct v4l2_requestbuffers req;
59 enum v4l2_buf_type type;
60 fd_set fds;
61 struct timeval tv;
62 int r, fd = -1;
63 unsigned int i, n_buffers;
64 char *dev_name = "/dev/video0";
65 char out_name[256];
66 FILE *fout;
67 struct buffer *buffers;
68
69 fd = v4l2_open(dev_name, O_RDWR | O_NONBLOCK, 0);
70 if (fd < 0) {
71 perror("Cannot open device");
72 exit(EXIT_FAILURE);
73 }
74
75 CLEAR(fmt);
76 fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
77 fmt.fmt.pix.width = 640;
78 fmt.fmt.pix.height = 480;
79 fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24;
80 fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
81 xioctl(fd, VIDIOC_S_FMT, &fmt);
82 if (fmt.fmt.pix.pixelformat != V4L2_PIX_FMT_RGB24) {
83 printf("Libv4l didn't accept RGB24 format. Can't proceed.\n");
84 exit(EXIT_FAILURE);
85 }
86 if ((fmt.fmt.pix.width != 640) || (fmt.fmt.pix.height != 480))
87 printf("Warning: driver is sending image at %dx%d\n",
88 fmt.fmt.pix.width, fmt.fmt.pix.height);
89
90 CLEAR(req);
91 req.count = 2;
92 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
93 req.memory = V4L2_MEMORY_MMAP;
94 xioctl(fd, VIDIOC_REQBUFS, &req);
95
96 buffers = calloc(req.count, sizeof(*buffers));
97 for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {
98 CLEAR(buf);
99
100 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
101 buf.memory = V4L2_MEMORY_MMAP;
102 buf.index = n_buffers;
103
104 xioctl(fd, VIDIOC_QUERYBUF, &buf);
105
106 buffers[n_buffers].length = buf.length;
107 buffers[n_buffers].start = v4l2_mmap(NULL, buf.length,
108 PROT_READ | PROT_WRITE, MAP_SHARED,
109 fd, buf.m.offset);
110
111 if (MAP_FAILED == buffers[n_buffers].start) {
112 perror("mmap");
113 exit(EXIT_FAILURE);
114 }
115 }
116
117 for (i = 0; i < n_buffers; ++i) {
118 CLEAR(buf);
119 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
120 buf.memory = V4L2_MEMORY_MMAP;
121 buf.index = i;
122 xioctl(fd, VIDIOC_QBUF, &buf);
123 }
124 type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
125
126 xioctl(fd, VIDIOC_STREAMON, &type);
127 for (i = 0; i < 20; i++) {
128 do {
129 FD_ZERO(&fds);
130 FD_SET(fd, &fds);
131
132 /* Timeout. */
133 tv.tv_sec = 2;
134 tv.tv_usec = 0;
135
136 r = select(fd + 1, &fds, NULL, NULL, &tv);
137 } while ((r == -1 && (errno == EINTR)));
138 if (r == -1) {
139 perror("select");
140 return errno;
141 }
142
143 CLEAR(buf);
144 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
145 buf.memory = V4L2_MEMORY_MMAP;
146 xioctl(fd, VIDIOC_DQBUF, &buf);
147
148 sprintf(out_name, "out%03d.ppm", i);
149 fout = fopen(out_name, "w");
150 if (!fout) {
151 perror("Cannot open image");
152 exit(EXIT_FAILURE);
153 }
154 fprintf(fout, "P6\n%d %d 255\n",
155 fmt.fmt.pix.width, fmt.fmt.pix.height);
156 fwrite(buffers[buf.index].start, buf.bytesused, 1, fout);
157 fclose(fout);
158
159 xioctl(fd, VIDIOC_QBUF, &buf);
160 }
161
162 type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
163 xioctl(fd, VIDIOC_STREAMOFF, &type);
164 for (i = 0; i < n_buffers; ++i)
165 v4l2_munmap(buffers[i].start, buffers[i].length);
166 v4l2_close(fd);
167
168 return 0;
169 }
170

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

파일 표제와 사용 허가

1-19

이 절은 `media/v4l/v4l2grab.c` 파일임을 밝히고 C 코드 블록을 시작합니다. 예제는 Mauro Carvalho Chehab가 2009년에 작성한 V4L2 영상 캡처 프로그램이며 GNU GPL 버전 2의 조건에 따라 재배포하거나 수정할 수 있습니다.

프로그램은 유용하기를 바라며 제공되지만 상품성이나 특정 목적 적합성을 포함한 어떤 보증도 하지 않습니다. 자세한 조건은 GNU General Public License를 참조해야 합니다.

.. SPDX-License-Identifier: GFDL-1.1-no-invariants-or-later

file: media/v4l/v4l2grab.c
==========================

.. code-block:: c

    /* V4L2 video picture grabber
       Copyright (C) 2009 Mauro Carvalho Chehab <[email protected]>

       This program is free software; you can redistribute it and/or modify
       it under the terms of the GNU General Public License as published by
       the Free Software Foundation version 2 of the License.

       This program is distributed in the hope that it will be useful,
       but WITHOUT ANY WARRANTY; without even the implied warranty of
       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       GNU General Public License for more details.
     */

헤더, 버퍼 구조체와 ioctl 래퍼

20-48

표준 입출력, 메모리, 문자열, 파일 제어, 오류, ioctl, 시간, MMAP과 `linux/videodev2.h` 헤더를 포함합니다. 마지막으로 libv4l2 헤더를 포함해 원시 시스템 호출 대신 `v4l2_open()`, `v4l2_ioctl()`, `v4l2_mmap()` 계열의 호환 처리 함수를 사용합니다.

`CLEAR(x)`는 구조체 전체를 0으로 초기화합니다. `struct buffer`는 매핑한 버퍼의 시작 주소와 길이를 보관합니다. `xioctl()`은 `EINTR` 또는 `EAGAIN`이면 `v4l2_ioctl()`을 다시 호출하고, 그 밖의 실패에서는 errno와 오류 문자열을 출력한 뒤 프로그램을 종료합니다.

도우미 구성
구성역할
`CLEAR(x)`V4L2 구조체의 예약 필드까지 0으로 초기화
`struct buffer`MMAP 주소와 매핑 길이를 쌍으로 보관
`xioctl()`일시적 중단을 재시도하고 최종 오류를 공통 처리

캡처 루프가 의존하는 작은 추상화입니다.


    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <sys/ioctl.h>
    #include <sys/types.h>
    #include <sys/time.h>
    #include <sys/mman.h>
    #include <linux/videodev2.h>
    #include "../libv4l/include/libv4l2.h"

    #define CLEAR(x) memset(&(x), 0, sizeof(x))

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

    static void xioctl(int fh, int request, void *arg)
    {
	    int r;

	    do {
		    r = v4l2_ioctl(fh, request, arg);
	    } while (r == -1 && ((errno == EINTR) || (errno == EAGAIN)));

	    if (r == -1) {

장치 열기와 RGB24 형식 설정

49-86

`main()`은 형식, 버퍼, 버퍼 요청, 버퍼 유형, `select()`용 fd 집합과 제한 시간, 장치 이름, 출력 파일 이름, 파일 포인터와 버퍼 배열을 준비합니다. 기본 장치는 `/dev/video0`이며 읽기·쓰기와 비차단 모드로 엽니다.

`VIDIOC_S_FMT`에 캡처 유형, 640x480 크기, `V4L2_PIX_FMT_RGB24`, `V4L2_FIELD_INTERLACED`를 전달합니다. libv4l이 RGB24 요청을 받아들이지 않으면 PPM으로 직접 저장할 수 없으므로 종료합니다. 드라이버가 크기를 조정했다면 실제 너비와 높이를 경고로 출력하고 이후 PPM 헤더에도 그 값을 사용합니다.

장치와 형식 준비
v4l2_open(/dev/video0, O_RDWR | O_NONBLOCK)v4l2_format을 0으로 초기화640x480 RGB24 인터레이스 요청VIDIOC_S_FMT반환된 픽셀 형식과 크기 확인

실제 스트리밍 전에 확정해야 하는 순서입니다.

		    fprintf(stderr, "error %d, %s\n", errno, strerror(errno));
		    exit(EXIT_FAILURE);
	    }
    }

    int main(int argc, char **argv)
    {
	    struct v4l2_format              fmt;
	    struct v4l2_buffer              buf;
	    struct v4l2_requestbuffers      req;
	    enum v4l2_buf_type              type;
	    fd_set                          fds;
	    struct timeval                  tv;
	    int                             r, fd = -1;
	    unsigned int                    i, n_buffers;
	    char                            *dev_name = "/dev/video0";
	    char                            out_name[256];
	    FILE                            *fout;
	    struct buffer                   *buffers;

	    fd = v4l2_open(dev_name, O_RDWR | O_NONBLOCK, 0);
	    if (fd < 0) {
		    perror("Cannot open device");
		    exit(EXIT_FAILURE);
	    }

	    CLEAR(fmt);
	    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	    fmt.fmt.pix.width       = 640;
	    fmt.fmt.pix.height      = 480;
	    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24;
	    fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;
	    xioctl(fd, VIDIOC_S_FMT, &fmt);
	    if (fmt.fmt.pix.pixelformat != V4L2_PIX_FMT_RGB24) {
		    printf("Libv4l didn't accept RGB24 format. Can't proceed.\n");
		    exit(EXIT_FAILURE);
	    }
	    if ((fmt.fmt.pix.width != 640) || (fmt.fmt.pix.height != 480))

MMAP 캡처 버퍼 요청과 매핑

87-121

`VIDIOC_REQBUFS`로 `V4L2_MEMORY_MMAP` 방식의 비디오 캡처 버퍼 2개를 요청합니다. 드라이버가 실제로 허용한 `req.count` 크기만큼 사용자 공간의 `struct buffer` 배열을 할당합니다.

각 인덱스에 `VIDIOC_QUERYBUF`를 호출해 버퍼 길이와 오프셋을 얻고, `v4l2_mmap()`으로 읽기·쓰기 가능한 공유 메모리에 매핑합니다. 매핑 결과가 `MAP_FAILED`이면 오류를 출력하고 즉시 종료합니다. 이후 프레임 데이터는 이 배열의 `start` 주소에서 읽습니다.

MMAP 버퍼 수명
단계API보존 값
요청`VIDIOC_REQBUFS`드라이버가 승인한 `req.count`
조회`VIDIOC_QUERYBUF``buf.length`, `buf.m.offset`
매핑`v4l2_mmap()``buffers[i].start`, `buffers[i].length`
해제`v4l2_munmap()`각 매핑의 주소와 길이

요청에서 해제까지 각 객체가 맡는 역할입니다.

		    printf("Warning: driver is sending image at %dx%d\n",
			    fmt.fmt.pix.width, fmt.fmt.pix.height);

	    CLEAR(req);
	    req.count = 2;
	    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	    req.memory = V4L2_MEMORY_MMAP;
	    xioctl(fd, VIDIOC_REQBUFS, &req);

	    buffers = calloc(req.count, sizeof(*buffers));
	    for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {
		    CLEAR(buf);

		    buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		    buf.memory      = V4L2_MEMORY_MMAP;
		    buf.index       = n_buffers;

		    xioctl(fd, VIDIOC_QUERYBUF, &buf);

		    buffers[n_buffers].length = buf.length;
		    buffers[n_buffers].start = v4l2_mmap(NULL, buf.length,
				  PROT_READ | PROT_WRITE, MAP_SHARED,
				  fd, buf.m.offset);

		    if (MAP_FAILED == buffers[n_buffers].start) {
			    perror("mmap");
			    exit(EXIT_FAILURE);
		    }
	    }

	    for (i = 0; i < n_buffers; ++i) {
		    CLEAR(buf);
		    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		    buf.memory = V4L2_MEMORY_MMAP;
		    buf.index = i;

큐잉, 스트리밍과 PPM 저장

122-164

모든 MMAP 버퍼를 `VIDIOC_QBUF`로 드라이버의 수신 큐에 넣고 `VIDIOC_STREAMON`으로 캡처를 시작합니다. 루프는 20회 실행되며, 각 반복에서 `select()`로 최대 2초 동안 장치 fd가 준비되기를 기다립니다. 신호로 중단된 `select()`만 재시도하고 다른 실패는 errno를 반환합니다.

준비된 프레임은 `VIDIOC_DQBUF`로 꺼냅니다. 출력 이름은 `out000.ppm` 형식으로 만들고 P6 PPM 헤더에 반환된 너비와 높이, 최대 색상값 255를 기록합니다. 이어서 `buffers[buf.index].start`에서 `buf.bytesused`바이트를 쓰고 파일을 닫은 뒤 같은 버퍼를 `VIDIOC_QBUF`로 다시 큐에 넣습니다.

이 예제는 `select()`가 제한 시간 0으로 끝난 경우를 별도로 처리하지 않으며 `sprintf()`와 텍스트 모드 `fopen(..., "w")`를 사용합니다. 문서의 핵심은 최소 캡처 절차를 보여 주는 것이므로, 실제 응용 프로그램은 timeout, 짧은 쓰기, 안전한 파일 이름 생성과 플랫폼별 이진 모드를 추가로 다뤄야 합니다.

20프레임 캡처 루프
모든 버퍼 VIDIOC_QBUFVIDIOC_STREAMONselect()로 프레임 대기VIDIOC_DQBUFP6 헤더와 RGB 데이터 저장VIDIOC_QBUF로 반환

버퍼 소유권이 드라이버와 응용 프로그램 사이를 왕복합니다.

		    xioctl(fd, VIDIOC_QBUF, &buf);
	    }
	    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	    xioctl(fd, VIDIOC_STREAMON, &type);
	    for (i = 0; i < 20; i++) {
		    do {
			    FD_ZERO(&fds);
			    FD_SET(fd, &fds);

			    /* Timeout. */
			    tv.tv_sec = 2;
			    tv.tv_usec = 0;

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

		    CLEAR(buf);
		    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		    buf.memory = V4L2_MEMORY_MMAP;
		    xioctl(fd, VIDIOC_DQBUF, &buf);

		    sprintf(out_name, "out%03d.ppm", i);
		    fout = fopen(out_name, "w");
		    if (!fout) {
			    perror("Cannot open image");
			    exit(EXIT_FAILURE);
		    }
		    fprintf(fout, "P6\n%d %d 255\n",
			    fmt.fmt.pix.width, fmt.fmt.pix.height);
		    fwrite(buffers[buf.index].start, buf.bytesused, 1, fout);
		    fclose(fout);

		    xioctl(fd, VIDIOC_QBUF, &buf);
	    }

	    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	    xioctl(fd, VIDIOC_STREAMOFF, &type);
	    for (i = 0; i < n_buffers; ++i)

스트림 중지와 자원 정리

165-169

20개 프레임 저장이 끝나면 캡처 유형을 다시 지정해 `VIDIOC_STREAMOFF`를 호출합니다. 모든 매핑을 원래 길이로 `v4l2_munmap()`하고 `v4l2_close()`로 장치를 닫은 뒤 성공을 뜻하는 0을 반환합니다. 사용자 공간의 `buffers` 배열을 `free()`하지는 않지만 프로세스 종료와 함께 회수됩니다.

		    v4l2_munmap(buffers[i].start, buffers[i].length);
	    v4l2_close(fd);

	    return 0;
    }