4 min read

v412 사용하기

리눅스에서는 카메라 캡처하기 위해 유저 인터페이스인 v412를 제공한다. 그래서 v412에 대해 정리를 해볼려고 한다.

v412는 다음과 같은 과정을 진행한다.

  1. 장치 열기
  2. 비디오 장치 데이터 얻기
  3. 비디오 장치 데이터 설정
  4. 비디오 버퍼 요청
  5. 카메라의 스트림을 킨다.

1. 장치 열기

리눅스인 경우 드라이버는 파일이기 파일로 취급을 한다. 그래서 장치를 여는 행위는 파일을 여는 행위랑 같습니다. 코드는 다음과 같다.

_fd = open(dev.c_str(), O_RDWR);
if (_fd == -1) {
	return false;
}

2. 비디오 장치 데이터 얻기

해당 드라이버가 가능한 기술들에 대해 데이터를 얻어야 한다.

데이터를 얻기 위해서는 하드웨어에 직접 질의를 해야합니다만, 유저레벨에서 하드웨어의 데이터를 직접 가져올수 없다. 그래서 리눅스는 IOCTL라는 인터페이스를 제공한다.

struct v4l2_capability cap;
result = ioctl(_fd, VIDIOC_QUERYCAP, &cap);

if (result == -1) {
	return false;
}
printf("driver:\t\t%s\n", cap.driver);
printf("card:\t\t%s\n", cap.card);
printf("bus_info:\t%s\n", cap.bus_info);
printf("version:\t%d\n", cap.version);
printf("capabilities:\t%x\n", cap.capabilities);

cap.capabilities의 경우 or 비트를 활용해서 구성되어 있다..

자세한 비트는 링크를 https://www.linuxtv.org/downloads/v4l-dvb-apis-old/vidioc-querycap.html 확인 할수 있다.

저희의 목적은캡처이므로 V4L2_CAP_STREAMING비트와 V4L2_CAP_VIDEO_CAPTURE비트가 있는지 확인해야 한다.

마지막으로 드라이브에서 제공하는 format을 알아야 한다. 이는 드라이버마다 제각각이지만 대부분의 경우 RAW데이터와 MJPG를 지원한다.

mfmtdesc.index = 0;
mfmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
while (ioctl(_fd, VIDIOC_ENUM_FMT, &mfmtdesc) != -1)
{
	std::cout << mfmtdesc.description << std::endl;
	mfmtdesc.index++;
}

마지막으로 드라이버에서 제공가능하는 해상도를 찾아야한다.

하드웨어마다 제공가능한 해상도가 제각기 다르기때문에 이를 설정해 주어야한다.

이는 다음과 같이 구할수 있다.

    fmt.type = type;
    while (ioctl(fd, VIDIOC_ENUM_FMT, &fmt) >= 0) {
        frmsize.pixel_format = fmt.pixelformat;
        frmsize.index = 0;
        while (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) >= 0) {
            if (frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
                printf("%dx%d\n", 
                                  frmsize.discrete.width,
                                  frmsize.discrete.height);
            } else if (frmsize.type == V4L2_FRMSIZE_TYPE_STEPWISE) {
                printf("%dx%d\n", 
                                  frmsize.stepwise.max_width,
                                  frmsize.stepwise.max_height);
            }
                frmsize.index++;
            }
            fmt.index++;
    }

3. 비디오 장치 데이터 설정

2번에서 구한 너비와 높이를 저장해두었다가.

ioctl의 VIDIOC_S_FMT를 주어서 fmt을 지정해준다.

코드는 다음과 같다.

v4l2_format request;

request.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

request.fmt.pix.pixelformat = far.pixel_format;
request.fmt.pix.width = far.width;
request.fmt.pix.height = far.height;


if (ioctl(_fd, VIDIOC_S_FMT, &request) == -1)
{
	return false;
}

4. 비디오 버퍼 요청

VIDIOC_REQBUFS를 디바이스에 버퍼를 할당을 요청을 할수있다.

버퍼는 mmap, user pointer을 설정할수있는데 , 유저 포인터를 이용하면 커널에 있던값을 유저에 또 다시 복사를 해주기 때문에 mmap 타입으로 요청하는게 좋다

v4l2_requestbuffers req = { 0 };
req.count = 1;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;

if (ioctl(_fd, VIDIOC_REQBUFS, &req) == -1)
{
	return -1;
}

앞서 버퍼 config를 설정요청을 하였으니 하드웨어에 해당하는 곳에 프레임을 넣어달라구 요청해야한한다.

v4l2_buffer buf = { 0 };
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = 0;
if (xioctl(_fd, VIDIOC_QBUF, &buf) == -1)
{
	return false;
}

5. 이미지 캡처

모든 준비가 되었으니 카메라 스트림을 켜주고 select등으로 값을 받아온다.

fd_set fds;
FD_ZERO(&fds);
FD_SET(_fd, &fds);
struct timeval tv = { 0 };
tv.tv_sec = 2;
int r = select(_fd + 1, &fds, NULL, NULL, &tv);
if (-1 == r)
{
	return false;
}

if (-1 == xioctl(_fd, VIDIOC_DQBUF, &buf))
{
	return false;
}

를 해주면 아까 mmap한 메모리에 데이터 이미지 데이터가 캡처된다.