Raspberry Pi DRM VIDEO0 출력
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <drm/drm.h>
#include <drm/drm_mode.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <sys/mman.h> // ✅ 이거 추가
#include <linux/videodev2.h>
#define WIDTH 640
#define HEIGHT 480
int main() {
// Open V4L2 device
int v4l2_fd = open("/dev/video0", O_RDWR);
if (v4l2_fd < 0) {
perror("Failed to open /dev/video0");
return 1;
}
// Set V4L2 format
struct v4l2_format fmt = {
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.fmt.pix.width = WIDTH,
.fmt.pix.height = HEIGHT,
.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24,
};
if (ioctl(v4l2_fd, VIDIOC_S_FMT, &fmt) < 0) {
perror("VIDIOC_S_FMT failed");
return 1;
}
// Allocate V4L2 buffer (simplified, no streaming)
unsigned char *frame_buf = malloc(WIDTH * HEIGHT * 3);
// Open DRM device
int drm_fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
if (drm_fd < 0) {
perror("Failed to open DRM device");
return 1;
}
// Get DRM resources
drmModeRes *res = drmModeGetResources(drm_fd);
drmModeConnector *conn = NULL;
drmModeEncoder *enc = NULL;
for (int i = 0; i < res->count_connectors; i++) {
conn = drmModeGetConnector(drm_fd, res->connectors[i]);
if (conn->connection == DRM_MODE_CONNECTED) {
break;
}
drmModeFreeConnector(conn);
conn = NULL;
}
if (!conn) {
fprintf(stderr, "No active connector found\n");
return 1;
}
drmModeModeInfo mode = conn->modes[0];
enc = drmModeGetEncoder(drm_fd, conn->encoder_id);
int crtc_id = enc->crtc_id;
// Dumb buffer 생성
struct drm_mode_create_dumb create = {0};
create.width = mode.hdisplay;
create.height = mode.vdisplay;
create.bpp = 24;
ioctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
uint32_t handle = create.handle;
uint32_t pitch = create.pitch;
uint64_t size = create.size;
struct drm_mode_map_dumb map = {0};
map.handle = handle;
ioctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
uint8_t *drm_buf = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, drm_fd, map.offset);
memset(drm_buf, 0, size);
// 프레임버퍼 생성
uint32_t fb_id;
drmModeAddFB(drm_fd, mode.hdisplay, mode.vdisplay, 24, 24, pitch, handle, &fb_id);
drmModeSetCrtc(drm_fd, crtc_id, fb_id, 0, 0, &conn->connector_id, 1, &mode);
// 영상 루프
while (1) {
read(v4l2_fd, frame_buf, WIDTH * HEIGHT * 3);
// center에 copy (640x480을 디스플레이 해상도 중앙에 표시)
for (int y = 0; y < HEIGHT; y++) {
memcpy(
drm_buf + (y + (mode.vdisplay - HEIGHT)/2) * pitch + (mode.hdisplay - WIDTH) * 3 / 2,
frame_buf + y * WIDTH * 3,
WIDTH * 3
);
}
usleep(33000); // ~30fps
}
return 0;
}
화면이 나타나긴 나는데... 4개 화면이 깨져서 나온다.
해결 방법
1. 캡처된 영상 포맷 확인
/dev/video0에서 가져오는 영상이 어떤 포맷(YUYV, MJPEG 등)인지 확인해야 합니다
v4l2-ctl --device=/dev/video0 --list-formats-ext
예: YUYV (YUV 4:2:2) 형식이면, 그대로 DRM에 뿌릴 수 없습니다.
대부분의 DRM 출력은 RGB888 또는 XRGB8888 형식을 요구합니다.
2. YUV → RGB 변환 필요
변환 없이 memcpy하면 위처럼 깨집니다.
==================================================================
수정코드
예제 코드: YUYV → RGB 변환 (OpenCV 사용)
sudo apt install libopencv-dev v4l-utils
g++ drm_example.cpp -o drm_example -I/usr/include/libdrm `pkg-config --cflags --libs opencv4` -ldrm
대부분의 시스템에서는 pkg-config --cflags --libs libdrm도 자동으로 경로를 처리해줍니다. 만약 pkg-config에 등록돼 있다면 아래처럼도 가능합니다:
g++ drm_example.cpp -o drm_example `pkg-config --cflags --libs opencv4 libdrm`
sudo apt-get install libv4l-dev libdrm-dev libgbm-dev
gcc -o camera_streamer camera_streamer.c \
-lv4l2 -ldrm -lgbm \
-I/usr/include/libdrm \
-Wall -g
카메라(video0)로부터 YUYV 실시간 스트리밍 → RGB 변환 → DRM 출력 소스
개념적인 흐름:
카메라 캡처 (YUYV): /dev/video0과 같은 V4L2(Video for Linux 2) 장치를 통해 카메라 데이터를 YUYV 포맷으로 캡처합니다.
YUYV → RGB 변환: 캡처된 YUYV 데이터를 소프트웨어 또는 하드웨어 가속을 통해 RGB 포맷으로 변환합니다.
DRM(Direct Rendering Manager) 출력: 변환된 RGB 데이터를 DRM을 통해 디스플레이에 직접 렌더링합니다.
필요한 주요 라이브러리 및 API:
V4L2 (Video for Linux 2): 카메라 장치 제어 및 프레임 캡처.
libyuv 또는 OpenCV: YUYV에서 RGB로의 색 공간 변환 (소프트웨어).
DRM (Direct Rendering Manager) API: 리눅스 커널의 그래픽 서브시스템에 접근하여 프레임버퍼, CRTC, 인코더, 커넥터 등을 직접 제어하여 디스플레이에 렌더링.
libdrm: DRM API를 사용자 공간에서 쉽게 사용할 수 있도록 래핑한 라이브러리.
gbm (Generic Buffer Management): DRM과 연동하여 GPU 친화적인 버퍼 할당 및 관리를 수행.
EGL (Embedded-System Graphics Library) / OpenGL ES (Embedded System): GPU 가속을 통한 렌더링 파이프라인 구축 (필요시).
중요 고려사항 및 한계:
- 에러 처리: 위 코드는 간소화된 에러 처리만을 포함하고 있습니다. 실제 애플리케이션에서는 모든 API 호출의 반환 값을 확인하고 적절한 에러 처리를 해야 합니다.
- 리소스 해제: cleanup_v4l2 및 cleanup_drm 함수가 호출되어 할당된 리소스를 해제해야 합니다. 프로그램 종료 시 반드시 호출되도록 해야 합니다.
- YUYV → RGB 변환: yuyv_to_rgb 함수는 매우 기본적인 예시입니다. 실제로는 libyuv 또는 OpenCV와 같은 최적화된 라이브러리를 사용하여 변환하는 것이 성능과 정확성 면에서 훨씬 좋습니다. 특히 GPU 가속을 사용하려면 OpenGL ES/EGL과 연동하여 셰이더를 통해 변환하는 것이 일반적입니다.
- DRM 모드 설정: setup_drm_mode는 첫 번째 커넥터의 첫 번째 모드를 선택하는 간소화된 예시입니다. 실제 애플리케이션에서는 사용자가 원하는 모드를 선택하거나, 디스플레이의 기본 모드를 사용하는 로직이 필요합니다.
- 페이지 플립 동기화: 위 drm_page_flip 함수는 drmModeSetCrtc를 사용하는데, 이는 매 프레임마다 모드를 다시 설정하는 것과 같아서 성능상 비효율적입니다. 진정한 실시간 스트리밍을 위해서는 drmModePageFlip을 사용하여 GPU의 수직 동기(VSync)에 맞춰 프레임버퍼를 전환하는 비동기 페이지 플립 메커니즘을 구현해야 합니다. 이는 drmEventContext와 poll()을 사용한 이벤트 루프를 필요로 합니다.
- 버퍼 관리: 더블 버퍼링 또는 트리플 버퍼링을 사용하여 티어링(Tearing) 현상을 방지하고 부드러운 애니메이션을 구현해야 합니다.
- GPU 가속: YUYV에서 RGB 변환 및 스케일링 등 영상 처리 작업에 GPU 가속을 사용하면 성능을 크게 향상시킬 수 있습니다. 이를 위해서는 EGL 및 OpenGL ES (또는 Vulkan) API를 DRM/GBM과 연동하여 사용해야 합니다. 이 과정은 매우 복잡하며, GPU 셰이더 프로그래밍이 필요합니다.
- 실시간 성능: usleep으로 간단히 딜레이를 주었지만, 실제 실시간 스트리밍에서는 카메라 프레임 속도와 디스플레이 재생 빈도를 맞춰주는 정교한 동기화 메커니즘이 필요합니다.
이 코드는 기본적인 개념을 보여주기 위한 출발점이며, 실제 제품 수준의 솔루션을 개발하려면 각 단계에 대한 더 깊은 이해와 추가적인 개발이 필요합니다. 특히 DRM 프로그래밍은 커널 드라이버와 사용자 공간 라이브러리 간의 복잡한 상호작용을 다루므로 난이도가 높습니다.
Bus error:
- 이것은 심각한 오류로, 프로그램이 잘못된 메모리 주소에 접근하려고 시도했을 때 발생합니다.
- 원인은 여러 가지가 있을 수 있지만, 이 경우에는 주로 YUYV 데이터를 RGB로 변환하는 과정이나, 변환된 RGB 데이터를 DRM 버퍼에 쓰는 과정에서 발생할 가능성이 높습니다.
- 가장 유력한 원인: 카메라 해상도(720x480)와 DRM 출력 해상도(1680x1050)가 다릅니다. 현재 코드는 카메라에서 읽은 YUYV 데이터를 DRM 출력 버퍼의 크기에 맞게 변환하는 로직을 가지고 있지 않습니다. yuyv_to_rgb 함수는 입력 width와 height를 받지만, 출력 rgb 버퍼의 크기가 이 입력 해상도에 맞춰져 있다고 가정합니다. 그러나 실제 출력 버퍼는 DRM 모드 설정에 따라 1680x1050 크기로 할당됩니다.
- 따라서 720x480 크기의 YUYV 데이터를 1680x1050 크기의 RGB 버퍼에 직접 변환하여 쓰면 메모리 오버런(buffer overrun)이 발생하여 'Bus error'가 발생할 수 있습니다. 720x480 크기의 데이터가 1680x1050 크기의 버퍼의 일부분에만 써지거나, 혹은 잘못된 주소에 쓰여서 문제가 될 수 있습니다.
- 해결책:
- 카메라 해상도와 DRM 출력 해상도가 다른 경우, 스케일링(scaling) 작업이 필요합니다.
- 간단한 방법은 카메라 해상도에 맞춰 yuyv_to_rgb 함수가 데이터를 변환한 후, 이 변환된 작은 RGB 데이터를 DRM 버퍼의 원하는 위치(예: 중앙)에 memcpy 등으로 복사하는 것입니다.
- 더 고급스러운 방법은 GPU 가속(OpenGL ES 등)을 사용하여 스케일링과 색 공간 변환을 동시에 처리하는 것입니다.
==================================================================
QT 로 DRM 출력
완료