2024年4月23日 星期二

linux 上使用 libuvc 開啟 usb camera

先安裝 libx11, libuvc, libusb 程式庫:

    sudo  apt   install   libx11-dev  libuvc-dev   libusb-1.0-0-dev

//  原始程式 xwin.cpp
#include "libuvc/libuvc.h"
#include <locale.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xresource.h>
#include <X11/cursorfont.h>
#include <signal.h>
class XwinClient {
    private:
        const char *title = "usbCamera";
        int         x, y, width, height;
        Display     *display= nullptr;
        XImage         *bg = nullptr;
        Window      winX;
        GC          gCtx;
        Colormap    cmap;
        XColor      fgColor;
    public:
        XColor     greyColor, blackColor, whiteColor ,redColor, greenColor, blueColor, yellowColor;
    ~XwinClient(){
        if (bg) XDestroyImage(bg);
        if (display) {
            XFreeColormap(display, cmap);
            XFreeGC(display, gCtx);
            XDestroyWindow(display, winX);
            XCloseDisplay(display);   
        }
    }
    XwinClient(int w = 800, int h = 600) {
        width = w;
        height = h;
        display = XOpenDisplay(getenv("DISPLAY"));
        winX = XCreateSimpleWindow(display,
            XDefaultRootWindow(display), 0, 0, w, h, 0,
            WhitePixel(display, 0),
            BlackPixel(display, 0)
        );
        gCtx = XCreateGC(display, winX, 0, 0);
        cmap = DefaultColormap(display, 0);        
        auto setColorMap = [this] (XColor &c, const char *str){
            XParseColor(display, cmap, str, &c);
            XAllocColor(display, cmap, &c);
        };
        setColorMap(whiteColor , "#FFFFFF");
        setColorMap(blackColor , "#000000");
        setColorMap(redColor   , "#FF0000");
        setColorMap(greenColor , "#00FF00");
        setColorMap(blueColor  , "#0000FF");
        setColorMap(greyColor  , "#7f7f7f");
        setColorMap(yellowColor, "#FFFF00");
        fgColor = redColor;
        XSetForeground(display, gCtx, fgColor.pixel);
        XSetStandardProperties(display, winX, title,
            "Icon", None, nullptr, 0, nullptr
        );
        XSelectInput(display, winX,
            StructureNotifyMask |
            ExposureMask         |
            ButtonPressMask     |
            KeyPressMask        |
            PointerMotionMask
        );
        XMapWindow(display, winX);
        XEvent         evt;
        while (true) {
            usleep(1000);
            XEvent evt;
            XNextEvent(display, &evt);
            if(evt.type == Expose && evt.xexpose.count == 0) break;
        }
        XFlush(display);
        XClearWindow(display, winX);
        XSync(display, false);
    }

    void setImageBGR(void *rgb888, int sw, int sh, int sch) {
        while (true) {
            if (bg) XDestroyImage(bg);
            bg = nullptr;
            XWindowAttributes info;
            XGetWindowAttributes(display, winX, &info);
            Visual *v  = info.visual;
            width  = info.width;
            height = info.height;
            int depth  = info.depth;
            int dline= 4 * width;
            void *fb_bg = malloc(dline * height);
            if (fb_bg == nullptr) break;
            bg = XCreateImage(display, v, depth, ZPixmap, 0, (char *)fb_bg, width, height, 32, 0);
            if (bg == nullptr) {
                free(fb_bg);
                break;
            }
            uint8_t *dst = (uint8_t *)fb_bg;
               uint8_t *src = (uint8_t *)rgb888;
            float xs = (float)sw / width ;
            float ys = (float)sh / height;
            int  sline = sch   * sw;
            int  pSize = sline * sh;
            int  zch = (sch > 4) ? 4 : sch;
            int   vk = 0;
            float fy = 0;
            for(int y = 0; y < height && vk < pSize; y ++) {
              int ld = 0;
              int ls = 0;
              float fx = 0;
              for (int x = 0; x < width && ls < sline; x ++) {
                dst[ld] = src[ls];
                for(int z = 1; z < zch; z ++) {
                  dst[ld + z] = src[ls + z];
                }
                ld += 4;
                fx += xs;
                ls = sch * (int)fx;
              }
              dst += dline;
              fy += ys;
              vk = sline * (int)fy;
              src = (uint8_t *)rgb888 + vk;
            }
            XPutImage(display, winX, gCtx, bg, 0, 0, 0, 0, width, height);
            break;
        }
        XSync(display, false);
    }
};

XwinClient x11 = XwinClient(800, 600);
 
void screenUpdate(uvc_frame_t *frame, void *user) {
    static bool active = false;
    int length = 2 * frame->width * frame->height;
    if (active || frame->frame_format != UVC_FRAME_FORMAT_YUYV || length != frame->data_bytes) return;
    active = true;
    auto int8bits = [](float v) { return  (v < 0) ? 0 : (v > 255) ? 0xff : (uint8_t)v; };
    uint8_t *yuyv = (uint8_t *)(frame->data);
    uint8_t *dest = (uint8_t *)user;
    for (int i = 0; i < length; i += 4) {
        int y0  = *yuyv;
        int u   = yuyv[1];
        int y1  = yuyv[2];
        int v   = yuyv[3];
        dest[2] = int8bits(y0 + 1.13983 * (v - 128));// R -> b0
        dest[1] = int8bits(y0 - 0.39465 * (u - 128) - 0.5806 * (v - 128));//G -> g0
        dest[0] = int8bits(y0 + 2.03211 * (u - 128));// B -> r0
        dest[5] = int8bits(y1 + 1.13983 * (v - 128));// R -> b1
        dest[4] = int8bits(y1 - 0.39465 * (u - 128) - 0.5806 * (v - 128));//G -> g1
        dest[3] = int8bits(y1 + 2.03211 * (u - 128));// B -> r1
        yuyv += 4;
        dest += 6;
    }
    x11.setImageBGR(user, frame->width , frame->height, 3);
    active = false;
}

int main(int argc, char **argv) {
      printf("press Ctrl+C to exit.\n");
    signal(SIGINT, [](int sig) { exit(1); });
      uvc_context_t *ctx;
      printf("uvc init ...\n");
      if (uvc_init(&ctx, NULL) == UVC_SUCCESS) {
        uvc_device_t *dev;
        printf("find device ...\n");
        if (uvc_find_device(ctx, &dev, 0, 0, NULL) == UVC_SUCCESS) {
          uvc_device_handle_t *handle;
              printf("open device ...\n");
              if (uvc_open(dev, &handle) == UVC_SUCCESS) { 
                const uvc_format_desc_t *descriptor = uvc_get_format_descs(handle); 
                const uvc_frame_desc_t *hFrame = descriptor->frame_descs;
                int fps = 10000000 / hFrame->dwDefaultFrameInterval;
                  uvc_stream_ctrl_t controller;
                  uvc_frame_format fmt = UVC_FRAME_FORMAT_YUYV;
                  uvc_error_t result = uvc_get_stream_ctrl_format_size(handle, &controller, fmt, hFrame->wWidth, hFrame->wHeight, 10);       
                  if (result == UVC_SUCCESS) {
                      uint8_t *image = new uint8_t[hFrame->wWidth * hFrame->wHeight * 3];
                      uvc_start_streaming(handle, &controller, screenUpdate, image, 0);
                    sleep(10);
                        uvc_stop_streaming(handle);
                    delete [] image;
                  }
                    uvc_close(handle);
              }
            uvc_unref_device(dev);
        }
        uvc_exit(ctx);
      }
}

編譯並執行, 因為開啟 /dev/video 需要權限, 暫時用 sudo 來執行:

     g++  xwin.cpp  -lX11  -luvc  -lusb-1.0  &&  sudo ./a.out


後記:使用原始 libuvc 及 libusb 程式庫編譯
   1. 先到官網下載 libusb 及 libuvc 原始程式庫
         https://github.com/libuvc/libuvc/tags
         https://github.com/libusb/libusb/releases
   2. 在專案根目錄內, 編輯一個檔案: CMakeLists.txt
   3. 在專案根目錄內, 另建三個目錄: lib/    src/    build/ 
   4. 將上述原始程式庫解壓縮, 放到專案 lib/ 目錄內
   5. 在專案根目錄 src/ 目錄內, 再建一個目錄命名為  libuvc/, 只要放入 libuvc_config.h 檔案
   6. 建一個空檔案命名為   libuvc_config.h 放到  src/libuvc/ 目錄內:  src/libuvc/libuvc_config.h
   7. 編輯主程式放到專案 src/ 目錄內:  src/xwin.cpp
   8. 切換到專案目錄下 build/  執行 cmake .. 產生 Makefile
   9. 編譯時, 在專案 build/ 目錄內執行 make 就會產生可執行檔 xwin, 暫時可用 root 權限或用 sudo  ./xwin 來執行
# 檔案 CMakeLists.txt 內容
cmake_minimum_required(VERSION 3.16)
project(xwin)
set(topDir ${PROJECT_SOURCE_DIR})
set(uvcDir ${topDir}/lib/libuvc-0.0.7)
set(usbDir ${topDir}/lib/libusb-1.0.27)
set(uvcSource 
  ${uvcDir}/src/device.c
  ${uvcDir}/src/init.c
  ${uvcDir}/src/stream.c
)
set(usbSource  
  ${usbDir}/libusb/core.c
  ${usbDir}/libusb/descriptor.c
  ${usbDir}/libusb/hotplug.c
  ${usbDir}/libusb/io.c
  ${usbDir}/libusb/strerror.c
  ${usbDir}/libusb/sync.c
  ${usbDir}/libusb/os/events_posix.c
  ${usbDir}/libusb/os/threads_posix.c
  ${usbDir}/libusb/os/linux_usbfs.c
  ${usbDir}/libusb/os/linux_netlink.c
)
include_directories(
    $(topDir)/src
    ${uvcDir}/include
    ${usbDir}/android
    ${usbDir}/libusb
    ${usbDir}/libusb/os
)
add_library(uvcdriver
    ${uvcSource}
    ${usbSource}
)
add_executable(xwin
    ${topDir}/src/xwin.cpp
)
target_link_libraries(xwin  
    X11
    uvcdriver
)

沒有留言:

張貼留言

使用 pcie 轉接器連接 nvme SSD

之前 AM4 主機板使用 pcie ssd, 但主機板故障了沒辦法上網, 只好翻出以前買的 FM2 舊主機板, 想辦法讓老主機復活, 但舊主機板沒有 nvme 的界面, 因此上網買了 pcie 轉接器用來連接 nvme ssd, 遺憾的是 grub2 bootloader 無法識...