blob: 5493222dd76f705e033c276de34f152f3903d421 [file] [log] [blame]
#include <array>
#include <vector>
#include <linux/fb.h>
#include "v4l2-ctl.h"
static unsigned int set_fbuf;
static unsigned int set_overlay_fmt;
static struct v4l2_format overlay_fmt; /* set_format/get_format video overlay */
static struct v4l2_framebuffer fbuf; /* fbuf */
static int overlay; /* overlay */
static const char *fb_device;
static std::vector<struct v4l2_clip> clips;
static std::vector<struct v4l2_rect> bitmap_rects;
void overlay_usage()
{
printf("\nVideo Overlay options:\n"
" --list-formats-overlay\n"
" display supported overlay formats [VIDIOC_ENUM_FMT]\n"
" --find-fb find the fb device corresponding with the overlay\n"
" --overlay <on> turn overlay on (1) or off (0) (VIDIOC_OVERLAY)\n"
" --get-fmt-overlay query the video or video output overlay format [VIDIOC_G_FMT]\n"
" --set-fmt-overlay\n"
" --try-fmt-overlay chromakey=<key>,global_alpha=<alpha>,\n"
" top=<t>,left=<l>,width=<w>,height=<h>,field=<f>\n"
" set/try the video or video output overlay format [VIDIOC_TRY/S_FMT]\n"
" <f> can be one of:\n"
" any, none, top, bottom, interlaced, seq_tb, seq_bt,\n"
" alternate, interlaced_tb, interlaced_bt\n"
" If the width or height changed then the old clip list and bitmap will\n"
" be invalidated.\n"
" --clear-clips clear any old clips, to be used in combination with --try/set-fmt-overlay\n"
" --clear-bitmap clear any old bitmap, to be used in combination with --try/set-fmt-overlay\n"
" --add-clip top=<t>,left=<l>,width=<w>,height=<h>\n"
" Add an entry to the clip list. May be used multiple times.\n"
" This clip list will be passed to --try/set-fmt-overlay\n"
" --add-bitmap top=<t>,left=<l>,width=<w>,height=<h>\n"
" Set the bits in the given rectangle in the bitmap to 1. May be\n"
" used multiple times.\n"
" The bitmap will be passed to --try/set-fmt-overlay\n"
" --get-fbuf query the overlay framebuffer data [VIDIOC_G_FBUF]\n"
" --set-fbuf chromakey=<b>,src_chromakey=<b>,global_alpha=<b>,local_alpha=<b>,local_inv_alpha=<b>,fb=<fb>\n"
" set the overlay framebuffer [VIDIOC_S_FBUF]\n"
" <b> is 0 or 1\n"
" <fb> is the framebuffer device (/dev/fbX)\n"
" if <fb> starts with a digit, then /dev/fb<fb> is used\n"
);
}
static void printfbuf(const struct v4l2_framebuffer &fb)
{
int is_ext = fb.capability & V4L2_FBUF_CAP_EXTERNOVERLAY;
printf("Framebuffer Format:\n");
printf("\tCapability : %s", fbufcap2s(fb.capability).c_str() + 3);
printf("\tFlags : %s", fbufflags2s(fb.flags).c_str() + 3);
if (fb.base)
printf("\tBase : %p\n", fb.base);
printf("\tWidth : %d\n", fb.fmt.width);
printf("\tHeight : %d\n", fb.fmt.height);
printf("\tPixel Format : '%s'\n", fcc2s(fb.fmt.pixelformat).c_str());
if (!is_ext) {
printf("\tBytes per Line: %d\n", fb.fmt.bytesperline);
printf("\tSize image : %d\n", fb.fmt.sizeimage);
printf("\tColorspace : %s\n", colorspace2s(fb.fmt.colorspace).c_str());
if (fb.fmt.priv)
printf("\tCustom Info : %08x\n", fb.fmt.priv);
}
}
static void find_fb(int fd)
{
struct v4l2_framebuffer fbuf;
unsigned int i;
int fb_fd;
if (doioctl(fd, VIDIOC_G_FBUF, &fbuf))
return;
if (fbuf.base == nullptr) {
printf("No framebuffer base address was defined\n");
return;
}
for (i = 0; i < 30; i++) {
struct fb_fix_screeninfo si;
char dev_name[16];
snprintf(dev_name, sizeof(dev_name), "/dev/fb%u", i);
fb_fd = open(dev_name, O_RDWR);
if (fb_fd == -1) {
switch (errno) {
case ENOENT: /* no such file */
case ENXIO: /* no driver */
continue;
default:
return;
}
}
if (!ioctl(fb_fd, FBIOGET_FSCREENINFO, &si))
if (si.smem_start == (unsigned long)fbuf.base) {
printf("%s is the framebuffer associated with base address %p\n",
dev_name, fbuf.base);
close(fb_fd);
return;
}
close(fb_fd);
fb_fd = -1;
}
printf("No matching framebuffer found for base address %p\n", fbuf.base);
}
struct bitfield2fmt {
unsigned red_off, red_len;
unsigned green_off, green_len;
unsigned blue_off, blue_len;
unsigned transp_off, transp_len;
__u32 pixfmt;
};
static constexpr std::array<bitfield2fmt, 8> fb_formats{
bitfield2fmt{ 10, 5, 5, 5, 0, 5, 15, 1, V4L2_PIX_FMT_ARGB555 },
bitfield2fmt{ 11, 5, 5, 6, 0, 5, 0, 0, V4L2_PIX_FMT_RGB565 },
bitfield2fmt{ 1, 5, 6, 5, 11, 5, 0, 1, V4L2_PIX_FMT_RGB555X },
bitfield2fmt{ 0, 5, 5, 6, 11, 5, 0, 0, V4L2_PIX_FMT_RGB565X },
bitfield2fmt{ 16, 8, 8, 8, 0, 8, 0, 0, V4L2_PIX_FMT_BGR24 },
bitfield2fmt{ 0, 8, 8, 8, 16, 8, 0, 0, V4L2_PIX_FMT_RGB24 },
bitfield2fmt{ 16, 8, 8, 8, 0, 8, 24, 8, V4L2_PIX_FMT_ABGR32 },
bitfield2fmt{ 8, 8, 16, 8, 24, 8, 0, 8, V4L2_PIX_FMT_ARGB32 },
};
static bool match_bitfield(const struct fb_bitfield &bf, unsigned off, unsigned len)
{
if (bf.msb_right || bf.length != len)
return false;
return !len || bf.offset == off;
}
static int fbuf_fill_from_fb(struct v4l2_framebuffer &fb, const char *fb_device)
{
struct fb_fix_screeninfo si;
struct fb_var_screeninfo vi;
int fb_fd;
if (fb_device == nullptr)
return 0;
fb_fd = open(fb_device, O_RDWR);
if (fb_fd == -1) {
fprintf(stderr, "cannot open %s\n", fb_device);
return -1;
}
if (ioctl(fb_fd, FBIOGET_FSCREENINFO, &si)) {
fprintf(stderr, "could not obtain fscreeninfo from %s\n", fb_device);
close(fb_fd);
return -1;
}
if (ioctl(fb_fd, FBIOGET_VSCREENINFO, &vi)) {
fprintf(stderr, "could not obtain vscreeninfo from %s\n", fb_device);
close(fb_fd);
return -1;
}
fb.base = (void *)si.smem_start;
fb.fmt.sizeimage = si.smem_len;
fb.fmt.width = vi.xres;
fb.fmt.height = vi.yres;
fb.fmt.bytesperline = si.line_length;
if (fb.fmt.height * fb.fmt.bytesperline > fb.fmt.sizeimage) {
fprintf(stderr, "height * bytesperline > sizeimage?!\n");
close(fb_fd);
return -1;
}
fb.fmt.pixelformat = 0;
if ((si.capabilities & FB_CAP_FOURCC) && vi.grayscale > 1) {
fb.fmt.pixelformat = vi.grayscale;
fb.fmt.colorspace = vi.colorspace;
} else {
if (vi.grayscale == 1) {
if (vi.bits_per_pixel == 8)
fb.fmt.pixelformat = V4L2_PIX_FMT_GREY;
} else {
for (const auto &p : fb_formats) {
if (match_bitfield(vi.blue, p.blue_off, p.blue_len) &&
match_bitfield(vi.green, p.green_off, p.green_len) &&
match_bitfield(vi.red, p.red_off, p.red_len) &&
match_bitfield(vi.transp, p.transp_off, p.transp_len)) {
fb.fmt.pixelformat = p.pixfmt;
break;
}
}
}
fb.fmt.colorspace = V4L2_COLORSPACE_SRGB;
}
printfbuf(fb);
close(fb_fd);
return 0;
}
void overlay_cmd(int ch, char *optarg)
{
struct v4l2_rect r;
char *value, *subs;
switch (ch) {
case OptSetOverlayFormat:
case OptTryOverlayFormat:
subs = optarg;
while (subs && *subs != '\0') {
static constexpr const char *subopts[] = {
"chromakey",
"global_alpha",
"left",
"top",
"width",
"height",
"field",
nullptr
};
switch (parse_subopt(&subs, subopts, &value)) {
case 0:
overlay_fmt.fmt.win.chromakey = strtoul(value, nullptr, 0);
set_overlay_fmt |= FmtChromaKey;
break;
case 1:
overlay_fmt.fmt.win.global_alpha = strtoul(value, nullptr, 0);
set_overlay_fmt |= FmtGlobalAlpha;
break;
case 2:
overlay_fmt.fmt.win.w.left = strtol(value, nullptr, 0);
set_overlay_fmt |= FmtLeft;
break;
case 3:
overlay_fmt.fmt.win.w.top = strtol(value, nullptr, 0);
set_overlay_fmt |= FmtTop;
break;
case 4:
overlay_fmt.fmt.win.w.width = strtoul(value, nullptr, 0);
set_overlay_fmt |= FmtWidth;
break;
case 5:
overlay_fmt.fmt.win.w.height = strtoul(value, nullptr, 0);
set_overlay_fmt |= FmtHeight;
break;
case 6:
overlay_fmt.fmt.win.field = parse_field(value);
set_overlay_fmt |= FmtField;
break;
default:
overlay_usage();
break;
}
}
break;
case OptAddClip:
case OptAddBitmap:
subs = optarg;
memset(&r, 0, sizeof(r));
while (*subs != '\0') {
static constexpr const char *subopts[] = {
"left",
"top",
"width",
"height",
nullptr
};
switch (parse_subopt(&subs, subopts, &value)) {
case 0:
r.left = strtoul(value, nullptr, 0);
break;
case 1:
r.top = strtoul(value, nullptr, 0);
break;
case 2:
r.width = strtoul(value, nullptr, 0);
break;
case 3:
r.height = strtoul(value, nullptr, 0);
break;
default:
overlay_usage();
return;
}
}
if (r.width == 0 || r.height == 0) {
overlay_usage();
return;
}
if (ch == OptAddBitmap) {
bitmap_rects.push_back(r);
} else {
struct v4l2_clip c;
c.c = r;
c.next = nullptr;
clips.push_back(c);
}
break;
case OptSetFBuf:
subs = optarg;
while (*subs != '\0') {
constexpr unsigned chroma_flags = V4L2_FBUF_FLAG_CHROMAKEY |
V4L2_FBUF_FLAG_SRC_CHROMAKEY;
constexpr unsigned alpha_flags = V4L2_FBUF_FLAG_GLOBAL_ALPHA |
V4L2_FBUF_FLAG_LOCAL_ALPHA |
V4L2_FBUF_FLAG_LOCAL_INV_ALPHA;
static constexpr const char *subopts[] = {
"chromakey",
"src_chromakey",
"global_alpha",
"local_alpha",
"local_inv_alpha",
"fb",
nullptr
};
switch (parse_subopt(&subs, subopts, &value)) {
case 0:
fbuf.flags &= ~chroma_flags;
fbuf.flags |= strtol(value, nullptr, 0) ? V4L2_FBUF_FLAG_CHROMAKEY : 0;
set_fbuf |= chroma_flags;
break;
case 1:
fbuf.flags &= ~chroma_flags;
fbuf.flags |= strtol(value, nullptr, 0) ? V4L2_FBUF_FLAG_SRC_CHROMAKEY : 0;
set_fbuf |= chroma_flags;
break;
case 2:
fbuf.flags &= ~alpha_flags;
fbuf.flags |= strtol(value, nullptr, 0) ? V4L2_FBUF_FLAG_GLOBAL_ALPHA : 0;
set_fbuf |= alpha_flags;
break;
case 3:
fbuf.flags &= ~alpha_flags;
fbuf.flags |= strtol(value, nullptr, 0) ? V4L2_FBUF_FLAG_LOCAL_ALPHA : 0;
set_fbuf |= alpha_flags;
break;
case 4:
fbuf.flags &= ~alpha_flags;
fbuf.flags |= strtol(value, nullptr, 0) ? V4L2_FBUF_FLAG_LOCAL_INV_ALPHA : 0;
set_fbuf |= alpha_flags;
break;
case 5:
fb_device = value;
if (fb_device[0] >= '0' && fb_device[0] <= '9' && strlen(fb_device) <= 3) {
static char newdev[20];
sprintf(newdev, "/dev/fb%s", fb_device);
fb_device = newdev;
}
break;
default:
overlay_usage();
break;
}
}
break;
case OptOverlay:
overlay = strtol(optarg, nullptr, 0);
break;
}
}
static void do_try_set_overlay(struct v4l2_format &fmt, int fd)
{
struct v4l2_window &win = fmt.fmt.win;
bool keep_old_clip = true;
bool keep_old_bitmap = true;
struct v4l2_clip *cliplist = nullptr;
unsigned stride = (win.w.width + 7) / 8;
unsigned char *bitmap = nullptr;
int ret;
if (((set_overlay_fmt & FmtWidth) && win.w.width != overlay_fmt.fmt.win.w.width) ||
((set_overlay_fmt & FmtHeight) && win.w.height != overlay_fmt.fmt.win.w.height))
keep_old_bitmap = keep_old_clip = false;
if (options[OptClearBitmap] || !bitmap_rects.empty())
keep_old_bitmap = false;
if (options[OptClearClips] || !clips.empty())
keep_old_clip = false;
win.bitmap = nullptr;
if (keep_old_bitmap) {
bitmap = static_cast<unsigned char *>(calloc(1, stride * win.w.height));
win.bitmap = bitmap;
}
if (keep_old_clip) {
if (win.clipcount)
cliplist = static_cast<struct v4l2_clip *>(malloc(win.clipcount * sizeof(*cliplist)));
win.clips = cliplist;
}
if (keep_old_clip || keep_old_bitmap)
if (doioctl(fd, VIDIOC_G_FMT, &fmt))
goto free;
if (set_overlay_fmt & FmtChromaKey)
win.chromakey = overlay_fmt.fmt.win.chromakey;
if (set_overlay_fmt & FmtGlobalAlpha)
win.global_alpha = overlay_fmt.fmt.win.global_alpha;
if (set_overlay_fmt & FmtLeft)
win.w.left = overlay_fmt.fmt.win.w.left;
if (set_overlay_fmt & FmtTop)
win.w.top = overlay_fmt.fmt.win.w.top;
if (set_overlay_fmt & FmtWidth)
win.w.width = overlay_fmt.fmt.win.w.width;
if (set_overlay_fmt & FmtHeight)
win.w.height = overlay_fmt.fmt.win.w.height;
if (set_overlay_fmt & FmtField)
win.field = overlay_fmt.fmt.win.field;
if (!clips.empty()) {
win.clipcount = clips.size();
win.clips = &clips[0];
}
if (!bitmap_rects.empty()) {
free(bitmap);
stride = (win.w.width + 7) / 8;
bitmap = static_cast<unsigned char *>(calloc(1, stride * win.w.height));
win.bitmap = bitmap;
for (const auto &r : bitmap_rects) {
if (r.left + r.width > win.w.width ||
r.top + r.height > win.w.height) {
fprintf(stderr, "rectangle is out of range\n");
return;
}
for (unsigned y = r.top; y < r.top + r.height; y++)
for (unsigned x = r.left; x < r.left + r.width; x++)
bitmap[y * stride + x / 8] |= 1 << (x & 7);
}
win.bitmap = bitmap;
}
if (options[OptSetOverlayFormat])
ret = doioctl(fd, VIDIOC_S_FMT, &fmt);
else
ret = doioctl(fd, VIDIOC_TRY_FMT, &fmt);
if (ret == 0 && (verbose || options[OptTryOverlayFormat]))
printfmt(fd, fmt);
free:
if (bitmap)
free(bitmap);
if (cliplist)
free(cliplist);
}
void overlay_set(cv4l_fd &_fd)
{
int fd = _fd.g_fd();
if ((options[OptSetOverlayFormat] || options[OptTryOverlayFormat]) &&
(set_overlay_fmt || options[OptClearClips] || options[OptClearBitmap] ||
!bitmap_rects.empty() || !clips.empty())) {
struct v4l2_format fmt;
memset(&fmt, 0, sizeof(fmt));
// You can never have both VIDEO_OVERLAY and VIDEO_OUTPUT_OVERLAY
if (capabilities & V4L2_CAP_VIDEO_OVERLAY)
fmt.type = V4L2_BUF_TYPE_VIDEO_OVERLAY;
else
fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY;
if (doioctl(fd, VIDIOC_G_FMT, &fmt) == 0)
do_try_set_overlay(fmt, fd);
}
if (options[OptSetFBuf]) {
struct v4l2_framebuffer fb;
if (doioctl(fd, VIDIOC_G_FBUF, &fb) == 0) {
fb.flags &= ~set_fbuf;
fb.flags |= fbuf.flags;
if (!fbuf_fill_from_fb(fb, fb_device))
doioctl(fd, VIDIOC_S_FBUF, &fb);
}
}
if (options[OptOverlay]) {
doioctl(fd, VIDIOC_OVERLAY, &overlay);
}
}
void overlay_get(cv4l_fd &_fd)
{
int fd = _fd.g_fd();
if (options[OptGetOverlayFormat]) {
struct v4l2_format fmt;
unsigned char *bitmap = nullptr;
memset(&fmt, 0, sizeof(fmt));
// You can never have both VIDEO_OVERLAY and VIDEO_OUTPUT_OVERLAY
if (capabilities & V4L2_CAP_VIDEO_OVERLAY)
fmt.type = V4L2_BUF_TYPE_VIDEO_OVERLAY;
else
fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY;
if (doioctl(fd, VIDIOC_G_FMT, &fmt) == 0) {
unsigned stride = (fmt.fmt.win.w.width + 7) / 8;
if (fmt.fmt.win.clipcount)
fmt.fmt.win.clips = static_cast<struct v4l2_clip *>(malloc(fmt.fmt.win.clipcount * sizeof(clips[0])));
bitmap = static_cast<unsigned char *>(calloc(1, stride * fmt.fmt.win.w.height));
fmt.fmt.win.bitmap = bitmap;
doioctl(fd, VIDIOC_G_FMT, &fmt);
printfmt(fd, fmt);
if (fmt.fmt.win.clips)
free(fmt.fmt.win.clips);
if (bitmap)
free(bitmap);
}
}
if (options[OptGetFBuf]) {
struct v4l2_framebuffer fb;
if (doioctl(fd, VIDIOC_G_FBUF, &fb) == 0)
printfbuf(fb);
}
}
void overlay_list(cv4l_fd &fd)
{
if (options[OptListOverlayFormats]) {
printf("ioctl: VIDIOC_ENUM_FMT\n");
print_video_formats(fd, V4L2_BUF_TYPE_VIDEO_OVERLAY, 0);
}
if (options[OptFindFb])
find_fb(fd.g_fd());
}