요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. 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.
*/
#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) {
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))
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;
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)
v4l2_munmap(buffers[i].start, buffers[i].length);
v4l2_close(fd);
return 0;
}
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와 오류 문자열을 출력한 뒤 프로그램을 종료합니다.
캡처 루프가 의존하는 작은 추상화입니다.
#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 헤더에도 그 값을 사용합니다.
실제 스트리밍 전에 확정해야 하는 순서입니다.
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` 주소에서 읽습니다.
요청에서 해제까지 각 객체가 맡는 역할입니다.
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, 짧은 쓰기, 안전한 파일 이름 생성과 플랫폼별 이진 모드를 추가로 다뤄야 합니다.
버퍼 소유권이 드라이버와 응용 프로그램 사이를 왕복합니다.
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-16920개 프레임 저장이 끝나면 캡처 유형을 다시 지정해 `VIDIOC_STREAMOFF`를 호출합니다. 모든 매핑을 원래 길이로 `v4l2_munmap()`하고 `v4l2_close()`로 장치를 닫은 뒤 성공을 뜻하는 0을 반환합니다. 사용자 공간의 `buffers` 배열을 `free()`하지는 않지만 프로세스 종료와 함께 회수됩니다.
v4l2_munmap(buffers[i].start, buffers[i].length);
v4l2_close(fd);
return 0;
}
요약·해설
v4l2grab.c.rst:1-169v4l2_open부터 RGB24 형식 협상, MMAP 버퍼, 스트리밍, 20개 PPM 프레임 저장과 정리까지 전체 C 예제를 해설합니다.